How to prevent pushing when using local avoidance

Hello, I have this setup with recast graph and local avoidance enabled and I am using the new FollowerEntity.

In the video there are enemies - the green orks, targeting the player units (smaller peoples). So there are 200~ orks targeting the 10~ humans. So most of the orks will have the same target destination - leading them to “push”.

How can I prevent the units from pushing each other? If I have a stationary unit (one with no target) it will be pushed by the moving ones as well. I will probably have to implement some sort of formation movement since there are so many units, but I wonder if anyone has some general advice on how to handle such cases. Thanks

I am working with a similar situation, and I already use a loose formation which does mitigate the issue a bit. My plan to completely fix pushing is to check if there is an ally in the way when targeting an enemy, and stop the movement until it dies or moves. I am hoping it still lets them cluster up around the enemy, but not push each other through them.

Yeah I was planning to try something along those lines too. Maybe have them “follow” the friendly in front if they are blocking the way to the player. And when that friendly dies, they can then move up and target the player.

I did find that one way to prevent pushing is when the units are engaged in fighting (e.g. stopped) to disable the updatePosition and updateRotation flags on the FollowerEntity. So it’s an improvement over the video I posted, but still not great as the units in the back will keep trying to path to the target, making them just spin in circles.

It would probably be better to change canMove instead. By changing updatePostion and updateRotation you are just preventing the gameobject movement. In the background, the Entity is still pathfinding and trying to get to the target, causing path processing that you don’t really need.

1 Like

In addition to Curtis’s suggestion of making them more loose (by changing their radius, primarily), you can also check your RVO Controller’s Max Neighbours setting. Lower values will cause some jittery movement. Bumping it up just a few values above the default of 10 may help. There’s a performance hit here, however there are some ways to mitigate this, such as making their max neighbors lower when not on screen. Let us know if any of this helps! :slight_smile:

We had a similar issue, but with way fewer mobs. What we did, and it seems to work ok, is that we modified the CalculateNeighbours method in RVOAgentBurst.cs so it filters neighbours that are considered to be behind. This prevents agents that are ahead to move aside in crowded areas. Instead the mobs that come up from behind will try to move around those ahead. We probably introduce other issues, but it works for us

1 Like

void CalculateNeighbours (int agentIndex, NativeArray neighbours, NativeArray neighbourDistances) { int maxNeighbourCount = math.min(SimulatorBurst.MaxNeighbourCount, agentData.maxNeighbours[agentIndex]); var outputIndex = agentIndex * SimulatorBurst.MaxNeighbourCount; int numNeighbours = quadtree.QueryKNearest(new RVOQuadtreeBurst.QuadtreeQuery { position = agentData.position[agentIndex], speed = agentData.maxSpeed[agentIndex], agentRadius = agentData.radius[agentIndex], timeHorizon = agentData.agentTimeHorizon[agentIndex], outputStartIndex = outputIndex, maxCount = maxNeighbourCount, result = neighbours, layerMask = agentData.collidesWith[agentIndex], layers = agentData.layer, resultDistances = neighbourDistances, }); output.numNeighbours[agentIndex] = numNeighbours; MovementPlaneWrapper movementPlane = default; movementPlane.Set(agentData.movementPlane[agentIndex]); movementPlane.ToPlane(agentData.position[agentIndex], out float localElevation); // Get agent position and forward direction float3 agentPosition = agentData.position[agentIndex]; float3 agentForwardDir = math.normalize(agentData.targetPoint[agentIndex] - agentPosition); // Filter out invalid neighbours for (int i = 0; i < numNeighbours; i++) { int otherIndex = neighbours[outputIndex + i]; if (otherIndex == -1) throw new System.Exception(“Invalid neighbour index”); movementPlane.ToPlane(agentData.position[otherIndex], out float otherElevation); float maxY = math.min(localElevation + agentData.height[agentIndex], otherElevation + agentData.height[otherIndex]); float minY = math.max(localElevation, otherElevation); // Check if agent is behind (dot product of direction to neighbor and agent’s forward direction) float3 otherPosition = agentData.position[otherIndex]; float3 directionToNeighbor = otherPosition - agentPosition; float dotProduct = math.dot(directionToNeighbor, agentForwardDir); bool isBehind = dotProduct < 0; // negative dot product means behind // Filter conditions: // 1. Agents on different y-levels cannot collide // 2. Don’t avoid the agent itself // 3. Don’t avoid agents behind this agent if ((maxY < minY) | (otherIndex == agentIndex) | isBehind) { numNeighbours–; neighbours[outputIndex + i] = neighbours[outputIndex + numNeighbours]; i–; } } // Add a token indicating the size of the neighbours list if (numNeighbours < SimulatorBurst.MaxNeighbourCount) neighbours[outputIndex + numNeighbours] = -1; }