Advertisement
  1. Game Development
  2. Programming
Gamedevelopment

Understanding Steering Behaviors: Queue

by
Difficulty:IntermediateLength:MediumLanguages:
This post is part of a series called Understanding Steering Behaviors.
Understanding Steering Behaviors: Leader Following

Imagine a game scene where a room is crowded with AI-controlled entities. For some reason, they must leave the room and pass through a doorway. Instead of making them walk over each other in a chaotic flow, teach them how to politely leave while standing in line. This tutorial presents the queue steering behavior with different approaches to make a crowd move while forming rows of entities.

Note: Although this tutorial is written using AS3 and Flash, you should be able to use the same techniques and concepts in almost any game development environment. You must have a basic understanding of math vectors.


Introduction

Queuing, in the context of this tutorial, is the process of standing in line, forming a row of characters that are patiently waiting to arrive somewhere. As the first in the line moves, the rest follow, creating a pattern that looks like a train pulling wagons. When waiting, a character should never leave the line.

In order to illustrate the queue behavior and show the different implementations, a demo featuring a "queuing scene" is the best way to go. A good example is a room crowded with AI-controlled entities, all trying to leave the room and pass through the doorway:


Boids leaving the room and passing through the doorway without the queue behavior. Click to show forces.

This scene was made using two previously described behaviors: seek and collision avoidance.

The doorway is made of two rectangular obstacles positioned side by side with a gap between them (the doorway). The characters seek a point located behind that. When there, the characters are re-positioned at the bottom of the screen.

Right now, without the queue behavior, the scene looks like a horde of savages stepping on each other's heads to arrive at the destination. When we're done, the crowd will smoothly leave the place, forming rows.


Seeing Ahead

The first ability a character must obtain to stand in line is to find out whether there is someone ahead of them. Based on that information, it can decide whether to continue or to stop moving.

Despite the existence of more sophisticated ways to check neighbors ahead, I'll use a simplified method based on the distance between a point and a character. This approach was used in the collision avoidance behavior to check for obstacles ahead:


Test for neighbors using the ahead point.

A point called ahead is projected in front of the character. If the distance between that point and a neighbor character is less than or equal to MAX_QUEUE_RADIUS, it means there is someone ahead and the character must stop moving.

The ahead point is calculated as follows (pseudo-code):

The velocity, which also gives the character's direction, is normalized and scaled by MAX_QUEUE_AHEAD to produce a new vector called qa. When qa is added to the position vector, the result is a point ahead of the character, and a distance of MAX_QUEUE_AHEAD units away from it.

All of this can be wrapped in the getNeighborAhead() method:

The method checks the distance between the ahead point and all other characters, returning the first character whose distance is less or equal to MAX_QUEUE_AHEAD. If no character is found, the method returns null.


Creating the Queuing Method

As with all other behaviors, the queuing force is calculated by a method named queue():

The result of getNeighborAhead() in stored in the variable neighbor. If neighbor != null it means that there is someone ahead; otherwise, the path is clear.

The queue(), like all other behavior methods, must return a force which is the steering force related to the method itself. queue() will return a force with no magnitude for now, so it will produce no effects.

The update() method of all characters in the doorway scene, until now, is (pseudo-code):

Since queue() returns a null force, the characters will continue to move without forming rows. It's time to make them take some action when a neighbor is detected right ahead.


Some Words About Stopping Movement

Steering behaviors are based on forces that constantly change, so the whole system becomes very dynamic. Depending on the implementation, the more forces that are involved, the harder it becomes to pinpoint and cancel a specific force vector.

The implementation used in this steering behavior series adds all forces together. As a consequence, to cancel a force, it must be re-calculated, inverted and added to the current steering force vector again.

That's pretty much what happens in the arrival behavior, where the velocity is canceled to make the character stop moving. But what happens when more forces are acting together, such as collision avoidance, flee, and more?

The following sections present two ideas for making a character stop moving. The first one uses a "hard stop" approach that acts directly on the velocity vector, ignoring all other steering forces. The second one uses a force vector, named brake, to gracefully cancel all other steering forces, eventually making the character stop moving.


