User Tools

Site Tools


tutorial:collision

Collisions

Introduction

In this tutorial we will add collision detection to our game. To help with this we will use the sprite engine to manage our game objects.

The api documumentation for the sprite engine can be found here

You can download the source for this tutorial here.

Creating shapes

The first thing we have to do is to create the shapes that will be used for collision testing. Start the image editor and load Shooter.phximg from the previous tutorials.

Now start the shape editor from the tool menu.

Select relative to pattern Player and add a new polygon shape. Rename this shape to Player and press the Create from image button to create a convex polygon that surrounds all the visible pixels of the pattern.

Add a polygon for the asteroid in the same way.

Save the shapes as a list named Shapes.shapes

Loading shapes

Add the phxShape and phxSprite unit to the uses list.

uses SysUtils,
  // Basic phoenix types
  phxTypes,
  // Contains phoenix utility classes
  phxClasses,
  // Math functions
  phxMath,
  // Device
  phxDevice,
  // Contains the application framework
  phxApplication,
  // Contains the canvas class for rendering 2D primitives
  phxCanvas,
  // Image classes
  phxImage,
  // Bitmap font
  phxFont,
  // Input framework
  phxInput,
  // Shapes for collision testing
  phxShape,
  // Sprite engine
  phxSprite;

Add a instance variable for the shape list to the game class.

TGame = class(TPHXApplication)
  private
    Device   : TPHXDevice;
    Canvas   : TPHXCanvas;
    Timer    : TPHXTimer;
    Input    : TPHXInput;
    Images   : TPHXImageList;
    Fonts    : TPHXFontList;
    Shapes   : TPHXShapeList;
    ...
  end;
procedure TGame.Init;
begin
  ...
  // Create the shape list
  Shapes:= TPHXShapeList.Create;
  // Load the shapes from disk
  Shapes.LoadFromFile('Shapes.shapes');
end;

Create sprites

Add a variable for the sprite engine and for our player sprite and a procedure for creating the sprites

TGame = class(TPHXApplication)
  private
    ...
    Sprites  : TPHXSpriteEngine;
    Player   : TPHXSprite;
    procedure CreateSprites;
    ...
  end;

Create the sprites, for more information about the properties of the sprites see TPHXSprite in the API documentation.

procedure TGame.CreateSprites;
var Index   : Integer;
var Asteroid: TPHXSprite;
begin
  // Create the sprite engine
  Sprites:= TPHXSpriteEngine.Create(Device);
  Sprites.Images:= Images;
  Sprites.Shapes:= Shapes;
 
  // Create the asteroid sprites
  for Index := 1 to 10 do
  begin
    Asteroid:= TPHXSprite.Create(Sprites);
    Asteroid.Name    := 'Asteroid ' + IntToStr(Index);
    Asteroid.X       := 20 + Random * (Device.Width  - 40);
    Asteroid.Y       := 20 + Random * (Device.Height - 40);
    Asteroid.Image   := 'Shooter';
    Asteroid.Shape   := 'Asteroid';
    Asteroid.Pattern := 'Asteroid';
    Asteroid.Collider:= True;
    Asteroid.Parent  := Sprites.Root;
  end;
 
  // Create the player sprite
  Player:= TPHXSprite.Create(Sprites);
  Player.Name    := 'Player';
  Player.X       := 100;
  Player.Y       := 100;
  Player.Image   := 'Shooter';
  Player.Shape   := 'Player';
  Player.Pattern := 'Player';
  Player.Collider:= True;
  Player.Parent  := Sprites.Root;
 
  // Initialize the sprite engine
  Sprites.Initialize;
end;

Add a call to the CreateSprites procedure from the TGame.Init procedure

procedure TGame.Init;
begin
  ...
  // Create the sprites
  CreateSprites;
end;

Update the sprites

Next we have to update the sprite engine using the Update method of the sprite engine.

procedure TGame.Update;
begin
  // Update the device
  Device.Update;
  // Update the timer
  Timer.Update;
  // Update the input
  Input.Update;
   // Update the sprite engine
  Sprites.Update(Timer.FrameTime);
