Programming Lab 3: Collision Detection

Return to CIS 300.

Announcement: Bugs Found

Several bugs have been discovered in the lab. You can either download the files again or make the following changes:

GameEngine.cs

The method

  public double RandomDouble(double min, double max) {
    return numGen.NextDouble() * max + min;
  }
should be changed to
  public double RandomDouble(double min, double max) {
    return numGen.NextDouble() * (max - min) + min;
  }

Furthermore, the method

  public int RandomInt(int min, int max) {
    return numGen.Next(min, max);
  }
should be changed to
  public int RandomInt(int min, int max) {
    return numGen.Next(min, max + 1); // upper bound is exclusive
  }

Shell.cs

In the Initialize() method, the code

  if (parent.RandomInt(0, 1) == 0) {
    objectImage = redTexture;
  } else {
    objectImage = greenTexture;
  }
should be changed to
  if (parent.RandomInt(0, 2) == 0) {
    objectImage = redTexture;
  } else {
    objectImage = greenTexture;
  }

Star.cs

The method

  public override void Draw(SpriteBatch spriteBatch, GameTime gameTime) {
    // Draw the shell
    spriteBatch.Draw(objectImage, new Rectangle((int)(position.X * GameEngine.GameWidth),
        (int)(position.Y * GameEngine.GameHeight), objectImage.Width, objectImage.Height),
        new Rectangle(0, 0, objectImage.Height, objectImage.Width),
        Color.White, (float)(gameTime.TotalGameTime.TotalMilliseconds % (2*Math.PI)), 
        new Vector2(objectImage.Width / 2, objectImage.Height / 2), SpriteEffects.None, 0);
  }
should be changed to
  public override void Draw(SpriteBatch spriteBatch, GameTime gameTime) {
    // Draw the shell
    spriteBatch.Draw(objectImage, new Rectangle((int)position.X,
        (int)position.Y, objectImage.Width, objectImage.Height),
        new Rectangle(0, 0, objectImage.Height, objectImage.Width),
        Color.White, (float)(gameTime.TotalGameTime.TotalMilliseconds % (2*Math.PI)), 
        new Vector2(objectImage.Width / 2, objectImage.Height / 2), SpriteEffects.None, 0);
  }


Objective

For this lab, you will implement somewhat sophisticated forms of collision detection in the framework of yet another already-created game demo. You will also add a few new elements to the gameplay.

For this lab you will work in pairs. You should pair up with another person in doing your work with this lab, preferably someone in your own group. If you cannot find a person to partner with, the TAs will find you someone to partner with. If there are an odd number of people in the class, the TAs will either form one group of three (or even a person is suitably advanced, let one person work alone).

Once again, this lab is not due during this lab period, but it is to your advantage to work through (and/or learn) as much as you can now, while assistance is immediately available. In addition, by the end of this lab person you must submit your group by e-mail to Yi Xu by the end of lab. If you are experiencing any other problems, please take advantage of the new office hours made available, or contact a programming TA by e-mail.


The Game

File: CollisionDetection.zip

The game is a remake of a minigame from Super Mario RPG. In this version, red and green shells fall down from the top of the screen, you control a beetle-shaped "ship" at the bottom of the screen, and if a shell hits your beetle, you lose. Your beetle can move from left to right to dodge shells and can shoot at the shells to destroy them. Green shells are destroyed in one hit, while red shells turn into green shells when hit by shots. When a green shell is destroyed it explodes into several stars which radiate outwards in semi-random directions and act as bullets (thereby possibly creating chain reactions of explosions).

Start out by unzipping the source somewhere convenient. You should know how to get this working from the previous lab. This contains the source code for an incomplete version of the game. In this version, shells will only collide with (and bounce off of) the ground. You will have to write code to make them collide with bullets (and react accordingly), collide with the player's ship (destroying it), and collide with each other (bouncing off without being destroyed).

Because shells will be constantly falling from the top of the screen, the number of objects on the screen may get quite high. As a result, checking for collisions between each shell and every other shell in the game will become prohibitively expensive. Therefore, you will also have to implement some sort of a system for dividing the screen into cells, and having each shell only check for collisions with other shells in its own or neighboring cells.

In the main directory should be a pre-compiled executable binary of an example solution to the collision detection part of this lab. This will show you a version with correctly working collision detection implemented. It could, of course, be improved upon, but we're looking for about this level of detection.


The Code

The code is split into two main sections. The first section is "Game Code", which contains code controlling the basic dynamics of gameplay. The other section is "Game Objects". This section contains the code for the various types of objects in the game: shells, ships, bullets and stars, as well as a generic GameObject class that is the superclass of them all.

The game is controlled through the GameEngine instance (contained in GameEngine.cs). The GameEngine's jobs include loading and unloading images, creating new objects, deleting objects and calling the game objects' member functions for updating and drawing.

Collision detection is handled by the GameObject class. When a GameObject's Collide(GameObject o) function is called, the GameObject will call the GetType() function of both itself and the object it is colliding with in order to determine the types of objects that are colliding (e.g. shell - shell, ship - star, etc.). It will then call a special HandleCollision(TYPE_1 a, TYPE_2 b) function corresponding to the two colliding types.

A large part of your job in this lab will be implementing these HandleCollision functions, which are currently just stubs, for the various possible types of colliding objects. However, keep in mind that these will never be called unless you also add code in the GameEngine to call the Collide() function of the GameObjects.

