summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts
diff options
context:
space:
mode:
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DocumentationButton.cs19
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DocumentationButton.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DoorController.cs59
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DoorController.cs.meta7
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor.meta8
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef52
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef.meta7
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/InteractableEditor.cs132
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/InteractableEditor.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/FollowerJumpLink.cs65
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/FollowerJumpLink.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/HighlightOnHover.cs21
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/HighlightOnHover.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs323
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ManualRVOAgent.cs38
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ManualRVOAgent.cs.meta12
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MecanimBridge2D.cs90
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MecanimBridge2D.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAI.cs54
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAI.cs.meta7
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAnimation.cs109
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAnimation.cs.meta12
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MinimumUnityVersionWarning.cs59
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MinimumUnityVersionWarning.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/NavmeshClamp.cs54
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/NavmeshClamp.cs.meta7
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ObjectPlacer.cs88
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ObjectPlacer.cs.meta7
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdate.cs50
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdate.cs.meta8
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs161
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs.meta8
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SmoothCameraFollow.cs37
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SmoothCameraFollow.cs.meta8
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SnapToNode.cs25
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SnapToNode.cs.meta12
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs189
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs.meta7
39 files changed, 1812 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DocumentationButton.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DocumentationButton.cs
new file mode 100644
index 0000000..f771b84
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DocumentationButton.cs
@@ -0,0 +1,19 @@
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ [ExecuteInEditMode]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/documentationbutton.html")]
+ public class DocumentationButton : MonoBehaviour {
+ public string page;
+
+ const string UrlBase = "https://arongranberg.com/astar/docs/";
+
+#if UNITY_EDITOR
+ void OnGUI () {
+ if (GUI.Button(new Rect(Screen.width - 250, Screen.height - 60, 240, 50), "Example Scene Documentation")) {
+ Application.OpenURL(UrlBase + page + ".html");
+ }
+ }
+#endif
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DocumentationButton.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DocumentationButton.cs.meta
new file mode 100644
index 0000000..9a420b4
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DocumentationButton.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3a705f1fd95105f4582d18bf472edc13
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DoorController.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DoorController.cs
new file mode 100644
index 0000000..7056294
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DoorController.cs
@@ -0,0 +1,59 @@
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ /// <summary>Example script used in the example scenes</summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/doorcontroller.html")]
+ public class DoorController : MonoBehaviour {
+ private bool open = false;
+
+ public PathfindingTag opentag = 1;
+ public PathfindingTag closedtag = 1;
+ public bool updateGraphsWithGUO = true;
+ public float yOffset = 5;
+
+ Bounds bounds;
+
+ public void Start () {
+ // Capture the bounds of the collider while it is closed
+ bounds = GetComponent<Collider>().bounds;
+
+ // Initially open the door
+ SetState(open);
+ }
+
+ void OnGUI () {
+ // Show a UI button for opening and closing the door
+ if (GUI.Button(new Rect(5, yOffset, 100, 22), "Toggle Door")) {
+ SetState(!open);
+ }
+ }
+
+ public void SetState (bool open) {
+ this.open = open;
+
+ if (updateGraphsWithGUO) {
+ // Update the graph below the door
+ // Set the tag of the nodes below the door
+ // To something indicating that the door is open or closed
+ GraphUpdateObject guo = new GraphUpdateObject(bounds);
+ var tag = open ? opentag : closedtag;
+
+ // There are only 32 tags
+ if (tag > 31) { Debug.LogError("tag > 31"); return; }
+
+ guo.modifyTag = true;
+ guo.setTag = tag;
+ guo.updatePhysics = false;
+
+ AstarPath.active.UpdateGraphs(guo);
+ }
+
+ // Play door animations
+ if (open) {
+ GetComponent<Animation>().Play("Open");
+ } else {
+ GetComponent<Animation>().Play("Close");
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DoorController.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DoorController.cs.meta
new file mode 100644
index 0000000..84b5992
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/DoorController.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e96422fbb088f477baaf6f2ada396863
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor.meta
new file mode 100644
index 0000000..a8ec202
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d8f8f1c1cdcd4c849af88d75ed47987e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef
new file mode 100644
index 0000000..637de03
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef
@@ -0,0 +1,52 @@
+{
+ "name": "AstarPathfindingProjectExamplesEditor",
+ "rootNamespace": "",
+ "references": [
+ "GUID:efa45043feb7e4147a305b73b5cea642",
+ "GUID:774e21169c4ac4ec8a01db9cdb98d33b",
+ "GUID:f4059aaf6c60a4a58a177a2609feb769",
+ "GUID:de4e6084e6d474788bb8c799d6b461eb",
+ "GUID:9fc7298e05e4fb644bfd10f41ae01ac3"
+ ],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": true,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [
+ "MODULE_BURST",
+ "MODULE_MATHEMATICS",
+ "MODULE_COLLECTIONS"
+ ],
+ "versionDefines": [
+ {
+ "name": "com.unity.burst",
+ "expression": "1.8.3",
+ "define": "MODULE_BURST"
+ },
+ {
+ "name": "com.unity.mathematics",
+ "expression": "1.2.6",
+ "define": "MODULE_MATHEMATICS"
+ },
+ {
+ "name": "com.unity.collections",
+ "expression": "1.5.1",
+ "define": "MODULE_COLLECTIONS"
+ },
+ {
+ "name": "com.unity.collections",
+ "expression": "0.11-preview",
+ "define": "MODULE_COLLECTIONS_0_11_OR_NEWER"
+ },
+ {
+ "name": "com.unity.entities",
+ "expression": "1.0.0-pre.47",
+ "define": "MODULE_ENTITIES"
+ }
+ ],
+ "noEngineReferences": false
+} \ No newline at end of file
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef.meta
new file mode 100644
index 0000000..8fef3ce
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f5d2c271bbce11444b0479e9cf877d6d
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/InteractableEditor.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/InteractableEditor.cs
new file mode 100644
index 0000000..321b8ab
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/InteractableEditor.cs
@@ -0,0 +1,132 @@
+using UnityEngine;
+using UnityEditor;
+using UnityEditorInternal;
+using Pathfinding.Examples;
+using System.Collections.Generic;
+using UnityEngine.AI;
+
+namespace Pathfinding.Examples {
+ [CustomEditor(typeof(Interactable))]
+ [CanEditMultipleObjects]
+ public class InteractableEditor : EditorBase {
+ ReorderableList actions;
+
+ static Rect SliceRow (ref Rect rect, float height) {
+ return GUIUtilityx.SliceRow(ref rect, height);
+ }
+
+ protected override void OnEnable () {
+ base.OnEnable();
+ actions = new ReorderableList(serializedObject, serializedObject.FindProperty("actions"), true, true, true, true);
+ actions.drawElementCallback = (Rect rect, int index, bool active, bool isFocused) => {
+ var item = actions.serializedProperty.GetArrayElementAtIndex(index);
+ var ob = item.managedReferenceValue as Interactable.InteractableAction;
+ if (ob == null) {
+ EditorGUI.LabelField(rect, "Null");
+ return;
+ }
+ var tp = ob.GetType();
+
+ var lineHeight = EditorGUIUtility.singleLineHeight;
+ if (tp == typeof(Interactable.AnimatorSetBoolAction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Set Animator Property", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("animator"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("propertyName"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("value"));
+ } else if (tp == typeof(Interactable.AnimatorPlay)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Play Animator State", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("animator"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("stateName"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("normalizedTime"));
+ } else if (tp == typeof(Interactable.ActivateParticleSystem)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Activate Particle System", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("particleSystem"));
+ } else if (tp == typeof(Interactable.DelayAction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Delay", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("delay"));
+ } else if (tp == typeof(Interactable.SetObjectActiveAction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Set Object Active", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("target"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("active"));
+ } else if (tp == typeof(Interactable.TeleportAgentAction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Teleport Agent", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("destination"));
+ } else if (tp == typeof(Interactable.TeleportAgentOnLinkAction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Teleport Agent on Off-Mesh Link", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("destination"));
+ } else if (tp == typeof(Interactable.SetTransformAction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Set Transform", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("transform"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("source"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("setPosition"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("setRotation"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("setScale"));
+ } else if (tp == typeof(Interactable.MoveToAction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Move To", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("destination"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("useRotation"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("waitUntilReached"));
+ } else if (tp == typeof(Interactable.InstantiatePrefab)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Instantiate Prefab", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("prefab"));
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("position"));
+ } else if (tp == typeof(Interactable.CallFunction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Call Function", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("function"));
+ } else if (tp == typeof(Interactable.InteractAction)) {
+ EditorGUI.LabelField(SliceRow(ref rect, lineHeight), "Interact", EditorStyles.boldLabel);
+ EditorGUI.PropertyField(SliceRow(ref rect, lineHeight), item.FindPropertyRelative("interactable"));
+ }
+ };
+ actions.elementHeightCallback = (int index) => {
+ var actions = (target as Interactable).actions;
+ var tp = index < actions.Count ? actions[index]?.GetType() : null;
+ var h = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+ if (tp == null) return h;
+ else if (tp == typeof(Interactable.AnimatorSetBoolAction)) return 4*h;
+ else if (tp == typeof(Interactable.AnimatorPlay)) return 4*h;
+ else if (tp == typeof(Interactable.ActivateParticleSystem)) return 2*h;
+ else if (tp == typeof(Interactable.DelayAction)) return 2*h;
+ else if (tp == typeof(Interactable.SetObjectActiveAction)) return 3*h;
+ else if (tp == typeof(Interactable.TeleportAgentAction)) return 2*h;
+ else if (tp == typeof(Interactable.TeleportAgentOnLinkAction)) return 2*h;
+ else if (tp == typeof(Interactable.SetTransformAction)) return 6*h;
+ else if (tp == typeof(Interactable.MoveToAction)) return 4*h;
+ else if (tp == typeof(Interactable.InstantiatePrefab)) return 3*h;
+ else if (tp == typeof(Interactable.InteractAction)) return 2*h;
+ else if (tp == typeof(Interactable.CallFunction)) {
+ return (3.5f + Mathf.Max(1, (actions[index] as Interactable.CallFunction).function.GetPersistentEventCount())*2.5f) * h;
+ } else throw new System.Exception("Unexpected type " + tp);
+ };
+ actions.drawHeaderCallback = (Rect rect) => {
+ EditorGUI.LabelField(rect, "Actions");
+ };
+ actions.onAddDropdownCallback = (rect, _) => {
+ GenericMenu menu = new GenericMenu();
+ var interactable = target as Interactable;
+ menu.AddItem(new GUIContent("AnimatorSetBool"), false, () => interactable.actions.Add(new Interactable.AnimatorSetBoolAction()));
+ menu.AddItem(new GUIContent("AnimatorPlay"), false, () => interactable.actions.Add(new Interactable.AnimatorPlay()));
+ menu.AddItem(new GUIContent("ActivateParticleSystem"), false, () => interactable.actions.Add(new Interactable.ActivateParticleSystem()));
+ menu.AddItem(new GUIContent("Delay"), false, () => interactable.actions.Add(new Interactable.DelayAction()));
+ menu.AddItem(new GUIContent("SetObjectActive"), false, () => interactable.actions.Add(new Interactable.SetObjectActiveAction()));
+ menu.AddItem(new GUIContent("TeleportAgent"), false, () => interactable.actions.Add(new Interactable.TeleportAgentAction()));
+ if (interactable.TryGetComponent<NodeLink2>(out var _)) {
+ menu.AddItem(new GUIContent("Teleport Agent on Off-Mesh Link"), false, () => interactable.actions.Add(new Interactable.TeleportAgentOnLinkAction()));
+ }
+ menu.AddItem(new GUIContent("SetTransform"), false, () => interactable.actions.Add(new Interactable.SetTransformAction()));
+ menu.AddItem(new GUIContent("MoveTo"), false, () => interactable.actions.Add(new Interactable.MoveToAction()));
+ menu.AddItem(new GUIContent("InstantiatePrefab"), false, () => interactable.actions.Add(new Interactable.InstantiatePrefab()));
+ menu.AddItem(new GUIContent("CallFunction"), false, () => interactable.actions.Add(new Interactable.CallFunction()));
+ menu.AddItem(new GUIContent("Interact with other interactable"), false, () => interactable.actions.Add(new Interactable.InteractAction()));
+ menu.DropDown(rect);
+ };
+ }
+
+ protected override void Inspector () {
+ var script = target as Interactable;
+
+ script.actions = script.actions ?? new List<Interactable.InteractableAction>();
+ actions.DoLayoutList();
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/InteractableEditor.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/InteractableEditor.cs.meta
new file mode 100644
index 0000000..a249926
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Editor/InteractableEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ebafa3cffbc5f3d4f90cbca561882b97
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/FollowerJumpLink.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/FollowerJumpLink.cs
new file mode 100644
index 0000000..4bd5cec
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/FollowerJumpLink.cs
@@ -0,0 +1,65 @@
+#if MODULE_ENTITIES
+/// <summary>[followerEntity.onTraverseOffMeshLink]</summary>
+using UnityEngine;
+using Pathfinding;
+using System.Collections;
+using Pathfinding.ECS;
+
+namespace Pathfinding.Examples {
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/followerjumplink.html")]
+ public class FollowerJumpLink : MonoBehaviour, IOffMeshLinkHandler, IOffMeshLinkStateMachine {
+ // Register this class as the handler for off-mesh links when the component is enabled
+ void OnEnable() => GetComponent<NodeLink2>().onTraverseOffMeshLink = this;
+ void OnDisable() => GetComponent<NodeLink2>().onTraverseOffMeshLink = null;
+
+ IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context) => this;
+
+ void IOffMeshLinkStateMachine.OnFinishTraversingOffMeshLink (AgentOffMeshLinkTraversalContext context) {
+ Debug.Log("An agent finished traversing an off-mesh link");
+ }
+
+ void IOffMeshLinkStateMachine.OnAbortTraversingOffMeshLink () {
+ Debug.Log("An agent aborted traversing an off-mesh link");
+ }
+
+ IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink (AgentOffMeshLinkTraversalContext ctx) {
+ var start = (Vector3)ctx.link.relativeStart;
+ var end = (Vector3)ctx.link.relativeEnd;
+ var dir = end - start;
+
+ // Disable local avoidance while traversing the off-mesh link.
+ // If it was enabled, it will be automatically re-enabled when the agent finishes traversing the link.
+ ctx.DisableLocalAvoidance();
+
+ // Move and rotate the agent to face the other side of the link.
+ // When reaching the off-mesh link, the agent may be facing the wrong direction.
+ while (!ctx.MoveTowards(
+ position: start,
+ rotation: Quaternion.LookRotation(dir, ctx.movementPlane.up),
+ gravity: true,
+ slowdown: true).reached) {
+ yield return null;
+ }
+
+ var bezierP0 = start;
+ var bezierP1 = start + Vector3.up*5;
+ var bezierP2 = end + Vector3.up*5;
+ var bezierP3 = end;
+ var jumpDuration = 1.0f;
+
+ // Animate the AI to jump from the start to the end of the link
+ for (float t = 0; t < jumpDuration; t += ctx.deltaTime) {
+ ctx.transform.Position = AstarSplines.CubicBezier(bezierP0, bezierP1, bezierP2, bezierP3, Mathf.SmoothStep(0, 1, t / jumpDuration));
+ yield return null;
+ }
+ }
+ }
+}
+/// <summary>[followerEntity.onTraverseOffMeshLink]</summary>
+#else
+using UnityEngine;
+namespace Pathfinding.Examples {
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/followerjumplink.html")]
+ public class FollowerJumpLink : MonoBehaviour {}
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/FollowerJumpLink.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/FollowerJumpLink.cs.meta
new file mode 100644
index 0000000..8ca502b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/FollowerJumpLink.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f063fb6ec3f77440b73fc7776d9cb53
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/HighlightOnHover.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/HighlightOnHover.cs
new file mode 100644
index 0000000..f1b1bcc
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/HighlightOnHover.cs
@@ -0,0 +1,21 @@
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ /// <summary>Activates a GameObject when the cursor is over this object.</summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/highlightonhover.html")]
+ public class HighlightOnHover : VersionedMonoBehaviour {
+ public GameObject highlight;
+
+ void Start () {
+ highlight.SetActive(false);
+ }
+
+ public void OnMouseEnter () {
+ highlight.SetActive(true);
+ }
+
+ public void OnMouseExit () {
+ highlight.SetActive(false);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/HighlightOnHover.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/HighlightOnHover.cs.meta
new file mode 100644
index 0000000..a89128b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/HighlightOnHover.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8153462638516974f975960db23f964b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs
new file mode 100644
index 0000000..03697d1
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs
@@ -0,0 +1,323 @@
+using System.Collections;
+using System.Collections.Generic;
+using Pathfinding.ECS;
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ /// <summary>
+ /// Example script for handling interactable objects in the example scenes.
+ ///
+ /// It implements a very simple and lightweight state machine.
+ ///
+ /// Note: This is an example script intended for the A* Pathfinding Project's example scenes.
+ /// If you need a proper state machine for your game, you may be better served by other state machine solutions on the Unity Asset Store.
+ ///
+ /// It works by keeping a linear list of states, each with an associated action.
+ /// When an agent iteracts with this object, it immediately does the first action in the list.
+ /// Once that action is done, it will do the next action and so on.
+ ///
+ /// Some actions may cancel the whole interaction. For example the MoveTo action will cancel the interaction if the agent
+ /// suddenly had its destination to something else. Presumably because the agent was interrupted by something.
+ ///
+ /// If this component is added to the same GameObject as a <see cref="NodeLink2"/> component, the interactable will automatically trigger when the agent traverses the link.
+ /// Some components behave differently when used during an off-mesh link component.
+ /// For example the <see cref="MoveToAction"/> will move the agent without taking the navmesh into account (becoming a thin wrapper for <see cref="AgentOffMeshLinkTraversalContext.MoveTowards"/>).
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/interactable.html")]
+ public class Interactable : VersionedMonoBehaviour, IOffMeshLinkHandler, IOffMeshLinkStateMachine {
+ public enum CoroutineAction {
+ Tick,
+ Cancel,
+ }
+
+ [System.Serializable]
+ public abstract class InteractableAction {
+ public virtual IEnumerator<CoroutineAction> Execute (IAstarAI ai) {
+ return Execute();
+ }
+
+#if MODULE_ENTITIES
+ public virtual IEnumerator<CoroutineAction> Execute (Pathfinding.ECS.AgentOffMeshLinkTraversalContext context) {
+ return Execute();
+ }
+#endif
+
+ public virtual IEnumerator<CoroutineAction> Execute () {
+ throw new System.NotImplementedException("This action has no implementation");
+ }
+ }
+
+
+ [System.Serializable]
+ public class AnimatorPlay : InteractableAction {
+ public string stateName;
+ public float normalizedTime = 0;
+ public Animator animator;
+
+ public override IEnumerator<CoroutineAction> Execute () {
+ animator.Play(stateName, -1, normalizedTime);
+ yield break;
+ }
+ }
+
+ [System.Serializable]
+ public class AnimatorSetBoolAction : InteractableAction {
+ public string propertyName;
+ public bool value;
+ public Animator animator;
+
+ public override IEnumerator<CoroutineAction> Execute () {
+ animator.SetBool(propertyName, value);
+ yield break;
+ }
+ }
+
+ [System.Serializable]
+ public class ActivateParticleSystem : InteractableAction {
+ public ParticleSystem particleSystem;
+
+ public override IEnumerator<CoroutineAction> Execute () {
+ particleSystem.Play();
+ yield break;
+ }
+ }
+
+ [System.Serializable]
+ public class DelayAction : InteractableAction {
+ public float delay;
+
+ public override IEnumerator<CoroutineAction> Execute () {
+ float time = Time.time + delay;
+ while (Time.time < time) yield return CoroutineAction.Tick;
+ yield break;
+ }
+ }
+
+ [System.Serializable]
+ public class SetObjectActiveAction : InteractableAction {
+ public GameObject target;
+ public bool active;
+
+ public override IEnumerator<CoroutineAction> Execute () {
+ target.SetActive(active);
+ yield break;
+ }
+ }
+
+ [System.Serializable]
+ public class InstantiatePrefab : InteractableAction {
+ public GameObject prefab;
+ public Transform position;
+
+ public override IEnumerator<CoroutineAction> Execute () {
+ if (prefab != null && position != null) {
+ GameObject.Instantiate(prefab, position.position, position.rotation);
+ }
+ yield break;
+ }
+ }
+
+ [System.Serializable]
+ public class CallFunction : InteractableAction {
+ public UnityEngine.Events.UnityEvent function;
+
+ public override IEnumerator<CoroutineAction> Execute () {
+ function.Invoke();
+ yield break;
+ }
+ }
+
+ [System.Serializable]
+ public class TeleportAgentAction : InteractableAction {
+ public Transform destination;
+
+ public override IEnumerator<CoroutineAction> Execute (IAstarAI ai) {
+ ai.Teleport(destination.position);
+ yield break;
+ }
+
+#if MODULE_ENTITIES
+ public override IEnumerator<CoroutineAction> Execute (AgentOffMeshLinkTraversalContext context) {
+ context.Teleport(destination.position);
+ yield break;
+ }
+#endif
+ }
+
+ [System.Serializable]
+ public class TeleportAgentOnLinkAction : InteractableAction {
+ public enum Destination {
+ /// <summary>The side of the link that the agent starts traversing it from</summary>
+ RelativeStartOfLink,
+ /// <summary>The side of the link that is opposite the one the agent starts traversing it from</summary>
+ RelativeEndOfLink,
+ }
+
+ public Destination destination = Destination.RelativeEndOfLink;
+
+ public override IEnumerator<CoroutineAction> Execute() => throw new System.NotImplementedException("This action only works for agents traversing off-mesh links.");
+
+#if MODULE_ENTITIES
+ public override IEnumerator<CoroutineAction> Execute (AgentOffMeshLinkTraversalContext context) {
+ context.Teleport(destination == Destination.RelativeStartOfLink ? context.link.relativeStart : context.link.relativeEnd);
+ yield break;
+ }
+#endif
+ }
+
+ [System.Serializable]
+ public class SetTransformAction : InteractableAction {
+ public Transform transform;
+ public Transform source;
+ public bool setPosition = true;
+ public bool setRotation;
+ public bool setScale;
+
+ public override IEnumerator<CoroutineAction> Execute () {
+ if (setPosition) transform.position = source.position;
+ if (setRotation) transform.rotation = source.rotation;
+ if (setScale) transform.localScale = source.localScale;
+ yield break;
+ }
+ }
+
+ [System.Serializable]
+ public class MoveToAction : InteractableAction {
+ public Transform destination;
+ public bool useRotation;
+ public bool waitUntilReached;
+
+ public override IEnumerator<CoroutineAction> Execute (IAstarAI ai) {
+ var dest = destination.position;
+#if MODULE_ENTITIES
+ if (useRotation && ai is FollowerEntity follower) {
+ follower.SetDestination(dest, destination.rotation * Vector3.forward);
+ } else
+#endif
+ {
+ if (useRotation) Debug.LogError("useRotation is only supported for FollowerEntity agents", ai as MonoBehaviour);
+ ai.destination = dest;
+ }
+
+ if (waitUntilReached) {
+ if (ai is AIBase || ai is AILerp) {
+ // Only the FollowerEntity component is good enough to set the reachedDestination property to false immediately.
+ // The other movement scripts need to wait for the new path to be available, which is somewhat annoying.
+ ai.SearchPath();
+ while (ai.pathPending) yield return CoroutineAction.Tick;
+ }
+
+ while (!ai.reachedDestination) {
+ if (ai.destination != dest) {
+ // Something else must have changed the destination
+ yield return CoroutineAction.Cancel;
+ }
+ if (ai.reachedEndOfPath) {
+ // We have reached the end of the path, but not the destination
+ // This must mean that we cannot get any closer
+ // TODO: More accurate 'cannot move forwards' check
+ yield return CoroutineAction.Cancel;
+ }
+ yield return CoroutineAction.Tick;
+ }
+ }
+ }
+
+#if MODULE_ENTITIES
+ public override IEnumerator<CoroutineAction> Execute (AgentOffMeshLinkTraversalContext context) {
+ while (!context.MoveTowards(destination.position, destination.rotation, true, true).reached) {
+ yield return CoroutineAction.Tick;
+ }
+ yield break;
+ }
+#endif
+ }
+
+ [System.Serializable]
+ public class InteractAction : InteractableAction {
+ public Interactable interactable;
+
+ public override IEnumerator<CoroutineAction> Execute (IAstarAI ai) {
+ var it = interactable.InteractCoroutine(ai);
+ while (it.MoveNext()) {
+ yield return it.Current;
+ }
+ }
+ }
+
+ [SerializeReference]
+ public List<InteractableAction> actions;
+
+ public void Interact (IAstarAI ai) {
+ StartCoroutine(InteractCoroutine(ai));
+ }
+
+#if MODULE_ENTITIES
+ IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context) => this;
+
+ IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink (AgentOffMeshLinkTraversalContext context) {
+ var it = InteractCoroutine(context);
+ while (it.MoveNext()) {
+ yield return null;
+ }
+ }
+
+ public IEnumerator<CoroutineAction> InteractCoroutine (Pathfinding.ECS.AgentOffMeshLinkTraversalContext context) {
+ if (actions.Count == 0) {
+ Debug.LogWarning("No actions have been set up for this interactable", this);
+ yield break;
+ }
+
+ var actionIndex = 0;
+ while (actionIndex < actions.Count) {
+ var action = actions[actionIndex];
+ if (action == null) {
+ actionIndex++;
+ continue;
+ }
+
+ var enumerator = action.Execute(context);
+ while (enumerator.MoveNext()) {
+ yield return enumerator.Current;
+ if (enumerator.Current == CoroutineAction.Cancel) yield break;
+ }
+
+ actionIndex++;
+ }
+ }
+#endif
+
+ public IEnumerator<CoroutineAction> InteractCoroutine (IAstarAI ai) {
+ if (actions.Count == 0) {
+ Debug.LogWarning("No actions have been set up for this interactable", this);
+ yield break;
+ }
+
+ var actionIndex = 0;
+ while (actionIndex < actions.Count) {
+ var action = actions[actionIndex];
+ if (action == null) {
+ actionIndex++;
+ continue;
+ }
+
+ var enumerator = action.Execute(ai);
+ while (enumerator.MoveNext()) {
+ yield return enumerator.Current;
+ if (enumerator.Current == CoroutineAction.Cancel) yield break;
+ }
+
+ actionIndex++;
+ }
+ }
+
+ void OnEnable () {
+ // Allow the interactable to be triggered by an agent traversing an off-mesh link
+ if (TryGetComponent<NodeLink2>(out var link)) link.onTraverseOffMeshLink = this;
+ }
+
+ void OnDisable () {
+ if (TryGetComponent<NodeLink2>(out var link) && link.onTraverseOffMeshLink == (IOffMeshLinkHandler)this) link.onTraverseOffMeshLink = null;
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs.meta
new file mode 100644
index 0000000..8cc3384
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cb8246096363e0b48b549a58001a0bc4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ManualRVOAgent.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ManualRVOAgent.cs
new file mode 100644
index 0000000..2b20d92
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ManualRVOAgent.cs
@@ -0,0 +1,38 @@
+using UnityEngine;
+using System.Collections;
+
+namespace Pathfinding.Examples {
+ using Pathfinding.RVO;
+
+ /// <summary>
+ /// Player controlled character which RVO agents will avoid.
+ /// This script is intended to show how you can make NPCs avoid
+ /// a player controlled (or otherwise externally controlled) character.
+ ///
+ /// See: Pathfinding.RVO.RVOController
+ /// </summary>
+ [RequireComponent(typeof(RVOController))]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/manualrvoagent.html")]
+ public class ManualRVOAgent : MonoBehaviour {
+ RVOController rvo;
+
+ public float speed = 1;
+
+ void Awake () {
+ rvo = GetComponent<RVOController>();
+ }
+
+ /// <summary>[ManualRVOVelocity]</summary>
+ void Update () {
+ var x = Input.GetAxis("Horizontal");
+ var y = Input.GetAxis("Vertical");
+
+ var v = new Vector3(x, 0, y) * speed;
+
+ // Override the RVOController's velocity. This will disable local avoidance calculations for one simulation step.
+ rvo.velocity = v;
+ transform.position += v * Time.deltaTime;
+ }
+ /// <summary>[ManualRVOVelocity]</summary>
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ManualRVOAgent.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ManualRVOAgent.cs.meta
new file mode 100644
index 0000000..ed1a0cf
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ManualRVOAgent.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 235da29a51432412ca18f3924e2d4c2d
+timeCreated: 1466892028
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MecanimBridge2D.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MecanimBridge2D.cs
new file mode 100644
index 0000000..809969c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MecanimBridge2D.cs
@@ -0,0 +1,90 @@
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ /// <summary>
+ /// Helper for adding animation to agents.
+ ///
+ /// This script will forward the movement velocity to the animator component using the following animator parameters:
+ ///
+ /// - InputMagnitude: Movement speed, normalized by the agent's natural speed. 1 if the agent is moving at its natural speed, and 0 if it is standing still.
+ /// - X: Horizontal movement speed, normalized by the agent's natural speed.
+ /// - Y: Vertical movement speed, normalized by the agent's natural speed.
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/mecanimbridge2d.html")]
+ public class MecanimBridge2D : VersionedMonoBehaviour {
+ /// <summary>
+ /// How much to smooth the velocity of the agent.
+ ///
+ /// The velocity will be smoothed out over approximately this number of seconds.
+ /// A value of zero indicates no smoothing.
+ /// </summary>
+ public float velocitySmoothing = 1;
+
+ /// <summary>
+ /// The natural movement speed is the speed that the animations are designed for.
+ ///
+ /// One can for example configure the animator to speed up the animation if the agent moves faster than this, or slow it down if the agent moves slower than this.
+ /// </summary>
+ public float naturalSpeed = 5;
+
+ /// <summary>
+ /// How the agent's rotation is handled.
+ ///
+ /// See: <see cref="RotationMode"/>
+ /// </summary>
+ public RotationMode rotationMode = RotationMode.Hide;
+
+ public enum RotationMode {
+ /// <summary>The agent's transform will rotate towards the movement direction</summary>
+ RotateTransform,
+ /// <summary>
+ /// The agent will not visibly rotate.
+ ///
+ /// This is useful if your animation changes the agent's sprite to show a rotation.
+ /// Internally, the agent's rotation property will still return the true rotation of the agent.
+ ///
+ /// This is implemented by setting <see cref="FollowerEntity.updateRotation"/> to false on the agent.
+ /// </summary>
+ Hide,
+ }
+
+ /// <summary>Cached reference to the movement script</summary>
+ IAstarAI ai;
+
+ /// <summary>Cached Animator component</summary>
+ Animator anim;
+
+ Vector2 smoothedVelocity;
+
+ protected override void Awake () {
+ base.Awake();
+ ai = GetComponent<IAstarAI>();
+ anim = GetComponentInChildren<Animator>();
+ }
+
+ void Update () {
+ if (ai == null || anim == null) return;
+
+ var updateRotation = rotationMode == RotationMode.RotateTransform;
+ // TODO: Expose this property using an interface
+ if (ai is AIBase aiBase) aiBase.updateRotation = updateRotation;
+ else if (ai is AILerp aiLerp) aiLerp.updateRotation = updateRotation;
+#if MODULE_ENTITIES
+ else if (ai is FollowerEntity follower) follower.updateRotation = updateRotation;
+#endif
+
+ var desiredVelocity = naturalSpeed > 0 ? ai.desiredVelocity / naturalSpeed : ai.desiredVelocity;
+ var movementPlane = ai.movementPlane;
+ var desiredVelocity2D = (Vector2)movementPlane.ToPlane(desiredVelocity, out var _);
+ anim.SetFloat("NormalizedSpeed", ai.reachedEndOfPath || desiredVelocity2D.magnitude < 0.1f ? 0f : desiredVelocity2D.magnitude);
+
+ smoothedVelocity = Vector3.Lerp(smoothedVelocity, desiredVelocity2D, velocitySmoothing > 0 ? Time.deltaTime / velocitySmoothing : 1);
+ if (smoothedVelocity.magnitude < 0.4f) {
+ smoothedVelocity = smoothedVelocity.normalized * 0.4f;
+ }
+
+ anim.SetFloat("X", smoothedVelocity.x);
+ anim.SetFloat("Y", smoothedVelocity.y);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MecanimBridge2D.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MecanimBridge2D.cs.meta
new file mode 100644
index 0000000..1b86506
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MecanimBridge2D.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dbd490669bd16fc4bab5cd103415a535
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAI.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAI.cs
new file mode 100644
index 0000000..100b23e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAI.cs
@@ -0,0 +1,54 @@
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ /// <summary>
+ /// AI controller specifically made for the spider robot.
+ /// Deprecated: This script has been replaced by Pathfinding.Examples.MineBotAnimation. Any uses of this script in the Unity editor will be automatically replaced by one AIPath component and one MineBotAnimation component.
+ /// </summary>
+ [RequireComponent(typeof(Seeker))]
+ [System.Obsolete("This script has been replaced by Pathfinding.Examples.MineBotAnimation. Any uses of this script in the Unity editor will be automatically replaced by one AIPath component and one MineBotAnimation component.")]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/minebotai.html")]
+ public class MineBotAI : AIPath {
+ /// <summary>
+ /// Animation component.
+ /// Should hold animations "awake" and "forward"
+ /// </summary>
+ public Animation anim;
+
+ /// <summary>Minimum velocity for moving</summary>
+ public float sleepVelocity = 0.4F;
+
+ /// <summary>Speed relative to velocity with which to play animations</summary>
+ public float animationSpeed = 0.2F;
+
+ /// <summary>
+ /// Effect which will be instantiated when end of path is reached.
+ /// See: OnTargetReached
+ /// </summary>
+ public GameObject endOfPathEffect;
+
+#if UNITY_EDITOR
+ protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) {
+ if (unityThread) {
+ var components = gameObject.GetComponents<Component>();
+ int index = System.Array.IndexOf(components, this);
+ foreach (System.Type newType in new [] { typeof(AIPath), typeof(MineBotAnimation) }) {
+ var newComp = gameObject.AddComponent(newType);
+ foreach (var field in newComp.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)) {
+ var oldField = this.GetType().GetField(field.Name);
+ try {
+ if (oldField != null) field.SetValue(newComp, oldField.GetValue(this));
+ } catch (System.Exception e) {
+ Debug.LogError("Failed to upgrade some fields.\n" + e);
+ }
+ }
+ for (int i = components.Length - 1; i > index; i--) UnityEditorInternal.ComponentUtility.MoveComponentUp(newComp);
+ }
+ GameObject.DestroyImmediate(this);
+ } else {
+ base.OnUpgradeSerializedData(ref migrations, unityThread);
+ }
+ }
+#endif
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAI.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAI.cs.meta
new file mode 100644
index 0000000..18bfa63
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAI.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 3f35b84b127b849e38b74966118e7e0f
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAnimation.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAnimation.cs
new file mode 100644
index 0000000..3fcab2a
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAnimation.cs
@@ -0,0 +1,109 @@
+using Pathfinding.Util;
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ /// <summary>
+ /// Animation helper specifically made for the spider robot in the example scenes.
+ /// The spider robot (or mine-bot) which has been copied from the Unity Example Project
+ /// can have this script attached to be able to pathfind around with animations working properly.
+ ///
+ /// This script should be attached to a parent GameObject however since the original bot has Z+ as up.
+ /// This component requires Z+ to be forward and Y+ to be up.
+ ///
+ /// A movement script (e.g AIPath) must also be attached to the same GameObject to actually move the unit.
+ ///
+ /// This script will forward the movement speed to the animator component (<see cref="anim)"/> using the following animator parameter:
+ /// - NormalizedSpeed: Movement speed in world units, divided by <see cref="MineBotAnimation.naturalSpeed"/> and the character's scale. This will be 1 when the agent is moving at the natural speed, and 0 when it is standing still.
+ ///
+ /// When the end of path is reached, if the <see cref="endOfPathEffect"/> is not null, it will be instantiated at the current position. However, a check will be
+ /// done so that it won't spawn effects too close to the previous spawn-point.
+ /// [Open online documentation to see images]
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/minebotanimation.html")]
+ public class MineBotAnimation : VersionedMonoBehaviour {
+ /// <summary>Animator component</summary>
+ public Animator anim;
+
+ /// <summary>
+ /// Effect which will be instantiated when end of path is reached.
+ /// See: <see cref="OnTargetReached"/>
+ /// </summary>
+ public GameObject endOfPathEffect;
+
+ /// <summary>
+ /// The natural movement speed is the speed that the animations are designed for.
+ ///
+ /// One can for example configure the animator to speed up the animation if the agent moves faster than this, or slow it down if the agent moves slower than this.
+ /// </summary>
+ public float naturalSpeed = 5f;
+
+ bool isAtEndOfPath;
+
+ IAstarAI ai;
+ Transform tr;
+
+ const string NormalizedSpeedKey = "NormalizedSpeed";
+ static int NormalizedSpeedKeyHash = Animator.StringToHash(NormalizedSpeedKey);
+
+ protected override void Awake () {
+ base.Awake();
+ ai = GetComponent<IAstarAI>();
+ tr = GetComponent<Transform>();
+ if (anim != null && !HasParameter(anim, NormalizedSpeedKey)) {
+ Debug.LogError($"No '{NormalizedSpeedKey}' parameter found on the animator. The animator must have a float parameter called '{NormalizedSpeedKey}'", this);
+ enabled = false;
+ }
+ }
+
+ static bool HasParameter (Animator animator, string paramName) {
+ foreach (AnimatorControllerParameter param in animator.parameters) if (param.name == paramName) return true;
+ return false;
+ }
+
+ /// <summary>Point for the last spawn of <see cref="endOfPathEffect"/></summary>
+ protected Vector3 lastTarget;
+
+ /// <summary>
+ /// Called when the end of path has been reached.
+ /// An effect (<see cref="endOfPathEffect)"/> is spawned when this function is called
+ /// However, since paths are recalculated quite often, we only spawn the effect
+ /// when the current position is some distance away from the previous spawn-point
+ /// </summary>
+ void OnTargetReached () {
+ if (endOfPathEffect != null && Vector3.Distance(tr.position, lastTarget) > 1) {
+ GameObject.Instantiate(endOfPathEffect, tr.position, tr.rotation);
+ lastTarget = tr.position;
+ }
+ }
+
+ void OnEnable () {
+ // Process all components in a batched fashion to avoid Unity overhead
+ // See https://blog.unity.com/engine-platform/10000-update-calls
+ BatchedEvents.Add(this, BatchedEvents.Event.Update, OnUpdate);
+ }
+
+ void OnDisable () {
+ BatchedEvents.Remove(this);
+ }
+
+ static void OnUpdate (MineBotAnimation[] components, int count) {
+ for (int i = 0; i < count; i++) components[i].OnUpdate();
+ }
+
+ void OnUpdate () {
+ if (ai == null) return;
+
+ if (ai.reachedEndOfPath) {
+ if (!isAtEndOfPath) OnTargetReached();
+ isAtEndOfPath = true;
+ } else isAtEndOfPath = false;
+
+ // Calculate the velocity relative to this transform's orientation
+ Vector3 relVelocity = tr.InverseTransformDirection(ai.velocity);
+ relVelocity.y = 0;
+
+ // Speed relative to the character size
+ anim.SetFloat(NormalizedSpeedKeyHash, relVelocity.magnitude / (naturalSpeed * anim.transform.lossyScale.x));
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAnimation.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAnimation.cs.meta
new file mode 100644
index 0000000..9fe7ec2
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MineBotAnimation.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 8503d71a898994d3288ce1708e2707fe
+timeCreated: 1516539312
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MinimumUnityVersionWarning.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MinimumUnityVersionWarning.cs
new file mode 100644
index 0000000..966d301
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MinimumUnityVersionWarning.cs
@@ -0,0 +1,59 @@
+#pragma warning disable IDE0051
+
+using System.Collections;
+using UnityEngine;
+using Pathfinding.Util;
+
+namespace Pathfinding.Examples {
+ [ExecuteInEditMode]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/minimumunityversionwarning.html")]
+ public class MinimumUnityVersionWarning : MonoBehaviour {
+#if !MODULE_ENTITIES || !UNITY_2022_2_OR_NEWER
+ bool requiresUnity2022_2;
+ bool requiresEntities;
+
+
+ void Awake () {
+ requiresEntities = UnityCompatibility.FindAnyObjectByType<Pathfinding.FollowerEntity>() != null || UnityCompatibility.FindAnyObjectByType<Pathfinding.Examples.LightweightRVO>() != null;
+ // Box colliders from scenes created in Unity 2022+ are not compatible with older versions of Unity. They will end with the wrong size.
+ // The minimum version of the entitites package also requires Unity 2022
+ requiresUnity2022_2 = UnityCompatibility.FindAnyObjectByType<BoxCollider>() != null || requiresEntities;
+ }
+
+ IEnumerator Start () {
+ // Catch dynamically spawned prefabs
+ yield return null;
+ Awake();
+ }
+#endif
+
+ void OnGUI () {
+#if !UNITY_2022_2_OR_NEWER
+ if (requiresUnity2022_2) {
+ var rect = new Rect(Screen.width/2 - 325, Screen.height/2 - 30, 650, 60);
+ GUILayout.BeginArea(rect, "", "box");
+ GUILayout.Label("<b>Unity version too low</b>\nThis example scene can unfortunately not be played in your version of Unity, due to compatibility issues.\nYou must upgrade to Unity 2022.2 or later.");
+ GUILayout.EndArea();
+ return;
+ }
+#endif
+
+#if !MODULE_ENTITIES
+ if (requiresEntities) {
+ var rect = new Rect(Screen.width/2 - 325, Screen.height/2 - 30, 650, 80);
+ GUILayout.BeginArea(rect, "", "box");
+#if UNITY_EDITOR
+ GUILayout.Label("<b>Just one more step</b>\nThis example scene requires version 1.0 or higher of the <b>Entities</b> package to be installed.");
+ if (GUILayout.Button("Install")) {
+ UnityEditor.PackageManager.Client.Add("com.unity.entities");
+ }
+#else
+ GUILayout.Label("<b>Just one more step</b>\nThis example scene requires version 1.0 or higher of the <b>Entities</b> package to be installed\nYou can install it from the Unity Package Manager");
+#endif
+ GUILayout.EndArea();
+ return;
+ }
+#endif
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MinimumUnityVersionWarning.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MinimumUnityVersionWarning.cs.meta
new file mode 100644
index 0000000..53896df
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/MinimumUnityVersionWarning.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 786ae4169b6714241b0ba3c766cdc57f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/NavmeshClamp.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/NavmeshClamp.cs
new file mode 100644
index 0000000..b255a71
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/NavmeshClamp.cs
@@ -0,0 +1,54 @@
+using UnityEngine;
+
+namespace Pathfinding {
+ /// <summary>
+ /// Attach to any GameObject and the object will be clamped to the navmesh.
+ /// If a GameObject has this component attached, one or more graph linecasts will be carried out every frame to ensure that the object
+ /// does not leave the navmesh area.
+ ///
+ /// It can be used with grid graphs, layered grid graphs, navmesh graphs and recast graphs.
+ /// </summary>
+ [AddComponentMenu("Pathfinding/Navmesh Clamp")]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/navmeshclamp.html")]
+ public class NavmeshClamp : MonoBehaviour {
+ GraphNode prevNode;
+ Vector3 prevPos;
+
+ // Update is called once per frame
+ void LateUpdate () {
+ if (prevNode == null || prevNode.Destroyed) {
+ var nninfo = AstarPath.active.GetNearest(transform.position);
+ prevNode = nninfo.node;
+ prevPos = transform.position;
+ }
+
+ if (prevNode == null) {
+ return;
+ }
+
+ if (prevNode != null) {
+ var graph = AstarData.GetGraph(prevNode) as IRaycastableGraph;
+ if (graph != null) {
+ GraphHitInfo hit;
+ if (graph.Linecast(prevPos, transform.position, out hit) && hit.node != null) {
+ hit.point.y = transform.position.y;
+ Vector3 closest = VectorMath.ClosestPointOnLine(hit.tangentOrigin, hit.tangentOrigin+hit.tangent, transform.position);
+ Vector3 ohit = hit.point;
+ ohit = ohit + Vector3.ClampMagnitude((Vector3)hit.node.position-ohit, 0.008f);
+ if (graph.Linecast(ohit, closest, out hit)) {
+ hit.point.y = transform.position.y;
+ transform.position = hit.point;
+ } else {
+ closest.y = transform.position.y;
+
+ transform.position = closest;
+ }
+ }
+ prevNode = hit.node;
+ }
+ }
+
+ prevPos = transform.position;
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/NavmeshClamp.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/NavmeshClamp.cs.meta
new file mode 100644
index 0000000..3b79a60
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/NavmeshClamp.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: c46a80ea33e9a403ea2308975022ec42
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ObjectPlacer.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ObjectPlacer.cs
new file mode 100644
index 0000000..bd266ea
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ObjectPlacer.cs
@@ -0,0 +1,88 @@
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ /// <summary>Small sample script for placing obstacles</summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/objectplacer.html")]
+ public class ObjectPlacer : MonoBehaviour {
+ /// <summary>
+ /// GameObject to place.
+ /// When using a Grid Graph you need to make sure the object's layer is included in the collision mask in the GridGraph settings.
+ /// </summary>
+ public GameObject go;
+
+ /// <summary>Flush Graph Updates directly after placing. Slower, but updates are applied immidiately</summary>
+ public bool direct = false;
+
+ /// <summary>Issue a graph update object after placement</summary>
+ public bool issueGUOs = true;
+
+ /// <summary>Align created objects to the surface normal where it is created</summary>
+ public bool alignToSurface = false;
+
+ /// <summary>Global offset of the placed object relative to the mouse cursor</summary>
+ public Vector3 offset;
+
+ /// <summary>Randomize rotation of the placed object</summary>
+ public bool randomizeRotation = false;
+
+ float lastPlacedTime;
+
+ /// <summary>Update is called once per frame</summary>
+ void Update () {
+ // Check if P is being pressed.
+ // Don't place objects if ctrl is pressed to avoid conflicts with the pause shortcut (ctrl+shift+P)
+ if (!Input.GetKey(KeyCode.LeftControl) && (Input.GetKeyDown("p") || (Input.GetKey("p") && Time.time - lastPlacedTime > 0.3f))) {
+ PlaceObject();
+ }
+
+ if (Input.GetKeyDown("r")) {
+ RemoveObject();
+ }
+ }
+
+ public void PlaceObject () {
+ lastPlacedTime = Time.time;
+ Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
+
+ // Figure out where the ground is
+ if (Physics.Raycast(ray, out var hit, Mathf.Infinity, ~0)) {
+ Vector3 p = hit.point + offset;
+ var rot = Quaternion.identity;
+ if (alignToSurface) rot = Quaternion.LookRotation(hit.normal, Vector3.right) * Quaternion.Euler(90, 0, 0);
+ if (randomizeRotation) rot = Random.rotation;
+ GameObject obj = GameObject.Instantiate(go, p, rot) as GameObject;
+
+ if (issueGUOs) {
+ Bounds b = obj.GetComponent<Collider>().bounds;
+ GraphUpdateObject guo = new GraphUpdateObject(b);
+ AstarPath.active.UpdateGraphs(guo);
+ if (direct) {
+ AstarPath.active.FlushGraphUpdates();
+ }
+ }
+ }
+ }
+
+ public void RemoveObject () {
+ Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
+
+ // Check what object is under the mouse cursor
+ if (Physics.Raycast(ray, out var hit, Mathf.Infinity)) {
+ // Ignore ground and triggers
+ if (hit.collider.isTrigger || hit.transform.gameObject.name == "Ground") return;
+
+ Bounds b = hit.collider.bounds;
+ Destroy(hit.collider);
+ Destroy(hit.collider.gameObject);
+
+ if (issueGUOs) {
+ GraphUpdateObject guo = new GraphUpdateObject(b);
+ AstarPath.active.UpdateGraphs(guo);
+ if (direct) {
+ AstarPath.active.FlushGraphUpdates();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ObjectPlacer.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ObjectPlacer.cs.meta
new file mode 100644
index 0000000..1f17b93
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/ObjectPlacer.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 687e3bc0934ac46d3957e59872965f3c
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdate.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdate.cs
new file mode 100644
index 0000000..2153625
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdate.cs
@@ -0,0 +1,50 @@
+using UnityEngine;
+
+namespace Pathfinding {
+ /// <summary>
+ /// Updates the recast tile(s) it is in at start, needs RecastTileUpdateHandler.
+ ///
+ /// If there is a collider attached to the same GameObject, the bounds
+ /// of that collider will be used for updating, otherwise
+ /// only the position of the object will be used.
+ ///
+ /// Note: This class needs a RecastTileUpdateHandler somewhere in the scene.
+ /// See the documentation for that class, it contains more information.
+ ///
+ /// Note: This does not use navmesh cutting. If you only ever add
+ /// obstacles, but never add any new walkable surfaces then you might
+ /// want to use navmesh cutting instead. See navmeshcutting (view in online documentation for working links).
+ ///
+ /// See: RecastTileUpdateHandler
+ ///
+ /// Deprecated: Since version 5.0, this component is not necessary, since normal graph updates on recast graphs are now batched together if they update the same tiles.
+ /// Use e.g. the <see cref="DynamicGridObstacle"/> component instead.
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/recasttileupdate.html")]
+ public class RecastTileUpdate : MonoBehaviour {
+ public static event System.Action<Bounds> OnNeedUpdates;
+
+ void Start () {
+ ScheduleUpdate();
+ }
+
+ void OnDestroy () {
+ ScheduleUpdate();
+ }
+
+ /// <summary>Schedule a tile update for all tiles that contain this object</summary>
+ public void ScheduleUpdate () {
+ var collider = GetComponent<Collider>();
+
+ if (collider != null) {
+ if (OnNeedUpdates != null) {
+ OnNeedUpdates(collider.bounds);
+ }
+ } else {
+ if (OnNeedUpdates != null) {
+ OnNeedUpdates(new Bounds(transform.position, Vector3.zero));
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdate.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdate.cs.meta
new file mode 100644
index 0000000..a8e90b4
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdate.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c17ca1d2569424ba1be3606fe1bb5c7d
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs
new file mode 100644
index 0000000..d9dcdb8
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs
@@ -0,0 +1,161 @@
+using UnityEngine;
+
+namespace Pathfinding {
+ /// <summary>
+ /// Helper for easier fast updates to recast graphs.
+ ///
+ /// When updating recast graphs, you might just have plonked down a few
+ /// GraphUpdateScene objects or issued a few GraphUpdateObjects to the
+ /// system. This works fine if you are only issuing a few but the problem
+ /// is that they don't have any coordination in between themselves. So if
+ /// you have 10 GraphUpdateScene objects in one tile, they will all update
+ /// that tile (10 times in total) instead of just updating it once which
+ /// is all that is required (meaning it will be 10 times slower than just
+ /// updating one tile). This script exists to help with updating only the
+ /// tiles that need updating and only updating them once instead of
+ /// multiple times.
+ ///
+ /// It is coupled with the RecastTileUpdate component, which works a bit
+ /// like the GraphUpdateScene component, just with fewer options. You can
+ /// attach the RecastTileUpdate to any GameObject to have it schedule an
+ /// update for the tile(s) that contain the GameObject. E.g if you are
+ /// creating a new building somewhere, you can attach the RecastTileUpdate
+ /// component to it to make it update the graph when it is instantiated.
+ ///
+ /// If a single tile contains multiple RecastTileUpdate components and
+ /// many try to update the graph at the same time, only one tile update
+ /// will be done, which greatly improves performance.
+ ///
+ /// If you have objects that are instantiated at roughly the same time
+ /// but not exactly the same frame, you can use the maxThrottlingDelay
+ /// field. It will delay updates up to that number of seconds to allow
+ /// more updates to be batched together.
+ ///
+ /// Note: You should only have one instance of this script in the scene
+ /// if you only have a single recast graph. If you have more than one
+ /// graph you can have more than one instance of this script but you need
+ /// to manually call the SetGraph method to configure it with the correct
+ /// graph.
+ ///
+ /// Note: This does not use navmesh cutting. If you only ever add
+ /// obstacles, but never add any new walkable surfaces then you might
+ /// want to use navmesh cutting instead. See navmeshcutting (view in online documentation for working links).
+ ///
+ /// Deprecated: Since version 5.0, this component is not necessary, since normal graph updates on recast graphs are now batched together if they update the same tiles.
+ /// Use e.g. the <see cref="DynamicGridObstacle"/> component instead.
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/recasttileupdatehandler.html")]
+ public class RecastTileUpdateHandler : MonoBehaviour {
+ /// <summary>Graph that handles the updates</summary>
+ RecastGraph graph;
+
+ /// <summary>True for a tile if it needs updating</summary>
+ bool[] dirtyTiles;
+
+ /// <summary>True if any elements in dirtyTiles are true</summary>
+ bool anyDirtyTiles = false;
+
+ /// <summary>Earliest update request we are handling right now</summary>
+ float earliestDirty = float.NegativeInfinity;
+
+ /// <summary>All tile updates will be performed within (roughly) this number of seconds</summary>
+ public float maxThrottlingDelay = 0.5f;
+
+ public void SetGraph (RecastGraph graph) {
+ this.graph = graph;
+
+ if (graph == null)
+ return;
+
+ dirtyTiles = new bool[graph.tileXCount*graph.tileZCount];
+ anyDirtyTiles = false;
+ }
+
+ /// <summary>Requests an update to all tiles which touch the specified bounds</summary>
+ public void ScheduleUpdate (Bounds bounds) {
+ if (graph == null) {
+ // If no graph has been set, use the first graph available
+ if (AstarPath.active != null) {
+ SetGraph(AstarPath.active.data.recastGraph);
+ }
+
+ if (graph == null) {
+ Debug.LogError("Received tile update request (from RecastTileUpdate), but no RecastGraph could be found to handle it");
+ return;
+ }
+ }
+
+ // Make sure that tiles which do not strictly
+ // contain this bounds object but which still
+ // might need to be updated are actually updated
+ int voxelCharacterRadius = Mathf.CeilToInt(graph.characterRadius/graph.cellSize);
+ int borderSize = voxelCharacterRadius + 3;
+
+ // Expand borderSize voxels on each side
+ bounds.Expand(new Vector3(borderSize, 0, borderSize)*graph.cellSize*2);
+
+ var touching = graph.GetTouchingTiles(bounds);
+
+ if (touching.Width * touching.Height > 0) {
+ if (!anyDirtyTiles) {
+ earliestDirty = Time.time;
+ anyDirtyTiles = true;
+ }
+
+ for (int z = touching.ymin; z <= touching.ymax; z++) {
+ for (int x = touching.xmin; x <= touching.xmax; x++) {
+ dirtyTiles[z*graph.tileXCount + x] = true;
+ }
+ }
+ }
+ }
+
+ void OnEnable () {
+ RecastTileUpdate.OnNeedUpdates += ScheduleUpdate;
+ }
+
+ void OnDisable () {
+ RecastTileUpdate.OnNeedUpdates -= ScheduleUpdate;
+ }
+
+ void Update () {
+ if (anyDirtyTiles && Time.time - earliestDirty >= maxThrottlingDelay && graph != null) {
+ UpdateDirtyTiles();
+ }
+ }
+
+ /// <summary>Update all dirty tiles now</summary>
+ public void UpdateDirtyTiles () {
+ if (graph == null) {
+ new System.InvalidOperationException("No graph is set on this object");
+ }
+
+ if (graph.tileXCount * graph.tileZCount != dirtyTiles.Length) {
+ Debug.LogError("Graph has changed dimensions. Clearing queued graph updates and resetting.");
+ SetGraph(graph);
+ return;
+ }
+
+ for (int z = 0; z < graph.tileZCount; z++) {
+ for (int x = 0; x < graph.tileXCount; x++) {
+ if (dirtyTiles[z*graph.tileXCount + x]) {
+ dirtyTiles[z*graph.tileXCount + x] = false;
+
+ var bounds = graph.GetTileBounds(x, z);
+
+ // Shrink it a bit to make sure other tiles
+ // are not included because of rounding errors
+ bounds.extents *= 0.5f;
+
+ var guo = new GraphUpdateObject(bounds);
+ guo.nnConstraint.graphMask = 1 << (int)graph.graphIndex;
+
+ AstarPath.active.UpdateGraphs(guo);
+ }
+ }
+ }
+
+ anyDirtyTiles = false;
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs.meta
new file mode 100644
index 0000000..a135a25
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e5e3ec6ef8d2e4827b1645ee843bbe16
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SmoothCameraFollow.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SmoothCameraFollow.cs
new file mode 100644
index 0000000..80efdfb
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SmoothCameraFollow.cs
@@ -0,0 +1,37 @@
+using UnityEngine;
+
+namespace Pathfinding.Examples {
+ /// <summary>
+ /// Smooth Camera Following.
+ /// \author http://wiki.unity3d.com/index.php/SmoothFollow2
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/smoothcamerafollow.html")]
+ public class SmoothCameraFollow : VersionedMonoBehaviour {
+ public Transform target;
+ public float distance = 3.0f;
+ public float height = 3.0f;
+ public float damping = 5.0f;
+ public bool enableRotation = true;
+ public bool smoothRotation = true;
+ public float rotationDamping = 10.0f;
+ public bool staticOffset = false;
+
+ void LateUpdate () {
+ Vector3 wantedPosition;
+
+ if (staticOffset) {
+ wantedPosition = target.position + new Vector3(0, height, distance);
+ } else {
+ wantedPosition = target.TransformPoint(0, height, -distance);
+ }
+ transform.position = Vector3.Lerp(transform.position, wantedPosition, Time.deltaTime * damping);
+
+ if (enableRotation) {
+ if (smoothRotation) {
+ Quaternion wantedRotation = Quaternion.LookRotation(target.position - transform.position, target.up);
+ transform.rotation = Quaternion.Slerp(transform.rotation, wantedRotation, Time.deltaTime * rotationDamping);
+ } else transform.LookAt(target, target.up);
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SmoothCameraFollow.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SmoothCameraFollow.cs.meta
new file mode 100644
index 0000000..a6c07b3
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SmoothCameraFollow.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 46fb00c7e6ad9485282a146ad398a2a5
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SnapToNode.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SnapToNode.cs
new file mode 100644
index 0000000..b776beb
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SnapToNode.cs
@@ -0,0 +1,25 @@
+using UnityEngine;
+using System.Collections;
+using Pathfinding;
+
+namespace Pathfinding.Examples {
+ /// <summary>
+ /// Helper editor script to snap an object to the closest node.
+ /// Used in the "Turn Based" example scene for snapping obstacles to the hexagon grid.
+ /// </summary>
+ [ExecuteInEditMode]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/snaptonode.html")]
+ public class SnapToNode : MonoBehaviour {
+ /// <summary>[Update]</summary>
+ void Update () {
+ if (transform.hasChanged && AstarPath.active != null) {
+ var node = AstarPath.active.GetNearest(transform.position, NNConstraint.None).node;
+ if (node != null) {
+ transform.position = (Vector3)node.position;
+ transform.hasChanged = false;
+ }
+ }
+ }
+ /// <summary>[Update]</summary>
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SnapToNode.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SnapToNode.cs.meta
new file mode 100644
index 0000000..f7cf3a1
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/SnapToNode.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: c0acfdba178b145239678a03b9df938a
+timeCreated: 1453723051
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs
new file mode 100644
index 0000000..a0d6fd3
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs
@@ -0,0 +1,189 @@
+#pragma warning disable 649
+using UnityEngine;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using Pathfinding.Util;
+
+namespace Pathfinding {
+ /// <summary>
+ /// Moves the target in example scenes.
+ /// This is a simple script which has the sole purpose
+ /// of moving the target point of agents in the example
+ /// scenes for the A* Pathfinding Project.
+ ///
+ /// It is not meant to be pretty, but it does the job.
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/targetmover.html")]
+ public class TargetMover : VersionedMonoBehaviour {
+ /// <summary>Mask for the raycast placement</summary>
+ public LayerMask mask;
+
+ public Transform target;
+
+ /// <summary>Determines if the target position should be updated every frame or only on double-click</summary>
+ bool onlyOnDoubleClick;
+ public Trigger trigger;
+ public GameObject clickEffect;
+ public bool use2D;
+ public PathUtilities.FormationMode formationMode = PathUtilities.FormationMode.SinglePoint;
+
+ Camera cam;
+
+ public enum Trigger {
+ Continuously,
+ SingleClick,
+ DoubleClick
+ }
+
+ public void Start () {
+ // Cache the Main Camera
+ cam = Camera.main;
+ }
+
+ public void OnGUI () {
+ if (trigger != Trigger.Continuously && cam != null && Event.current.type == EventType.MouseDown) {
+ if (Event.current.clickCount == (trigger == Trigger.DoubleClick ? 2 : 1)) {
+ UpdateTargetPosition();
+ }
+ }
+ }
+
+ /// <summary>Update is called once per frame</summary>
+ void Update () {
+ if (trigger == Trigger.Continuously && cam != null) {
+ UpdateTargetPosition();
+ }
+ }
+
+ public void UpdateTargetPosition () {
+ Vector3 newPosition = Vector3.zero;
+ bool positionFound = false;
+ Transform hitObject = null;
+
+ // If the game view has never been rendered, the mouse position can be infinite
+ if (!float.IsFinite(Input.mousePosition.x)) return;
+
+ if (use2D) {
+ newPosition = cam.ScreenToWorldPoint(Input.mousePosition);
+ newPosition.z = 0;
+ positionFound = true;
+ var collider = Physics2D.OverlapPoint(newPosition, mask);
+ if (collider != null) hitObject = collider.transform;
+ } else {
+ // Fire a ray through the scene at the mouse position and place the target where it hits
+ if (cam.pixelRect.Contains(Input.mousePosition) && Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out var hit, Mathf.Infinity, mask)) {
+ newPosition = hit.point;
+ hitObject = hit.transform;
+ positionFound = true;
+ }
+ }
+
+ if (positionFound) {
+ if (target != null) target.position = newPosition;
+
+ if (trigger != Trigger.Continuously) {
+ // Slightly inefficient way of finding all AIs, but this is just an example script, so it doesn't matter much.
+ // FindObjectsByType does not support interfaces unfortunately.
+ var ais = UnityCompatibility.FindObjectsByTypeSorted<MonoBehaviour>().OfType<IAstarAI>().ToList();
+ StopAllCoroutines();
+
+ if (hitObject != null && hitObject.TryGetComponent<Pathfinding.Examples.Interactable>(out var interactable)) {
+ // Pick the first AI to interact with the interactable
+ if (ais.Count > 0) interactable.Interact(ais[0]);
+ } else {
+ if (clickEffect != null) {
+ GameObject.Instantiate(clickEffect, newPosition, Quaternion.identity);
+ }
+
+ // This will calculate individual destinations for each agent, like in a formation pattern.
+ // The simplest mode, FormationMode.SinglePoint, just assigns newPosition to all entries of the 'destinations' list.
+ var destinations = PathUtilities.FormationDestinations(ais, newPosition, formationMode, 0.5f);
+ for (int i = 0; i < ais.Count; i++) {
+#if MODULE_ENTITIES
+ var isFollowerEntity = ais[i] is FollowerEntity;
+#else
+ var isFollowerEntity = false;
+#endif
+ if (ais[i] != null) {
+ ais[i].destination = destinations[i];
+
+ // Make the agents recalculate their path immediately for slighly increased responsiveness.
+ // The FollowerEntity is better at doing this automatically.
+ if (!isFollowerEntity) ais[i].SearchPath();
+ }
+ }
+
+ StartCoroutine(OptimizeFormationDestinations(ais, destinations));
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Swap the destinations of pairs of agents if it reduces the total distance they need to travel.
+ ///
+ /// This is a simple optimization algorithm to make group movement smoother and more efficient.
+ /// It is not perfect and may not always find the optimal solution, but it is very fast and works well in practice.
+ /// It will not work great for large groups of agents, as the optimization becomes too hard for this simple algorithm.
+ ///
+ /// See: https://en.wikipedia.org/wiki/Assignment_problem
+ /// </summary>
+ IEnumerator OptimizeFormationDestinations (List<IAstarAI> ais, List<Vector3> destinations) {
+ // Prevent swapping the same agents multiple times.
+ // This is because the distance measurement is only an approximation, and agents
+ // may temporarily have to move away from their destination before they can move towards it.
+ // Allowing multiple swaps could make the agents move back and forth indefinitely as the targets shift around.
+ var alreadySwapped = new HashSet<(IAstarAI, IAstarAI)>();
+
+ const int IterationsPerFrame = 4;
+
+ while (true) {
+ for (int i = 0; i < IterationsPerFrame; i++) {
+ var a = Random.Range(0, ais.Count);
+ var b = Random.Range(0, ais.Count);
+ if (a == b) continue;
+ if (b < a) Memory.Swap(ref a, ref b);
+ var aiA = ais[a];
+ var aiB = ais[b];
+
+ if ((MonoBehaviour)aiA == null) continue;
+ if ((MonoBehaviour)aiB == null) continue;
+
+ if (alreadySwapped.Contains((aiA, aiB))) continue;
+
+ var pA = aiA.position;
+ var pB = aiB.position;
+ var distA = (pA - destinations[a]).sqrMagnitude;
+ var distB = (pB - destinations[b]).sqrMagnitude;
+
+ var newDistA = (pA - destinations[b]).sqrMagnitude;
+ var newDistB = (pB - destinations[a]).sqrMagnitude;
+ var cost1 = distA + distB;
+ var cost2 = newDistA + newDistB;
+ if (cost2 < cost1 * 0.98f) {
+ // Swap the destinations
+ var tmp = destinations[a];
+ destinations[a] = destinations[b];
+ destinations[b] = tmp;
+
+ aiA.destination = destinations[a];
+ aiB.destination = destinations[b];
+
+ alreadySwapped.Add((aiA, aiB));
+ }
+ }
+ yield return null;
+ }
+ }
+
+ protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) {
+ if (migrations.TryMigrateFromLegacyFormat(out var legacyVersion)) {
+ if (legacyVersion < 2) {
+ trigger = onlyOnDoubleClick ? Trigger.DoubleClick : Trigger.Continuously;
+ }
+ }
+ base.OnUpgradeSerializedData(ref migrations, unityThread);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs.meta
new file mode 100644
index 0000000..0fbd5b7
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f903c2eb621d8418d88f8dad81b13dc6
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}