diff options
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts')
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} |