Collision detection in Pine-2K

The latest version of Pine-2K now supports collision detection between sprites. It does this using a simple collision declaration that is placed immediately before rendering a sprite and a function callback.

The format for the collision declaration is as follows:

io("COLLISION", spriteIdx, mask, callback_function);

In addition to setting up the call back function, this function allows you to assign an index for the sprite that is about to be rendered. This is specified via the `spriteIdx` parameter. Sprite indices can be any positive value other than zero and in the range of a normal 32-bit integer. Sprite indices do not have to be unique and it might be valid in your game to have all enemies or bullets have the same index. Examples for handling multiple enemies are shown below.

The mask parameter specifies which other sprites you wish to test for collisions. It can be the index of another sprite, a mask that can be applied for a range of sprites or a zero to indicate no matching should occur. A mask value of -1 (which equates to the hex value of 0xFFFFFFFF) indicates that the sprite should be tested against all other sprites.

The collision detection relies on the order in which the sprites are rendered in a single frame - it is only possible to detect a collision with the sprite currently being rendered and those already rendered to the screen.


A simple scenario.


Let’s look at a simple example of a player and an enemy sprite traveling around the screen.
001  const ENEMY_IDX = 1;
002  const PLAYER_IDX = 2;
003
004  function collide() {
005    console("Houston we have a problem.");
006  }
007
008  // Render the enemy ..
009  io("COLLISION", ENEMY_IDX, 0);
010  sprite(enemyX, enemyY, enemyImg);
011
012  // Render the player ..
013  io("COLLISION", PLAYER_IDX, ENEMY_IDX, collide);
014  sprite(playerX, playerY, playerImg);

Breaking this down, line 009 declares that the next sprite rendered (the enemy on line 010) will have a collision index of ENEMY_IDX (or 1). You will note that there is no mask or callback function specified as this is the first sprite to be rendered in the current frame and hence there would be nothing to collide with!

After rendering the enemy sprite, we proceed to render the player. This time, at line 013, we declare the upcoming sprite to have an index of PLAYER_IDX (or 2) and that we are interested in detecting collisions with the enemy whose collision index is ENEMY_IDX. If a collision is detected when rendering the sprite, the function collide() will be called automatically.

6 Likes

Using a Mask for Detection

The example detects a collision between two specific sprites however often you are looking for collisions between a number of sprites. This can be accommodated using masks when detecting collisions instead of specifying the exact sprite indices.

001  const ENEMY_1_IDX = 1;
002  const ENEMY_2_IDX = 2;
003  const ENEMY_3_IDX = 4;
004  const PLAYER_IDX = 8;
005
006  function collide() {
007    console("Houston we have a problem.");
008  }
009
010  // Render the enemy ..
011  io("COLLISION", ENEMY_1_IDX, 0);
012  sprite(enemyX1, enemyY1, enemyImg);
013
014  io("COLLISION", ENEMY_2_IDX, 0);
015  sprite(enemyX2, enemyY2, enemyImg);
016
017  io("COLLISION", ENEMY_3_IDX, 0);
018  sprite(enemyX3, enemyY3, enemyImg);
019
020  // Render the player ..
021  io("COLLISION", PLAYER_IDX, -1, collide);
022  sprite(playerX, playerY, playerImg);

The above example extends the original by increasing the number of enemies to three. You will note that these have collision indices of 1, 2 and 4 respectively.

Line 021 sets up the collision detection between the player and the enemies. Unlike the previous example, rather than specifying the exact collision index we are interested in we have instead specified a mask of -1 which will detect a collision with any previously rendered sprite.

Alternatively, we could have changed line 021 to read:

io("COLLISION", PLAYER_IDX, ENEMY_1_IDX | ENEMY_3_IDX, collide);

This would detect a collision with the player and only enemies one and three.

3 Likes

Handling Collisions from an Array of Enemies

Extending the simple example to handle multiple enemies reveals more features of the new collision detection.

001  const PLAYER_IDX = 255;
002
003  function collide(enemyIdx) {
004    console("Houston we have a problem with enemy number ");
005    console(enemyIdx - 1);
006  }
007
008  // Render the enemy ..
009  for (var i = 0; i < NUMBER_OF_ENEMIES; i++) {
010    io("COLLISION", i + 1, 0);
011    sprite(enemyX[i], enemy[i], enemyImg);
012  }
013
014  // Render the player ..
015  io("COLLISION", PLAYER_IDX, -1, collide);
016  sprite(playerX, playerY, playerImg);

In this new example, the enemies are assigned a collision index ranging from 1 to
NUMBER_OF_ENEMIES by using the loop’s variable i. When the function collide is called, the index of the sprite the player has collided with is implicitly passed to the function as its first parameter.

The function callback will also pass the player’s collision index as well but in the above example, we only have one player so it is pretty obvious which player the enemy collided with! If needed, you can capture this information using the function template shown below. As shown previously, the parameters can be omitted from right to left.


001  function collide(otherSpriteIdx, spriteJustRenderedIdx) {
002   ...
003  }

Hope this helps!

4 Likes

This is a great feature in the API! Does it do a pixel-perfect collision or a rect based collision?

Excellent documentation too!

1 Like

Pretty sure is rectangular based but to be honest, I would have to test that to be sure.

It’s a simple bounding-box collision detection.

1 Like

That is almost always good enough. Though, it would be good to be able to specify the bounding box.
Maybe a workaround would be using a second, fully transparent, “bbox sprite”.
Edit: for debugging that sprite could be visible.

It would, but storing custom bounding boxes for each of the 100 sprites would cost 400 bytes of RAM. :eyes:

3 Likes

Then it could make even more sense to have a separate invisible bb-sprite, which can be created and used only when needed. If we just could prevent the drawing routine from trying to draw it, as it creates unnecessary work for cpu.

1 Like