end;

To make the player move we use the rotate and move helpers from the sprite class

procedure TGame.Update;
begin
  ...
  // Rotate left with 90 degrees per second
  if isLeft in Input.States then
  begin
    Player.RotateLeft(90 * Timer.FrameTime);
  end;
  // Rotate rightwith 90 degrees per second
  if isRight in Input.States then
  begin
    Player.RotateRight(90 * Timer.FrameTime);
  end;
  // Move forward along the rotation with 200 pixels per second
  if isUp in Input.States then
  begin
    Player.MoveForward(200 * Timer.FrameTime);
  end;
  // Move backward along the rotation with 200 pixels per second
  if isDown in Input.States then
  begin
    Player.MoveBackward(200 * Timer.FrameTime);
  end;
end;

We want the screen to be centered on the player, this can be done with the CenterOn function of the sprite camera.

procedure TGame.Update;
begin
  ...
  // Making the sprite engine follow the player
  Sprites.Camera.CenterOn(Player);
end;

Rendering sprites

To render the sprites we only have to call the Render function of the sprite engine.

We will also render the shapes and the bounding boxes of all sprites. You don't have to add this but it might help to visualize what is happening.

procedure TGame.Render;
begin
  // Clear the back buffer
 
  Device.Clear;
  // Render the sprites
  Sprites.Render;
  // Render the shapes for the sprites
  Sprites.RenderShapes(Canvas, clrWhite);
  // Render the bounding boxes for the sprites
  Sprites.RenderBounds(Canvas, clrSilver);
 
  // Flush the canvas
  Canvas.Flush;
  // Flip the front and back buffers to show the scene
  Device.Flip;
end;

When running the application it should look like this

Collision testing

When calling the TPHXSpriteEngine.Update function all collisions are updated and the virtual TPHXSprite.Collided function for the collided sprites are called. But as we are using the sprite class without creating a subclass of it we cant use this function.

Instead we can use the TPHXSpriteEngine.Collide function to query for collisions. This function collides one sprite against all other sprites and fills a collision list with all the collisions.

In this case we just use this for rendering the name of the collided sprites.

procedure TGame.Render;
var Collisions: TPHXSpriteCollisionList;
var Index     : Integer;
begin
  ...
  Collisions:= TPHXSpriteCollisionList.Create;
  try
    Sprites.Collide(Player, Collisions);
 
    for Index:= 0 to Collisions.Count-1 do
    begin
      Fonts[0].TextOut(4,4 + Fonts[0].Height * Index, Collisions[Index].B.Name);
    end;
  finally
    Collisions.Free;
  end;
  ...
end;
 

Now the name of the collided sprites should be rendered

Collision masking

In the code above all sprites are tested against all other sprites for collisions. This will be quite slow when you add a lot of sprites. But we don't need the asteroids to collide against each other. To disable this we can change their TPHXSprite.Mode to static.

  // Mark asteroids as static
  Asteroid.Mode:= cmStatic;
  ...
  // Mark the player as dynamic (this is the default value)
  Player.Mode:= cmDynamic;

It is also possible to only test for collisions with a specific set of sprites using the TPHXSprite.Group property.

  // Make the asteroids belong to group 2
  Asteroid.Group:= cgGroup2;
  ...
  // Make the player belong to group 1
  Player.Group:= cgGroup1;
  ..
 
  // Only collide the player against asteroids
  Sprites.Collide(Player, Collisions, cgGroup2);

To see the collision masking in action see the Platformer demo.

Shooting

Drawing asteroids is all fun, but what is even more fun is to blow them up! Lets add some code for firring bullets from our ship.

