Hacking Collision Detection With Slick2D

2013-12-31 11:47:00 -0500
Dec 31st, 2013

A Brief, Java-based Primer On Collision Detection

Collision detection is a game programming fundamental. If your
spaceship fires a ray gun or evades enemies, you’ve likely implemented
some form of detection. If you’re using Slick2D or a similar game
engine, things might not be working exactly as expected. Below is a
quick primer covering:

Basic collision detection in Slick2D (or similar game engine)

A collision detection dirty hack when you entities are stuck post-collision.

Entities

Your spaceship or enemy is typically a subclass inheriting from an
abstract “Entity” class (you probably won’t have an instance of
a generic entity flying around.) Your spaceship may have lives, the
enemy hitpoints – you fill out the particulars – but movement,
rendering, and collisions are aspects usually shared by all entities.

A basic Entity
class might start off looking like the code below,
courtesy of Slick2D author Kevin Glass. We’ll assume you have a
similar Sprite class
already in your project.

publicabstractclassEntity{/** The current x location of this entity */protectedfloatx;/** The current y location of this entity */protectedfloaty;/** The sprite that represents this entity */protectedSpritesprite;/** The current speed of this entity horizontally (pixels/sec) */protectedfloatdx;/** The current speed of this entity vertically (pixels/sec) */protectedfloatdy;/** The rectangle used for this entity during collisions resolution */privateRectangleme=newRectangle();/** The rectangle used for other entities during collision resolution */privateRectanglehim=newRectangle();/** * Construct a entity based on a sprite image and a location. * * @param sprite The reference to the image to be displayed * for this entity * @param x The initial x location of this entity * @param y The initial y location of this entity */protectedEntity(Spritesprite,intx,inty){this.sprite=sprite;this.x=x;this.y=y;}/** * Request that this entity move itself based on a certain * ammount * of time passing. * * @param delta The amount of time that has passed in milliseconds */publicvoidmove(longdelta){// update the location of the entity based on move speedsx+=(delta*dx)/1000;y+=(delta*dy)/1000;}/** * Draw this entity to the graphics context provided */publicvoiddraw(){sprite.draw((int)x,(int)y);}/** * Check if this entity collised with another. * * @param other The other entity to check collision against * @return True if the entities collide with each other */publicbooleancollidesWith(Entityother){me.setBounds((int)x,(int)y,sprite.getWidth(),sprite.getHeight());him.setBounds((int)other.x,(int)other.y,other.sprite.getWidth(),other.sprite.getHeight());returnme.intersects(him);}/** * Notification that this entity collided with another. * * @param other The entity with which this entity collided. */publicabstractvoidcollidedWith(Entityother);}

collidesWith and collidedWith

My apologies to the dyslexic coders out there, but consider the naming
convention a straightforward separation of concerns.

collidesWith: creates our bounding box (typically from the
dimensions of your sprite graphic) and indicates a collision. All we’re saying is, “hey he collides with me.”

collidedWith: it’s an abstract method, so we need to actually
implement some logic when a collision occurs. “he collided with me,
now what?” Maybe you remove a player’s life, or an enemy’s hitpoints.

Think of the former as indicative (but generic) of two somethings
colliding, and the latter as the entity-specific implementation (what do we actually need to happen.)

Update, Render, and Variable Delta

A game loop alternates between update and render blocks of
code, where update performs calculations and logic, while render draws
the resulting operations to the screen.

If we revist our entities move method, you’ll see it takes the
parameter delta, indicating how many milliseconds have passed
between each frame. From this value we calculate the movement of our
entities, and render them anew on the screen.

What does a variable delta have to do with collision detection? Let’s
visit an example where we have an enemy that bounces off obstacles in
our game world. For simplicity, we’ll have a “Ball” entity, and a fixed “Obstacle”
entity. When they collide, we change the direction of our
Ball.

BallEntity.java

123456789101112131415161718

publicclassBallEntityextendsEntity{/** other ball specific code **///called in the update loop, we recalculate our Ball's position publicvoidmove(delta,ArrayList<ObstacleEntity>obstacles){super.move(delta)for(ObstacleEntityo:obstacles){if(this.collidesWith(o)){dx=-dx;dy=-dy;}}}}

If we aren’t concerned with efficiency, and somewhat charitable with
our code, we can easily imagine our Ball moving bit-by-bit until it
encounters an Obstacle, and then reversing itself.

The Problem

Consider what happens when we have our initial collision. We reverse
our ball, and render it. Now consider the second iteration of the
update loop, but with an extremely small delta. Maybe so small that we haven’t
moved anywhere. Our Ball detects a collision and reverses direction, now
going even “deeper” into the direction of the original
collision. Next iteration, another collision. Our Ball is now
effectively stuck in an “infinite collision”, where we move back and
forth but go nowhere – we’re stuck.

The Hack

Just “undo” it. When the Ball moves and detects the intial collision,
just undo the move. It’s not precise, but recall that the game loop is
so quick and frequent, the “distance” covered in a single frame is
practically indistinguishable. Sometimes the hacks that work best are
straightforward and quick to implement.

BallEntity.java

1234567891011121314151617181920

publicclassBallEntityextendsEntity{/** other ball specific code **///called in the update loop, we recalculate our Ball's position publicvoidmove(delta,ArrayList<ObstacleEntity>obstacles){super.move(delta)for(ObstacleEntityo:obstacles){if(this.collidesWith(o)){super.unmove(delta)// or super.nudge(delta)dx=-dx;dy=-dy;}}}}