Compatible with Entities (ECS) and DOTS Framework?

Im only using the free version of the A*Pathfinding project, so I know I cannot jobify the calculations, but can I use it to calculate paths and move EntityComponentSystem (ECS) Entities? If yes, how would I do that?

Hi

You can request paths directly from the AstarPath component. If you want to use modifiers you could use a shared Seeker component (only one for the entire scene) to later post-process the path (with the Seeker.PostProcess call).
See https://arongranberg.com/astar/docs/callingpathfinding.html#calling-directly
You could write a custom movement script that uses ECS code to move the entities: https://arongranberg.com/astar/docs/custom_movement_script.html

1 Like

Thanks! Ill go check those out.

1 Like

@aron_granberg Is requesting paths directly from the AstarPath component a Pro feature? If it is, there will be debate in purchasing it, but it would also be neat if there was a free+ version for students (although I have no idea how bad verification of “active student status” would be, so that’s more an idle thought than an actual recommendation).

EDIT: I was trying to use AstarPath.active. I figured it out now.

1 Like

Are there any example on using ECS with A* Pathfinding Project?

Hi

@Ph0t0n Not at the moment. With ECS you probably want to use the raw path calculation api as described here: https://arongranberg.com/astar/docs/callingpathfinding.html#calling-directly

This works fine in ECS as long as you’re calling StartPath() from the main thread. I you try to call it from a worker thread, it’ll cough up a unity exception complaining that "if (!Application.isPlaying) { BlockUntilCalculated(path); } must be called from the main thread. That line is located in AstarPath.cs in the StartPath() function.

I attempted to comment out that line, but I’m not sure if it’s because of something in the package manager or whatever, but when that line is commented out the unity editor crashes.

Hi

I think just commenting that line out should allow you to call it from different threads.
It should already be thread safe.

Are you sure the crash is not due to something else?

I’m not sure why it was crashing. I had restarted the Unity Editor several times and the editor would always crash when hitting the play button. I finally did get it working though!

For anyone else wondering how to do it… here’s what I did:

  1. Update to the latest version of A* Pathfinding Project via the package manager (4.3.27). I don’t have any evidence that this is needed, but it’s one of the few things I changed before getting it working.

  2. Close Unity Editor

  3. Open C:\Users\Me\AppData\Local\Unity\cache\packages\arongranberg.com\packages\8fbb73c301f50f24d5a3eef8ed9e27185eb38da58a90e\com.arongranberg.astar@4.3.27\Core\AStarPath.cs . (Substituting your username and different hex numbered folder names.)

  4. Find the StartPath() function. Comment out the “if (!Application.isPlaying) { BlockUntilCalculated(path); }” section (3 lines).

  5. Open Unity Editor again. It should reload the package,

Now you can run AstarPath.StartPath() outside of the main thread… like in an Entities.ForEach().ScheduleParallel() for example.

1 Like

Hey Ph0t0n

Did you manage to get the A star pathfinding working on and entity?
Is so would you give and example of how you changed the A* to work with Entitys?

Regards

Well… yes and no. The only changes to APFP are what I wrote about in my previous post, which allows it to calculate paths in threads other than the main thread. Although I would love to use more of the APFP with ECS, I was not able to get any of the AI classes working (AIPath, RichAI, AILerp, etc). I think if one had enough time, he/she could rewrite those classes to be burst/ECS compatible.

The only part of A*PFP I’m using is the static method for calculating a path. So in my “SpawnSystem”, I spawn enemies and give them a path. Each enemy entity stores their own path in a PathData component that contains a FixedList4096. The spawner calls this function:

// calculates a path and copies it from A*PFP's vectorPath to a FixedList4096
protected static void MakePath(ref FixedList4096<float3> destPath, float3 spawnerPos, float3 destPos)
    {
        ABPath path = null;
        path = ABPath.Construct(spawnerPos, destPos);
        AstarPath.StartPath(path);
        path.BlockUntilCalculated();
        for (int i = 0; i < path.vectorPath.Count; i++) { destPath.Add(path.vectorPath[i]); }
    }

This will break if paths have more than 340 points (because of FixedList4096). You could use a DynamicBuffer if your map is huge and you need more points, but the fixed list method is super easy to use.

