Hi everyone,
I’m working on a project for an RTS developed with ECS and Netcode for Entities.
I’ve purchased A* for managing the movement of all units.
I’m having an issue with dynamic obstacles. Let me explain:
Netcode for Entities is server-authoritative, and all entities’ positions and movements are controlled by the server. However, when they move with A*, they reference the initial ground scan. In cases of new constructions or the movement of another object, the scan is not updated or replicated to the clients.
Do you have any ideas on how this could be resolved?
Do the clients have their own graphs that need to by synced? And if so, what would they need the graph for? If a new construction is added, I think the better play here would be to send the information of the building to the server and only have the server change the graph. How does that sound?
From the tests I’ve conducted, what disrupts the dynamic obstacle is the Ghost component, which in Netcode for Entities is used by the server to replicate all entities to the clients. It seems that when it is replicated by the server, the graph on the server ignores the entities. For now, the workaround I found is to create colliders aligned with the
owned entities. In this case, the graph updates properly. So, it only works with a companion GameObject. However, I didn’t want to add unnecessary objects to the game. But if you think it’s the only alternative, I’ll adapt. The project is worth it, and I don’t think I could rewrite an A* algorithm as efficient as this one.
Since Netcode is server-authoritative, it is crucial for the graph to work on the server because the server determines the paths.
So it sounds like your obstacles are entities on the server side? For navmesh cuts to change the navmesh they need to be gameobjects.
I made this system to create gameobject navmesh cuts at the entity positions. It is not fully featured, it just manages creation and destruction. One thing I need to add is dynamically updating the navmesh cut if the obstacle is moved/rotated. It should get you started though
public class NavmeshCutter : IComponentData
{
public GameObject NavmeshCutGO;
}
public class NavmeshCutterCleanup : ICleanupComponentData
{
public GameObject NavmeshCutGO;
}
//Entities need gameobjects with a NavmeshCut to modify the navmesh
public class NavmeshCutAuthoring : MonoBehaviour
{
public NavmeshCut navmeshCut;
public class Baker : Baker<NavmeshCutAuthoring>
{
public override void Bake(NavmeshCutAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponentObject(entity, new NavmeshCutter { NavmeshCutGO = authoring.navmeshCut.gameObject });
}
}
}
[UpdateInGroup(typeof(TransformSystemGroup))]
public partial struct NavmeshCutterSystem : ISystem
{
void OnUpdate(ref SystemState state)
{
var world = World.DefaultGameObjectInjectionWorld;
var ecb = new EntityCommandBuffer(Allocator.Temp);
//Create a gameobject navmesh cutter for those without cleanups
foreach (var (navmeshCutter, localTransform, entity) in SystemAPI.Query<NavmeshCutter, LocalTransform>().WithNone<NavmeshCutterCleanup>().WithEntityAccess())
{
GameObject instance = GameObject.Instantiate(navmeshCutter.NavmeshCutGO, localTransform.Position, localTransform.Rotation);
//Sometimes duplicates are created at world center if the subscene is disabled in the hierarchy
if (!navmeshCutter.NavmeshCutGO.scene.isSubScene)
{
GameObject.Destroy(navmeshCutter.NavmeshCutGO);
}
ecb.AddComponent(entity, new NavmeshCutterCleanup { NavmeshCutGO = instance });
}
//Any cleanups that no longer have NavmeshCutter have been destroyed, the navmeshcutter gameobjects needs to be destroyed and cleaned up
foreach (var (navmeshCutContainer, entity) in SystemAPI.Query<NavmeshCutterCleanup>().WithNone<NavmeshCutter>().WithEntityAccess())
{
GameObject.Destroy(navmeshCutContainer.NavmeshCutGO);
ecb.RemoveComponent(entity, new ComponentTypeSet(typeof(NavmeshCutterCleanup)));
}
ecb.Playback(world.EntityManager);
ecb.Dispose();
}
}
Hi @Curtis , I implemented similar behavior but used the Dynamic Obstacle component without getting any results. I hadn’t thought about the NavmeshCut component. I’ll definitely try it right away and let you know!
A question for you: do you put the AstarPath in the subscene or outside?
I put AstarPath outside in the main scene.