编辑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();
}
}