首页 > 代码库 > NavMesh系统动态碰撞的探讨

NavMesh系统动态碰撞的探讨

  Unity3D提供的NavMesh系统可以方便的解决游戏的寻路问题,但是该系统有一个比较让人不理解的问题:

  NavMesh导航时会忽略Physics系统本身的碰撞,也就是说NavMeshAgent在移动的过程中不会被Collider阻挡,而是会直接走过去(但是OnTriggerEnter等触发功能正常)。

  动态碰撞的功能对很多游戏都是一个基本的需求,而根据NavMesh提供的接口,唯一可以实现阻挡功能的只有NavMeshObstacle,而NavMeshObstacle只有一种形状:圆柱体,而且up方向固定,不能调整为侧向。总结起来就是以下几点:

  (1)导航网格的行走/碰撞区域只能预烘焙;

  (2)动态碰撞体只能通过挂载NavMeshObstacle组件来实现;

  (3)碰撞体的形状只有一种——圆柱体,严格来说就是圆形,而且是正圆还不能是椭圆。

  所以说到这里,基本上可以放弃使用各种形状的Collider来制作场景阻挡物了。不过,替代方案也还是有的:如果一定要使用Unity3D提供的NavMesh来做导航,那么可以将圆作为基本元素来模拟其它形状。

  

  上图展示了通过NavMeshObjstacle来模拟立方体阻挡物,为了方便的编辑该立方体的大小,可以写一个辅助脚本来实现:

using UnityEngine;using System.Collections;using System.Collections.Generic;[ExecuteInEditMode]public class MultiObstacleHelper : MonoBehaviour {    public float Interval = 1f;         // Obstacle之间的间隔    public int Num = 1;                 // Obstacle的个数    private float curInterval = 1f;    private int curNum = 1;    private Transform template = null;    void Awake()    {        template = gameObject.transform.Find("Obstacle");    }    void Start()    {        Adjust();    }    void Update()    {        if (Num <= 0) Num = curNum;        Adjust();    }    private void Adjust()    {        if (template == null) return;        AdjustInterval(AdjustNum());    }    private bool AdjustNum()    {        if (curNum == Num) return false;        if (Num > curNum)        {            for (int i = 0; i < Num - curNum; ++i)            {                GameObject go = GameObject.Instantiate(template.gameObject) as GameObject;                go.transform.parent = template.parent;                go.transform.localPosition = Vector3.zero;                go.transform.localScale = Vector3.one;                go.transform.localRotation = Quaternion.identity;            }        }        else if (Num < curNum)        {            int count = curNum - Num;            List<Transform> lst = new List<Transform>();for (int i = 0; i < template.parent.transform.childCount; ++i)            {                if (count <= 0) break;                if (template.parent.GetChild(i) != template)                {                    lst.Add(template.parent.GetChild(i));                    count--;                }            }            while(lst.Count > 0)            {                Transform tran = lst[0];                GameObject.DestroyImmediate(tran.gameObject);                lst.RemoveAt(0);            }            lst.Clear();        }        curNum = Num;        return true;    }    private void AdjustInterval(bool numChange)    {        if (numChange == false && curInterval == Interval)            return;        int half = Num / 2;        int index = 0;        foreach (Transform tran in template.parent.gameObject.transform)        {            // 奇数个            if (Num % 2 == 1)            {                Vector3 pos = tran.localPosition;                pos.x = (index - half) * Interval;                tran.localPosition = pos;            }            else            {                Vector3 pos = tran.localPosition;                pos.x = (index - half + 0.5f) * Interval;                tran.localPosition = pos;            }            index++;        }        curInterval = Interval;    }}

  上述代码可以调整Obstacle的个数和间距,然后再配合调整缩放比例基本上可以做出各种尺寸的立方体。

  单向阻挡的实现,可以通过组合Trigger和NavMeshObstacle来实现一个单向阻挡的功能:

  

  实现思路是当角色进入红色Trigger区域时,将后面的阻挡物隐掉,过1秒之后再激活,这样就可以实现一个单向阻挡物的功能,实现的代码比较简单,如下面所示:

using UnityEngine;using System.Collections;#if UNITY_EDITORusing UnityEditor;#endifpublic class SinglePassTrigger : MonoBehaviour {    [HideInInspector]    public Transform Object = null;    public Transform Collider = null;    public float PassTime = 1f;    void Start()    {        Object = transform.parent.transform.Find("Object");        Collider = transform.parent.transform.Find("Collider");    }    protected virtual void OnTriggerEnter(Collider other)    {        StopCoroutine("LetPassCoroutine");        StartCoroutine("LetPassCoroutine");    }    protected virtual void OnTriggerExit(Collider other)    { }    IEnumerator LetPassCoroutine()    {        SetPassState(true);        float startTime = Time.time;        while(Time.time < startTime + PassTime)        {            yield return null;        }        SetPassState(false);    }    private void SetPassState(bool value)    {        if (Collider == null) return;        Collider.gameObject.SetActive(!value);    }#if UNITY_EDITOR    void OnDrawGizmos()    {        // 设置旋转矩阵        Matrix4x4 rotationMatrix = Matrix4x4.TRS(Vector3.zero, transform.rotation, Vector3.one);        Gizmos.matrix = transform.localToWorldMatrix;        // 在Local坐标原点绘制标准尺寸的对象        Gizmos.color = new Color(1f, 0f, 0f, 0.8f);        Gizmos.DrawCube(Vector3.zero, Vector3.one);        Gizmos.color = Color.black;        Gizmos.DrawWireCube(Vector3.zero, Vector3.one);        Gizmos.DrawIcon(transform.position + Vector3.up, "ban.png");    }#endif}

 

  

NavMesh系统动态碰撞的探讨