GameObject

Take a look at GameObject.cs to learn what functions and data members it has available. In particular, note the properties Position and Velocity, and the Destroy() method. These will be your main way of getting information about and affecting objects.

As mentioned, there are various stubbed HandleCollision functions in GameObject.cs. You will be filling these in to implement collision detection and response. You can treat all collisions in this game as simple circle-on-circle collisions, with the radius of each circle determined by the radius member variable.

void HandleCollision(shell s1, shell s2)

First, you will have to detect whether or not the two indicated shells are colliding. Then, if they do collide, you will have to react to this collision (the shells should bounce off of each other). It is a good idea to not only change the colliding shells' velocities, but to also change their positions such that they are no longer interpenetrating. Since we do not want you getting stuck on the physics details of this situation, there is some additional help at the end of this section.

void HandleCollision(Shell se, Star st)
void HandleCollision(Shell se, Bullet bu)
void HandleCollision(Shell se, Ship sh)

In these cases, both the shell and the object it collides with should be destroyed. If the ship is destroyed, this will end the game. The shell's Destroy() method takes into account that for red shells, being "destroyed" simply results in turning green, so you don't have to worry about that. If you want, you can have shells be pushed away from whatever object they are colliding with (e.g. to have a bullet knock a red shell upwards when they hit).

void HandleCollision(Star st, Shipe sh)

If you wish, you can have this also result in both parties being destroyed.

void HandleCollision(others...)

Generally speaking, other types of collisions should not result in anything happening, but you can change this if you want.

To help you out with the shell-shell collisions, here is an overview of the math involved. You should treat these collisions as two circles colliding, as depicted in the figure below.

Detecting the collision is the easy part. Once you know that the shells have hit each other, you need to find their new velocities. This can be confusing, so here is the easy way to make it work. Each Shell has its velocity, so you already know v1 and v2. You can find w1and w2 by projecting v1 and v2 onto a vector pointing from the center of s1 to the center of s2. A w vector represents the component of the velocity that acts in the collision (it is normal to the collision surface). To get a reasonable final velocity for a shell, simply take away its contribution and add the other shell's contribution. In short, the final velocity for the first shell would be v1-w1+w2. Note that this model is perfectly elastic. Since you would probably like some energy loss, try scaling the final velocities by some factor to make it happen. Also, do not forget to change the positions of the shells to ensure they are no longer in contact after the collision has been handled.

GameEngine

The GameEngine is where you will be implementing checking for (as opposed to reacting to) collisions. As mentioned before, you will have to create a grid of collision cells. To do this, you will first have to define a collision cell class. How you do this is up to your discretion. In general, the role of collision cells is to record what objects are in a certain area of the screen. Then, when you are updating an object, you can minimize the number of collision checks by only calling Collide(GameObject o) with the objects in its own and neighboring collision cells.

Take note of the various member variables of GameEngine,in particular the arrays of shells, ships, bullets and stars. These arrays are where all the game objects are stored. You can add new GameObjects using the AddObject(GameObject o) method.

void Initialize()

If you need to do anything to initialize your collision cell matrix, which should be declared as a member variable of the GameEngine, this would be the place to do it.

void Update()

This function is called every frame. Its responsibilities are to call the Run() functions of every GameObject in the game, to add new shells as they fall from the top of the screen, to remove old objects from the object lists when if they have been destroyed, to make shells properly bounce off of the special terrain elements (the rectangular step and the circular bump) and to initiate collision handling. All of these tasks except for the last are already implemented. To implement the last, you will have to go through the GameObjects and call the appropriate Collide(GameObject o) functions, using the information stored in the collision cells about what objects are nearby to minimize the number of calls.

When you are designing your collision cells, try to be speed-conscious. Given that there will be a relatively small number of collision cells it probably will not matter for this particular game, but in games with wide open scrollable terrain you could have tens of thousands of collision cells, at which point speed would become a real factor. Try to think of a framework that doesn't require performing some action on all collision cells (even empty ones) every frame.

To test out your code, there is a Flood button ('F'), which floods the playfield with shells. The game should not slow down with tons of shells handled. Try shrinking down the shells and making sure you can handle a couple thousand shells without any slowdown caused by collision.

NOTE: If you are running your code in Debug mode it may not be as efficient. Be sure to test your code in Release mode to confirm this number.


Gameplay

In addition to implementing collision detection, you will improve upon the game with at least two good new gameplay elements. There are no limits on what these can be, but they should be non-trivial, cool, and make the game more fun. These additions will be graded not only on implementation quality but also on creativity.

We are intentionally not giving you any ideas so you can come up with your own. However, it is very important that your changes are significant. Simply adding a counter and calling it a score is not enough to be considered a significant change. On the other hand, designing and implementing a more complex scoring system (including all appropriate mechanics and display modifications) might be interesting. If you're wondering if your idea is acceptable, please contact the programming TAs, and we will gladly help you on this.

Make sure your additions actually make the game more fun, interesting, or unique. Your README file should include what sort of additions you made, how you did them, and why you decided they would be good things to include. Careful documentation of your gameplay changes is very important.


Submission

You should create a ZIP file with three components:

Submit this ZIP file as "lab3prog.zip" to CMS.

Due Wednesday February 13, 2008 at 11:59pm