Chapter 4. Collision Detection

Thanks to the code developed over the last two chapters, we can draw and animate sprites on the screen. In this chapter, we will make them more lifelike by giving them the ability to bump into each other. This is done using a technique called collision detection. A collision occurs when two sprites touch or overlap each other. To demonstrate this new concept, we will create a simple project called Archery Game. Collision is a higher-level technique than previous topics you have learned so far, which have focused more on just getting something up on the screen. This is a very direct way to test for collisions. Another technique, which is ultimately used in Dungeon Crawler, is to calculate the distance between two sprites. Let’s start with the simpler of the two in this chapter, and the distance approach down the road in the gameplay chapters.

Here’s what we’ll cover in this chapter:

  • Reacting to solid objects

  • Rectangle intersection

  • Collision test

  • Archery Game (Collision demo)

Reacting to Solid Objects

Collision detection is an important technique that you should learn. It is a requirement for every game ever made. I can’t think of any game that does not need collision detection, because it is such an essential aspect of gameplay. Without collisions, there is no action, goal, or purpose in a game. There is no way to interact with the game without collisions taking place. In other words, collision detection makes the sprites in a game come to life and makes the game believable. Not every situation in which collision detection occurs necessarily means that something is hit or destroyed. We can also use collision testing to prevent the player from going into certain areas (such as a lake or mountain area that is impassible).

Rectangle Intersection

Collision detection is pretty easy to do using the System.Drawing.Rectangle class. First, you will create a rectangle based on the position and size of one object, such as a sprite. Then you will need to create a similar rectangle for a second object. Once you have two rectangles, which represent the position and size of two objects, then you can test to see whether the rectangles are intersecting. We can do this with a function in the Rectangle class called IntersectsWith(). Figure 4.1 is an illustration showing the bounding rectangles of two sprites from the example program. In most cases, the image itself is used as the bounding rectangle, which includes the transparent pixels that usually surround an image.

The dimensions of a sprite define its bounding rectangle.

Figure 4.1. The dimensions of a sprite define its bounding rectangle.

Collision Test

In the previous chapter, where we learned about sprite programming with the Sprite class, we added a method called IsColliding—but didn’t use it right away, as it was created in advance for our needs in this chapter! Here is the IsColliding() function:

public bool IsColliding(ref Sprite other)
{
    //test for bounding rectangle collision
    bool collision = Bounds.IntersectsWith(other.Bounds);
    return collision;
}

Hint

You will get better results in your game if you make sure there is very little empty space around the edges of your sprite images, since the image is used as the bounding rectangle!

Let’s dissect this method to determine what it does. First, notice that IsColliding returns a bool value (true or false). Notice also that there’s only one Sprite passed by reference (ref). Thus, the entire sprite object in memory (with all of its properties and methods) is not copied as a parameter, only a reference to the sprite is passed. This method is small thanks in part to the Sprite.Bounds property, which returns a Rectangle representing a sprite’s position and size as it appears on the screen. Thus, two rectangles are essentially created based on the position and size of each sprite, and then IntersectsWith() is used to see whether they are overlapping each other. Figure 4.2 shows an illustration of a collision taking place between two sprites.

The two bounding rectangles have intersected.

Figure 4.2. The two bounding rectangles have intersected.

Definition

“Collision” is a misnomer since nothing actually collides in a game unless we write code to make it happen. Sprites do not automatically bump into each other. That’s yet another thing we have to deal with as game programmers!

Often, the code to perform a collision test is trivial compared to the code we need to write to respond to the collision event!

Archery Game (Collision Demo)

To demonstrate sprite collision testing with our new code, I’ve put together a quick demo based on the overall theme of the book, shown in Figure 4.3. Let me show you how to create this project. We’ll reuse classes written previously to simplify the game and cut down on the amount of code that would otherwise be required. This new game is done entirely in graphics mode with real collision detection.

The Collision demo program demonstrates bounding rectangle collision testing.

Figure 4.3. The Collision demo program demonstrates bounding rectangle collision testing.

Sprite Class

