unity检测周围物体的方法总结

说明

检测的方法会有很多,关键在于分析出什么时候用什么方法最合适(效果好,性能高)

1.碰撞检测

(1)碰撞检测的条件:
二者均有碰撞组件,运动的物体具有刚体组件,且其中至少一个碰撞器附加非动力学刚体。
(2)碰撞检测的方法:
在这些方法中做想做的事即可,这些方法会在unity脚本生命周期中自动调用,不用我们操心。

  • 当前collider/rigidbody开始碰到另一个rigidbody/collider时OnCollisionEnter被调用。
void OnCollisionEnter(Collision collision)
  • 每当该collider/rigidbody碰到其他rigidbody/collider,将在每帧调用OnCollisionStay。通俗的说,一个碰撞器或刚体碰到另一个刚体或碰撞器,在每帧都会调用OnCollisionStay,直到它们之间离开不接触。
void OnCollisionStay(Collision collisionInfo)
  • 当前collider/rigidbody停止碰撞另一个 rigidbody/collider时,OnCollisionExit被调用。
void OnCollisionExit(Collision collisionInfo)

这几个方法中的参数即为与当前碰撞体发生碰撞的Collision,而触发器检测到的是当前碰撞体发生碰撞的Collider,Collision中有collider和rigidbody属性,并且Collision中有一个至关重要的属性:contacts,碰撞点集合,类型为ContactPoint[],ContactPoint类也有很多常用的属性和方法,去文档看!!!我们可以由contacts[0]得到第一个碰撞点contacts[0].point,在那里搞一些事情,比如碰撞的火花… …,还可以由contacts[0].normal得到接触面的法线(碰撞方向就得到了)。

2.触发器检测

触发器就是带碰撞器组件且勾选Is Trigger的物体,没有碰撞效果仅用来检测碰撞(相交)。
(1)触发条件:
两者均有碰撞器,至少一个勾选Trigger。
(2)触发方法:
在这些方法中做想做的事即可,这些方法会在unity脚本生命周期中自动调用,不用我们操心。

  • 当碰撞器进入触发器时OnTriggerEnter被调用。
void OnTriggerEnter(Collider other)
  • 每当碰撞器从进入触发器,几乎每帧调用OnTriggerStay。
    注意:OnTriggerStay函数是基于物理计时器,因此它未必每帧都运行。
    也就是说OnTriggerStay是在每一个Time.fixedDeltaTime的时间节点上运行,不是Time.deltaTime的时间节点上运行
void OnTriggerStay(Collider other)
  • 当该碰撞器停止接触触发器时,OnTriggerExit被调用。也就是说,当碰撞器离开触发器时,调用OnTriggerExit。
void OnTriggerExit(Collider other)

注意这里的参数就是与当前碰撞器接触的Collider了。

3.射线检测

前两种方法在一些细致的地方需要我们对物理引擎有很好的了解才能做的更真实,设置刚体的属性一直是我最恶心的事情,所以我宁愿用角色控制器也不会自己去给模型配置刚体、碰撞器(当然这只是对角色,对地形(死物)用前两种还是很方便的)。
那么有时候我们也必须舍弃前两种方法,尽管它们的事件响应方式很香,比如快速飞行的物体(如子弹)可能会在一帧之内穿过,而前两种方法一帧检测一次,很容易出现物体穿过但检测不到的情况,这是我们就必须给快速飞行的物体做射线检测。

(1)射线检测需要用到的类/结构:

又很多现成的属性和方法供我们很方便的进行这些事情。

i.RaycastHit 射线投射碰撞信息:

重要属性:

  • collider 碰到的碰撞器。
  • distance 从射线的原点到触碰点的距离。
  • normal 射线触碰表面的法线。
  • point 在世界坐标空间,射线碰到碰撞器的接触点。
ii.Ray
  • origin 射线的原点。
  • direction 射线的方向。
  • Ray(Vector3 origin, Vector3 direction); 构造器。

