Skip to content

Arx 05 Detecting Collisions

noooway edited this page Jan 25, 2017 · 2 revisions

Now it is time to deal with collisions. A collision happens when two objects overlap. First we need to detect an overlap, then to react on it (i.e. resolve collision). Collisions are the part of the game, where a good piece of it's logic resigns. I'm going to start from some basic things and deal with collision detection first.

It is possible to code an overlap-checking from scratch. However, I'm going to use an external library, called HardonCollider (HC). Unlike love.physics module, HC only serves to detect collisions, but does not resolve them. The resolution of the collisions will be done purely manually.

So, how does HC work? It maintains it's own world, so to say, populated by different geometrical primitives, called shapes. Those shapes are added to the world manually, and each time you add one, you receive a reference to this shape. Then you can query this HC world by this shape-reference and it will tell you, what are the other shapes, that overlap with the one you specified. Here is a slightly modified code from the official example:

HC = require 'HC' -- (*1) 
.....

function love.load()
	--(*2)
	rect = HC.rectangle(200,400,400,20)
	circ = HC.circle(400,300,20)
	.....
end

function love.update(dt)
	-- (*3) 
	circ:moveTo( love.mouse.getPosition() )
	rect:rotate(dt)

	-- (*4)
	for another_shape, delta in pairs( HC.collisions( circ ) ) do
		local toprint = string.format("Colliding. Separating vector = (%s,%s)",
									  delta.x, delta.y)
		
	end
	.....
end

(*1) - first, it is necessary to load this library.
(*2) - rectangle and circle are added to the HC 'world' to check collisions between them.
(*3) - both shapes positions are updated: circ is moved under the mouse coursor, and rect is rotated.
(*4) - we query HC on which shapes collide with circ. For each collision, the query returns two things: the first one is another shape, that circ collides with; the second one is separation vector delta. The separating vector points outside of the circ and we need to displace another shape by this vector to make sure that it will no longer overlap with the circ.

How to use this library in our project? First we need to create an HC world and add there all our objects. Obviously, creation should be done in love.load() ( don't forget to download the library and require it first. I've renamed the folder simply to HC ).

local Platform = require "Platform"
local Ball = require "Ball"
local BricksContainer = require "BricksContainer"
local WallsContainer = require "WallsContainer"
local HC = require "HC"

function love.load()
   collider = HC.new()
   .....
end

After the creation, it is necessary to pass the collider instance to the constructors of the other objects, so we can later add appropriate shapes into it:

function love.load()
   collider = HC.new()
   ball = Ball:new( { collider = collider } )
   platform = Platform:new( { collider = collider } )
   bricks_container = BricksContainer:new( { collider = collider } )
   walls_container = WallsContainer:new( { collider = collider } )
end

Now at each constructor we need to add the appropriate shape to the collider. Let's start from the Ball.

function Ball:new( o )
   .....
   o.speed = o.speed or vector( 300, 300 )
   o.collider = o.collider or {} --(*1)
   o.collider_shape = o.collider:circle( o.position.x,   --(*2)
                                         o.position.y,
                                         o.radius )
   .....
   return o
end

(*1): First, inside each ball object we are going to store a reference to the collider object.
(*2): When each new ball object is constructed, we are going to add an appropriate shape to the collider.

We need to keep the ball.position and the position of the ball.collider_shape in sync. Therefore, during each update we are going to call a moveTo method of the ball.collider_shape to make it coincide with the ball.position.

function Ball:update( dt )
   self.position = self.position + self.speed * dt
   self.collider_shape:moveTo( self.position:unpack() )
end

This might seem like an unnecessary complication to have to duplicating variables, however for now it is better to leave it that way.

HC also has a number of debugging methods that allow to draw each shape added to the collider. It is good to use it for now to check that everything works as expected. I'm going to draw the shape of the ball in the transparent green.