First we have to add some weapon hardpoints to the ship sprite, start the image editor and open Shooter.phximg and select the Tags page (or cheat and download the image here.

Add two tags called “Hardpoint1” and “Hardpoint” and move them to the front edge of each wing of the ship.

Now we have to add the image for the bullet sprite.

Download the image and use the import tool from the pattern toolbar (This is only visible if you have the pattern page open, if you are still on the tags page it will be hidden).

When pressing the button you get a open dialog where you can select the bullet image, place the image in a empty position by clicking with the mouse and pres enter to insert the bullet image into the image. A pattern will be automatically created with the same name as the image. Make sure it is named “Bullet”

Next start the shape editor open our Shapes.shapes file and add a circle for the bullet with a radius of 4.

Save the list again

Bullet class

To create bullets we add a new class that contains the logic of the bullet. We need to override the Update and the Collided functions.

TBullet = class(TPHXSprite)
  public
    procedure Update(const DeltaTime: Double); override;
    procedure Collided(Sprite: TPHXSprite); override;
  end;

In the update function we move the bullet forward and kills it after one second.

procedure TBullet.Update(const DeltaTime: Double);
begin
  inherited;
  // Move forward with 400 pixels per second
  MoveForward(250 * DeltaTime);
 
  // Kill the bullet after one second 
  if Time > 1 then Kill;
end;

When the bullet has collided with another sprite we will kill the both bullet and the sprite it has collided with.

procedure TBullet.Collided(Sprite: TPHXSprite);
begin
  inherited;
 
  // We only have asteroids the player and bullets in the sprite list so when we have collided
  // with something other then the player its safe to assume its a asteroid we have collided with.
  // A better way is to make a class for asteroids and test with if Sprite is TAsteroid instead.
  if(Sprite.Name  <> 'Player') then
  begin
    // Kill the bullet
    Kill;
    // Kill the asteroid
    Sprite.Kill;
  end;
end;

Fire a bullet

To fire the bullet class create a new function and add it to the game. We will use the TPHXSprite.AttatchTo function to move the sprite to the tags of the player sprite.

Note that the rotation of the tags and the sprite rotation is considered to calculate the rotation of the bullet sprite. If you change the rotation of the hardpoint tags in the image editor you can get the player to shoot in different directions.

procedure TGame.Fire;
var Bullet: TPHXSprite;
begin
  // Create the bullet sprite for the first hardpoint
  Bullet:= TBullet.Create(Sprites);
  Bullet.Name    := 'Bullet';
  Bullet.Image   := 'Shooter';
  Bullet.Pattern := 'Bullet';
  Bullet.Shape   := 'Bullet';
  Bullet.Collider:= True;
  Bullet.Parent  := Sprites.Root;
 
  // Attatch the first bullet to the "Hardpoint1" tag of the image
  Bullet.AttatchTo(Player, 'Hardpoint1');
 
  // Create the bullet sprite for the second hardpoint
  Bullet:= TBullet.Create(Sprites);
  Bullet.Name    := 'Bullet';
  Bullet.Image   := 'Shooter';
  Bullet.Pattern := 'Bullet';
  Bullet.Shape   := 'Bullet';
  Bullet.Collider:= True;
  Bullet.Parent  := Sprites.Root;
 
  // Attatch the second bullet to the "Hardpoint2" tag of the image
  Bullet.AttatchTo(Player, 'Hardpoint2');
end;

Then its just a matter of calling the Fire function when a button is pressed.

procedure TGame.Update;
begin
  ..
  // Fire a bullet
  if isButton1 in Input.States then
  begin
    Fire;
    Input.States:= Input.States - [isButton1];
  end;
end;

Run the application.

Adding cooldown between each shot

The player now shoots two bullet every time you press the fire button. If we want to fire while holding the button we have to add a cooldown between each shot.

Create a variable for the cooldown

TGame = class(TPHXApplication)
  private
     ...
    Cooldown: Single;
  end;

Then replace the fire logic with this

procedure TGame.Update;
begin
  ..
  Cooldown:= Cooldown - Timer.FrameTime;
  if (isButton1 in Input.States) and (Cooldown < 0) then
  begin
    Fire;
    // We must wait 0.2 seconds before fireing again
    Cooldown:= 0.2;
  end;
end;

A better way then the above would be to add this code to a TPlayer class, but I will leave that as a exercise for the reader.

Back to tutorial index

tutorial/collision.txt · Last modified: 2013/08/31 09:04 by amnoxx