Make the seekers avoid other seekers

Hi, I’m trying A* for a 2D game with procedural generated dungeons and I’m using the GridGraph; the only problem so far is that the seekers bump into each other instead of looking for a clear path.
Is there a way to check if the path is clear? If the chosen path isn’t clear I’d like them to recalculate the path or stop until there is a clear way to the target.

For example if the player is surrounded by enemies and more of them are coming, I’d like those behind to stand still until those in front of them are dead; right now they just push each other to try to reach the player.
Do I need to use the Local Avoidance for this? I don’t want to slow the movement speed, I just need to stop the seeker until the way it’s clear.
If I need the LA, do I need to remove the RigidBody2D from my seekers?

Thanks.

Hi

See also How to make different players can avoid each other - without using RVO
If it is not a tile based game, in addition to the changes discussed in the above link, I would suggest using a raycast every frame or so to check if there was another unit in front of the current unit, and if so, stop the character. Local avoidance would also work.

Hello, thanks for the answer.

I think I’m going to use RVO, I have just a couple of questions.
Do I need the beta version for 2D?
I read the documentation and for 3D you need a plane and cylinders, can I use 2D colliders instead? Is there an example/documentation about implementing RVO in 2D too?

Thanks.

Yes, for the XY plane you need the beta.

Colliders are not used by the RVO system, the RVO system will take into account other RVO agents as well as RVO obstacles.

Hi, I’ve bought the Pro version, downloaded and installed the Beta version and I have a problem with my seekers; basically when they are going to collide, they try to deviate from the path to avoid each other, but instead of deviating they tremble and smash into each other.
I think the movement script is conflicting with the one that make the seekers deviate. How do I fix it?

I’m using the movement script you posted in this thread: RVO Beta Version

I get the path with this:
seeker.StartPath(transform.position, target.position, OnPathComplete);

And I move the seekers in the FixedUpdate() with this:
controller.SetTarget(path.vectorPath[currentWaypoint], 1, 1); // Where to move the AI to, desired speed, max speed.
transform.position += controller.CalculateMovementDelta(Time.fixedDeltaTime); // Processed velocity.

Thanks.

Hi

Do you think you could share a video of the problem?

One potential issue I can see with your setup is that your next waypoint distance is relatively small (I assume the node size is around 1 meter here), so the target point of the agent would be just a small distance in front of the character. It can reach this point almost up until the point of collision so the RVO system will not be of much help. I would suggest that you either scale up the relative distance to that point or use some other modifier like the funnel modifier (and no smooth modifier) to make sure the target point of the character is a bit further away so that the RVO system knows where the agent is actually going.

Also. I think that in the latest beta the AIPath movement script is compatible with the new RVOController, but I am not completely sure (it is in my dev version, but I’m not sure when I made that change).

Thanks, that worked, I increased the distance to 0.5 and everything works even with the smooth modifier.

I tried the funnel modifier but the path it’s weird, it bounces from a wall to another, so it’s not good; probably I’m doing somethign wrong.

I’m going to check the AIPath now.

I have a couple of questions:

  • Should I update the path while the agent is moving and how often?
  • Is it possible to look for the nearest free node around a position or somethihng like that? I’d like my enemies to surround the player, so if the position in the front is occupied, I like them to flank the player or go behind it. Is something like that alredy build in the package?
    Thanks.

Ah, right. You are using 2D. The funnel modifier only supports the XZ plane (though I am planning to rewrite that at some point…).

  1. Yes, that is usually reasonable. Depending on your game you can use a very simple approach like recalculating the path every 0.5 or 1 or 5 seconds (it really depends on the game) or you can use a more complicated heuristic like recalculating the path more often when the character is close to the target and less often when it is far away.

  2. Yes. Use AstarPath.GetNearest(somePoint, NNConstraint.Default);

Even if I update the path with the nearest walkable node, when my agents are close to the destination they don’t avoid each other; do I need to update the nodes of the Graph currently occupied by stationary agents and set those nodes as unwalkable?

