summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors
diff options
context:
space:
mode:
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs110
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs141
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/MoveInCircle.cs51
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/MoveInCircle.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/Patrol.cs68
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/Patrol.cs.meta11
8 files changed, 414 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs
new file mode 100644
index 0000000..5834f6f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs
@@ -0,0 +1,110 @@
+using UnityEngine;
+using Pathfinding.Util;
+#if MODULE_ENTITIES
+using Unity.Entities;
+#endif
+
+namespace Pathfinding {
+ /// <summary>
+ /// Sets the destination of an AI to the position of a specified object.
+ /// This component should be attached to a GameObject together with a movement script such as AIPath, RichAI or AILerp.
+ /// This component will then make the AI move towards the <see cref="target"/> set on this component.
+ ///
+ /// Essentially the only thing this component does is to set the <see cref="Pathfinding.IAstarAI.destination"/> property to the position of the target every frame.
+ /// There is some additional complexity to make sure that the destination is updated immediately before the AI searches for a path as well, in case the
+ /// target moved since the last Update. There is also some complexity to reduce the performance impact, by using the <see cref="BatchedEvents"/> system to
+ /// process all AIDestinationSetter components in a single batch.
+ ///
+ /// When using ECS, this component is instead added as a managed component to the entity.
+ /// The destination syncing is then handled by the <see cref="SyncDestinationTransformSystem"/> for better performance.
+ ///
+ /// See: <see cref="Pathfinding.IAstarAI.destination"/>
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ [UniqueComponent(tag = "ai.destination")]
+ [AddComponentMenu("Pathfinding/AI/Behaviors/AIDestinationSetter")]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/aidestinationsetter.html")]
+ public class AIDestinationSetter : VersionedMonoBehaviour
+#if MODULE_ENTITIES
+ , IComponentData, IRuntimeBaker
+#endif
+ {
+ /// <summary>The object that the AI should move to</summary>
+ public Transform target;
+
+ /// <summary>
+ /// If true, the agent will try to align itself with the rotation of the <see cref="target"/>.
+ ///
+ /// This can only be used together with the <see cref="FollowerEntity"/> movement script.
+ /// Other movement scripts will ignore it.
+ ///
+ /// [Open online documentation to see videos]
+ ///
+ /// See: <see cref="FollowerEntity.SetDestination"/>
+ /// </summary>
+ public bool useRotation;
+
+ IAstarAI ai;
+#if MODULE_ENTITIES
+ Entity entity;
+ World world;
+#endif
+
+ void OnEnable () {
+ ai = GetComponent<IAstarAI>();
+#if MODULE_ENTITIES
+ if (ai is FollowerEntity follower) {
+ // This will call OnCreatedEntity on this component, if the entity has already been created.
+ follower.RegisterRuntimeBaker(this);
+ } else
+#endif
+ {
+ // Update the destination right before searching for a path as well.
+ // This is enough in theory, but this script will also update the destination every
+ // frame as the destination is used for debugging and may be used for other things by other
+ // scripts as well. So it makes sense that it is up to date every frame.
+ if (ai != null) ai.onSearchPath += UpdateDestination;
+
+ // Will make OnUpdate be called once every frame with all components.
+ // This is significantly faster than letting Unity call the Update method
+ // on each component.
+ // See https://blog.unity.com/technology/1k-update-calls
+ BatchedEvents.Add(this, BatchedEvents.Event.Update, OnUpdate, 0);
+ }
+ }
+
+ void OnDisable () {
+#if MODULE_ENTITIES
+ if (world != null && world.IsCreated && world.EntityManager.Exists(entity)) {
+ world.EntityManager.RemoveComponent<AIDestinationSetter>(entity);
+ }
+ if (ai != null && !(ai is FollowerEntity)) ai.onSearchPath -= UpdateDestination;
+#else
+ if (ai != null) ai.onSearchPath -= UpdateDestination;
+#endif
+ BatchedEvents.Remove(this);
+ }
+
+#if MODULE_ENTITIES
+ void IRuntimeBaker.OnCreatedEntity (World world, Entity entity) {
+ // Do nothing except add the component. Actual syncing is handled by the SyncDestinationTransformSystem.
+ this.entity = entity;
+ this.world = world;
+ world.EntityManager.AddComponentData<AIDestinationSetter>(entity, this);
+ }
+#endif
+
+ /// <summary>Updates the AI's destination every frame</summary>
+ static void OnUpdate (AIDestinationSetter[] components, int count) {
+ for (int i = 0; i < count; i++) {
+ components[i].UpdateDestination();
+ }
+ }
+
+ /// <summary>Updates the AI's destination immediately</summary>
+ void UpdateDestination () {
+ if (target != null && ai != null) ai.destination = target.position;
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs.meta
new file mode 100644
index 0000000..b0a55d0
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c9679e68a0f1144e79c664d9a11ca121
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8dca5cfad5de0f444847aaaaa7cf73b8, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs
new file mode 100644
index 0000000..c6a58c6
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs
@@ -0,0 +1,141 @@
+using UnityEngine;
+using System.Collections.Generic;
+using UnityEngine.Profiling;
+
+namespace Pathfinding {
+ using Pathfinding.Util;
+ using Unity.Collections.LowLevel.Unsafe;
+
+ /// <summary>
+ /// Movement script for curved worlds.
+ /// This script inherits from AIPath, but adjusts its movement plane every frame using the ground normal.
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/aipathalignedtosurface.html")]
+ public class AIPathAlignedToSurface : AIPath {
+ /// <summary>Scratch dictionary used to avoid allocations every frame</summary>
+ static readonly Dictionary<Mesh, int> scratchDictionary = new Dictionary<Mesh, int>();
+
+ protected override void OnEnable () {
+ base.OnEnable();
+ movementPlane = new Util.SimpleMovementPlane(rotation);
+ }
+
+ protected override void ApplyGravity (float deltaTime) {
+ // Apply gravity
+ if (usingGravity) {
+ // Gravity is relative to the current surface.
+ // Only the normal direction is well defined however so x and z are ignored.
+ verticalVelocity += deltaTime * (float.IsNaN(gravity.x) ? Physics.gravity.y : gravity.y);
+ } else {
+ verticalVelocity = 0;
+ }
+ }
+
+ /// <summary>
+ /// Calculates smoothly interpolated normals for all raycast hits and uses that to set the movement planes of the agents.
+ ///
+ /// To support meshes that change at any time, we use Mesh.AcquireReadOnlyMeshData to get a read-only view of the mesh data.
+ /// This is only efficient if we batch all updates and make a single call to Mesh.AcquireReadOnlyMeshData.
+ ///
+ /// This method is quite convoluted due to having to read the raw vertex data streams from unity meshes to avoid allocations.
+ /// </summary>
+ public static void UpdateMovementPlanes (AIPathAlignedToSurface[] components, int count) {
+ Profiler.BeginSample("UpdateMovementPlanes");
+ var meshes = ListPool<Mesh>.Claim();
+ var componentsByMesh = new List<List<AIPathAlignedToSurface> >();
+ var meshToIndex = scratchDictionary;
+ for (int i = 0; i < count; i++) {
+ var c = components[i].lastRaycastHit.collider;
+ // triangleIndex can be -1 if the mesh collider is convex, and the raycast started inside it.
+ // This is not a documented behavior, but it seems to happen in practice.
+ if (c is MeshCollider mc && components[i].lastRaycastHit.triangleIndex != -1) {
+ var sharedMesh = mc.sharedMesh;
+ if (meshToIndex.TryGetValue(sharedMesh, out var meshIndex)) {
+ componentsByMesh[meshIndex].Add(components[i]);
+ } else if (sharedMesh != null && sharedMesh.isReadable) {
+ meshToIndex[sharedMesh] = meshes.Count;
+ meshes.Add(sharedMesh);
+ componentsByMesh.Add(ListPool<AIPathAlignedToSurface>.Claim());
+ componentsByMesh[meshes.Count-1].Add(components[i]);
+ } else {
+ // Unreadable mesh
+ components[i].SetInterpolatedNormal(components[i].lastRaycastHit.normal);
+ }
+ } else {
+ // Not a mesh collider, or the triangle index was -1
+ components[i].SetInterpolatedNormal(components[i].lastRaycastHit.normal);
+ }
+ }
+ var meshDatas = Mesh.AcquireReadOnlyMeshData(meshes);
+ for (int i = 0; i < meshes.Count; i++) {
+ var m = meshes[i];
+ var meshIndex = meshToIndex[m];
+ var meshData = meshDatas[meshIndex];
+ var componentsForMesh = componentsByMesh[meshIndex];
+
+ var stream = meshData.GetVertexAttributeStream(UnityEngine.Rendering.VertexAttribute.Normal);
+
+ if (stream == -1) {
+ // Mesh does not have normals
+ for (int j = 0; j < componentsForMesh.Count; j++) componentsForMesh[j].SetInterpolatedNormal(componentsForMesh[j].lastRaycastHit.normal);
+ continue;
+ }
+ var vertexData = meshData.GetVertexData<byte>(stream);
+ var stride = meshData.GetVertexBufferStride(stream);
+ var normalOffset = meshData.GetVertexAttributeOffset(UnityEngine.Rendering.VertexAttribute.Normal);
+ unsafe {
+ var normals = (byte*)vertexData.GetUnsafeReadOnlyPtr() + normalOffset;
+
+ for (int j = 0; j < componentsForMesh.Count; j++) {
+ var comp = componentsForMesh[j];
+ var hit = comp.lastRaycastHit;
+ int t0, t1, t2;
+
+ // Get the vertex indices corresponding to the triangle that was hit
+ if (meshData.indexFormat == UnityEngine.Rendering.IndexFormat.UInt16) {
+ var indices = meshData.GetIndexData<ushort>();
+ t0 = indices[hit.triangleIndex * 3 + 0];
+ t1 = indices[hit.triangleIndex * 3 + 1];
+ t2 = indices[hit.triangleIndex * 3 + 2];
+ } else {
+ var indices = meshData.GetIndexData<int>();
+ t0 = indices[hit.triangleIndex * 3 + 0];
+ t1 = indices[hit.triangleIndex * 3 + 1];
+ t2 = indices[hit.triangleIndex * 3 + 2];
+ }
+
+ // Get the normals corresponding to the 3 vertices
+ var n0 = *((Vector3*)(normals + t0 * stride));
+ var n1 = *((Vector3*)(normals + t1 * stride));
+ var n2 = *((Vector3*)(normals + t2 * stride));
+
+ // Interpolate the normal using the barycentric coordinates
+ Vector3 baryCenter = hit.barycentricCoordinate;
+ Vector3 interpolatedNormal = n0 * baryCenter.x + n1 * baryCenter.y + n2 * baryCenter.z;
+ interpolatedNormal = interpolatedNormal.normalized;
+ Transform hitTransform = hit.collider.transform;
+ interpolatedNormal = hitTransform.TransformDirection(interpolatedNormal);
+ comp.SetInterpolatedNormal(interpolatedNormal);
+ }
+ }
+ }
+ meshDatas.Dispose();
+ for (int i = 0; i < componentsByMesh.Count; i++) ListPool<AIPathAlignedToSurface>.Release(componentsByMesh[i]);
+ ListPool<Mesh>.Release(ref meshes);
+ scratchDictionary.Clear();
+ Profiler.EndSample();
+ }
+
+ void SetInterpolatedNormal (Vector3 normal) {
+ if (normal != Vector3.zero) {
+ var fwd = Vector3.Cross(movementPlane.rotation * Vector3.right, normal);
+ movementPlane = new Util.SimpleMovementPlane(Quaternion.LookRotation(fwd, normal));
+ }
+ if (rvoController != null) rvoController.movementPlane = movementPlane;
+ }
+
+ protected override void UpdateMovementPlane () {
+ // The UpdateMovementPlanes method will take care of this
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs.meta
new file mode 100644
index 0000000..91969e5
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4cf55afa704da4bada4b28145f5f4685
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/MoveInCircle.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/MoveInCircle.cs
new file mode 100644
index 0000000..9d5c464
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/MoveInCircle.cs
@@ -0,0 +1,51 @@
+using UnityEngine;
+using Pathfinding.Drawing;
+
+namespace Pathfinding {
+ /// <summary>
+ /// Moves an agent in a circle around a point.
+ ///
+ /// This script is intended as an example of how you can make an agent move in a circle.
+ /// In a real game, you may want to replace this script with your own custom script that is tailored to your game.
+ /// The code in this script is simple enough to copy and paste wherever you need it.
+ ///
+ /// [Open online documentation to see videos]
+ ///
+ /// See: move_in_circle (view in online documentation for working links)
+ /// See: <see cref="AIDestinationSetter"/>
+ /// See: <see cref="FollowerEntity"/>
+ /// See: <see cref="AIPath"/>
+ /// See: <see cref="RichAI"/>
+ /// See: <see cref="AILerp"/>
+ /// </summary>
+ [UniqueComponent(tag = "ai.destination")]
+ [AddComponentMenu("Pathfinding/AI/Behaviors/MoveInCircle")]
+ /// <summary>[MoveInCircle]</summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/moveincircle.html")]
+ public class MoveInCircle : VersionedMonoBehaviour {
+ /// <summary>Target point to rotate around</summary>
+ public Transform target;
+ /// <summary>Radius of the circle</summary>
+ public float radius = 5;
+ /// <summary>Distance between the agent's current position, and the destination it will get. Use a negative value to make the agent move in the opposite direction around the circle.</summary>
+ public float offset = 2;
+
+ IAstarAI ai;
+
+ void OnEnable () {
+ ai = GetComponent<IAstarAI>();
+ }
+
+ void Update () {
+ var normal = (ai.position - target.position).normalized;
+ var tangent = Vector3.Cross(normal, target.up);
+
+ ai.destination = target.position + normal * radius + tangent * offset;
+ }
+
+ public override void DrawGizmos () {
+ if (target) Draw.Circle(target.position, target.up, radius, Color.white);
+ }
+ }
+ /// <summary>[MoveInCircle]</summary>
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/MoveInCircle.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/MoveInCircle.cs.meta
new file mode 100644
index 0000000..0572f6b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/MoveInCircle.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3124c7cfbe5ac4b1cbe42bd6b1e4279d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8cf8c12a4f0221b438c0d7ffa0eab6f4, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/Patrol.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/Patrol.cs
new file mode 100644
index 0000000..47f4c00
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/Patrol.cs
@@ -0,0 +1,68 @@
+using UnityEngine;
+using System.Collections;
+
+namespace Pathfinding {
+ /// <summary>
+ /// Simple patrol behavior.
+ /// This will set the destination on the agent so that it moves through the sequence of objects in the <see cref="targets"/> array.
+ /// Upon reaching a target it will wait for <see cref="delay"/> seconds.
+ ///
+ /// [Open online documentation to see videos]
+ ///
+ /// See: <see cref="Pathfinding.AIDestinationSetter"/>
+ /// See: <see cref="Pathfinding.AIPath"/>
+ /// See: <see cref="Pathfinding.RichAI"/>
+ /// See: <see cref="Pathfinding.AILerp"/>
+ /// </summary>
+ [UniqueComponent(tag = "ai.destination")]
+ [AddComponentMenu("Pathfinding/AI/Behaviors/Patrol")]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/patrol.html")]
+ public class Patrol : VersionedMonoBehaviour {
+ /// <summary>Target points to move to in order</summary>
+ public Transform[] targets;
+
+ /// <summary>Time in seconds to wait at each target</summary>
+ public float delay = 0;
+
+ /// <summary>
+ /// If true, the agent's destination will be updated every frame instead of only when switching targets.
+ ///
+ /// This is good if you have moving targets, but is otherwise unnecessary and slightly slower.
+ /// </summary>
+ public bool updateDestinationEveryFrame = false;
+
+ /// <summary>Current target index</summary>
+ int index = -1;
+
+ IAstarAI agent;
+ float switchTime = float.NegativeInfinity;
+
+ protected override void Awake () {
+ base.Awake();
+ agent = GetComponent<IAstarAI>();
+ }
+
+ /// <summary>Update is called once per frame</summary>
+ void Update () {
+ if (targets.Length == 0) return;
+
+ // Note: using reachedEndOfPath and pathPending instead of reachedDestination here because
+ // if the destination cannot be reached by the agent, we don't want it to get stuck, we just want it to get as close as possible and then move on.
+ if (agent.reachedEndOfPath && !agent.pathPending && float.IsPositiveInfinity(switchTime)) {
+ switchTime = Time.time + delay;
+ }
+
+ if (Time.time >= switchTime) {
+ index++;
+ switchTime = float.PositiveInfinity;
+
+ index = index % targets.Length;
+ agent.destination = targets[index].position;
+ agent.SearchPath();
+ } else if (updateDestinationEveryFrame) {
+ index = index % targets.Length;
+ agent.destination = targets[index].position;
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/Patrol.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/Patrol.cs.meta
new file mode 100644
index 0000000..7d5bfd6
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/Patrol.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 22e6c29e32504465faa943c537d8029b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: af22be3b7ab2b3b44afe7297460363ff, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant: