Add new meta files and interfaces for project structure

This commit is contained in:
2025-07-11 21:46:14 +02:00
commit 43c1730ed5
3230 changed files with 1428743 additions and 0 deletions

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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");
}
}
}
}

View File

@@ -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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d8f8f1c1cdcd4c849af88d75ed47987e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}

View File

@@ -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

View File

@@ -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();
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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>
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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));
}
}
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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();
}
}
}
}
}
}

View File

@@ -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

View File

@@ -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));
}
}
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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

View File

@@ -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>
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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