Unity 编辑Prefab的NavMesh

编辑Prefab生成NavMesh文件

一、注意问题
1、注意NavMeshPrefabInstanceEditor,控制NavMeshPrefabInstance组件上是否显示按钮(Clear和Bake按钮是否显示)
2、注意NavMeshPrefabInstance组件的navMeshData的赋值,生成NavMeshData后没有成功赋值将不能寻路
3、Build NavMeshData数据时,注意settings参数,当settings参数中AgentRadius 大于 预设中要生成NavMeshData的物体体积时,烘焙生成的数据没有效果,不能使用。bounds控制生成的NavMeshData的大小。

二、清理和烘焙NavMeshData
在这里插入图片描述
三、需要生成NavMeshData的GameObject设置
在这里插入图片描述
四、生成效果图
在这里插入图片描述

五、生成NavMeshData需要的脚本
1、NavMeshPrefabInstance

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.AI;

[ExecuteInEditMode]
[DefaultExecutionOrder(-102)]
public class NavMeshPrefabInstance : MonoBehaviour
{
    [SerializeField]
    NavMeshData m_NavMesh;
    public NavMeshData navMeshData
    {
        get { return m_NavMesh; }
        set { m_NavMesh = value; }
    }

    [SerializeField]
    bool m_FollowTransform;
    public bool followTransform
    {
        get { return m_FollowTransform; }
        set { SetFollowTransform(value); }
    }

    NavMeshDataInstance m_Instance;

    // Position Tracking
    static readonly List<NavMeshPrefabInstance> s_TrackedInstances = new List<NavMeshPrefabInstance>();
    public static List<NavMeshPrefabInstance> trackedInstances {get {return s_TrackedInstances; }}
    Vector3 m_Position;
    Quaternion m_Rotation;

    void OnEnable()
    {
        AddInstance();

        if (m_Instance.valid && m_FollowTransform)
            AddTracking();
    }

    void OnDisable()
    {
        m_Instance.Remove();
        RemoveTracking();
    }

    public void UpdateInstance()
    {
        m_Instance.Remove();
        AddInstance();
    }

    void AddInstance()
    {
#if UNITY_EDITOR
        if (m_Instance.valid)
        {
            Debug.LogError("Instance is already added: " + this);
            return;
        }
#endif
        if (m_NavMesh)
            m_Instance = NavMesh.AddNavMeshData(m_NavMesh, transform.position, transform.rotation);

        m_Rotation = transform.rotation;
        m_Position = transform.position;
    }

    void AddTracking()
    {
#if UNITY_EDITOR
        // At runtime we don't want linear lookup
        if (s_TrackedInstances.Contains(this))
        {
            Debug.LogError("Double registration of " + this);
            return;
        }
#endif
        if (s_TrackedInstances.Count == 0)
            NavMesh.onPreUpdate += UpdateTrackedInstances;
        s_TrackedInstances.Add(this);
    }

    void RemoveTracking()
    {
        s_TrackedInstances.Remove(this);
        if (s_TrackedInstances.Count == 0)
            NavMesh.onPreUpdate -= UpdateTrackedInstances;
    }

    void SetFollowTransform(bool value)
    {
        if (m_FollowTransform == value)
            return;
        m_FollowTransform = value;
        if (value)
            AddTracking();
        else
            RemoveTracking();
    }

    bool HasMoved()
    {
        return m_Position != transform.position || m_Rotation != transform.rotation;
    }

    static void UpdateTrackedInstances()
    {
        foreach (var instance in s_TrackedInstances)
        {
            if (instance.HasMoved())
                instance.UpdateInstance();
        }
    }

#if UNITY_EDITOR
    void OnValidate()
    {
        // Only when the instance is valid (OnEnable is called) - we react to changes caused by serialization
        if (!m_Instance.valid)
            return;
        // OnValidate can be called several times - avoid double registration
        // We afford this linear lookup in the editor only
        if (!m_FollowTransform)
        {
            RemoveTracking();
        }
        else if (!s_TrackedInstances.Contains(this))
        {
            AddTracking();
        }
    }
#endif
}

2、NavMeshPrefabInstanceEditor

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
using NavMeshBuilder = UnityEngine.AI.NavMeshBuilder;

[CanEditMultipleObjects]
[CustomEditor(typeof(NavMeshPrefabInstance))]
class NavMeshPrefabInstanceEditor : Editor
{
    //SerializedObject和SerializedProperty是提供通用的方式编辑对象属性的类,可自动处理预制件和UI
    SerializedProperty m_NavMeshDataProp;
    SerializedProperty m_FollowTransformProp;

    public void OnEnable()
    {
        m_NavMeshDataProp = serializedObject.FindProperty("m_NavMesh");//序列化获取当前class的"m_NavMesh"属性
        m_FollowTransformProp = serializedObject.FindProperty("m_FollowTransform");//序列化获取当前class的"m_FollowTransform"属性
    }

    public override void OnInspectorGUI()
    {
        var instance = (NavMeshPrefabInstance)target;
        var go = instance.gameObject;

        serializedObject.Update();

        GUI.enabled = false;
        EditorGUILayout.PropertyField(m_NavMeshDataProp);
        GUI.enabled = true;
        EditorGUILayout.PropertyField(m_FollowTransformProp);

        EditorGUILayout.Space();

        OnInspectorGUIPrefab(go);

        serializedObject.ApplyModifiedProperties();
    }

