Follow me

May 17, 2013

A simple platformer engine (part 2): collisions

In the previous article, I presented a simple, yet functional, 2D engine that you can use in any language (I used the Haxe language). It handles basic physics and level collisions.

But what about collisions between entities?



It may sound tricky, but it's actually not that hard, if your game doesn't require super high quality collisions.



Right now, let's see what we have. Here is the pseudo code of the Entity class:

class Entity {
 // Graphical object
 public var sprite : flash.display.Sprite;

 // Base coordinates
 public var cx : Int;
 public var cy : Int;
 public var xr : Float;
 public var yr : Float;

 // Resulting coordinates
 public var xx : Float;
 public var yy : Float;
 
 // Movements
 public var dx : Float;
 public var dy : Float;

 public function new() {
  //...
 }

 public function update() {
  // X coord management (ie. movements + level collisions)
  //...

  // Y coord management (ie. movements + level collisions)
  //...

  // Graphical update
  //...
 }
}

Detecting entities collisions

The idea here is, again, simplicity.
Each entity will have a radius which will define it's "hitbox" for entity collisions. Note that, for the level collisions, we will still use the simple coordinate based code.

public var radius : Float;

Unless the precision of the collisions between entities is absolutely crucial in your gameplay (like in a physics based gameplay), simple circular collisions like this are more than enough.

Also, remember that this approach is only a basis: it's minimal, so easy to maintain and expand.

In the following screenshot (source: arkadeo.com), the real collision areas are shown by the yellow circles. In a shoot em up, small hitboxes like these allow the player to actually brush past enemies (which is usually a satisfying feeling) :)



We can write a pretty simple collision detection method:
public inline function overlaps(e:Entity) {
  var maxDist = radius + e.radius;
  // classic distance formula
  var distSqr = (e.xx-xx)*(e.xx-xx) + (e.yy-yy)*(e.yy-yy);
  if( distSqr<=maxDist*maxDist )
   // square root computed here for performances sake
   return Math.sqrt(distSqr) <= maxDist; 
  else
   return false;
 }
Note that the method is inlined (ie. it's content will be copied in place of its call by the Haxe compiler) because some target platforms, like Flash, have a small performance cost for function calls.

Ok! Now you can answer the question "does the hero collides with this other entity (like, say, an enemy or an item)". Cool, we can already make a game out of it.

Repel

In a game, many collisions between 2 entities result in the actual destruction of one (ex: picking up an item, getting hit by a bullet...). However, sometime, you need to put these entities in a situation where they don't collide anymore (aka. repeling).

Let's say we have 2 entities colliding: Blue and Yellow. We can say they are colliding because the distance between them is lower than the sum of their radiuses.



What we simply have to do is to slightly push Blue to the bottom left, and Yellow to the upper right. There are many approaches to do that :

  1. add some repel force to the dx,dy values of each entity so they will bounce on each other.
  2. recalculate the position of each Entity so their angle of incidence remains unchanged but they don't overlap anymore
  3. on each frame, slightly push their xr,yr away from the center of the collision without changing their current forces (dx,dy).
  4. ...a world of possibilities.

The choice depends on what you want to achieve, and the tolerance you actually have to how much they will overlap. For example, in solution 1, if two entities suddenly overlap (like Yellow falls with high velocity on Blue), they will really overlap for a few frames, before the repel force applies enough to separate them.

I usually use solution 1, the simplest (you know I like that). If it doesn't fit my needs, I add into the mix other things like brutal repositioning (solution 2)...

Note: in the above example, the red line is the distance of overlapping. That's a useful information if you want to calibrate the repel force based on how much the entities are actually overlapping. Or even choose to brutally reposition them if this overlap is really TOO much.

So!

The collision code will be placed in the update, before the X and Y management in our current source code. The reason: we usually want the level collisions to prevail on potential entity collision repeling (you probably don't want your entities to get stuck into a wall). With repel force, this should not occur, but I don't know what kind of horrible code my repel algorithm will evolve into, as the game evolves.

For each Entity, you need to check every other entities for collision.

THEORICAL WARNING: this can be really expensive if you have lots of entities in your game.
REALITY: usually no, you don't have that much entities that collide and need to be repeled. Example: usually, bullets should not collide like this, they hit their target and simply die. Well, even if you do have many entities, their are lots of solutions to filter out the ones that have no chance to collide with the active one.

Like comparing their cx,cy coordinates.

for( e in ALL ) {
 // Fast distance check
 if( e!=this && Math.abs(cx-e.cx) <= 2 && Math.abs(cy-e.cy) <= 2 ) {
  // Real distance check
  var dist = Math.sqrt( (e.xx-xx)*(e.xx-xx) + (e.yy-yy)*(e.yy-yy) );
  if( dist <= radius+e.radius ) {
   var ang = Math.atan2(e.yy-yy, e.xx-xx);
   var force = 0.2;
   var repelPower = (radius+e.radius - dist) / (radius+e.radius);
   dx -= Math.cos(ang) * repelPower * force;
   dy -= Math.sin(ang) * repelPower * force;
   e.dx += Math.cos(ang) * repelPower * force;
   e.dy += Math.sin(ang) * repelPower * force;
  }
 }
}
The first IF is our basic "Do I really need to check this one? Is it even close enough?".

Then we go with the real Square root distance thing. Then, if we actually have collision, we calculate their angle of incidence with the magic ATan2 formula.

We also compute a repelPower which is a factor (0 to 1) depending on how much the entities actually overlap.

We have an angle, we have "penetration factor", we can add the scaled force to dx using the Cos of the angle, and to dy using the Sin.
The opposite (negative) is applied to the other entity.

That's it.

The following demo is not a platformer, but a top-down demo. That's the same thing as a platformer, without gravity. That's the engine used in Atomic Creep Spawner.

Note : the "brutal" repositioning that occurs sometime is due to way the level collisions are handled. Instead of setting, say, xr to 0.3 if it's lower than 0.3, you get better looking results adding a force to dx to repel the entity from the wall (and cap xr to 0.1 as a hard limit).