If you could check the image please, the agent moves along the the green line and hit the other agents, instead I woud like him to follow the blue line and turn around the other agents.

Thanks again.

I’ve found a couple of solution in this thread: I guess I’m trying to block a occupied node, how do I do that?

  • add a penalty to the occupied nodes and check the path to verify they are not used
  • set the occupied nodes as unwalkable

Resource wise what’s the better solution between the two?

I tried the second solution and it works fine, the only problem is I had to halve the size of the nodes to have a decent result. Could this create any problem?

Thanks.

Edit:
This is the problem I have now, even if I set the nodes as unwalkable, some agents keep trying to push their way in.

If I increase the number of nodes set as unwalkable, the problem persists.

Any idea? Thanks.

Hi

Maybe you have Seeker -> Start End Modifier -> End Point set to ‘Original’. You might want to set it to ‘Closest On Node’.

No, it’s set on “Closest On Node”.

Hm, yes maybe that is correct.
I would expect them to try to move to the closest point they can reach.
Those agents look a bit buggy though, esp. the one in the lower left corner in the second image. I’m not sure if that’s some bug related to my code or your code however.

Yes, it looks like they think the node they are going to is still walkable, even if it’s not.

This is the code I’m using in that test project, it is very simple.

using UnityEngine;
using System.Collections;
using Pathfinding.RVO;
using Pathfinding;

public class SimpleRVOAI : MonoBehaviour
{
private Seeker _seeker;
private RVOController _controller;

public Transform target;
public Path path;
public float speed = 2;
public float nextWaypointDistance = 3;
private int _currentWaypoint = 0;

private bool _stopMovement;
private float _updatePathCD = 1.0f;
private float _updatePathIn;

void Start()
{
    _seeker = GetComponent<Seeker>();
    _controller = GetComponent<RVOController>();
    CreateNewPath();
}

private void CreateNewPath()
{
    NNInfo nodeInfo = AstarPath.active.GetNearest(target.position, NNConstraint.Default);
    _seeker.StartPath(transform.position, nodeInfo.position, OnPathComplete);
    _updatePathIn = _updatePathCD;
}

public void OnPathComplete(Path p)
{
    Debug.Log("Any error? " + p.error);
    if (!p.error)
    {
        path = p;
        // Reset the waypoint counter
        _currentWaypoint = 0;
    }
}

public void FixedUpdate()
{
    _updatePathIn -= Time.fixedDeltaTime;
    if (_stopMovement == false && _updatePathIn <= 0)
        CreateNewPath();
    
    if (path == null)
    {
        // We have no path to move after yet
        return;
    }

    if (_stopMovement == false && _currentWaypoint >= path.vectorPath.Count)
    {
        Debug.Log("End Of Path Reached");
        _stopMovement = true;
        UpdateNode();
    }

    if (_stopMovement == false)
    {
        _controller.SetTarget(path.vectorPath[_currentWaypoint], speed, speed); // Where to move the AI to, desired speed, max speed.
        transform.position += _controller.CalculateMovementDelta(Time.fixedDeltaTime); // Processed velocity.

        if (Vector3.Distance(transform.position, path.vectorPath[_currentWaypoint]) < nextWaypointDistance)
        {
            _currentWaypoint++;
            return;
        }
    }
}

private void UpdateNode()
{
    Bounds bounds = GetComponent<CircleCollider2D>().bounds;
    bounds.extents = new Vector3(0.5f, 0.5f, 0f);
    GraphUpdateObject guo = new GraphUpdateObject(bounds);
    guo.modifyWalkability = true;
    guo.setWalkability = false;
    AstarPath.active.UpdateGraphs(guo);
    return;
}

}

Did you have time to check if that is a bug related to your code?

If I increase the time between path upadates inside the code I posted (from 1 sec to 3 sec), some of the agents are less buggy and can find the right node, but others remain buggy no matter what…even if I pull them away from their position, they recalculate the path and go back to that node they could not reach before.

Thanks.

Hi

It’s hard to test without being able to replicate it.
Would it be possible for you to share your project with me?