function Ball:draw()
   .....
   local r, g, b, a = love.graphics.getColor( )
   love.graphics.setColor( 0, 255, 0, 100 )
   self.collider_shape:draw( 'fill' )
   love.graphics.setColor( r, g, b, a )
end

It is necessary to make the same changes in the Wall, Brick and Platform classes. They are similar in nature, so I won't stop on them. Some care should be taken regarding rectangle:moveTo. It positions the center of the rectangle shape to the provided coordinates, so a shift is necessary.

function Platform:update( dt )
   .....
   self.collider_shape:moveTo( self.position.x + self.width / 2,
                               self.position.y + self.height / 2 )
end

Walls and bricks do not move, so there is no need to call moveTo method for their collider_shape. Still, it is useful to draw appropriate shapes.

function Brick:draw()
   love.graphics.rectangle( 'line',
				self.position.x,
				self.position.y,
				self.width,
				self.height )
   local r, g, b, a = love.graphics.getColor( )
   love.graphics.setColor( 255, 0, 0, 100 )
   self.collider_shape:draw( 'fill' )
   love.graphics.setColor( r, g, b, a )
end

We do not construct bricks and walls directly - this is done by bricks_container and walls_container. We have to make sure that reference to collider is passed to bricks and walls.

function BricksContainer:new( o )
   .....
   o.name = o.name or "bricks_container"
   o.bricks = o.bricks or {}
   o.collider = o.collider or {}
   .....
	 local new_brick = Brick:new{
		width = o.brick_width,
		height = o.brick_height,
		position = new_brick_position,
		collider = o.collider
	 }
	 new_row[ col ] = new_brick
   .....
   return o
end

A final touch for this chapter is to display some information when actual collision happens. This is done by resolve_collisions function, placed in love.update.

function love.update( dt )
   .....
   resolve_collisions( dt )
end

For now, it's definition is simple. As suggested by the HC example, it prints a displacement vector when the ball collides with something

function resolve_collisions( dt )
   for another_shape, delta in pairs( collider:collisions( ball.collider_shape ) ) do
	  local toprint = string.format("Ball is colliding. Separating vector = (%s,%s)",
					delta.x, delta.y)
	  print( toprint )     
   end
end

For now we do not know what is ball colliding with exactly - a brick, a wall or the platform. Obviously, we need to know this information to resolve collisions properly. This is done in the next chapter.

    Home
    Acknowledgements
    Todo

Chapter 1: Prototype

  1. The Ball, The Brick, The Platform
  2. Game Objects as Lua Tables
  3. Bricks and Walls
  4. Detecting Collisions
  5. Resolving Collisions
  6. Levels

    Appendix A: Storing Levels as Strings
    Appendix B: Optimized Collision Detection (draft)

Chapter 2: General Code Structure

  1. Splitting Code into Several Files
  2. Loading Levels from Files
  3. Straightforward Gamestates
  4. Advanced Gamestates
  5. Basic Tiles
  6. Different Brick Types
  7. Basic Sound
  8. Game Over

    Appendix C: Stricter Modules (draft)
    Appendix D-1: Intro to Classes (draft)
    Appendix D-2: Chapter 2 Using Classes.

Chapter 3 (deprecated): Details

  1. Improved Ball Rebounds
  2. Ball Launch From Platform (Two Objects Moving Together)
  3. Mouse Controls
  4. Spawning Bonuses
  5. Bonus Effects
  6. Glue Bonus
  7. Add New Ball Bonus
  8. Life and Next Level Bonuses
  9. Random Bonuses
  10. Menu Buttons
  11. Wall Tiles
  12. Side Panel
  13. Score
  14. Fonts
  15. More Sounds
  16. Final Screen
  17. Packaging

    Appendix D: GUI Layouts
    Appendix E: Love-release and Love.js

Beyond Programming:

  1. Game Design
  2. Minimal Marketing (draft)
  3. Finding a Team (draft)

Archive

Clone this wiki locally