Sometimes, a custom navigation mesh is necessary for dealing with difficult situations such as different types of graphs, but placing the graph's vertices manually is troublesome because it requires a lot of time to cover large areas.
We will learn how to use a model's mesh in order to generate a navigation mesh based on its triangles' centroids as vertices, and then leverage the heavy lifting from the previous recipe we learned.
This recipe requires some knowledge of custom editor scripting and understanding and implementing the points of visibility in the graph representation. Also, it is worth mentioning that the script instantiates a CustomNavMesh
game object automatically in the scene and requires a prefab assigned, just like any other graph representation.
Finally, it's important to create the following class, deriving from GraphVisibility
:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class CustomNavMesh : GraphVisibility { public override void Start() { instIdToId = new Dictionary<int, int>(); } }
We will create an editor window for easily handling the automation process without weighing down the graph's Start
function, delaying the scene loading.
CustomNavMeshWindow
class and place it in a directory called Editor
:using UnityEngine; using UnityEditor; using System.Collections; using System.Collections.Generic; public class CustomNavMeshWindow : EditorWindow { // next steps here }
static bool isEnabled = false; static GameObject graphObj; static CustomNavMesh graph; static CustomNavMeshWindow window; static GameObject graphVertex;
[MenuItem("UAIPC/Ch02/CustomNavMeshWindow")] static void Init() { window = EditorWindow.GetWindow<CustomNavMeshWindow>(); window.title = "CustomNavMeshWindow"; SceneView.onSceneGUIDelegate += OnScene; graphObj = GameObject.Find("CustomNavMesh"); if (graphObj == null) { graphObj = new GameObject("CustomNavMesh"); graphObj.AddComponent<CustomNavMesh>(); graph = graphObj.GetComponent<CustomNavMesh>(); } else { graph = graphObj.GetComponent<CustomNavMesh>(); if (graph == null) graphObj.AddComponent<CustomNavMesh>(); graph = graphObj.GetComponent<CustomNavMesh>(); } }
OnDestroy
function:void OnDestroy() { SceneView.onSceneGUIDelegate -= OnScene; }
OnGUI
function for drawing the window's interior:void OnGUI() { isEnabled = EditorGUILayout.Toggle("Enable Mesh Picking", isEnabled); if (GUILayout.Button("Build Edges")) { if (graph != null) graph.LoadGraph(); } }
OnScene
function for handling the left-click on the scene window:private static void OnScene(SceneView sceneView) { if (!isEnabled) return; if (Event.current.type == EventType.MouseDown) { graphVertex = graph.vertexPrefab; if (graphVertex == null) { Debug.LogError("No Vertex Prefab assigned"); return; } Event e = Event.current; Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition); RaycastHit hit; GameObject newV; // next step } }
if (Physics.Raycast(ray, out hit)) { GameObject obj = hit.collider.gameObject; Mesh mesh = obj.GetComponent<MeshFilter>().sharedMesh; Vector3 pos; int i; for (i = 0; i < mesh.triangles.Length; i += 3) { int i0 = mesh.triangles[i]; int i1 = mesh.triangles[i + 1]; int i2 = mesh.triangles[i + 2]; pos = mesh.vertices[i0]; pos += mesh.vertices[i1]; pos += mesh.vertices[i2]; pos /= 3; newV = (GameObject)Instantiate(graphVertex, pos, Quaternion.identity); newV.transform.Translate(obj.transform.position); newV.transform.parent = graphObj.transform; graphObj.transform.parent = obj.transform; } }
We create a custom editor window and set up the delegate function OnScene
for handling events on the scene window. Also, we create the graph nodes by traversing the mesh vertex arrays, computing each triangle's centroid. Finally, we make use of the graph's LoadGraph
function in order to compute neighbors.