Then, I have a PathMovementSystem that queries for any entities with a path and uses a movement script very similar to the one Aaron talked about here: https://arongranberg.com/astar/docs/custom_movement_script.html , except instead of using CharacterController, it just sets the rotation and translation component values (example: position.Value += velocity * deltaTime; rotation.Value = lookRotation; ) It’s kinda crappy compared to AIPath or RichAI, but it runs really fast with Burst and for my simple purposes it does the trick.

If you need entities to repath because something blocks their path, then you can call MakePath() again. I don’t have it implemented yet, but when I have some time, I’m hoping to find a way to query the grid/navmesh and determine which enemy paths were affected by the obstacle so that paths only need to be recalculated for affected enemies instead of all enemies. I don’t know if that’s even possible though. If you have moving targets that the enemies are heading towards, you’ll need to periodically recalculate paths for each enemy (in my case the targets are fixed so I don’t need to do that).

Maybe in the future when I have some spare time I’ll look into getting RVO local avoidance working with ECS… I think some other people may have gotten it working. Currently I’m using unity.physics for local avoidance, but I suspect it’s a lot more expensive than A*PFP’s RVO.

3 Likes

So your not using obstacles that will be placed?
I mean are you pathfinding just a road the AI should follow?

I cant seem to get my head around this ECS, only thing i know is when i convert my Enemys to Entitys, then i lose the A Star Pathfinding cause its not optimized for ECS/DOTS.

I actually just wanted the Seeker and AI DestinationSetter to work with IAstarAI.
Then everything would be nice and work just as i have it now :D!

1 Like

Yeah, some of my obstacles are placed by the user (I haven’t added that functionality yet though), and others are hand placed on the map and static. Currently all of my enemies get spawned, but it would be trivial to place them manually if I wanted to.

I too wanted to use Seeker and AI DestinationSetter, but without converting all of those scripts, I couldn’t find a way. It would take me months to figure out how to convert them and I can’t afford to do that right now.

So… yes, like you said, you can’t attach any A* Pathfinding stuff to your enemies. I’m not an expert by any means, but perhaps if I list the steps I took it might help you:

  1. First I created a terrain with roads, static obstacles, etc. Just like any normal non-ECS project.

  2. I added an empty game object called A* and added the PathFinder script to it and setup my navigation grid just like you would in an non-ECS project.

  3. I converted my terrain to a mesh and gave it a static physics body+shape and convert-to-entity script. This step is optional. I had to do this because I want to use unity.physics - it does things like allowing you to put physics bodies on enemies and detect bullet collisions with the ground.

  4. I made enemy prefabs and added a convert-to-entity script to them. They’re similar to any normal monobehaviour prefab with a mesh renderer + material. I also added a MoveSpeedData component and a few other things like HealthData or whatever.

  5. The only unique thing about these enemy prefabs is that when they get spawned, I add two extra components WaypointIndexData, and PathData. If you’re not spawning enemies, you could use [GenerateAuthoringComponent] and add them to your prefab instead of doing it in the spawner. They look like this:

public struct PathData : IComponentData
{
    public FixedList4096<float3> path;
}
public struct WaypointIndexData : IComponentData
{
    public int index; // current waypoint index
}

Notice that there is nothing in these components that use A*PFP code. The paths are there in an ECS appropriate form though… so the movement system can propel the enemies forward.

  1. I made a spawning system, that spawns the prefabs (there are lots of tutorials on this). Right before it spawns them, it uses that MakePath() function I mentioned earlier to find the path from the newly spawned enemy to the destination. This is the only place that I use A*PFP. My spawn system’s query roughly like this:
Entities.ForEach((ref PathData spawnPath, in SpawnOptionsData spawnOpts, in LocalToWorld spawnLtw) => { }).Run();
  1. Then I made a PathMovementSystem that sets the velocity and rotation of the enemy to the next point in the path until the path completes (the path was precomputed in the spawner). (See my previous comment about the PathMovementSystem) The query looks something like this:
Entities.WithNone<DeathData>().ForEach((ref Translation pos, ref Rotation rot, ref WaypointIndexData waypoint, in MoveSpeedData speed, in PathData path) => { }).ScheduleParallel();
  1. For games with moving targets (like ones that chase the player for example), you would need to create another system that would query all entities with path and waypoint data components and recalculate the path. I wouldn’t suggest doing it every frame because it’ll likely be way to slow… maybe every 1/2 to a couple of seconds depending on how many enemies there are.
2 Likes

I’ve begun implementing this as well, in a relatively complex environment. I am curious if you have tried running hybrid gameobjects for the pathfinding. I know that “pure-ecs” GOs would be best, but maybe to ease the transition a little the GO that is converted to ecs could remain with just the pathfinding component, and then a system that syncs the gameobject to the entity position and running the pathfinding updates through to the entity. This way the unity.physics or havok system can still be used.

It may be a terrible idea but I will update on what I find out, just wanted to see if you have tried this at all.

Most of the performance used for my game is from rendering and physics so I’m thinking this might be okay.

I have not tried it, but I think it might be possible to do that (use pathfinding with hybrid objects)… but I can’t say for sure. The only thing I’ve used hybrid objects for so far are to allow particles and audio clips to follow agents. I know that if you move an entity, the GO automatically moves with it (there must be a built in system that does it), but I don’t know if that is automatic the other way around.

If your bottleneck is physics and rendering, I think the hybrid approach would help - as long as you make sure all the rendering and physics are done on the entity and not the gameobject. If you get it working, let me know - If it works, I might have to try it!

1 Like

I built a quick prototype in HDRP using the RVO example.
The RVO agent quad mesh was replaced with a complex LOD mesh.
This picture is the stats from 3 runs:

  • base scene non DOTS
  • non DOTS with custom mesh (3 meshes: body, spinning blade right and spinning blade left. total: LOD0: ~ 5000 triangles
  • DOTS

Obviously, the non-DOTS with custom lod mesh example could be improved by combining all of the LODs into either a skinned mesh or some type of custom batching, but the ECS with physics is promising. Also, HDRP has a lot of overhead, but that’s what I’m using so I used it for the test. Keep in mind all of this is with the entity viewer and profiler running at the same time.

System

  • Threadripper 3970x (32 core/64 thread)
  • Geforce rtx 2080 ti
  • 64 GB DDR4

RVO EXAMPLE DOTS TEST (IN HDRP)
Base: 122.4 fps
Non-DOTS w LOD mesh: 98.6 fps
DOTS: 98.7 fps

The time saved from the entity batching on the renderers is offset by roughly the same amount of the added overhead from the physics engine running. And I still need to investigate the physics workflow because it seems high to me at the moment.

There wasn’t much modification to the code:
RVOExampleAgent: at the start of Update I set the rvoagent transform to the entity translation and capture the starting translation before the calculations. At the end of the update, I assign the change in position to an entity component. In an ECS system, I add this offset to the translation of the entity.

I will continue to update as I test bullet collisions and other things.

So I’ve rewritten a lot of the rvo controller and created agent movement scripts using DOTS/ECS and am finally getting to the point where I see really good results. At 10,000 units with pathfinding/local avoidance, I am running at above 60fps and that is with all units touching each other.

This is with all rvo agents as entities, not just inside the rvo simulator. The positional data is getting assigned as a parallel scheduled job in an ECS system.

Not sure how to post a video on here, but I put it up on twitter, check it out: https://twitter.com/BigRookGames/status/1335360569235427328

This is really promising, as even at 10,000 there is very little time spent in calculation for the pathfinding system, and even the movement system of the agents. I will continue to update as I progress (there is still a lot of room for optimization)

5 Likes

Whoa! That’s awesome! Nice job!

I almost do that for my game too, 10K with AIPath movement is so smooth.
I have test 30K too but Astar logic doesn’t match performance of ECS so it’s laggy.

I need to add RVO as @HeroSyndromeTheGame but his work confirms me this the good way for my project ( thank :slight_smile: )

@aron_granberg Waiting A* pathdinfing 5 based on ECS even if I have to repay the package!

2 Likes

That’s cool man! So you rewrote the RVO controller to use ECS instead of needing to be attached to game objects and you’re not using hybrid entities anymore? If so… any chance you could share it?