Add new meta files and interfaces for project structure
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
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/";
|
||||
|
||||
GUIContent buttonContent = new GUIContent("Example Scene Documentation");
|
||||
|
||||
void Awake () {
|
||||
useGUILayout = false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnGUI () {
|
||||
if (GUI.Button(new Rect(Screen.width - 250, Screen.height - 60, 240, 50), buttonContent)) {
|
||||
Application.OpenURL(UrlBase + page + ".html");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a705f1fd95105f4582d18bf472edc13
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/DocumentationButton.cs
|
||||
uploadId: 764484
|
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e96422fbb088f477baaf6f2ada396863
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/DoorController.cs
|
||||
uploadId: 764484
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8f8f1c1cdcd4c849af88d75ed47987e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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.7",
|
||||
"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_0_OR_NEWER"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.entities",
|
||||
"expression": "1.1.0-pre.3",
|
||||
"define": "MODULE_ENTITIES"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5d2c271bbce11444b0479e9cf877d6d
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/Editor/AstarPathfindingProjectExamplesEditor.asmdef
|
||||
uploadId: 764484
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebafa3cffbc5f3d4f90cbca561882b97
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/Editor/InteractableEditor.cs
|
||||
uploadId: 764484
|
@@ -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
|
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f063fb6ec3f77440b73fc7776d9cb53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/FollowerJumpLink.cs
|
||||
uploadId: 764484
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8153462638516974f975960db23f964b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/HighlightOnHover.cs
|
||||
uploadId: 764484
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb8246096363e0b48b549a58001a0bc4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/Interactable.cs
|
||||
uploadId: 764484
|
@@ -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>
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 235da29a51432412ca18f3924e2d4c2d
|
||||
timeCreated: 1466892028
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/ManualRVOAgent.cs
|
||||
uploadId: 764484
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbd490669bd16fc4bab5cd103415a535
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/MecanimBridge2D.cs
|
||||
uploadId: 764484
|
@@ -0,0 +1,3 @@
|
||||
// This file has been removed from the package. Since UnityPackages cannot
|
||||
// delete files, only replace them, this message is left here to prevent old
|
||||
// files from causing compiler errors.
|
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f35b84b127b849e38b74966118e7e0f
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/MineBotAI.cs
|
||||
uploadId: 764484
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8503d71a898994d3288ce1708e2707fe
|
||||
timeCreated: 1516539312
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/MineBotAnimation.cs
|
||||
uploadId: 764484
|
@@ -0,0 +1,72 @@
|
||||
#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 || !UNITY_2022_3_OR_NEWER
|
||||
bool requiresUnity2022_2;
|
||||
bool requiresUnity2022_3;
|
||||
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;
|
||||
// Navmesh cutting requires Unity 2022.3 or newer due to unity bugs in earlier versions
|
||||
requiresUnity2022_3 = UnityCompatibility.FindAnyObjectByType<NavmeshCut>() != null || UnityCompatibility.FindAnyObjectByType<NavmeshAdd>() != null;
|
||||
}
|
||||
|
||||
IEnumerator Start () {
|
||||
// Catch dynamically spawned prefabs
|
||||
yield return null;
|
||||
Awake();
|
||||
}
|
||||
|
||||
void OnGUI () {
|
||||
#if !UNITY_2022_3_OR_NEWER
|
||||
if (requiresUnity2022_3) {
|
||||
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 a Unity bug.\nYou must upgrade to Unity 2022.3 or later.");
|
||||
GUILayout.EndArea();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#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
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 786ae4169b6714241b0ba3c766cdc57f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/MinimumUnityVersionWarning.cs
|
||||
uploadId: 764484
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c46a80ea33e9a403ea2308975022ec42
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/NavmeshClamp.cs
|
||||
uploadId: 764484
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 687e3bc0934ac46d3957e59872965f3c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/ObjectPlacer.cs
|
||||
uploadId: 764484
|
@@ -0,0 +1,51 @@
|
||||
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="DynamicObstacle"/> component instead.
|
||||
/// </summary>
|
||||
[System.Obsolete("This component is no longer necessary. Normal graph updates on recast graphs are now batched together if they update the same tiles. Use the DynamicObstacle component instead")]
|
||||
[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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c17ca1d2569424ba1be3606fe1bb5c7d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/RecastTileUpdate.cs
|
||||
uploadId: 764484
|
@@ -0,0 +1,162 @@
|
||||
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="DynamicObstacle"/> component instead.
|
||||
/// </summary>
|
||||
[System.Obsolete("This component is no longer necessary. Normal graph updates on recast graphs are now batched together if they update the same tiles. Use the DynamicObstacle component instead")]
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5e3ec6ef8d2e4827b1645ee843bbe16
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/RecastTileUpdateHandler.cs
|
||||
uploadId: 764484
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46fb00c7e6ad9485282a146ad398a2a5
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/SmoothCameraFollow.cs
|
||||
uploadId: 764484
|
@@ -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>
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0acfdba178b145239678a03b9df938a
|
||||
timeCreated: 1453723051
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/SnapToNode.cs
|
||||
uploadId: 764484
|
@@ -0,0 +1,190 @@
|
||||
#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;
|
||||
useGUILayout = false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f903c2eb621d8418d88f8dad81b13dc6
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 87744
|
||||
packageName: A* Pathfinding Project Pro
|
||||
packageVersion: 5.3.8
|
||||
assetPath: Packages/com.arongranberg.astar/ExampleScenes~/ExampleScripts/TargetMover.cs
|
||||
uploadId: 764484
|
Reference in New Issue
Block a user