(2)用Physics中的众多射线检测方法:

Physics类中又很多不同形式的射线检测,这里总结我常用的几个以及例子:
注:下面这些方法会有很多重载,但大多都是功能性和表达上的一些区别,根据需要看官方文档去选择合适的重载。

i.线投射:适用于有检测距离的方向性检测

public static bool Linecast(Vector3 start, Vector3 end, out RaycastHit hitInfo, int layerMask = DefaultRaycastLayers);
在线的开始和结束位置之间,如果与任何碰撞器相交返回真,hitInfo存储相交碰撞器的信息,Layer mask是用来指定投射的层。

ii.射线投射:在场景中投下可与所有碰撞器碰撞的一条光线,并返回碰撞的细节信息。

public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
参数
origin 在世界坐标,射线的起始点。
direction 射线的方向
hitInfo 将包含碰到碰撞器的更多信息。
distance 射线的长度。
layermask 选择投射的层蒙版。
queryTriggerInteraction 指定是否查询碰到触发器。

iii.射线投射列表:在场景投射一条射线并返回所有碰撞。注意不保证顺序。

public static RaycastHit[] RaycastAll(Ray ray, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
参数
origin射线的开始点。
direction射线的方向。
maxDistance 从射线开始允许投射的最大距离。
layermask 选择投射的层蒙版。
queryTriggerInteraction指定是否查询碰到触发器。

iv.球形射线:返回球内或与之接触的所有碰撞器。

我常用它去检测周围的各种物体。比如施放技能打到的物体,敌人巡逻区域发现玩家… …
public static Collider[] OverlapSphere(Vector3 position, float radius, int layerMask = AllLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
参数
position球体中心。
radius 球的半径。
layerMask 选择投射的层蒙版。
queryTriggerInteraction 指定是否查询碰到触发器。

v.球形投射列表

这个和上面的区别就是这个返回所有球形扫描到的碰撞信息。 功能更强大。
public static RaycastHit[] SphereCastAll(Vector3 origin, float radius, Vector3 direction, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers);
origin 扫描开始的球形中心点。
radius 球的半径。
direction 球形扫描的方向
distance 扫描的长度。
layerMask 仅扫描指定层的碰撞器。

(3)实例:

发射子弹看看是否击中游戏对象,以及击中的是什么游戏对象,其实这和子弹就没关系了,我们只需要对枪口做射线检测,根据不同的需求去选择合适的重载方法,这里我们就是单纯的去检测:下面是项目中的子弹类(开始位置就是枪口位置)

/// <summary>
/// 子弹,定义子弹共有行为
/// </summary>
public class Bullet : MonoBehaviour
{
    //计算目标点(用前面讲的射线检测)
    protected RaycastHit hit;
    //攻击距离
    public float attackMaxDistance = 200f;
    //打哪些层
    public LayerMask mask;
    //目标点
    private Vector3 target;
    //移动速度
    public float moveSpeed = 200;
    //攻击力
    [HideInInspector]//在编译器中隐藏
    public float atk;
    /// <summary>
    /// 计算目标点
    /// </summary>
    private void CalculateTargetPoint()
    {
        if(Physics.Raycast(transform.position, transform.forward,out hit,attackMaxDistance, mask))
        {
            target = hit.point;
        }
        else
        {
            target = transform.position + transform.forward * attackMaxDistance;
        }
    }
    private void Awake()
    {
        CalculateTargetPoint();
    }
    //移动
    private void Update()
    {
        Movement();
        if ((transform.position - target).sqrMagnitude < 0.1f)
        {
            Destroy(this.gameObject);
            GenerateContactEffect();
        }
    }
    private void Movement()
    {
        transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);
    }
    //达到目标点:摧毁、创建相关特效(根据目标点的便签(在射线检测中的信息有碰撞器组件collider.tag))
    //创建相关特效(根据目标点的便签(在射线检测中的信息有碰撞器组件collider.tag))
    private void GenerateContactEffect()
    {
        if (hit.collider == null)//没打到东西就没效果
        {
            return;
        }
        //switch (hit.collider.tag)
        //{
        //    case "":

        //        break;
        //}
        //对于资源较多时通过代码读取(资源必须放在Resources文件夹下)[以后会用对象池替代]
        GameObject prefabGo = Resources.Load<GameObject>("ContactEffects/Effects"+hit.collider.tag);
        if (prefabGo)
            //创建资源(应朝向目标点的法线并向法线方向向上移动一点)
            Instantiate(prefabGo, target+hit.normal*0.01f, Quaternion.LookRotation(hit.normal));
    }
}

4.使用代码(如标签)

很多时候我们对周围物体的检测有各种各样的要求,比如在攻击选择时,我们只想选取攻击范围内且离我们最近、血量大于0、给的金币最多…的小怪,这样不太好用射线了,而且射线消耗性能要远高于我们去通过标签找到想要选择的游戏对象类型,我们就需要自己写代码了。这里主要通过项目中的例子来体现:
eg:技能的攻击选择方法

namespace ARPGDemo.Skills
{
    /// <summary>
    /// 圆形攻击选择类:选择圆形区域中的敌人作为攻击目标
    /// </summary>
    public class CircleAttackSelector: IAttackSelector
    {
        /// <summary>
        /// 选择目标方法
        /// </summary>
        /// <param name="skillData">技能对象,选择目标取决于它</param>
        /// <param name="transform">选择时的参考点(一般就是以主角自身(技能拥有者)呗)</param>
        /// <returns></returns>
        public GameObject[] SelectTarget(SkillData skillData, Transform ownerTransform)
        {
            //1.通过射线找:在物体没有tag标记时这样找
            //2.有tag标记通过tag找性能高
            //在这通过射线查找(攻击距离为半径),在扇形中用tag
            var colliders = Physics.OverlapSphere(ownerTransform.position, skillData.attackDistance);
            if (colliders == null || colliders.Length == 0)
            {
                return null;
            }
            //找出标记为xx(在attackTargetTags中的)的所有物体
            //并且是活着的(Hp>0)
            var allCanBeSelected = ArrayHelper.FindAll(colliders, 
                c => (Array.IndexOf(skillData.attackTargetTags, c.tag) >= 0) 
                && (c.gameObject.GetComponent<CharacterStatus>().HP > 0));
            if (allCanBeSelected == null || allCanBeSelected.Count == 0)
            {
                return null;
            }
            //根本不用这样,列表有ToArray方法
            //因为数组助手类的参数都是数组,而tmp从FindAll中的返回类型是List,所以要转为数组
            //Collider[] allCanBeSelected = new Collider[tmp.Count];
            //for (int i = 0; i < tmp.Count; i++)
            //{
            //    allCanBeSelected[i] = tmp[i];

            //}
            //根据技能攻击类型确定返回单个或多个目标
            switch (skillData.attackType)
            {
                case SkillAttackType.Group://群攻就都选
                    return ArrayHelper.Select(allCanBeSelected.ToArray(), a => a.gameObject);
                    break;
                case SkillAttackType.Single://单攻就选最近的
                    var collider = ArrayHelper.Min(allCanBeSelected.ToArray(), 
                        a => Vector3.Distance(ownerTransform.position, a.transform.position));
                    return new GameObject[] { collider.gameObject };
                    break;
            }
            return null;
        }

    }
}

扇形选择就是多了角度的限制:

			//再找范围以内的
            //并且是活着的(Hp>0)
            //并且有角度
            var allCanBeSelected = allTargets.FindAll(
                a => Vector3.Distance(ownerTransform.position, a.transform.position) <= skillData.attackDistance
                && a.GetComponent<CharacterStatus>().HP > 0
                && TransformHelper.RangeFind(a.transform, ownerTransform, skillData.attackAngle));

这里面是lambda表达式,非常方便,不过主要还是用代码体现检测的思想。

5.角色控制器CharacterController

角色控制器让你在受制于碰撞的情况下很容易的进行运动,而不用处理刚体。
这简直是我的最爱了,就是因为他不用为角色配置复杂的刚体属性和碰撞器,却能通过void OnControllerColliderHit(ControllerColliderHit hit)检测到与之碰撞的物体。
ControllerColliderHit中也有如collider、controller、normal、point、rigidbody这些重要的属性来帮助我们获取碰撞体的信息,具体看官方手册。
在这里插入图片描述

  • 15
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Unity3D模拟物体笔刷是一种功能强大的工具,可用于在Unity游戏引擎中快速创建和放置大量的物体。它的工作原理类似于绘图软件中的画笔工具,可以用来将预先定义好的物体复制到场景中的指定位置。 使用Unity3D模拟物体笔刷的方法比较简单。首先,我们需要创建一个空物体,并向其添加脚本来控制笔刷的行为。在脚本中,我们可以定义一个游戏对象的数组,存储我们想要在场景中复制的物体。 在场景中,我们需要定义一个可以触发笔刷功能的触发器,例如一个键盘按键、按钮或触屏事件。当触发器被激活时,我们的脚本会开始执行。在脚本中,我们可以使用Instantiate函数来实例化我们事先定义好的物体,并将其放置在指定位置。这个过程可以重复执行,直到我们完成了想要的物体布局。 为了提高笔刷的效率,我们可以通过在场景中绘制一个格子网格来将物体放置在规律的位置。我们可以使用循环来遍历整个网格,并在每个单元格中进行物体的复制。此外,我们还可以根据需要进行调整,例如在每个复制的物体周围进行旋转、缩放或随机化。 Unity3D模拟物体笔刷的应用非常广泛。它可以用于创建游戏中的环境、道具、粒子效果等等。通过灵活的参数设置和组合,我们可以在短时间内快速生成复杂的场景,提高开发效率。同时,将物体笔刷与其他Unity3D功能结合使用,我们还可以实现更多独特的效果,使我们的游戏更加丰富和引人入胜。 ### 回答2: Unity3D是一种用于开发三维游戏和应用程序的强大工具。在Unity3D中,我们可以使用脚本编写自定义的功能和特效。模拟物体笔刷是一种常用的功能,它允许玩家在游戏中使用笔刷工具来绘制、放置和操控物体。 实现一个unity3d模拟物体笔刷的步骤如下: 1. 首先,我们需要创建一个笔刷对象,并将其添加到场景中。这个笔刷对象可以是一个简单的3D模型,也可以是一个粒子系统。它的作用是指示玩家在何处绘制物体。 2. 接下来,我们需要定义要绘制的物体的属性,比如名称、模型、材质等。我们可以通过在脚本中创建一个物体模板,并在使用笔刷绘制物体时克隆该模板来实现。 3. 实现笔刷的交互功能。玩家可以通过鼠标或触摸手势在场景中绘制物体。我们可以在脚本中检测鼠标点击或触摸事件,并将物体根据玩家的位置和方向绘制到场景中。可以选择性地在笔刷绘制的物体上添加物理效果,使其可以与场景中的其他物体发生交互。 4. 添加额外的功能来提高物体笔刷的灵活性。比如添加物体的旋转、缩放功能,使玩家可以在绘制物体时调整其大小和方向。还可以添加撤销和重做功能,以便玩家可以撤销不满意或错误的笔刷绘制操作。 总结起来,unity3d模拟物体笔刷是一种允许玩家在游戏中使用笔刷工具绘制、放置和操控物体的功能。实现这个功能需要创建一个笔刷对象,定义要绘制的物体属性,并实现交互功能,以及添加额外的功能来提高笔刷的灵活性。通过这样的模拟物体笔刷,玩家可以在游戏中自由创造并互动。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值