Sure, it’s just a test project with a couple of scripts. I’ll send the file to aron.granberg@gmail.com

Hi

Found the issue with the buggy agents at least.
There was a bug in my GridNode class. To fix it, change line 170 in the GridNode.cs file from

float y = gg.inverseMatrix.MultiplyPoint3x4((Vector3)p).y;

to

float y = gg.inverseMatrix.MultiplyPoint3x4((Vector3)position).y;

Also, you made some modifications to the SimpleRVOAI script. What they did was that if _stopMovement was true, it would simply not take any commands from the RVO system anymore and would not set the target either so the RVO system would think that it still had the same target as earlier and that it would continue to move towards it, when the agents did not move as the RVO system expected the agents would move strangely and could even intersect other agents. To solve this, turn off lockWhenNotMoving on all agents, then add

if (_stopMovement == false)
    _controller.locked = false;
    // some other code here
} else {
    _controller.locked = true;
}

Alternatively you can leave lockWhenNotMoving enabled and call SetTarget with a zero speed when they are supposed to be stationary.

Hey, thanks a lot, your line fixed it, even without the rest, at least in the new code, I didn’t check the old one.

I have a few more questions if you don’t mind please.

Is it possible to add an offset to the center of the controller?
The center field doesn’t work in 2D I think, or if it does, it doesn’t update the Gizmo (the yellow circle), or I don’t get how it works hehe. The Unity controller has a center option too and it’s a Vector3 with 3 fields (x, y ,z), while yours it only has 1 field and if I change the value it doesn’t move the yellow circle and I’d like to add an offset along the Y axis. It’s not a big deal, just wanted to know if it’s possible and how.

The second problem is about flipping the sprite of the character, which in 2D you usually do according to the position of your target or to the direction. To flip the sprite, I tried to use the current waypoint the character is going to, but sometimes while the character is following the path, it turns backward for no reason. It’s like the current waypoint, which shoud be in front of the character, just for a single frame, fell back. You can see it here:

The AI is following the path, the current waypoint should always be in front of the AI, because as soon as the AI gets close to the current waypoint, that waypoint is updated with the next one…and still it flips backward.

This is the function of the movement:
private void MoveToTarget()
{
_controller.locked = false;
_isMoving = true;
Flip(path.vectorPath[_currentWaypoint]);

    // Move the AI towards the current waypoint.
    _controller.SetTarget(path.vectorPath[_currentWaypoint], speed, speed);
    transform.position += _controller.CalculateMovementDelta(Time.fixedDeltaTime);
    
    // Check the distance from the current waypoint and switch to the next one. 
    if (Vector3.Distance(transform.position, path.vectorPath[_currentWaypoint]) < nextWaypointDistance)
        _currentWaypoint++;
}

This is the Flip function:
private void Flip(Vector2 targetPos)
{
if (transform.position.x < targetPos.x && !_isFacingRight)
{
_spriteRen.flipX = false;
_shadowRen.flipX = false;
_isFacingRight = true;
}
else if (transform.position.x > targetPos.x && _isFacingRight)
{
_spriteRen.flipX = true;
_shadowRen.flipX = true;
_isFacingRight = false;
}
}

Now if I Flip the sprite before I switch to the next waypoint I get the weird behaviour, like in the GIF.
If I Flip the sprite after the switch, then I get an Array out of index error, because the _currentWaypoint can exceed the Array length…the weird thing is, even if I set the the _currentWaypoint equals to the Array length (in case it exceeds it), it still gives me the same error.
Am I missing something about the .SetTarget or the .CalculateMovementDelta? Maybe an update to the Array that I don’t know?

I tried to use the current and the previous positions of the AI to flip it and that weird behaviour still happens…it’s like every now and then, just for a single frame, the AI is pulled back and for this reason it flips backward, because the current poisition, just for that frame, instead of been in front of the previous position, it’s behind it.
Could it be the updating of the path? Maybe it uses a previous stored position as a starting point of the new the path and AI slightly jump backwards?

Thanks again for your time.