关于Unity3D游戏中攻击判定的一些思考

1. 基于碰撞检测

这是最容易想到的做法,利用Unity引擎自带的碰撞器组件,为攻击者和受击者添加碰撞器,在碰撞器的回调函数中处理想要实现的功能。在Unity中发生碰撞的物体分为两种:

  • 发起碰撞的物体:Rigidbody,CharacterController
  • 接受碰撞的物体:所有Collider

所有的Collider上都会带有一个isTrigger开关,当碰撞双方其中一个勾选时视为触发器,不参与后续物理反应,调用碰撞双方的onTrigger脚本

    MonoBehaviour.OnTriggerEnter( Collider other ) //当进入触发器
    MonoBehaviour.OnTriggerExit( Collider other ) //当退出触发器
    MonoBehaviour.OnTriggerStay( Collider other ) //当逗留触发器

当双方均不勾选isTrigger时,调用碰撞双方的onCollision脚本,此时发起碰撞的物体的Rigidbody组件不能勾选isKinematic

    MonoBehaviour.OnCollisionEnter( Collision collisionInfo ) //当进入碰撞器
    MonoBehaviour.OnCollisionExit( Collision collisionInfo ) //当退出碰撞器
    MonoBehaviour.OnCollisionStay( Collision collisionInfo ) // 当逗留碰撞器

具体这两种碰撞什么时候可以检测到如图所示

碰撞检测的优点:

  • 碰撞体做的越精细越贴合模型,攻击判定就越真实,不会出现穿模的情况
  • 复杂度在模型上,代码工作量小
  • 碰撞检测的缺点:

  • 三维的碰撞体参与计算性能开销较大,且碰撞体越精细开销就越大
  • 绘制不规则碰撞体需要额外的建模技能,美术工作量大
  • 由于发生碰撞的复杂条件,细节的偏差会产生意想不到的效果,使得必须制定规则设定不同图层间的碰撞逻辑,大大增加了工作量
  • 脚本调用需要满足“进入”/“离开条件”,在运动速度过快时可能会发生碰撞体穿透。常见的有FPS游戏中的子弹,在一帧内移动距离超过自身碰撞体的长度,就不会触发对应函数。解决办法有降速,加大碰撞体的长度,使用射线进行预判等,Unity官方也提供了一种解决方案——提高检测级别。
  • 将默认的离散检测Discrete改为连续检测Continuous,当然这又加大了性能的开销。

    2. 基于向量计算

    向量有方向也有长度,使用若干向量就可以绘制出攻击范围,遍历攻击范围内的所有目标就可以触发攻击判定了。

      Physics.OverlapSphere(Vector3 position, float radius) //返回position为球心,radius为半径的球体空间
                                   //内所有带有碰撞体的物体
      Vector3.Dot(Vector A, Vector B) //返回两个物体间的夹角
      Vector3.Distance(Vector positionA, Vector positionB)//返回两个物体间的距离
    

    为了配合攻击动画,通常采用帧事件实现

    在攻击动画的某些关键帧执行函数,遍历空间删选出处在攻击范围内的目标,触发攻击判定。向量计算的优点:

    • 性能开销小
    • 没有复杂的条件和逻辑,代码简单

    向量计算的缺点:

    • 当无用的小物体很多时需要考虑遍历的优化
    • 当目标体积过大时,可能出现一小部分进入攻击范围导致误判,视觉效果不真实
    • 较多用于范围类技能、爆炸物等的攻击判定,对于单体攻击类的判定效果不好

    3. 基于射线检测

    射线检测可以看作是对碰撞检测和向量计算的一个折中,在Unity中,射线可以设置发射点、长度、方向,返回射线穿透的物体。

     public static bool Raycast(Vector3 origin,Vector3 direction,RaycastHit,float distance,int layerMask)
    

    使用射线可以解决FPS游戏中子弹速度过快导致的碰撞体穿透问题,且性能开销更小,而对于较为复杂的攻击方式,比如刀剑的挥舞,一条射线就不能解决问题了。

      public static bool Linecast (Vector3 start,Vector3 end,int layerMask= DefaultRaycastLayers, 
      QueryTriggerInteraction queryTriggerInteraction=QueryTriggerInteraction.UseGlobal)
      //如果有任何碰撞体与 start 和 end 之间的线相交,则返回 true
    

      public static int RaycastNonAlloc (Ray ray, RaycastHit[] results, float maxDistance= Mathf.Infinity, 
      int layerMask= DefaultRaycastLayers, 
      QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);
      //返回int 存储到 results 缓冲区的命中对象数量
    

    在刀剑上记录若干点,攻击时,保存射线点这一帧的位置,然后在下一帧时,从上一帧发出射线到当前这一帧,检测连成的线段是否触碰目标物体,效果如下图所示(红线代表攻击动作每一帧射线点的连线,蓝线代表不同帧射线轨迹)。

    此时构造的攻击平面的二维的,如果对攻击判定的厚度有需求,则可以使用球体投射碰撞检测,每帧以每个射线点为球心投射球体,形成的判定轨迹就是三维空间。

     public static int SphereCastNonAlloc (Vector3 origin, float radius, Vector3 direction, 
     RaycastHit[] results, float maxDistance= Mathf.Infinity, int layerMask= DefaultRaycastLayers, 
     QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);
     //返回RaycastHit[] 扫描中命中的所有碰撞体的数组
    

    射线检测的优点:

    • 表现效果好,性能开销一般
    • 适合多种需求,便于开发扩展

    射线检测的缺点:

    • 代码工作量大,检测时会产生大量缓存,需要垃圾处理和优化

    参考资料:https://segmentfault.com/a/1190000012636643/

         https://blog.csdn.net/cycler_725/article/details/119485577

         https://blog.csdn.net/wch3351028/article/details/122326021

         https://github.com/biangeqian/Graduation-project/tree/master/Scripts

    全部评论
    以为来到了知乎
    点赞 回复
    分享
    发布于 03-23 16:38 浙江

    相关推荐

    点赞 11 评论
    分享
    牛客网
    牛客企业服务