Defining collision behaviour

Each entity in Gamma defines what happens when a collision with another entity occurs via their gma.shapes.rectangle.collided method. When Gamma detects that a collision has occurred, this function is invoked on the entities that have collided.

An understanding of this function is helpful if you wish to extend the functionality of the character or enemies, or create new entities.

The collided method

For the following explanation, self refers to the entity defining the behaviour, and focus refers to the entity that is being collided with.

The gma.shapes.rectangle.collided method accepts four arguments:

  • where – The side of self that was collided with as a gma.constant

  • focus – The entity that was collided with

  • focusSide – The side of the focus that was collided with

  • focusVector – The movement vector [x, y] that the focus was trying to move by before it collided

To make configuring and customising behaviour flexible, we have implemented this function so that it looks at the tags on the entity. If a particular tag exists, then an internal method (with the same method signature) is called to carry out the functionality.

In this example the function to pick up a collectable (eg. coin) is called if the focus has a collectable tag:

var oldCollided = self.collided;
self.collided = function(where, focus, focusSide, focusVector) {
    oldCollided.apply(this, arguments);
    if (focus.tags.collectable) {
        self.collided__pickupCollectable.apply(this, arguments);
    }
};

In this extract from gma.enemy, the focus (character) is killed if the enemy is alive and has the deathtouch tag:

var oldCollided = self.collided;
self.collided = function() {
    oldCollided.apply(this, arguments);
    ...

    if (self.alive && self.tags.deathtouch) {
        self.collided__deathtouch.apply(this, arguments);
    }
};

// Defined elsewhere
self.collided__deathtouch || function(w, focus, fs, fv) {
    if (focus.tags.character) {
        focus.kill();
    }
};

As a convention, these internal methods are named by prepending the tag name with “collided__”. See gma.enemy.collided__deathtouch, gma.enemy.collided__weakhead, gma.enemy.collided__rebound and gma.character.collided__pickupCollectable

Writing custom collision behaviour

Let’s say we want to make a cryer that yells in pain every time something hits it.

To create a cryer we can create a rectangle and define a custom collided function on that rectangle. We create a reference of the old collided function before we define the new one. Then, inside the new collided function we invoke the old function:

myRectangle = gma.shapes.rectangle({x:0, y:0, width:1, height:1});

oldCollided = myRectangle.collided;
myRectangle.collided = function() {
    oldCollided.apply(this, arguments);
    manager.hud.message("OOOOWWWW", 20)
}

Alternatively, we can create a function that can be used to add this functionality to any entity.

cryerFunctionality = function(entity) {
    var self = entity || gma.shapes.rectangle({x:0, y:0, width:1, height:1});

    var oldCollided = self.collided;
    self.collided = function() {
        oldCollided && oldCollided.apply(this, arguments);
        manager.hud.message("OOOOWWWW", 20)
    }

    return self;
}

If we follow the convention that this logic should be defined in an internal function, would would write the function as:

cryerFunctionality = function(entity) {
    var self = entity || gma.shapes.rectangle({x:0, y:0, width:1, height:1});

    var oldCollided = self.collided;
    self.collided = function() {
        oldCollided && oldCollided.apply(this, arguments);
        self.collided__announce.apply(this, arguments);
    }

    self.collided__announce = function() {
        manager.hud.message("OOOOWWWW", 20)
    }

    return self;
}

We could take this further and use the functions arguments to change the message:

cryerFunctionality = function(entity) {
    var self = entity || gma.shapes.rectangle({x:0, y:0, width:1, height:1});

    self.sides = {}
    self.sides[gma.constants.TOP]    = 'top'
    self.sides[gma.constants.LEFT]   = 'left'
    self.sides[gma.constants.RIGHT]  = 'right'
    self.sides[gma.constants.BOTTOM] = 'bottom'

    oldCollided = self.collided;
    self.collided = function() {
        oldCollided.apply(this, arguments);
        self.collided__announce.apply(this, arguments);
    }

    self.collided__announce = function(where, focus, focusSide) {
        manager.hud.message("OWWWWWW, damnit!, something hit my " + sides[where] + " with their " + sides[focusSide], 20);
    }

    return self;
};

We could also change the message based on the entity’s tags:

cryerFunctionality = function(entity) {
    var self = entity || gma.shapes.rectangle({x:0, y:0, width:1, height:1});

    self.sides = {}
    self.sides[gma.constants.TOP]    = 'top'
    self.sides[gma.constants.LEFT]   = 'left'
    self.sides[gma.constants.RIGHT]  = 'right'
    self.sides[gma.constants.BOTTOM] = 'bottom'

    oldCollided = self.collided;
    self.collided = function() {
        oldCollided.apply(this, arguments);
        if (self.tags.angry) {
            self.collided__anger.apply(this, arguements);
        }
        else {
            self.collided__announce.apply(this, arguments);
        }
    }

    self.collided__announce = function(where, environ, environSide) {
        manager.hud.message("OWWWWWW, damnit!, something hit my " + sides[where] + " with their " + sides[environSide], 20);
    }

    self.collided__anger = function(where, environ, environSide) {
        manager.hud.message("GO AWAY, ANNOYING THING", 20);
    }

    return self;
};

We can then use it as follows:

captainObvious = gma.jumpingEnemy({x:0, y:0, width:2, height:5});
cryerFunctionality(captainObvious);

anAngryEnemy = gma.enemy({x:0, y:0, width:2, height:4, tags=['angry']});
cryerFunctionality(anAngryEnemy);