HowToRts.github.io

Website/Blog about the things you need to know to make a modern RTS game


Avoidance Behaviours

14 Jan 2014

Here is an example of 2 groups trying to cross without trying to avoid each other. As you can see, things don’t go very well!

To try improve this, I previously implemented Continuum Crowds, which worked somewhat, certainly better than just crashing in to each other.

In this article I’m going to try using basic collision avoidance (Based on Reynolds Obstacle Avoidance steering behavior).

The algorithm is simple:

  • We perform a ray cast in front of each Agent, the length based on the velocity the agent is travelling at (The faster we are travelling, the further ahead we should look to avoid)
  • If we find an obstacle, we ask it if it is turning to avoid. If it is, we turn the same way (two agents travelling in opposite direction towards each other but both turning left will avoid each other).
  • If it is not turning, we work out whether we should turn left or right to avoid them and do so.
  • The strength of the steering force varies depending how close the other agent is compared to our velocity (The sooner we will collide, the harder we should turn).

The code ends up being quite big, so lets jump in to it.

function steeringBehaviourAvoid(agent) {

	//If we aren't moving much, we don't need to try avoid
	if (agent.velocity().LengthSquared() <= agent.radius) {
		return B2Vec2.Zero;
	}

	//Do some ray casts to work out what is in front of us
	var minFraction = 2;
	var closestFixture = null;

	var callback = function (fixture, point, normal, fraction) {
		//Ignore ourself
		if (fixture == agent.fixture) {
			return fraction;
		}
		//Only care about dynamic (moving) things
		if (fraction < minFraction && fixture.GetBody().GetType() == B2Body.b2_dynamicBody) {
			minFraction = fraction;
			closestFixture = fixture;
		}
		return 0;
	};

	//Do a straight forward cast from our center
	world.RayCast(callback, agent.position(), agent.position().Copy().Add(agent.velocity()));

	//Calculate an offset so we can do casts from our edge
	var velCopy = agent.velocity().Copy();
	velCopy.Normalize();
	var temp = velCopy.x;
	velCopy.x = velCopy.y;
	velCopy.y = -temp;
	velCopy.Multiply(agent.radius);

	//Do a raycast forwards from our right and left edge
	world.RayCast(callback, agent.position().Copy().Add(velCopy), agent.position().Copy().Add(agent.velocity()).Add(velCopy));
	world.RayCast(callback, agent.position().Copy().Subtract(velCopy), agent.position().Copy().Add(agent.velocity()).Subtract(velCopy));

	//If we aren't going to collide, we don't need to avoid
	if (closestFixture == null) {
		return B2Vec2.Zero;
	}

	var resultVector = null;
	var collisionBody = closestFixture.GetBody();
	var ourVelocityLengthSquared = agent.velocity().LengthSquared();
	
	//Add our velocity and the other Agents velocity
	//If this makes the total length longer than the individual length of one of them, then we are going in the same direction
	var combinedVelocity = agent.velocity().Copy().Add(collisionBody.GetLinearVelocity());
	var combinedVelocityLengthSquared = combinedVelocity.LengthSquared();

	//We are going in the same direction and they aren't avoiding
	if (combinedVelocityLengthSquared > ourVelocityLengthSquared && closestFixture.GetUserData().avoidanceDirection === null) {
		return B2Vec2.Zero;
	}

	//We need to Steer to go around it, we assume the other shape is also a circle

	var vectorInOtherDirection = closestFixture.GetBody().GetPosition().Copy().Subtract(agent.position());

	//Are we more left or right of them
	var isLeft;
	if (closestFixture.GetUserData().avoidanceDirection !== null) {
		//If they are avoiding, avoid with the same direction as them, so we go the opposite way
		isLeft = closestFixture.GetUserData().avoidanceDirection;
	} else {
		//http://stackoverflow.com/questions/13221873/determining-if-one-2d-vector-is-to-the-right-or-left-of-another
		var dot = agent.velocity().x * -vectorInOtherDirection.y + agent.velocity().y * vectorInOtherDirection.x;
		isLeft = dot > 0;
	}
	agent.avoidanceDirection = isLeft;

	//Calculate a right angle of the vector between us
	//http://www.gamedev.net/topic/551175-rotate-vector-90-degrees-to-the-right/#entry4546571
	resultVector = isLeft ? new B2Vec2(-vectorInOtherDirection.y, vectorInOtherDirection.x) : new B2Vec2(vectorInOtherDirection.y, -vectorInOtherDirection.x);
	resultVector.Normalize();

	//Move it out based on our radius + theirs
	resultVector.Multiply(agent.radius + closestFixture.GetShape().GetRadius());

	//Steer torwards it, increasing force based on how close we are
	return steerTowards(agent, resultVector).Divide(minFraction);
}

We then combine the resulting force with our other steering behaviours.

This simple collision avoidance works well for one agent in each direction
And it works okay for small groups

But when the individual units are coming in on a big angle
Or the groups are large. Then things don’t work quite as well.

The green lines shown in these examples are the avoidance steering forces.

So, how can we improve this? Using Reciprocal Velocity Obstacles would be one way to improve, it may have a high CPU cost however. video demonstration.

I think we could do some fudging of the physics which would allow our agents to slide past each other better. Currently when two agents collide head on, they are both pushed backwards (in the opposite direction than they want to go), we could instead push them sideways (the right angle), which would let them slide past each other much easier! This is probably a cheaper way for a game to go, even if the physics aren’t quite real :)

comments powered by Disqus

Site By @daveleaver Theme by mattgraham