public override void OnInspectorGUI (NavGraph target) { var graph = target as LayerGridGraph; base.OnInspectorGUI (target); if (graph.neighbours != NumNeighbours.Four) { Debug.Log ("Note: Only 4 neighbours per grid node is allowed in this graph type"); } }
public override void OnInspectorGUI (NavGraph target) { var graph = target as NavMeshGraph; graph.sourceMesh = ObjectField ("Source Mesh", graph.sourceMesh, typeof(Mesh), false) as Mesh; graph.offset = EditorGUILayout.Vector3Field ("Offset",graph.offset); graph.rotation = EditorGUILayout.Vector3Field ("Rotation",graph.rotation); graph.scale = EditorGUILayout.FloatField (new GUIContent ("Scale","Scale of the mesh"),graph.scale); graph.scale = (graph.scale < 0.01F && graph.scale > -0.01F) ? (graph.scale >= 0 ? 0.01F : -0.01F) : graph.scale; graph.accurateNearestNode = EditorGUILayout.Toggle (new GUIContent ("Accurate Nearest Node Queries","More accurate nearest node queries. See docs for more info"),graph.accurateNearestNode); }
public override void OnInspectorGUI (NavGraph target) { var graph = target as PointGraph; graph.root = ObjectField (new GUIContent ("Root","All childs of this object will be used as nodes, if it is not set, a tag search will be used instead (see below)"),graph.root,typeof(Transform),true) as Transform; graph.recursive = EditorGUILayout.Toggle (new GUIContent ("Recursive","Should childs of the childs in the root GameObject be searched"),graph.recursive); graph.searchTag = EditorGUILayout.TagField (new GUIContent ("Tag","If root is not set, all objects with this tag will be used as nodes"),graph.searchTag); if (graph.root != null) { EditorGUILayout.HelpBox ("All childs "+(graph.recursive ? "and sub-childs ":"") +"of 'root' will be used as nodes\nSet root to null to use a tag search instead", MessageType.None); } else { EditorGUILayout.HelpBox ("All object with the tag '"+graph.searchTag+"' will be used as nodes"+(graph.searchTag == "Untagged" ? "\nNote: the tag 'Untagged' cannot be used" : ""), MessageType.None); } graph.maxDistance = EditorGUILayout.FloatField (new GUIContent ("Max Distance","The max distance in world space for a connection to be valid. A zero counts as infinity"),graph.maxDistance); graph.limits = EditorGUILayout.Vector3Field ("Max Distance (axis aligned)",graph.limits); graph.raycast = EditorGUILayout.Toggle (new GUIContent ("Raycast","Use raycasting to check if connections are valid between each pair of nodes"),graph.raycast); if ( graph.raycast ) { EditorGUI.indentLevel++; graph.use2DPhysics = EditorGUILayout.Toggle (new GUIContent ("Use 2D Physics", "If enabled, all raycasts will use the Unity 2D Physics API instead of the 3D one."), graph.use2DPhysics); graph.thickRaycast = EditorGUILayout.Toggle (new GUIContent ("Thick Raycast","A thick raycast checks along a thick line with radius instead of just along a line"),graph.thickRaycast); if ( graph.thickRaycast ) { EditorGUI.indentLevel++; graph.thickRaycastRadius = EditorGUILayout.FloatField (new GUIContent ("Raycast Radius","The radius in world units for the thick raycast"),graph.thickRaycastRadius); EditorGUI.indentLevel--; } graph.mask = EditorGUILayoutx.LayerMaskField ("Mask",graph.mask); EditorGUI.indentLevel--; } graph.optimizeForSparseGraph = EditorGUILayout.Toggle (new GUIContent ("Optimize For Sparse Graph","Check online documentation for more information."),graph.optimizeForSparseGraph); if ( graph.optimizeForSparseGraph ) { EditorGUI.indentLevel++; graph.optimizeFor2D = EditorGUILayout.Toggle (new GUIContent ("Optimize For XZ Plane","Check online documentation for more information."),graph.optimizeFor2D); EditorGUI.indentLevel--; } }
public override void OnInspectorGUI (NavGraph target) { var graph = target as GridGraph; DrawFirstSection (graph); Separator (); DrawMiddleSection (graph); Separator (); DrawCollisionEditor (graph.collision); if ( graph.collision.use2D ) { if ( Mathf.Abs ( Vector3.Dot ( Vector3.forward, Quaternion.Euler (graph.rotation) * Vector3.up ) ) < 0.9f ) { EditorGUILayout.HelpBox ("When using 2D it is recommended to rotate the graph so that it aligns with the 2D plane.", MessageType.Warning ); } } Separator (); DrawLastSection (graph); }
public override void OnSceneGUI (NavGraph target) { Event e = Event.current; var graph = target as GridGraph; Matrix4x4 matrixPre = graph.matrix; graph.GenerateMatrix (); if (e.type == EventType.MouseDown) { isMouseDown = true; } else if (e.type == EventType.MouseUp) { isMouseDown = false; } if (!isMouseDown) { savedMatrix = graph.boundsMatrix; } Handles.matrix = savedMatrix; if ((graph.GetType() == typeof(GridGraph) && graph.nodes == null) || (graph.uniformWidthDepthGrid && graph.depth*graph.width != graph.nodes.Length) || graph.matrix != matrixPre) { //Rescan the graphs if (AutoScan ()) { GUI.changed = true; } } Matrix4x4 inversed = savedMatrix.inverse; Handles.color = AstarColor.BoundsHandles; Handles.DrawCapFunction cap = Handles.CylinderCap; Vector2 extents = graph.unclampedSize*0.5F; Vector3 center = inversed.MultiplyPoint3x4 (graph.center); if (Tools.current == Tool.Scale) { const float HandleScale = 0.1f; EditorGUI.BeginChangeCheck (); Vector3 p1 = Handles.Slider (center+new Vector3 (extents.x,0,0), Vector3.right, HandleScale*HandleUtility.GetHandleSize (center+new Vector3 (extents.x,0,0)),cap,0); Vector3 p2 = Handles.Slider (center+new Vector3 (0,0,extents.y), Vector3.forward, HandleScale*HandleUtility.GetHandleSize (center+new Vector3 (0,0,extents.y)),cap,0); Vector3 p4 = Handles.Slider (center+new Vector3 (-extents.x,0,0), -Vector3.right, HandleScale*HandleUtility.GetHandleSize (center+new Vector3 (-extents.x,0,0)),cap,0); Vector3 p5 = Handles.Slider (center+new Vector3 (0,0,-extents.y), -Vector3.forward, HandleScale*HandleUtility.GetHandleSize (center+new Vector3 (0,0,-extents.y)),cap,0); Vector3 p6 = Handles.Slider (center, Vector3.up, HandleScale*HandleUtility.GetHandleSize (center),cap,0); var r1 = new Vector3 (p1.x,p6.y,p2.z); var r2 = new Vector3 (p4.x,p6.y,p5.z); if (EditorGUI.EndChangeCheck ()) { graph.center = savedMatrix.MultiplyPoint3x4 ((r1+r2)/2F); Vector3 tmp = r1-r2; graph.unclampedSize = new Vector2(tmp.x,tmp.z); } } else if (Tools.current == Tool.Move) { if (Tools.pivotRotation == PivotRotation.Local) { EditorGUI.BeginChangeCheck (); center = Handles.PositionHandle (center,Quaternion.identity); if (EditorGUI.EndChangeCheck () && Tools.viewTool != ViewTool.Orbit) { graph.center = savedMatrix.MultiplyPoint3x4 (center); } } else { Handles.matrix = Matrix4x4.identity; EditorGUI.BeginChangeCheck (); center = Handles.PositionHandle (graph.center,Quaternion.identity); if (EditorGUI.EndChangeCheck () && Tools.viewTool != ViewTool.Orbit) { graph.center = center; } } } else if (Tools.current == Tool.Rotate) { //The rotation handle doesn't seem to be able to handle different matrixes of some reason Handles.matrix = Matrix4x4.identity; EditorGUI.BeginChangeCheck (); var rot = Handles.RotationHandle (Quaternion.Euler (graph.rotation),graph.center); if (EditorGUI.EndChangeCheck () && Tools.viewTool != ViewTool.Orbit) { graph.rotation = rot.eulerAngles; } } Handles.matrix = Matrix4x4.identity; #if ASTARDEBUG //Draws some info over the node closest to the mouse Ray ray = HandleUtility.GUIPointToWorldRay (Event.current.mousePosition); Vector3 p = ray.GetPoint (100); if (Event.current.shift) { GraphNode close = graph.GetNearest (p).node; if (close != null) { node1 = close; } if (node1 == null) { return; } Handles.SphereCap (0,(Vector3)node1.position,Quaternion.identity,graph.nodeSize*0.5F); //Node node = node1; GUI.color = Color.white; //Handles.Label((Vector3)node.position + Vector3.up*2,"G : "+node.+"\nH : "+node.h+"\nF : "+node.f+"\nPosition : "+node.position.ToString (),EditorStyles.whiteBoldLabel); } #endif }
/** Draws the inspector for the given graph with the given graph editor */ bool DrawGraph (NavGraph graph, GraphEditor graphEditor) { // Graph guid, just used to get a unique value string graphGUIDString = graph.guid.ToString(); Color tmp1 = GUI.color; EditorGUILayoutx.FadeArea topFadeArea = guiLayoutx.BeginFadeArea (graph.open, "", graphGUIDString, graphBoxStyle); Color tmp2 = GUI.color; GUI.color = tmp1; GUILayout.BeginHorizontal (); // Make sure that the graph name is not null graph.name = graph.name ?? graphEditorTypes[graph.GetType ().Name].displayName; GUI.SetNextControlName (graphGUIDString); graph.name = GUILayout.TextField (graph.name, EditorGUILayoutx.defaultLabelStyle, GUILayout.ExpandWidth(false),GUILayout.ExpandHeight(false)); // If the graph name text field is not focused and the graph name is empty, then fill it in if (graph.name == "" && Event.current.type == EventType.Repaint && GUI.GetNameOfFocusedControl() != graphGUIDString) { graph.name = graphEditorTypes[graph.GetType ().Name].displayName; } if (GUILayout.Button ("", EditorGUILayoutx.defaultLabelStyle)) { graph.open = !graph.open; if (!graph.open) { graph.infoScreenOpen = false; } RepaintSceneView (); return true; } if (script.prioritizeGraphs) { if (GUILayout.Button (new GUIContent ("Up","Increase the graph priority"),GUILayout.Width (40))) { int index = script.astarData.GetGraphIndex (graph); // Find the previous non null graph int next = index-1; for (;next >= 0;next--) if (script.graphs[next] != null) break; if (next >= 0) { NavGraph tmp = script.graphs[next]; script.graphs[next] = graph; script.graphs[index] = tmp; GraphEditor tmpEditor = graphEditors[next]; graphEditors[next] = graphEditors[index]; graphEditors[index] = tmpEditor; } CheckGraphEditors (); Repaint (); } if (GUILayout.Button (new GUIContent ("Down","Decrease the graph priority"),GUILayout.Width (40))) { int index = script.astarData.GetGraphIndex (graph); // Find the next non null graph int next = index+1; for (;next<script.graphs.Length;next++) if (script.graphs[next] != null) break; if (next < script.graphs.Length) { NavGraph tmp = script.graphs[next]; script.graphs[next] = graph; script.graphs[index] = tmp; GraphEditor tmpEditor = graphEditors[next]; graphEditors[next] = graphEditors[index]; graphEditors[index] = tmpEditor; } CheckGraphEditors (); Repaint (); } } bool drawGizmos = GUILayout.Toggle (graph.drawGizmos, "Draw Gizmos", graphGizmoButtonStyle); if (drawGizmos != graph.drawGizmos) { graph.drawGizmos = drawGizmos; // Make sure that the scene view is repainted when gizmos are toggled on or off RepaintSceneView (); } if (GUILayout.Toggle (graph.infoScreenOpen,"Info",graphInfoButtonStyle)) { if (!graph.infoScreenOpen) { graph.infoScreenOpen = true; graph.open = true; } } else { graph.infoScreenOpen = false; } if (GUILayout.Button ("Delete",graphDeleteButtonStyle)) { RemoveGraph (graph); return true; } GUILayout.EndHorizontal (); if (topFadeArea.Show () ) { EditorGUILayoutx.FadeArea fadeArea = guiLayoutx.BeginFadeArea (graph.infoScreenOpen,"graph_info_"+graphGUIDString,0); if (fadeArea.Show ()) { bool nodenull = false; int total = 0; int numWalkable = 0; KeyValuePair<float,KeyValuePair<int,int>> pair; graphNodeCounts = graphNodeCounts ?? new Dictionary<NavGraph, KeyValuePair<float, KeyValuePair<int, int>>>(); if ( !graphNodeCounts.TryGetValue ( graph, out pair ) || (Time.realtimeSinceStartup-pair.Key) > 2 ) { GraphNodeDelegateCancelable counter = node => { if (node == null) { nodenull = true; } else { total++; if (node.Walkable) numWalkable++; } return true; }; graph.GetNodes (counter); pair = new KeyValuePair<float, KeyValuePair<int, int>> (Time.realtimeSinceStartup, new KeyValuePair<int,int>( total, numWalkable ) ); graphNodeCounts[graph] = pair; } total = pair.Value.Key; numWalkable = pair.Value.Value; EditorGUI.indentLevel++; if (nodenull) { //EditorGUILayout.HelpBox ("Some nodes in the graph are null. Please report this error.", MessageType.Info); Debug.LogError ("Some nodes in the graph are null. Please report this error."); } EditorGUILayout.LabelField ("Nodes",total.ToString()); EditorGUILayout.LabelField ("Walkable",numWalkable.ToString ()); EditorGUILayout.LabelField ("Unwalkable",(total-numWalkable).ToString ()); if (total == 0) EditorGUILayout.HelpBox ("The number of nodes in the graph is zero. The graph might not be scanned",MessageType.Info); EditorGUI.indentLevel--; } guiLayoutx.EndFadeArea (); GUI.color = tmp2; graphEditor.OnInspectorGUI (graph); graphEditor.OnBaseInspectorGUI (graph); } guiLayoutx.EndFadeArea (); return false; }
void RemoveGraph (NavGraph graph) { guiLayoutx.RemoveID (graph.guid.ToString()); script.astarData.RemoveGraph (graph); CheckGraphEditors (); GUI.changed = true; Repaint (); }
public void SerializeGraphs (NavGraph[] _graphs) { if (graphs != null) throw new InvalidOperationException ("Cannot serialize graphs multiple times."); graphs = _graphs; if (zip == null) throw new NullReferenceException ("You must not call CloseSerialize before a call to this function"); if (graphs == null) graphs = new NavGraph[0]; for (int i=0;i<graphs.Length;i++) { //Ignore graph if null if (graphs[i] == null) continue; // Serialize the graph to a byte array byte[] bytes = Serialize(graphs[i]); AddChecksum (bytes); zip.AddEntry ("graph"+i+jsonExt,bytes); } }
/** Removes the specified graph from the #graphs array and Destroys it in a safe manner. * To avoid changing graph indices for the other graphs, the graph is simply nulled in the array instead * of actually removing it from the array. * The empty position will be reused if a new graph is added. * * \returns True if the graph was sucessfully removed (i.e it did exist in the #graphs array). False otherwise. * * * \version Changed in 3.2.5 to call SafeOnDestroy before removing * and nulling it in the array instead of removing the element completely in the #graphs array. * */ public bool RemoveGraph (NavGraph graph) { // Make sure all graph updates and other callbacks are done active.FlushWorkItems (false, true); // Make sure the pathfinding threads are stopped active.BlockUntilPathQueueBlocked (); // //Safe OnDestroy is called since there is a risk that the pathfinding is searching through the graph right now, // //and if we don't wait until the search has completed we could end up with evil NullReferenceExceptions graph.OnDestroy (); int i = System.Array.IndexOf (graphs, graph); if (i == -1) { return false; } graphs[i] = null; UpdateShortcuts (); return true; }
/** Draws common graph settings */ public void OnBaseInspectorGUI (NavGraph target) { int penalty = EditorGUILayout.IntField (new GUIContent ("Initial Penalty","Initial Penalty for nodes in this graph. Set during Scan."),(int)target.initialPenalty); if (penalty < 0) penalty = 0; target.initialPenalty = (uint)penalty; }
/** This performs a linear search through all polygons returning the closest one. * This will fill the NNInfo with .node for the closest node not necessarily complying with the NNConstraint, and .constrainedNode with the closest node * complying with the NNConstraint. * \see GetNearestForce(Node[],Int3[],Vector3,NNConstraint,bool) */ public static NNInfo GetNearestForceBoth (NavGraph graph, INavmeshHolder navmesh, Vector3 position, NNConstraint constraint, bool accurateNearestNode) { var pos = (Int3)position; float minDist = -1; GraphNode minNode = null; float minConstDist = -1; GraphNode minConstNode = null; float maxDistSqr = constraint.constrainDistance ? AstarPath.active.maxNearestNodeDistanceSqr : float.PositiveInfinity; GraphNodeDelegateCancelable del = delegate (GraphNode _node) { var node = _node as TriangleMeshNode; if (accurateNearestNode) { Vector3 closest = node.ClosestPointOnNode (position); float dist = ((Vector3)pos-closest).sqrMagnitude; if (minNode == null || dist < minDist) { minDist = dist; minNode = node; } if (dist < maxDistSqr && constraint.Suitable (node)) { if (minConstNode == null || dist < minConstDist) { minConstDist = dist; minConstNode = node; } } } else { if (!node.ContainsPoint ((Int3)position)) { float dist = (node.position-pos).sqrMagnitude; if (minNode == null || dist < minDist) { minDist = dist; minNode = node; } if (dist < maxDistSqr && constraint.Suitable (node)) { if (minConstNode == null || dist < minConstDist) { minConstDist = dist; minConstNode = node; } } } else { int dist = AstarMath.Abs (node.position.y-pos.y); if (minNode == null || dist < minDist) { minDist = dist; minNode = node; } if (dist < maxDistSqr && constraint.Suitable (node)) { if (minConstNode == null || dist < minConstDist) { minConstDist = dist; minConstNode = node; } } } } return true; }; graph.GetNodes (del); var nninfo = new NNInfo (minNode); //Find the point closest to the nearest triangle if (nninfo.node != null) { var node = nninfo.node as TriangleMeshNode;//minNode2 as MeshNode; Vector3 clP = node.ClosestPointOnNode (position); nninfo.clampedPosition = clP; } nninfo.constrainedNode = minConstNode; if (nninfo.constrainedNode != null) { var node = nninfo.constrainedNode as TriangleMeshNode;//minNode2 as MeshNode; Vector3 clP = node.ClosestPointOnNode (position); nninfo.constClampedPosition = clP; } return nninfo; }
/** This performs a linear search through all polygons returning the closest one */ public static NNInfo GetNearestForce (NavGraph graph, INavmeshHolder navmesh, Vector3 position, NNConstraint constraint, bool accurateNearestNode) { NNInfo nn = GetNearestForceBoth (graph, navmesh,position,constraint,accurateNearestNode); nn.node = nn.constrainedNode; nn.clampedPosition = nn.constClampedPosition; return nn; }
/** Deserializes graph settings. * \note Stored in files named "graph#.json" where # is the graph number. */ public NavGraph[] DeserializeGraphs () { // Allocate a list of graphs to be deserialized graphs = new NavGraph[meta.graphs]; int nonNull = 0; for (int i=0;i<meta.graphs;i++) { // Get the graph type from the metadata we deserialized earlier var tp = meta.GetGraphType(i); // Graph was null when saving, ignore if (System.Type.Equals (tp, null)) continue; nonNull++; var entry = zip["graph"+i+jsonExt]; if (entry == null) throw new FileNotFoundException ("Could not find data for graph "+i+" in zip. Entry 'graph+"+i+jsonExt+"' does not exist"); // Create a new graph of the right type NavGraph graph = data.CreateGraph(tp); graph.graphIndex = (uint)(i + graphIndexOffset); #if !ASTAR_NO_JSON var entryText = GetString(entry); var reader = new JsonReader(entryText,readerSettings); reader.PopulateObject (ref graph); #else var mem = new MemoryStream (); entry.Extract(mem); mem.Position = 0; var reader = new BinaryReader (mem); var ctx = new GraphSerializationContext(reader, null, i + graphIndexOffset); graph.DeserializeSettings (ctx); #endif graphs[i] = graph; if (graphs[i].guid.ToString () != meta.guids[i]) throw new Exception ("Guid in graph file not equal to guid defined in meta file. Have you edited the data manually?\n"+graphs[i].guid+" != "+meta.guids[i]); } // Remove any null entries from the list var compressed = new NavGraph[nonNull]; nonNull = 0; for ( int i=0;i<graphs.Length;i++) { if ( graphs[i] != null ) { compressed[nonNull] = graphs[i]; nonNull++; } } graphs = compressed; return graphs; }
/** Serializes the graph settings to JSON and returns the data */ public byte[] Serialize (NavGraph graph) { #if !ASTAR_NO_JSON // Grab a cached string builder to avoid allocations var output = GetStringBuilder (); var writer = new JsonWriter (output,writerSettings); writer.Write (graph); return encoding.GetBytes (output.ToString()); #else var mem = new System.IO.MemoryStream(); var writer = new System.IO.BinaryWriter(mem); var ctx = new GraphSerializationContext (writer); graph.SerializeSettings (ctx); return mem.ToArray(); #endif }
/** Returns whether or not the graph conforms to this NNConstraint's rules. * Note that only the first 31 graphs are considered using this function. * If the graphMask has bit 31 set (i.e the last graph possible to fit in the mask), all graphs * above index 31 will also be considered suitable. */ public virtual bool SuitableGraph (int graphIndex, NavGraph graph) { return ((graphMask >> graphIndex) & 1) != 0; }
/** Adds obstacles for a graph */ public void AddGraphObstacles (Simulator sim, NavGraph graph) { if (obstacles.Count > 0 && lastSim != null && lastSim != sim) { Debug.LogError ("Simulator has changed but some old obstacles are still added for the previous simulator. Deleting previous obstacles."); RemoveObstacles (); } //Remember which simulator these obstacles were added to lastSim = sim; INavmesh ng = graph as INavmesh; if (ng == null) return; //Assume less than 20 vertices per node (actually assumes 3, but I will change that some day) int[] uses = new int[20]; ng.GetNodes (delegate(GraphNode _node) { TriangleMeshNode node = _node as TriangleMeshNode; uses[0] = uses[1] = uses[2] = 0; if (node != null) { //Find out which edges are shared with other nodes for (int j=0;j<node.connections.Length;j++) { TriangleMeshNode other = node.connections[j] as TriangleMeshNode; // Not necessarily a TriangleMeshNode if (other != null) { int a = node.SharedEdge(other); if (a != -1) uses[a] = 1; } } //Loop through all edges on the node for (int j=0;j<3;j++) { //The edge is not shared with any other node //I.e it is an exterior edge on the mesh if (uses[j] == 0) { //The two vertices of the edge Vector3 v1 = (Vector3)node.GetVertex(j); Vector3 v2 = (Vector3)node.GetVertex((j+1) % node.GetVertexCount()); //I think node vertices always should be clockwise, but it's good to be certain /*if (!Polygon.IsClockwise (v1,v2,(Vector3)node.GetVertex((j+2) % node.GetVertexCount()))) { Vector3 tmp = v2; v2 = v1; v1 = tmp; }*/ #if ASTARDEBUG Debug.DrawLine (v1,v2,Color.red); Debug.DrawRay (v1,Vector3.up*wallHeight,Color.red); #endif //Find out the height of the wall/obstacle we are about to add float height = System.Math.Abs(v1.y-v2.y); height = System.Math.Max (height,5); //Enqueue the edge as a line obstacle obstacles.Add (sim.AddObstacle (v1, v2, wallHeight)); } } } return true; }); }
/** Override to implement graph inspectors */ public virtual void OnInspectorGUI (NavGraph target) { }
/** Adds the specified graph to the #graphs array */ public void AddGraph (NavGraph graph) { // Make sure to not interfere with pathfinding AstarPath.active.BlockUntilPathQueueBlocked(); //Try to fill in an empty position for (int i=0;i<graphs.Length;i++) { if (graphs[i] == null) { graphs[i] = graph; graph.active = active; graph.Awake (); graph.graphIndex = (uint)i; UpdateShortcuts (); return; } } if (graphs != null && graphs.Length >= GraphNode.MaxGraphIndex) { throw new System.Exception("Graph Count Limit Reached. You cannot have more than " + GraphNode.MaxGraphIndex + " graphs. Some compiler directives can change this limit, e.g ASTAR_MORE_AREAS, look under the " + "'Optimizations' tab in the A* Inspector"); } //Enqueue a new entry to the list var ls = new List<NavGraph> (graphs); ls.Add (graph); graphs = ls.ToArray (); UpdateShortcuts (); graph.active = active; graph.Awake (); graph.graphIndex = (uint)(graphs.Length-1); }
/** Override to implement scene GUI drawing for the graph */ public virtual void OnSceneGUI (NavGraph target) { }
/** Gets the index of the NavGraph in the #graphs array */ public int GetGraphIndex (NavGraph graph) { if (graph == null) throw new System.ArgumentNullException ("graph"); if ( graphs != null ) { for (int i=0;i<graphs.Length;i++) { if (graph == graphs[i]) { return i; } } } Debug.LogError ("Graph doesn't exist"); return -1; }
public override void OnInspectorGUI (NavGraph target) { var graph = target as RecastGraph; bool preEnabled = GUI.enabled; System.Int64 estWidth = Mathf.RoundToInt (Mathf.Ceil (graph.forcedBoundsSize.x / graph.cellSize)); System.Int64 estDepth = Mathf.RoundToInt (Mathf.Ceil (graph.forcedBoundsSize.z / graph.cellSize)); // Show a warning if the number of voxels is too large if (estWidth*estDepth >= 1024*1024 || estDepth >= 1024*1024 || estWidth >= 1024*1024) { GUIStyle helpBox = GUI.skin.FindStyle ("HelpBox") ?? GUI.skin.FindStyle ("Box"); Color preColor = GUI.color; if (estWidth*estDepth >= 2048*2048 || estDepth >= 2048*2048 || estWidth >= 2048*2048) { GUI.color = Color.red; } else { GUI.color = Color.yellow; } GUILayout.Label ("Warning : Might take some time to calculate",helpBox); GUI.color = preColor; } GUI.enabled = false; EditorGUILayout.LabelField ("Width (voxels)",estWidth.ToString ()); EditorGUILayout.LabelField ("Depth (voxels)",estDepth.ToString ()); GUI.enabled = preEnabled; graph.cellSize = EditorGUILayout.FloatField (new GUIContent ("Cell Size","Size of one voxel in world units"),graph.cellSize); if (graph.cellSize < 0.001F) graph.cellSize = 0.001F; graph.cellHeight = EditorGUILayout.FloatField (new GUIContent ("Cell Height","Height of one voxel in world units"),graph.cellHeight); if (graph.cellHeight < 0.001F) graph.cellHeight = 0.001F; graph.useTiles = (UseTiles)EditorGUILayout.EnumPopup ("Use Tiles", graph.useTiles?UseTiles.UseTiles:UseTiles.DontUseTiles) == UseTiles.UseTiles; if (graph.useTiles) { EditorGUI.indentLevel++; graph.editorTileSize = EditorGUILayout.IntField (new GUIContent ("Tile Size", "Size in voxels of a single tile.\n" + "This is the width of the tile.\n" + "\n" + "A large tile size can be faster to initially scan (but beware of out of memory issues if you try with a too large tile size in a large world)\n" + "smaller tile sizes are (much) faster to update.\n" + "\n" + "Different tile sizes can affect the quality of paths. It is often good to split up huge open areas into several tiles for\n" + "better quality paths, but too small tiles can lead to effects looking like invisible obstacles."), graph.editorTileSize); EditorGUI.indentLevel--; } graph.minRegionSize = EditorGUILayout.FloatField (new GUIContent ("Min Region Size", "Small regions will be removed. In square world units"), graph.minRegionSize); graph.walkableHeight = EditorGUILayout.FloatField (new GUIContent ("Walkable Height","Minimum distance to the roof for an area to be walkable"),graph.walkableHeight); graph.walkableHeight = Mathf.Max (graph.walkableHeight, 0); graph.walkableClimb = EditorGUILayout.FloatField (new GUIContent ("Walkable Climb","How high can the character climb"),graph.walkableClimb); // A walkableClimb higher than this can cause issues when generating the navmesh since then it can in some cases // Both be valid for a character to walk under an obstacle and climb up on top of it (and that cannot be handled with a navmesh without links) if (graph.walkableClimb >= graph.walkableHeight) { graph.walkableClimb = graph.walkableHeight; EditorGUILayout.HelpBox ("Walkable climb should be less than walkable height. Clamping to " + graph.walkableHeight+".",MessageType.Warning); } else if (graph.walkableClimb < 0) { graph.walkableClimb = 0; } graph.characterRadius = EditorGUILayout.FloatField (new GUIContent ("Character Radius","Radius of the character. It's good to add some margin.\nIn world units."),graph.characterRadius); graph.characterRadius = Mathf.Max (graph.characterRadius, 0); graph.maxSlope = EditorGUILayout.Slider (new GUIContent ("Max Slope","Approximate maximum slope"),graph.maxSlope,0F,90F); graph.maxEdgeLength = EditorGUILayout.FloatField (new GUIContent ("Max Edge Length","Maximum length of one edge in the completed navmesh before it is split. A lower value can often yield better quality graphs"),graph.maxEdgeLength); graph.maxEdgeLength = graph.maxEdgeLength < graph.cellSize ? graph.cellSize : graph.maxEdgeLength; graph.contourMaxError = EditorGUILayout.FloatField (new GUIContent ("Max Edge Error","Amount of simplification to apply to edges.\nIn world units."),graph.contourMaxError); graph.rasterizeTerrain = EditorGUILayout.Toggle (new GUIContent ("Rasterize Terrain","Should a rasterized terrain be included"), graph.rasterizeTerrain); if (graph.rasterizeTerrain) { EditorGUI.indentLevel++; graph.rasterizeTrees = EditorGUILayout.Toggle (new GUIContent ("Rasterize Trees", "Rasterize tree colliders on terrains. " + "If the tree prefab has a collider, that collider will be rasterized. " + "Otherwise a simple box collider will be used and the script will " + "try to adjust it to the tree's scale, it might not do a very good job though so " + "an attached collider is preferable."), graph.rasterizeTrees); if (graph.rasterizeTrees) { EditorGUI.indentLevel++; graph.colliderRasterizeDetail = EditorGUILayout.FloatField (new GUIContent ("Collider Detail", "Controls the detail of the generated collider meshes. "+ "Increasing does not necessarily yield better navmeshes, but lowering will speed up scan.\n"+ "Spheres and capsule colliders will be converted to meshes in order to be able to rasterize them, a higher value will increase the number of triangles in those meshes."), graph.colliderRasterizeDetail); EditorGUI.indentLevel--; } graph.terrainSampleSize = EditorGUILayout.IntField (new GUIContent ("Terrain Sample Size","Size of terrain samples. A lower value is better, but slower"), graph.terrainSampleSize); graph.terrainSampleSize = graph.terrainSampleSize < 1 ? 1 : graph.terrainSampleSize;//Clamp to at least 1 EditorGUI.indentLevel--; } graph.rasterizeMeshes = EditorGUILayout.Toggle (new GUIContent ("Rasterize Meshes", "Should meshes be rasterized and used for building the navmesh"), graph.rasterizeMeshes); graph.rasterizeColliders = EditorGUILayout.Toggle (new GUIContent ("Rasterize Colliders", "Should colliders be rasterized and used for building the navmesh"), graph.rasterizeColliders); if (graph.rasterizeColliders) { EditorGUI.indentLevel++; graph.colliderRasterizeDetail = EditorGUILayout.FloatField (new GUIContent ("Collider Detail", "Controls the detail of the generated collider meshes. "+ "Increasing does not necessarily yield better navmeshes, but lowering will speed up scan.\n"+ "Spheres and capsule colliders will be converted to meshes in order to be able to rasterize them, a higher value will increase the number of triangles in those meshes."), graph.colliderRasterizeDetail); EditorGUI.indentLevel--; } Separator (); graph.forcedBoundsCenter = EditorGUILayout.Vector3Field ("Center",graph.forcedBoundsCenter); graph.forcedBoundsSize = EditorGUILayout.Vector3Field ("Size",graph.forcedBoundsSize); if (GUILayout.Button (new GUIContent ("Snap bounds to scene","Will snap the bounds of the graph to exactly contain all meshes that the bounds currently touches"))) { graph.SnapForceBoundsToScene (); GUI.changed = true; } Separator (); EditorGUILayout.HelpBox ("Objects contained in any of these masks will be rasterized", MessageType.None); graph.mask = EditorGUILayoutx.LayerMaskField ("Layer Mask",graph.mask); tagMaskFoldout = EditorGUILayoutx.UnityTagMaskList (new GUIContent("Tag Mask"), tagMaskFoldout, graph.tagMask); Separator (); graph.showMeshOutline = EditorGUILayout.Toggle (new GUIContent ("Show mesh outline","Toggles gizmos for drawing an outline of the mesh"),graph.showMeshOutline); graph.showNodeConnections = EditorGUILayout.Toggle (new GUIContent ("Show node connections","Toggles gizmos for drawing node connections"),graph.showNodeConnections); if (GUILayout.Button ("Export to .obj file")) { ExportToFile (graph); } Separator (); GUILayout.Label (new GUIContent ("Advanced"), EditorStyles.boldLabel); graph.relevantGraphSurfaceMode = (RecastGraph.RelevantGraphSurfaceMode)EditorGUILayout.EnumPopup (new GUIContent ("Relevant Graph Surface Mode", "Require every region to have a RelevantGraphSurface component inside it.\n" + "A RelevantGraphSurface component placed in the scene specifies that\n" + "the navmesh region it is inside should be included in the navmesh.\n\n" + "If this is set to OnlyForCompletelyInsideTile\n" + "a navmesh region is included in the navmesh if it\n" + "has a RelevantGraphSurface inside it, or if it\n" + "is adjacent to a tile border. This can leave some small regions\n" + "which you didn't want to have included because they are adjacent\n" + "to tile borders, but it removes the need to place a component\n" + "in every single tile, which can be tedious (see below).\n\n" + "If this is set to RequireForAll\n" + "a navmesh region is included only if it has a RelevantGraphSurface\n" + "inside it. Note that even though the navmesh\n" + "looks continous between tiles, the tiles are computed individually\n" + "and therefore you need a RelevantGraphSurface component for each\n" + "region and for each tile."), graph.relevantGraphSurfaceMode); graph.nearestSearchOnlyXZ = EditorGUILayout.Toggle (new GUIContent ("Nearest node queries in XZ space", "Recomended for single-layered environments.\nFaster but can be inacurate esp. in multilayered contexts."), graph.nearestSearchOnlyXZ); }