Hacking Collision Detection With Slick2D

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.

Entity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public abstract class Entity {

        /** The current x location of this entity */
        protected float        x;

        /** The current y location of this entity */
        protected float        y;

        /** The sprite that represents this entity */
        protected Sprite        sprite;

        /** The current speed of this entity horizontally (pixels/sec)
        */
        protected float        dx;

        /** The current speed of this entity vertically (pixels/sec)
        */
        protected float        dy;

        /** The rectangle used for this entity during collisions resolution
        */
        private Rectangle        me        = new Rectangle();

        /** The rectangle used for other entities during collision
        resolution */
        private Rectangle        him        = new Rectangle();


        /**
         * 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
         */
        protected Entity(Sprite sprite, int x, int y) {
                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
         */
        public void move(long delta) {
            // update the location of the entity based on move speeds
                x += (delta * dx) / 1000;
                y += (delta * dy) / 1000;
        }


        /**
         * Draw this entity to the graphics context provided
         */
        public void draw() {
                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
         */
        public boolean collidesWith(Entity other) {

                me.setBounds((int) x, (int) y, sprite.getWidth(),
     sprite.getHeight());

                him.setBounds((int) other.x, (int) other.y,
     other.sprite.getWidth(), other.sprite.getHeight());

                return me.intersects(him);
        }

        /**
         * Notification that this entity collided with another.
         *
         * @param other The entity with which this entity collided.
         */
        public abstract void collidedWith(Entity other);

}

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class BallEntity extends Entity {

       /** other ball specific code **/

       //called in the update loop, we recalculate our Ball's position       

       public void move(delta, ArrayList<ObstacleEntity> obstacles) {

          super.move(delta)

          for (ObstacleEntity o : 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class BallEntity extends Entity {

       /** other ball specific code **/

       //called in the update loop, we recalculate our Ball's position       

       public void move(delta, ArrayList<ObstacleEntity> obstacles) {

          super.move(delta)

          for (ObstacleEntity o : obstacles) {
                  if (this.collidesWith(o) ) {
                               super.unmove(delta)
                  // or super.nudge(delta)
                                dx = -dx;
                            dy = -dy;
              }
          }    
  }
}
Entity.java
1
2
3
4
5
6
7
8
9
10
11
public abstract class Entity {

       /*** other Entity code ***/

        public void unmove(long delta) {
            /* undo our location by subtracting the delta (undoing the
            move method) */
                x -= (delta * dx) / 1000;
                y -= (delta * dy) / 1000;
        }
}

For a look at a silly promotional game I made using Slick2D, check out the Sulfonic Avenger applet. If you’re too lazy to install Java – and I do not blame you one bit – just browse the source.

Comments