    void OnInspectorGUIPrefab(GameObject go)
    {
        //var prefab = PrefabUtility.GetPrefabObject(go);
        //var path = AssetDatabase.GetAssetPath(prefab);

        //var prefab = PrefabUtility.GetPrefabInstanceHandle(go);
        //var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go);

        //注意:获取预制体和预制体路径,没获取时将导致按钮"Clear"和"Bake"不显示
        var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
        var path = AssetDatabase.GetAssetPath(prefab);

        if (prefab && string.IsNullOrEmpty(path))
        {
            if (GUILayout.Button("Select the Prefab asset to bake or clear the navmesh", EditorStyles.helpBox))
            {
                Selection.activeObject = PrefabUtility.GetCorrespondingObjectFromSource(go);
                EditorGUIUtility.PingObject(Selection.activeObject);
            }
        }

        if (string.IsNullOrEmpty(path))
            return;

        GUILayout.BeginHorizontal();
        GUILayout.Space(EditorGUIUtility.labelWidth);

        if (GUILayout.Button("Clear"))
            OnClear();

        if (GUILayout.Button("Bake"))
            OnBake();

        GUILayout.EndHorizontal();
    }

    void OnClear()
    {
        foreach (var tgt in targets)
        {
            var instance = (NavMeshPrefabInstance)tgt;
            var go = instance.gameObject;

            //var prefab = PrefabUtility.GetPrefabObject(go);
            //var path = AssetDatabase.GetAssetPath(prefab);

            //var prefab = PrefabUtility.GetPrefabInstanceHandle(go);
            //var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go);

            var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
            var path = AssetDatabase.GetAssetPath(prefab);

            if (string.IsNullOrEmpty(path))
            {
                Debug.LogError("GameObject: " + go + " has no valid prefab path");
                continue;
            }

            DestroyNavMeshData(path);
            AssetDatabase.SaveAssets();
        }
    }

    void OnBake()
    {
        foreach (var tgt in targets)
        {
            var instance = (NavMeshPrefabInstance)tgt;
            var go = instance.gameObject;

            //var prefab = PrefabUtility.GetPrefabObject(go);
            //var path = AssetDatabase.GetAssetPath(prefab);

            //var prefab = PrefabUtility.GetPrefabInstanceHandle(go);
            //var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go);

            var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
            var path = AssetDatabase.GetAssetPath(prefab);

            if (string.IsNullOrEmpty(path))
            {
                Debug.LogError("GameObject: " + go + " has no valid prefab path");
                continue;
            }

            DestroyNavMeshData(path);

            var navmesh = Build(instance);
            AssetDatabase.AddObjectToAsset(navmesh, prefab);
            //AssetDatabase.AddObjectToAsset(navmesh, path);

            //注意navMeshData的赋值,生成NavMeshData后没有成功赋值将不能寻路
            //instance.navMeshData = navmesh;
            (prefab as GameObject).GetComponent<NavMeshPrefabInstance>().navMeshData = navmesh;
            AssetDatabase.SaveAssets();
        }
    }

    /// <summary>
    /// 构建 NavMesh
    /// </summary>
    NavMeshData Build(NavMeshPrefabInstance instance)
    {
        var root = instance.transform;
        var sources = new List<NavMeshBuildSource>();
        var markups = new List<NavMeshBuildMarkup>();

        //NavMeshBuilder.CollectSources(root, ~0, NavMeshCollectGeometry.RenderMeshes, 0, markups, sources);
        UnityEditor.AI.NavMeshBuilder.CollectSourcesInStage(root, ~0, NavMeshCollectGeometry.PhysicsColliders, 0, markups, instance.gameObject.scene, sources);

        var settings = NavMesh.GetSettingsByID(0);
        //var bounds = new Bounds(Vector3.zero, 1000.0f * Vector3.one);
        var bounds = new Bounds(Vector3.zero, new Vector3(1000, 1, 1000));

        //注意settings参数,当settings参数中AgentRadius 大于 预设中要生成NavMeshData的物体体积时,烘焙生成的数据没有效果,不能使用。bounds控制生成的NavMeshData的大小。
        var navmesh = NavMeshBuilder.BuildNavMeshData(settings, sources, bounds, root.position, root.rotation);
        navmesh.name = "Navmesh";

        return navmesh;
    }

    /// <summary>
    /// 清除NavMesh
    /// </summary>
    void DestroyNavMeshData(string path)
    {
        var assets = AssetDatabase.LoadAllAssetsAtPath(path);
        foreach (var o in assets)
        {
            var data = o as NavMeshData;
            if (data != null)
                DestroyImmediate(o, true);
        }
    }

    [DrawGizmo(GizmoType.Selected | GizmoType.Active | GizmoType.Pickable)]
    static void RenderGizmo(NavMeshPrefabInstance instance, GizmoType gizmoType)
    {
        if (!EditorApplication.isPlaying)
            instance.UpdateInstance();
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值