Stopping Movement: "Hard Stop"

Several steering forces are based on the character's velocity vector. If that vector changes, all other forces will be affected when they are recalculated. The "hard stop" idea is quite simple: if there is a character ahead, we "shrink" the velocity vector:

In the code above, the velocity vector is scaled to 30% of its current magnitude (length) while a character is ahead. As a consequence, the movement is drastically reduced, but it will eventually come back to its normal magnitude when the character that is blocking the way moves.

That's easier to understand by analyzing how movement is calculated every update:

If the velocity force keeps shrinking, so does the steering force, because it is based on the velocity force. It creates a vicious cycle that will end up with an extremely low value for velocity. That's when the character stops moving.

When the shrinking process ends, every game update will increase the velocity vector a little, affecting the steering force too. Eventually several updates after will bring both velocity and steering vector back to their normal magnitudes.

The "hard stop" approach produces the following result:


Queue behavior with "hard stop" approach. Click to show forces.

Even though this result is quite convincing, it feels like a "robotic" outcome. A real crowd usually has no empty spaces between their members.


Stopping Movement: Braking Force

The second approach for stopping movement tries to create a less "robotic" result by canceling all active steering forces using a brake force:

Instead of creating the brake force by re-calculating and inverting each one of the active steering forces, brake is calculated based on the current steering vector, which holds all steering forces added to the moment:


Representation of brake force.

The brake force receives both its x and y components from the steering force, but inverted and with a scale of 0.8. It means that brake has 80% of the magnitude of steering and points in the opposite direction.

Tip: Using the steering force directly is dangerous. If queue() is the first behavior to be applied to a character, the steering force will be "empty". As a consequence, queue() must be invoked after all other steering methods, so that it can access the complete and final steering force.

The brake force also needs to cancel the character's velocity. That's is done by adding -velocity to the brake force. After that, the method queue() can return the final brake force.

The result of using the brake force is the following:


Queue behavior using the brake force approach. Click to show forces.

Mitigating Characters' Overlap

The braking approach produces a more natural result compared to the "robotic" old one, because all characters are trying to fill the empty spaces. However, it introduces a new problem: characters are overlapping.

In order to fix that, the brake approach can be enhanced with a slightly modified version of the "hard stop" approach:

A new test is used to check nearby neighbors. This time instead of using the ahead point to measure the distance, the new test checks the distance between the characters position vector:


Check nearby neighbors within the MAX_QUEUE_RADIUS radius centered at the position instead of the ahead point.

This new test checks whether there are any nearby characters within the MAX_QUEUE_RADIUS radius, but now it is centered at the position vector. If someone is in range, it means the surrounding area is becoming too crowded and characters are probably starting to overlap.

The overlapping is mitigated by scaling the velocity vector to 30% of its current magnitude every update. Just like in the "hard stop" approach, shrinking the velocity vector drastically reduces the movement.

The result seems less "robotic", but it's not ideal, since the characters are still overlapping at the doorway:


Queue behavior with "hard stop" and brake force combined. Click to show forces.

Adding Separation

Even though the characters are trying to reach the doorway in a convincing way, filling all empty spaces when the path becomes narrow, they are getting too close to each other at the doorway.

This can be solved by adding a separation force:

Previously used in the leader following behavior, the separation force added to the brake force will make characters stop moving at the same time they try to stay away from each other.

The result is a convincing crowd trying to reach the doorway:


Queue behavior with "hard stop", brake force and separation combined. Click to show forces.

Conclusion

The queue behavior allows characters to stand in line and patiently wait to arrive at the destination. Once in line, a character will not try to "cheat" by jumping positions; it will move only when the character right in front of it moves.

The doorway scene used in this tutorial presented how versatile and tweakable this behavior can be. A few changes produce different results, which can be fine adjusted to a wide variety of situations. The behavior can also be combined with others, such as collision avoidance.

I hope you liked this new behavior and start using it to add moving crowds to your game!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.