Lightweight Local Avoidance

  • A* version: 5.3.0
  • Unity version: 6000.0.28f1
  • What I’m looking for is a centeralized system for handling all of my enemies avoidance without making extra calls for player transform from each enemy, I want to be able to get a movement delta for each enemy and that’s it. No pathfinding whatsoever. For now I’m using the RVOController but it makes me set the target every frame which costs additional calls to player transform and also the RVOSimulator probably makes the calculation step by step for each additional enemy’s fixed update such as
    1. first enemy tries to get a delta and RVO simulator makes calculations
    1. second enemy tries `````````````
      I want a system that takes all the enemy positions as a list and gives a delta list as an output but I couldn’t find anything that can be used as this and since (as I understood) it is a mostly closed system I dont know how can I make use of the local avoidance logic inside A* package like this explanation. Did anyone tried to do something like this ? is there any guide on where to start ?

Hi

You can take a look at the LightweightRVO example scene. It shows how to use entities to handle local avoidance.

If you want, you can alternatively use the RVOSimulator.instance.GetSimulator().AddAgent (or AddAgentBurst) to perform low-level access.

The system needs to keep track of the agents, since the output from the previous frame feeds into the calculations for the next frame. Not just the positions are needed.

I tried to utilize it by myself but it is so tangled with the spesific usage of rendering them by itself but I managed to extract it, now it doesnt give anything different than zero vector as delta and Since I don’t know anything about ECS I couldn’t figure out where the problem is.

#pragma warning disable 0282 // Allows the 'partial' keyword without warnings
using System.Collections.Generic;
using Pathfinding.ECS;
using Pathfinding.ECS.RVO;
using Pathfinding.Examples;
using UnityEngine;
using Pathfinding.RVO;
using Pathfinding.Util;
using Unity.Mathematics;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;

    public partial class EnemyMovementManager : MonoBehaviour {

        public float radius = 1;
        public float maxSpeed = 20;
        public float agentTimeHorizon = 10;
        [HideInInspector]
        public float obstacleTimeHorizon = 10;
        public int maxNeighbours = 10;
        public AgentDebugFlags debug;
		[SerializeField]
        private Transform targetTransform;

		private EntityArchetype archetype;
		EntityManager entityManager;
		private World world;
		EntityCommandBuffer buffer;
		private EntityQuery entityQuery;
		public Dictionary<Entity, Vector3> enemyDeltas = new Dictionary<Entity, Vector3>();

        public void Start () {
            CreateAgents ();
            var simulationGroup = world.GetOrCreateSystemManaged<AIMovementSystemGroup>();
            simulationGroup.AddSystemToUpdateList(world.CreateSystem<LightweightRVOControlSystem>());
            simulationGroup.AddSystemToUpdateList(world.CreateSystem<LightweightRVO.LightweightRVOMoveSystem>());
        }

		public void CreateAgents() {
			world = World.DefaultGameObjectInjectionWorld;
			entityManager = world.EntityManager;
			archetype = entityManager.CreateArchetype(
													  typeof(LocalTransform),
													  typeof(LocalToWorld),
													  typeof(AgentCylinderShape),
													  typeof(ResolvedMovement),
													  typeof(DestinationPoint),
													  typeof(MovementControl),
													  typeof(RVOAgent),
													  typeof(AgentMovementPlane),
													  typeof(LightweightRVO.LightweightAgentData),
													  typeof(SimulateMovement),
													  typeof(SimulateMovementRepair),
													  typeof(SimulateMovementControl),
													  typeof(SimulateMovementFinalize)
													 );

			using (var buffer = new EntityCommandBuffer(Allocator.TempJob)) {
				entityQuery = entityManager.CreateEntityQuery(typeof(LightweightRVO.LightweightAgentData));

				#if MODULE_ENTITIES_1_0_8_OR_NEWER
        buffer.DestroyEntity(existingEntities, EntityQueryCaptureMode.AtPlayback);
				#else
				buffer.DestroyEntity(entityQuery);
				#endif

				buffer.Playback(entityManager);
			}
		}

		public void AssignEnemyEntity(Enemy enemy) {
			using (EntityCommandBuffer buffer = new EntityCommandBuffer(Allocator.TempJob))
			{
				Entity entity = CreateAgent(archetype, buffer, Vector3.zero, GameManager.Instance.PlayerTransform.position);
				enemyDeltas.Add(entity, Vector3.zero);
				enemy.SetEntity(entity,this);
				buffer.Playback(entityManager);
			}
		}


		public void Update () {
            var system = world.GetOrCreateSystem<LightweightRVOControlSystem>();
            world.Unmanaged.GetUnsafeSystemRef<LightweightRVOControlSystem>(system).debug = debug;

            if (targetTransform != null) {
                CalculateAndAssignDeltas(targetTransform.position);
            }
        }

		public struct LightweightAgentData : IComponentData {
			public float maxSpeed;
		}
		Entity CreateAgent(EntityArchetype archetype, EntityCommandBuffer buffer, Vector3 position, Vector3 destination, float priority = 0.5f) {
			var entity = buffer.CreateEntity(archetype);
			buffer.AddComponent(entity, LocalTransform.FromPosition(position));
			buffer.AddComponent(entity, new DestinationPoint { destination = destination });
			buffer.AddComponent(entity, new RVOAgent {
				agentTimeHorizon = agentTimeHorizon,
				obstacleTimeHorizon = obstacleTimeHorizon,
				maxNeighbours = maxNeighbours,
				layer = RVOLayer.DefaultAgent,
				collidesWith = (RVOLayer)(-1),
				priority = priority,
				priorityMultiplier = 1,
				flowFollowingStrength = 0,
				debug = AgentDebugFlags.Nothing,
				locked = false
			});
			buffer.AddComponent(entity, new AgentMovementPlane { value = new NativeMovementPlane(quaternion.identity) });
			buffer.AddComponent(entity, new AgentCylinderShape { radius = radius, height = 1.0f });
			buffer.AddComponent(entity, new LightweightAgentData { maxSpeed = maxSpeed });
			buffer.AddComponent(entity, new MovementControl {
				targetPoint = position,
				endOfPath = destination,
				speed = maxSpeed,
				maxSpeed = maxSpeed * 1.1f,
				hierarchicalNodeIndex = -1,
				targetRotation = 0,
				targetRotationOffset = 0,
				rotationSpeed = 0,
				overrideLocalAvoidance = false
			});


			return entity;
		}

		private void CalculateAndAssignDeltas(Vector3 targetPosition) {
			EntityQuery query = entityManager.CreateEntityQuery(typeof(LocalTransform), typeof(RVOAgent));
			NativeArray<Entity> entities = query.ToEntityArray(Allocator.TempJob);
			var positions = new NativeArray<float3>(entities.Length, Allocator.TempJob);
			var deltas = new NativeArray<float3>(entities.Length, Allocator.TempJob);

			for (int i = 0; i < entities.Length; i++) {
				positions[i] = entityManager.GetComponentData<LocalTransform>(entities[i]).Position;
			}

			for (int i = 0; i < positions.Length; i++) {
				deltas[i] = CalculateAvoidanceDelta(positions[i], positions, targetPosition);
			}

			for (int i = 0; i < entities.Length; i++) {
				if (!enemyDeltas.ContainsKey(entities[i])) {
					enemyDeltas[entities[i]] = deltas[i];
				}
			}

			entities.Dispose();
			positions.Dispose();
			deltas.Dispose();
		}

        private float3 CalculateAvoidanceDelta(float3 position, NativeArray<float3> positions, float3 targetPosition) {
            float3 delta = math.normalize(targetPosition - position);

            for (int i = 0; i < positions.Length; i++) {
                if (!positions[i].Equals(position)) {
                    float3 avoidance = position - positions[i];
                    if (math.length(avoidance) < 1.0f) {
                        delta += math.normalize(avoidance);
                    }
                }
            }

            return math.normalize(delta);
        }
    }
		[UpdateBefore(typeof(RVOSystem))]
		[UpdateInGroup(typeof(AIMovementSystemGroup))]
		[DisableAutoCreation]
		public partial struct LightweightRVOControlSystem : ISystem {
			public AgentDebugFlags debug;
			EntityQuery entityQueryDirection;
			EntityQuery entityQueryControl;

			public void OnCreate (ref SystemState state) {
				entityQueryDirection = state.GetEntityQuery(
					ComponentType.ReadWrite<LocalTransform>(),
					ComponentType.ReadOnly<AgentCylinderShape>(),
					ComponentType.ReadOnly<AgentMovementPlane>(),
					ComponentType.ReadOnly<MovementControl>(),
					ComponentType.ReadOnly<ResolvedMovement>()
					);
				entityQueryControl = state.GetEntityQuery(
					ComponentType.ReadOnly<LightweightRVO.LightweightAgentData>(),
					ComponentType.ReadOnly<DestinationPoint>(),
					ComponentType.ReadWrite<RVOAgent>(),
					ComponentType.ReadWrite<MovementControl>()
					);
			}

			public void OnUpdate (ref SystemState state) {
				state.Dependency = new AlignAgentWithMovementDirectionJob {
					deltaTime = SystemAPI.Time.DeltaTime,
					rotationSpeed = 5,
				}.ScheduleParallel(entityQueryDirection, state.Dependency);

				state.Dependency = new JobControlAgents {
					deltaTime = SystemAPI.Time.DeltaTime,
					debug = debug,
				}.Schedule(entityQueryControl, state.Dependency);
			}

			[BurstCompile]
			public partial struct JobControlAgents : IJobEntity {
				public float deltaTime;
				public AgentDebugFlags debug;

				public void Execute (in LightweightRVO.LightweightAgentData agentData, in DestinationPoint destination, ref RVOAgent rvoAgent, ref MovementControl movementControl, [EntityIndexInQuery] int index) {
					movementControl = new MovementControl {
						targetPoint = destination.destination,
						endOfPath = destination.destination,
						speed = agentData.maxSpeed,
						maxSpeed = agentData.maxSpeed * 1.1f,
						hierarchicalNodeIndex = -1,
						targetRotation = 0,
						targetRotationOffset = 0,
						rotationSpeed = 0,
						overrideLocalAvoidance = false,
					};

					if (index == 0) {
						rvoAgent.debug = debug;
					} else {
						rvoAgent.debug = debug & AgentDebugFlags.ReachedState;
					}
				}
			}

			[BurstCompile(FloatMode = FloatMode.Fast)]
			public partial struct AlignAgentWithMovementDirectionJob : IJobEntity {
				public float deltaTime;
				public float rotationSpeed;

				public void Execute (ref LocalTransform transform, in AgentCylinderShape shape, in AgentMovementPlane movementPlane, in MovementControl movementControl, in ResolvedMovement resolvedMovement) {
					if (resolvedMovement.speed > shape.radius*0.01f) {
						var speedFraction = math.sqrt(math.clamp(resolvedMovement.speed / movementControl.maxSpeed, 0, 1));
						// If the agent is moving, align it with the movement direction
						var actualDirection = movementPlane.value.ToPlane(resolvedMovement.targetPoint - transform.Position);
						var actualAngle = math.atan2(actualDirection.y, actualDirection.x) - math.PI*0.5f;
						var targetRotation = movementPlane.value.ToWorldRotation(actualAngle);
						transform.Rotation = math.slerp(transform.Rotation, targetRotation, deltaTime*speedFraction*rotationSpeed);
					}
				}
			}
		}

Hi, I couldn’t find RVOSimulator.instance.AddAgent method, is it still present inside 5.3.0 ?

Might be this? Accessible using RVOSimulator.GetSimulator().AddAgent(IAstarAI). Let me know if that’s not the same method you’re looking for :+1: