I studied the moving platform example and the corresponding code. Since there is no corresponding system for follower entities atm, I wonder what can be done.
Background:
I have multiple ships moving around and being constructed/destroyed dynamically. I’d like units to be able to move both on land and water (no problem), but also on deck of the ships. On the ships, it’s not necessary to have a complex navmesh, it even could que as simple as a rectangle (if I can integrate a “real” navmesh though, of course the better). Also, to enter the ships or jump from one ship to another, it could be done with proximity checks in case node links will over-complicate things.
I just wonder what would be the best strategy implementing that and I’ve identified these options:
A custom simple navigation system: A custom ecs system that moves the agents around on the ships collider or on a simple navmesh, integrating the ships world position.
Pros: Probably the easiest to implement
Cons: Dedicated solution, systems for animations, destination setting etc need to integrate additional interfaces
Using the A* pathfinding solution partly:
This could consist in using the movement systems of the package, but e.g. manipulating the paths and positions to integrate the ships world position.
Pros: Make use of existing systems, can be integrated seamlessly to the rest of the framework
Cons: Complicated to integrate, error prone for breaking changes with future package upgrades
Custom graph:
A custom graph dedicated entirely to moving platforms.
Pros: Best integration, portability for other projects, “professional” solution
Cons: Probably requires the most work and will be complicated to develop
From your extensive experience with pathfinding, maybe you can give me a hint what would be the best call to make here? Or can you think of another solution not mentioned?
And more general:
Are you planning to integrate moving platforms for follower entities at some point yourself? I think it’s actually quite a common challenge.
With the FollowerEntity, this is currently not possible. The LocalSpaceRichAI movement script can do this, however.
However, if you only need a rectangle, you don’t need pathfinding at all, as every straight line is a valid path. So a custom movement system might be a lot less work.
Thanks for the suggestion, yes, that’s pretty much what I thought.
Since my animation systems take stuff like the estimated velocity of agents into account I still might write to these components though. But let’s see, it’s probably the most straight forward way.
I was analyzing the LocalSpaceRichAI example and some other stuff.
If I understood it correctly, the idea is to fake it, leaving the navmesh in a static place far off and work out the rest with transformation matrices.
So one would take the current agent position and the destination position (both on the moving platform), apply a transformation matrix to represent positions on the static navmesh and calculate a path based on the transformed start and end point, correct?
Until here this should be achievable with the current framework as it is, I guess, by calculating a path manually.
However we would have to transform the corners of the path back to the world positions on the moving platform and this is where it get’s tricky, as a lot of shenanigans are going on between path and movement systems.What could we do here?
I guess one solution would be to deactivate the movement system and replace it with a custom one, applying the transformation backwards to each corner of the path?
Alternatively how about something like an “offset” component with a transformation matrix to be integrated to the movement system? If this could be generalized and be applied right before updating the agents position, it might solve other peoples problems simultaneously (e.g. for flying units, or path modifiers etc thinking a bit broader)…
Hm, I’m was going through the JobRepairPath, but I’m not sure if rewriting it would be enough. Especially because the localtransform of the entity is used in plenty of other places for distance checks etc.
Maybe it’s a stupid idea, but just for the sake of thinking out loud:
You use the localTransform for pretty much everything in pathfinding, right? However if I understand the ecs transform system correctly, the localTransform is actually equivalent to the monobehavior Transform.localPosition/localRotation stuff, and this is not necessarily the world position, if the entity is child of another parent. The world position would be localToWorld, but this is not used by the pathfinding project (which is not a problem, because mostly a navigation agent won’t have a parent anyways.
But maybe we could leverage this to our advantage:
If we reparent the follower entity to a placeholder parent entity (or even the moving platform itself?), we should be able to manipulate the follower entity’s world position through the parent without interfering in the follower entity’s localTransform (and such the actual pathfinding). And there we have pretty much our offset that can bridge the gap between the static “fake positioned” navmesh of the moving platform, the follower entity’s path using the “fake positions” and the follower entity’s world position.
I admit, I did not think this through in every detail, but does the general idea make sense to you?
I also saw that the follower entities allow now for some hooks through movementOverrides. This could also be of help maybe.
I mean. Come to think of it.
Maybe what you could do is just to change the SyncTransformsToEntitiesJob and JobSyncEntitiesToTransforms jobs. Then the agent can think it’s always on the graph, but you change the transform so that it is actually on the moving ship.
However, properties like ai.position, ai.destination and similar would then be relative to the graph, instead of the world.
The basic idea was to add a custom component to the follower entity and assigning the moving platform entity (also a hybrid entity) to its value. The moving platform component references a transformation matrix to the static “fake” navmesh far off like discussed above (I used NavmeshAdd for now, but other stuff like a recast graph are suitable also). A system then uses these components and applies a transformation matrix to the localToWorld value at the end of the simulation. So while the localTransform is running around on the static navmesh, the localToWorld is transformed to real world space. I decided to manipulate the localToWorld, not the gamobjects transform, since I primarly work with ECS anyways and it also seems a bit cleaner.
For enter/exit behavior, I wrote a custom NodeLink helper script (shamelessly copying from Follower Jump Link) that triggers the OnEnterMovingPlatform/OnExitMovingPlatform functions on the agent, but in the end, any trigger (e.g. by raycast or colliders) should also work.
It’s still a bit experimental, but seems to work so far.
A few questions though:
For enter/exit nodes, I used NodeLink2. The node points move around with the moving platforms and the graph is automatically updated in almost realtime (the default behavior). How expensive is that in terms of performance? Does it matter if like 100 dynamic NodeLink2 are active at the same time (e.g. multiple entry points for multiple platforms)?
The NodeLink2 points need to be quite close to the navmesh. Is there a radius somewhere to widen the search radius? So e.g. if a nodelink point is like 3-5 meter in range of a navmesh, it still counts as enabled?
For now I need to clone not only the navmesh to the far off location of the moving platform, but also it’s collider, otherwise the entities will fall through the ground. Any chance to overcome this? I think it’s also related a bit to the ClampToNavmesh/gravity stuff. I admit I don’t really understand that part well. E.g. why does it fall through the ground, when a) the groundmask is set to graph and b) the ClampToNavmesh should prevent this?
For now, I need to deactivate updatePosition and updateRotation of the follower entity and set it’s gameobject transform to the localToWorld position and rotation. Arguable the SyncTransformsToEntitiesJob or the other one should use per default the localToWorld position and rotation, not the localTransform. Or is there any particular reason for using the localTransform?
In my real game I use entities directly, so I don’t care, but I have the feeling that the gameobjects transform should always be synced to the localToWorld component
A minor thing:
NavmeshClipper has an internal function: internal abstract void NotifyUpdated
which prohibits to extend it (I tried to make a class similar to NavmeshAdd). Maybe you could make it public?
I’m on the road next week, but when I’m back, I can share more details of the setup/code. It could be a nice additional example scene for the package if you are interested
Thanks for the answers. I think most is clear now and if you can add the small changes like NotifyUpdated in NavmeshClipper and maxSnappingdistance in NodeLink2 in one of the upcoming updates I’d be very grateful.
The only small doubt remaining is the part about clamp/colliders/gravity. The ideal here would be to avoid the necessity to duplicate the collider for the static “far-off” navmesh. But if that is not possible, it’s also ok.
If I understand the code correctly, the raycastcommands are basically responsible for checking the surface below. I could not find exactly where the raycasts are performed - but I assume these are the “default” UnityEngine.Physics.Raycast calls (just a bit abstracted in some fancy job) and work quite normally like ray hits collider etc…
There is not any chance to use the navmesh surface directly as ground instead of a collider?
In my real game I use ECS physics - I guess I could deactivate gravity on the follower entities and let ECS physics take care of it. Or do you see any obvious downside on that?
Maybe you could also have a look at my other question about the clampToNavmesh part, it seems a bit interconnected. I’ll add some details there.
Also like I said, if you are interested in adding this little example to the samples, I’d be happy to share it (completely free of course and without any claim for licensing or alike) - just let me know. If not, it’s also completely fine, I get it, if you want to avoid cluttering you package with external stuff.
This helps me a lot. First get LocalSpaceGraph moveMatrix(from origin to now), Then SyncTransformsToEntitiesJob use moveMatrix.inverse.MultiplyPoint3x4(transform.position) set to entities and JobSyncEntitiesToTransforms use moveMatrix.MultiplyPoint3x4(tr.ValueRO.Position) to sync to transform.
The scene should contain all relevant code and data for the example above. Let me know if you have any trouble running it. Obviously requires A* Pro to be installed…
The code can certainly be improved and optimized - but for better understanding the idea, I hope that helps.