NodeLink2.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. namespace Pathfinding {
  4. using Pathfinding.Util;
  5. /// <summary>
  6. /// Connects two nodes via two intermediate point nodes.
  7. /// In contrast to the NodeLink component, this link type will not connect the nodes directly
  8. /// instead it will create two point nodes at the start and end position of this link and connect
  9. /// through those nodes.
  10. ///
  11. /// If the closest node to this object is called A and the closest node to the end transform is called
  12. /// D, then it will create one point node at this object's position (call it B) and one point node at
  13. /// the position of the end transform (call it C), it will then connect A to B, B to C and C to D.
  14. ///
  15. /// This link type is possible to detect while following since it has these special point nodes in the middle.
  16. /// The link corresponding to one of those intermediate nodes can be retrieved using the <see cref="GetNodeLink"/> method
  17. /// which can be of great use if you want to, for example, play a link specific animation when reaching the link.
  18. ///
  19. /// See: The example scene RecastExample2 contains a few links which you can take a look at to see how they are used.
  20. /// </summary>
  21. [AddComponentMenu("Pathfinding/Link2")]
  22. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_node_link2.php")]
  23. public class NodeLink2 : GraphModifier {
  24. protected static Dictionary<GraphNode, NodeLink2> reference = new Dictionary<GraphNode, NodeLink2>();
  25. public static NodeLink2 GetNodeLink (GraphNode node) {
  26. NodeLink2 v;
  27. reference.TryGetValue(node, out v);
  28. return v;
  29. }
  30. /// <summary>End position of the link</summary>
  31. public Transform end;
  32. /// <summary>
  33. /// The connection will be this times harder/slower to traverse.
  34. /// Note that values lower than 1 will not always make the pathfinder choose this path instead of another path even though this one should
  35. /// lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether.
  36. /// </summary>
  37. public float costFactor = 1.0f;
  38. /// <summary>Make a one-way connection</summary>
  39. public bool oneWay = false;
  40. public Transform StartTransform {
  41. get { return transform; }
  42. }
  43. public Transform EndTransform {
  44. get { return end; }
  45. }
  46. public PointNode startNode { get; private set; }
  47. public PointNode endNode { get; private set; }
  48. GraphNode connectedNode1, connectedNode2;
  49. Vector3 clamped1, clamped2;
  50. bool postScanCalled = false;
  51. [System.Obsolete("Use startNode instead (lowercase s)")]
  52. public GraphNode StartNode {
  53. get { return startNode; }
  54. }
  55. [System.Obsolete("Use endNode instead (lowercase e)")]
  56. public GraphNode EndNode {
  57. get { return endNode; }
  58. }
  59. public override void OnPostScan () {
  60. InternalOnPostScan();
  61. }
  62. public void InternalOnPostScan () {
  63. if (EndTransform == null || StartTransform == null) return;
  64. #if ASTAR_NO_POINT_GRAPH
  65. throw new System.Exception("Point graph is not included. Check your A* optimization settings.");
  66. #else
  67. if (AstarPath.active.data.pointGraph == null) {
  68. var graph = AstarPath.active.data.AddGraph(typeof(PointGraph)) as PointGraph;
  69. graph.name = "PointGraph (used for node links)";
  70. }
  71. if (startNode != null && startNode.Destroyed) {
  72. reference.Remove(startNode);
  73. startNode = null;
  74. }
  75. if (endNode != null && endNode.Destroyed) {
  76. reference.Remove(endNode);
  77. endNode = null;
  78. }
  79. // Create new nodes on the point graph
  80. if (startNode == null) startNode = AstarPath.active.data.pointGraph.AddNode((Int3)StartTransform.position);
  81. if (endNode == null) endNode = AstarPath.active.data.pointGraph.AddNode((Int3)EndTransform.position);
  82. connectedNode1 = null;
  83. connectedNode2 = null;
  84. if (startNode == null || endNode == null) {
  85. startNode = null;
  86. endNode = null;
  87. return;
  88. }
  89. postScanCalled = true;
  90. reference[startNode] = this;
  91. reference[endNode] = this;
  92. Apply(true);
  93. #endif
  94. }
  95. public override void OnGraphsPostUpdate () {
  96. // Don't bother running it now since OnPostScan will be called later anyway
  97. if (AstarPath.active.isScanning)
  98. return;
  99. if (connectedNode1 != null && connectedNode1.Destroyed) {
  100. connectedNode1 = null;
  101. }
  102. if (connectedNode2 != null && connectedNode2.Destroyed) {
  103. connectedNode2 = null;
  104. }
  105. if (!postScanCalled) {
  106. OnPostScan();
  107. } else {
  108. Apply(false);
  109. }
  110. }
  111. protected override void OnEnable () {
  112. base.OnEnable();
  113. #if !ASTAR_NO_POINT_GRAPH
  114. if (Application.isPlaying && AstarPath.active != null && AstarPath.active.data != null && AstarPath.active.data.pointGraph != null && !AstarPath.active.isScanning) {
  115. // Call OnGraphsPostUpdate as soon as possible when it is safe to update the graphs
  116. AstarPath.active.AddWorkItem(OnGraphsPostUpdate);
  117. }
  118. #endif
  119. }
  120. protected override void OnDisable () {
  121. base.OnDisable();
  122. postScanCalled = false;
  123. if (startNode != null) reference.Remove(startNode);
  124. if (endNode != null) reference.Remove(endNode);
  125. if (startNode != null && endNode != null) {
  126. startNode.RemoveConnection(endNode);
  127. endNode.RemoveConnection(startNode);
  128. if (connectedNode1 != null && connectedNode2 != null) {
  129. startNode.RemoveConnection(connectedNode1);
  130. connectedNode1.RemoveConnection(startNode);
  131. endNode.RemoveConnection(connectedNode2);
  132. connectedNode2.RemoveConnection(endNode);
  133. }
  134. }
  135. }
  136. void RemoveConnections (GraphNode node) {
  137. //TODO, might be better to replace connection
  138. node.ClearConnections(true);
  139. }
  140. [ContextMenu("Recalculate neighbours")]
  141. void ContextApplyForce () {
  142. if (Application.isPlaying) {
  143. Apply(true);
  144. }
  145. }
  146. public void Apply (bool forceNewCheck) {
  147. //TODO
  148. //This function assumes that connections from the n1,n2 nodes never need to be removed in the future (e.g because the nodes move or something)
  149. NNConstraint nn = NNConstraint.None;
  150. int graph = (int)startNode.GraphIndex;
  151. //Search all graphs but the one which start and end nodes are on
  152. nn.graphMask = ~(1 << graph);
  153. startNode.SetPosition((Int3)StartTransform.position);
  154. endNode.SetPosition((Int3)EndTransform.position);
  155. RemoveConnections(startNode);
  156. RemoveConnections(endNode);
  157. uint cost = (uint)Mathf.RoundToInt(((Int3)(StartTransform.position-EndTransform.position)).costMagnitude*costFactor);
  158. startNode.AddConnection(endNode, cost);
  159. endNode.AddConnection(startNode, cost);
  160. if (connectedNode1 == null || forceNewCheck) {
  161. var info = AstarPath.active.GetNearest(StartTransform.position, nn);
  162. connectedNode1 = info.node;
  163. clamped1 = info.position;
  164. }
  165. if (connectedNode2 == null || forceNewCheck) {
  166. var info = AstarPath.active.GetNearest(EndTransform.position, nn);
  167. connectedNode2 = info.node;
  168. clamped2 = info.position;
  169. }
  170. if (connectedNode2 == null || connectedNode1 == null) return;
  171. //Add connections between nodes, or replace old connections if existing
  172. connectedNode1.AddConnection(startNode, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor));
  173. if (!oneWay) connectedNode2.AddConnection(endNode, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor));
  174. if (!oneWay) startNode.AddConnection(connectedNode1, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor));
  175. endNode.AddConnection(connectedNode2, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor));
  176. }
  177. private readonly static Color GizmosColor = new Color(206.0f/255.0f, 136.0f/255.0f, 48.0f/255.0f, 0.5f);
  178. private readonly static Color GizmosColorSelected = new Color(235.0f/255.0f, 123.0f/255.0f, 32.0f/255.0f, 1.0f);
  179. public virtual void OnDrawGizmosSelected () {
  180. OnDrawGizmos(true);
  181. }
  182. public void OnDrawGizmos () {
  183. OnDrawGizmos(false);
  184. }
  185. public void OnDrawGizmos (bool selected) {
  186. Color color = selected ? GizmosColorSelected : GizmosColor;
  187. if (StartTransform != null) {
  188. Draw.Gizmos.CircleXZ(StartTransform.position, 0.4f, color);
  189. }
  190. if (EndTransform != null) {
  191. Draw.Gizmos.CircleXZ(EndTransform.position, 0.4f, color);
  192. }
  193. if (StartTransform != null && EndTransform != null) {
  194. Draw.Gizmos.Bezier(StartTransform.position, EndTransform.position, color);
  195. if (selected) {
  196. Vector3 cross = Vector3.Cross(Vector3.up, (EndTransform.position-StartTransform.position)).normalized;
  197. Draw.Gizmos.Bezier(StartTransform.position+cross*0.1f, EndTransform.position+cross*0.1f, color);
  198. Draw.Gizmos.Bezier(StartTransform.position-cross*0.1f, EndTransform.position-cross*0.1f, color);
  199. }
  200. }
  201. }
  202. internal static void SerializeReferences (Pathfinding.Serialization.GraphSerializationContext ctx) {
  203. var links = GetModifiersOfType<NodeLink2>();
  204. ctx.writer.Write(links.Count);
  205. foreach (var link in links) {
  206. ctx.writer.Write(link.uniqueID);
  207. ctx.SerializeNodeReference(link.startNode);
  208. ctx.SerializeNodeReference(link.endNode);
  209. ctx.SerializeNodeReference(link.connectedNode1);
  210. ctx.SerializeNodeReference(link.connectedNode2);
  211. ctx.SerializeVector3(link.clamped1);
  212. ctx.SerializeVector3(link.clamped2);
  213. ctx.writer.Write(link.postScanCalled);
  214. }
  215. }
  216. internal static void DeserializeReferences (Pathfinding.Serialization.GraphSerializationContext ctx) {
  217. int count = ctx.reader.ReadInt32();
  218. for (int i = 0; i < count; i++) {
  219. var linkID = ctx.reader.ReadUInt64();
  220. var startNode = ctx.DeserializeNodeReference();
  221. var endNode = ctx.DeserializeNodeReference();
  222. var connectedNode1 = ctx.DeserializeNodeReference();
  223. var connectedNode2 = ctx.DeserializeNodeReference();
  224. var clamped1 = ctx.DeserializeVector3();
  225. var clamped2 = ctx.DeserializeVector3();
  226. var postScanCalled = ctx.reader.ReadBoolean();
  227. GraphModifier link;
  228. if (usedIDs.TryGetValue(linkID, out link)) {
  229. var link2 = link as NodeLink2;
  230. if (link2 != null) {
  231. if (startNode != null) reference[startNode] = link2;
  232. if (endNode != null) reference[endNode] = link2;
  233. // If any nodes happened to be registered right now
  234. if (link2.startNode != null) reference.Remove(link2.startNode);
  235. if (link2.endNode != null) reference.Remove(link2.endNode);
  236. link2.startNode = startNode as PointNode;
  237. link2.endNode = endNode as PointNode;
  238. link2.connectedNode1 = connectedNode1;
  239. link2.connectedNode2 = connectedNode2;
  240. link2.postScanCalled = postScanCalled;
  241. link2.clamped1 = clamped1;
  242. link2.clamped2 = clamped2;
  243. } else {
  244. throw new System.Exception("Tried to deserialize a NodeLink2 reference, but the link was not of the correct type or it has been destroyed.\nIf a NodeLink2 is included in serialized graph data, the same NodeLink2 component must be present in the scene when loading the graph data.");
  245. }
  246. } else {
  247. throw new System.Exception("Tried to deserialize a NodeLink2 reference, but the link could not be found in the scene.\nIf a NodeLink2 is included in serialized graph data, the same NodeLink2 component must be present in the scene when loading the graph data.");
  248. }
  249. }
  250. }
  251. }
  252. }