Collisions

The only piece of functionality that still needs to be added to the game, before we can call it Pong, is collisions. And that’s exactly what we will be doing next. Colliders are the missing Components in our Actors so they can interact with each other. But before adding them, we need to understand In-Game Types and Collider Layers.

# In-Game Types

An In-Game Type is an Actor enum attribute that can be used to identify the type of game object that one is interacting with without having to rely on RTTI (which can be very expensive). So, let’s create an InGameTypes file and add to it two entries - “Disk” and “Paddle”.

# Collider Layers

A Collider Layer is an enum that is useful to cull off uncessary collision checks between Colliders that belong to Actors that don’t need to interact with each other. For example, a paddle doesn’t need to check collisions against the other. So, their Colliders should mutually ignore each other. To do so, each Collider has a property that flags on which Collider Layers it “exists” and another that signals which Collider Layers to ignore when checking for collisions. With this idea in mind, the paddles’ Colliders should live in the “Paddle” layer and they should ignore the “Paddle” layer, too. In contrast, the disk’s Collider should not ignore the “Paddle” Collider layer.

So, let’s create the Collider Layers by adding a ColliderLayers file in the config folder and add the following to it:

# Adding Colliders

Now we are ready to add Colliders to the paddles and disk. Just open their respective .actor files and add the corresponding component to them. The Disk’s Collider configuration should look like this:

And the paddles’ should be configured as follows:

Notice that the toggle for collision checking in the paddle’s Collider configuration is disabled. This is because there is no reason to waste performance by performing the test for collisions against the disk when the latter will already be doing checks for itself.

# Adding Walls

Almost there! We are just missing walls for the disk to not go out through the screen’s top or bottom sides. Let’s first add a “Wall” In-Game Type in the config/InGameTypes file and a “Wall” Collider Layer in the config/ColliderLayers file:

Wall In-Game Type
Wall Collider Layer

We can now create a Wall.actor in assets/Actor/Wall and add a Collider that lives in the “Wall” layer to it.

Also make sure that the Disk’s Collider checks collisions against others in the “Wall” Collider Layer.

Finally, we’ll place the walls at the top and bottom of the screen in the Stage’s PongStageActors array.

[...]
extern ActorSpec WallActorSpec;

PositionedActorROMSpec PongStageActors[] =
{
    {&DiskActorSpec,                {0, 0, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
    {&PlayerPaddleActorSpec,        {-180, 0, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
    {&AIPaddleActorSpec,            {180, 0, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
    {&WallActorSpec,                {0, -120, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
    {&WallActorSpec,                {0, 120, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
    {&LowPowerIndicatorActorSpec,   {-192 + 8, 112 - 4, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},

    {NULL, {0, 0, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
};

If you don’t like that the paddles get out of the screen, enable the collision checks in their Collider and add the “Wall” Collider Layer to the layers that it checks.

We now have a basic Pong clone!

# Improving the disk’s behavior

The Pong’s disk reaction is not very interesting at the moment. We can improve it by artificially modifying its vertical speed in function of the collision point in relation go the paddle. To do so, in the Disk class, override the Entity::collisionStarts:

mutation class Disk : Actor
{
    /// Process a newly detected collision by one of the component colliders.
    /// @param collisionInformation: Information struct about the collision to resolve
    /// @return True if the collider must keep track of the collision to detect if it persists and when it
    /// ends; false otherwise
    override bool collisionStarts(const CollisionInformation* collisionInformation);

    [...]
}
#include <InGameTypes.h>

[...]

bool Disk::collisionStarts(const CollisionInformation* collisionInformation)
{
    bool returnValue = Base::collisionStarts(this, collisionInformation);

    Entity collidingEntity = Collider::getOwner(collisionInformation->otherCollider);

    switch(Entity::getInGameType(collidingEntity))
    {
        case kTypePaddle:
        {
            Vector3D velocity = *Body::getVelocity(this->body);

            fixed_t yDisplacement = this->transformation.position.y - Entity::getPosition(collidingEntity)->y;

            velocity.y += yDisplacement;

            Body::setVelocity(this->body, &velocity);
        }

        break;
    }

    return returnValue;
}

We now have a basic but fully functional Pong game. But it would be more interesting if it displayed scores, right? Let’s add that in the next step .