Copy the Sprite.cs file from the Sprite demo project in the previous chapter over to the new one so we don’t have to re-list the source code over again in this chapter! No changes have been made to the Sprite class since the previous chapter.

Game Class

We don’t need to list the source code for Game.cs here again because it hasn’t changed since the previous chapter either—just copy the file from your last project into the new one for this chapter.

Form1 Class

Both the game loop and gameplay code are found in the Form source code file Form1.cs. When you create the new project, Form1 will be added automatically, so you can open the source code for it and enter this code. Add Game.cs and Sprite.cs to the project, grab the bitmap files, and watch it run. The collision-specific code is highlighted in bold.

using System;
using System.Drawing;
using System.Windows.Forms;
using RPG;

namespace Collision_Demo
{
    public partial class Form1 : Form
    {
        Game game;
        bool p_gameOver = false;
        int p_startTime = 0;
        int p_currentTime = 0;
        int frameCount = 0;
        int frameTimer = 0;
        float frameRate = 0;
        int score = 0;
        Sprite dragon;
        Sprite zombie;
        Sprite spider;
        Sprite skeleton;
        Bitmap grass;
        Sprite archer;
        Sprite arrow;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Main();
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            Game_KeyPressed(e.KeyCode);
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            Shutdown();
        }

        public bool Game_Init()
        {
            this.Text = "Archery Shooting Game";

            //load the grassy background
            grass = game.LoadBitmap("grass.bmp");
            //load the archer
            archer = new Sprite(ref game);
            archer.Image = game.LoadBitmap("archer_attack.png");
            archer.Size = new Size(96, 96);
            archer.Columns = 10;
            archer.TotalFrames = 80;
            archer.AnimationRate = 20;
            archer.Position = new PointF(360, 500);
            archer.AnimateDirection = Sprite.AnimateDir.NONE;

            //load the arrow
            arrow = new Sprite(ref game);
            arrow.Image = game.LoadBitmap("arrow.png");
            arrow.Size = new Size(32, 32);
            arrow.TotalFrames = 1;
            arrow.Velocity = new PointF(0, -12.0f);
            arrow.Alive = false;

            //load the zombie
            zombie = new Sprite(ref game);
            zombie.Image = game.LoadBitmap("zombie walk.png");
            zombie.Size = new Size(96, 96);
            zombie.Columns = 8;
            zombie.TotalFrames = 64;
            zombie.Position = new PointF(100, 10);
            zombie.Velocity = new PointF(-2.0f, 0);
            zombie.AnimationRate = 10;

            //load the spider
            spider = new Sprite(ref game);
            spider.Image = game.LoadBitmap("redspiderwalking.png");
            spider.Size = new Size(96, 96);
            spider.Columns = 8;
            spider.TotalFrames = 64;
            spider.Position = new PointF(500, 80);
            spider.Velocity = new PointF(3.0f, 0);
            spider.AnimationRate = 20;

            //load the dragon
            dragon = new Sprite(ref game);
            dragon.Image = game.LoadBitmap("dragonflying.png");
            dragon.Size = new Size(128, 128);
            dragon.Columns = 8;
            dragon.TotalFrames = 64;
            dragon.AnimationRate = 20;
            dragon.Position = new PointF(300, 130);
            dragon.Velocity = new PointF(-4.0f, 0);

            //load the skeleton
            skeleton = new Sprite(ref game);
            skeleton.Image = game.LoadBitmap("skeleton_walk.png");
            skeleton.Size = new Size(96, 96);
            skeleton.Columns = 9;
            skeleton.TotalFrames = 72;
            skeleton.Position = new PointF(400, 190);
            skeleton.Velocity = new PointF(5.0f, 0);
            skeleton.AnimationRate = 30;

            return true;
        }

        public void Game_Update(int time)
        {
            if (arrow.Alive)
            {
                //see if arrow hit spider
                if (arrow.IsColliding(ref spider))
                {
                    arrow.Alive = false;
                    score++;
                    spider.X = 800;
                }

                //see if arrow hit dragon
                if (arrow.IsColliding(ref dragon))
                {
                    arrow.Alive = false;
                    score++;
                    dragon.X = 800;
                }
                //see if arrow hit zombie
                if (arrow.IsColliding(ref zombie))
                {
                    arrow.Alive = false;
                    score++;
                    zombie.X = 800;
                }

                //see if arrow hit skeleton
                if (arrow.IsColliding(ref skeleton))
                {
                    arrow.Alive = false;
                    score++;
                    skeleton.X = 800;
                }
            }
        }

