CollisionManager

Introduction

The CollisionManager class provides methods for handling collision (the touching of game objects) in FlatRedBall games. The CollisionManager has built-in methods for handling collision between collidable entities (entities which implement the ICollidable interface). Use of the CollisionManager is not necessary, but it can reduce and standardize code for handling collisions. The CollisionManager provides a rich set of functionality for performing collision in code, but usually games do not need to write code against the CollisionManager. Instead, the FlatRedBall Editor provides support for creating CollisionRelationships without any code. For more information, see the FlatRedBall Editor CollisionRelationship page.

Collision Relationships

The CollisionManager is built around the concept of collision relationships. A collision relationship is created by specifying the two objects (or groups of objects) which are used in the relationship. The following list shows common relationships in games:

  • Player vs. Enemy Bullet List

  • Player Bullet List vs. Environment

  • Race Car List vs. Boost Entity List

  • Explosion List vs. Destructible Block List

The relationship detects collision and provides automatic and custom handling of the collision. Automatic handling includes separating objects (preventing overlapping) and adjusting the velocity of colliding objects (bouncing the objects). Custom handling is done by assigning a collision event.

Creating a Collidable Entity

The CollisionManager is built to work with collidable entities. To create a collidable entity in Glue:

  1. Open Glue

  2. Right-click on the Entities folder

  3. Click Add Entity

  4. Check one of the objects under the Collisions category, such as Circle

  5. Notice that the ICollidable checkbox is automatically checked

The entity will now implement the ICollidable interface, which means it can be used as an instance or in a list with the CollisionManager.

Example - Entity vs. Entity Collision

A relationship may define that two individual entities should perform collision logic against one another. The following assumes that PlayerInstance and EnemyInstance are entities added to the screen in Glue:

private void CreateCollisionRelationships()
{
    var relationship = CollisionManager.Self.CreateRelationship(
        PlayerInstance, EnemyInstance);
    relationship.CollisionOccurred = HandlePlayerVsEnemyCollision;
}

private void HandlePlayerVsEnemyCollision(Player player, Enemy enemy)
{
    enemy.TakeDamage();
    player.TakeDamage();
}

Notice that the HandlePlayerVsEnemyCollision is used to perform custom logic whenever a collision occurs.

Example - Entity List Relationships

Entities which are dynamically created or destroyed during the life of a screen (such as bullets fired by a turret) are usually added to lists. The following code can be used to detect collision between a single Player instance and a list of Bullets:

private void CreateCollisionRelationships()
{
    var relationship = CollisionManager.Self.CreateRelationship(
        PlayerInstance, BulletList);
    relationship.CollisionOccurred = HandlePlayerVsBulletCollision;
}

private void HandlePlayerVsBulletCollision(Player player, Bullet bullet)
{
    bullet.Destroy();
    player.TakeDamage();
}

Notice that the CollisionOccurred delegate passes a single bullet even though the type passed to CreateRelationship is a list. This allows the code to handle collision on a case-by-case basis. Similarly, relationships can be created between two lists. The following example shows how to respond to collision between a list of Enemies and a list of Bullets:

private void CreateCollisionRelationships()
{
    var relationship = CollisionManager.Self.CreateRelationship(
        EnemyList, BulletList);
    relationship.CollisionOccurred = HandleEnemyVsBulletCollision;
}

private void HandleEnemyVsBulletCollision(Enemy enemy, Bullet bullet)
{
    bullet.Destroy();
    player.TakeDamage();
}

Example - Entity/Entity List vs. TileShapeCollection

Entities can be collied against TileShapeCollections through the CollisionManager. TileShapeCollections are an efficient and convenient way to store a group of rectangles for collision.

// Since this is an extension method, you must have the following
// using statement in your class:
using FlatRedBall.Math.Collision;

private void CreateCollisionRelationships()
{
    // SolidCollisions is assumed to be a TileShapeCollection
    var relationship = CollisionManager.Self.CreateTileRelationship(PlayerList, SolidCollisions);
    // Make it so the solid collisions can't be moved:
    relationship.SetMoveCollision(0, 1);
}

See the Move Collision section below for more information.

Example - Move Collision

Collision relationships can specify automatic behavior in addition to assigning the CollisionOccurred delegate. This can be achieved by calling various Set methods. For example, the following results in colliding objects being separated. The SetMoveCollision method takes the relative masses of each object. In this example the colliding player and blocks have equal mass:

private void CreateCollisionRelationships()
{
    var relationship = CollisionManager.Self.CreateRelationship(
        Player, BlockList);
    var playerMass = 1;
    var blockMass = 1;
    relationship.SetMoveCollision(playerMass, blockMass);
    // custom collision can also be handled if needed
}

The most common ways to call is either with the values of (0,1) and (1,1)

  • (0,1) - The first object (typically an entity) will have a mass of 0, meaning it will not be able to move the second object.

  • (1,1) - Both objects have equal masses, and will push each other

Example - Bounce Collision with Per-Entity Mass

Both Move and Bounce collisions can be performed automatically by the relationship by calling either SetMoveCollision or SetBounceCollision. While convenient, these methods require the same mass to be used for all entities in a relationship. In some cases entities may need to have a custom mass per entity. In this example a list of asteroids collides with itself (every asteroid tests for collision against every other asteroid). Each Asteroid has a different Mass value so we can't set one mass for the entire relationship. Instead, the collision is detected and an event is raised, then the bounce collision occurs inside the collision event handler.

private void CreateCollisionRelationships()
{
    var relationship = CollisionManager.Self.CreateRelationship(
        AsteroidList, AsteroidList);
    relationship.CollisionOccurred = HandleAsteroidVsAsteroidCollision;
}

private void HandleEnemyVsBulletCollision(Asteroid first, Asteroid second)
{
    const float elasticity = .5f;
    first.CollideAgainstBounce(second, first.Mass, second.Mass, elasticity);
}

Note that the example above shows how to do self-collision, but the same approach of handling the move or bounce collision in an event could be performed between two different separate lists.

Example - Partitioning

The CollisionManager can perform partitioning to improve collision performance. In most cases partitioning can make even large numbers of objects (tens of thousands) have minimal or no impact on your game's frame rate. Partitioning requires two pieces of information:

  1. The axis to partition (X or Y). This should be the axis along which objects are most distributed. For example, a platformer with horizontal levels should partition on the X axis.

  2. The width or height of each object involved in partitioning.

Both objects in a relationship must be partitioned on the same axis before a relationship will be able to use the partitioning to reduce collision calls. Partitioned objects must be sorted for partitioning to function properly. If the order of objects in the list can change, the CollisionManager can automatically sort the lists. If the order does not change once the list has been created, or if the order is explicitly maintained in custom code, then the sortEveryFrame parameter can be false. The following creates a partitioned collision relationship between a list of Enemies and a list of Bullets.

private void CreateCollisionRelationships()
{
    var maxEnemyWidth = 48;
    var maxBulletWidth = 12;
    CollisionManager.Self.Partition(EnemyList, FlatRedBall.Math.Axis.X, 
        maxEnemyWidth, sortEveryFrame = true);
    CollisionManager.Self.Partition(BulletList, FlatRedBall.Math.Axis.X, 
        maxBulletWidth, sortEveryFrame = true);

    var relationship = CollisionManager.Self.CreateRelationship(EnemyList, BulletList);
    relationship.CollisionOccurred += HandeEnemyVsBulletCollision;
}

private void HandeEnemyVsBulletCollision(Enemy enemy player, Bullet bullet)
{
    ...
}

Partitioning must set on both objects in a relationship even if one of the objects is a single instance - in that case the Partition call is needed to get the width or height of the object's collision.

private void CreateCollisionRelationships()
{
    var maxPlayerWidth = 30;
    var maxBulletWidth = 12;
    CollisionManager.Self.Partition(Player, FlatRedBall.Math.Axis.X, 
        maxPlayerWidth);
    CollisionManager.Self.Partition(BulletList, FlatRedBall.Math.Axis.X, 
        maxBulletWidth, sortEveryFrame = true);

    var relationship = CollisionManager.Self.CreateRelationship(Player, BulletList);
    relationship.CollisionOccurred += HandeEnemyVsBulletCollision;
}

private void HandeEnemyVsBulletCollision(Enemy enemy player, Bullet bullet)
{
    ...
}

The Partition method only needs to be called once, even if an object is used in multiple relationships.

private void CreateCollisionRelationships()
{
    var maxPlayerWidth = 30;
    var maxBulletWidth = 12;