        public void Game_Draw()
        {
            int row = 0;

            //draw background
            game.DrawBitmap(ref grass, 0, 0, 800, 600);

            //draw the arrow
            if (arrow.Alive)
            {
                arrow.Y += arrow.Velocity.Y;
                if (arrow.Y < -32)
                    arrow.Alive = false;
                arrow.Draw();
            }

            //draw the archer
            archer.Animate(10, 19);
            if (archer.CurrentFrame == 19)
            {
                archer.AnimateDirection = Sprite.AnimateDir.NONE;
                archer.CurrentFrame = 10;
                arrow.Alive = true;
                arrow.Position = new PointF(
                    archer.X + 32, archer.Y);
            }
            archer.Draw();

            //draw the zombie
            zombie.X += zombie.Velocity.X;
            if (zombie.X < -96) zombie.X = 800;
            row = 6;
            zombie.Animate(row * 8 + 1, row * 8 + 7);
            zombie.Draw();

            //draw the spider
            spider.X += spider.Velocity.X;
            if (spider.X > 800) spider.X = -96;
            row = 2;
            spider.Animate(row * 8 + 1, row * 8 + 7);
            spider.Draw();

            //draw the skeleton
            skeleton.X += skeleton.Velocity.X;
            if (skeleton.X > 800) skeleton.X = -96;
            row = 2;
            skeleton.Animate(row * 9 + 1, row * 9 + 8);
            skeleton.Draw();

            //draw the dragon
            dragon.X += dragon.Velocity.X;
            if (dragon.X < -128) dragon.X = 800;
            row = 6;
            dragon.Animate(row * 8 + 1, row * 8 + 7);
            dragon.Draw();

            game.Print(0, 0, "SCORE " + score.ToString());
        }

        public void Game_End()
        {
            dragon.Image.Dispose();
            dragon = null;
            archer.Image.Dispose();
            archer = null;
            spider.Image.Dispose();
            spider = null;
            zombie.Image.Dispose();
            zombie = null;
            grass = null;
        }

        public void Game_KeyPressed(System.Windows.Forms.Keys key)
        {
            switch (key)
            {
                case Keys.Escape: Shutdown(); break;
                case Keys.Space:
                    if (!arrow.Alive)
                    {
                        archer.AnimateDirection = Sprite.AnimateDir.FORWARD;
                        archer.CurrentFrame = 10;
                    }
                    break;
                case Keys.Right: break;
                case Keys.Down:   break;
                case Keys.Left:   break;
            }
        }

        public void Shutdown()
        {
            p_gameOver = true;
        }

        /*
         * real time game loop
         */
        public void Main()
        {
            Form form = (Form)this;
            game = new Game(ref form, 800, 600);
            Game_Init();
            while (!p_gameOver)
            {
                p_currentTime = Environment.TickCount;
                Game_Update(p_currentTime - p_startTime);
                if (p_currentTime > p_startTime + 16)
                {
                    p_startTime = p_currentTime;
                    Game_Draw();
                    Application.DoEvents();
                    game.Update();
                }
                frameCount += 1;
                if (p_currentTime > frameTimer + 1000)
                {
                    frameTimer = p_currentTime;
                    frameRate = frameCount;
                    frameCount = 0;
                }
            }
            //free memory and shut down
            Game_End();
            Application.Exit();
        }
    }
}

Level Up!

That’s about all there is to sprite collision detection at this point. You learned about the basic collision between two sprites—or more accurately, between two rectangles—using the Rectangle.IntersectsWith() method, encapsulated in the Sprite class within the method called IsColliding(), which simplifies the collision code that you would otherwise have to write yourself. We will be using another form of collision detection later on when we are working with the dungeon levels, made up of a tile map, in which certain areas in the world will be impassible based on the tile values.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.172.38