Dwarfsoft [GPA]

XNA in a Day… Almost

by on Mar.03, 2009, under Game Development, XNA


Print This Post Print This Post

A screenshot of Breakout during development

A screenshot of Breakout during development

So, after starting my look into XNA under the guise of “Forays in XNA”, I was able to determine that the core code for developing a Breakout clone was achievable within a day. Considering that I work full time, and currently my wife is heavily pregnant, the fact that I can achieve this within a day should motivate the rest of you to do the same, or at least work harder ;).

My fantastically crafted White Block

My fantastically crafted White Block

So, the first thing I did was I went out and created my images. I used the template from the previous image, which was created by creating some shapes in MS Paint, and then making the background transparent and saving as a PNG using The GIMP.

My fantastically crafted White Ball image

My fantastically crafted White Ball image

My fantastically crafted White Paddle image

My fantastically crafted White Paddle image

I have also created a blank project as before and from the main project Window in C# I create a couple of classes. The first class is called Sprite.cs, the second is Wall.cs, and the third is Collision.cs. The biggest issue I had out of the creation of my Breakout game was the Collision detection. I used some primitive collision detection algorithms that just failed to rebound the ball correctly when it hit the side of an object, and as I am also using blocks with transparencies (and the ball has transparencies as well) the collision detection needs to account for that also.

So firstly, the smallest class is the Collision detection class. I copied most of it from ZiggyWare. It did require a slight bit of modification to work for me in XNA Game Studio 3.0, and I have modified my own Sprite Class to at least cater for what the Collision Detection class is looking for.

using System;
using Microsoft.Xna.Framework;
using Forays_in_XNA;
using System.Collections;
 
namespace Forays_in_XNA
{
    public class Collision
    {
        public static bool Intersects(Rectangle a, Rectangle b)
        {
            // check if two Rectangles intersect
            return (a.Right > b.Left && a.Left < b.Right &&
                    a.Bottom > b.Top && a.Top < b.Bottom);
        }
 
        public static bool Intersects(Sprite a, Sprite b)
        {
            if (Collision.Intersects(a.bounds(), b.bounds()))
            {
                uint[] bitsA = new uint[a.Width() * a.Height()];
                a.texture().GetData(bitsA);
 
                uint[] bitsB = new uint[b.Width() * b.Height()];
                b.texture().GetData(bitsB);
 
                int x1 = Math.Max(a.bounds().X, b.bounds().X);
                int x2 = Math.Min(a.bounds().X + a.bounds().Width, b.bounds().X + b.bounds().Width);
 
                int y1 = Math.Max(a.bounds().Y, b.bounds().Y);
                int y2 = Math.Min(a.bounds().Y + a.bounds().Height, b.bounds().Y + b.bounds().Height);
 
                for (int y = y1; y < y2; ++y)
                {
                    for (int x = x1; x < x2; ++x)
                    {
                        if (((bitsA[(x - a.bounds().X) +
                                    (y - a.bounds().Y) * a.texture().Width] &
                                    0xFF000000) >> 24) > 20 &&
                            ((bitsB[(x - b.bounds().X) + b.texture().Width] &
                                    0xFF000000) >> 24) > 20)
                        {
                            return true;
                        }
                    }
                }
 
            }
            return false;
        }
 
        private ArrayList SpriteArray = new ArrayList();
 
        public void Register(Sprite sprite)
        {
            SpriteArray.Add(sprite);
            sprite.collision = this;
        }
 
        public Sprite IsCollision(Sprite sprite)
        {
            foreach (Sprite block in SpriteArray)
            {
                // don't check for collision between the same object
                if (!block.Equals(sprite) && block.mVisible)
                {
                    if (Collision.Intersects(sprite, block))
                    {
                        //mCollisionWith = block;
                        return block;
                    }
                }
            }
            return null;
        }
 
        public void Move()
        {
            foreach (Sprite sprite in SpriteArray)
            {
                sprite.Move();
            }
        }
    }
}

As you can see, I have extended the standard Collision Class to also act as a Sprite Handler. This was purely done on a laziness front, and I can push that out into a class on its own if I had a need to. The Collision class would otherwise be a class of static functions which would never need to be instantiated.

Considering that the Collision Class requires the use of the Sprite Class, the next thing we will look at is the Sprite Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
 
namespace Forays_in_XNA
{
    public class Sprite
    {
        //The current position of the Sprite
        public Vector2 Position = new Vector2(0, 0);
        public Vector2 Vector = new Vector2(0, 0);
        public Rectangle Bounds = new Rectangle(-1,-1,-1,-1);
 
        //The texture object used when drawing the sprite
        private Texture2D mSpriteTexture;
 
        //What this object collided into in the Move() function
        private Sprite mCollidedWith = null;
 
        //Whether this sprite is visible or not (at least, to the collision detecion system)
        public bool mVisible = true;
 
        //The texture colour
        private Color mColor;
 
        //The Collision Detection System that this Sprite is Registered With
        public Collision collision;
 
        //Load the texture for the sprite using the Content Pipeline
        public void LoadContent(ContentManager theContentManager, string theAssetName)
        {
            mSpriteTexture = theContentManager.Load(theAssetName);
            mColor = Color.White;
        }
 
        //Load the texture for the sprite using the Content Pipeline
        public void LoadContent(ContentManager theContentManager, string theAssetName, Color colour)
        {
            mSpriteTexture = theContentManager.Load(theAssetName);
            mColor = colour;
        }
 
        //Draw the sprite to the screen
        public void Draw(SpriteBatch theSpriteBatch)
        {
            theSpriteBatch.Draw(mSpriteTexture, Position, mColor);
        }
 
        // Cater for returning the texture directly from the Sprite Class
        public Texture2D texture()
        {
            return mSpriteTexture;
        }
 
        // Cater for returning the texture Width directly from the Sprite Class
        public int Width()
        {
            return mSpriteTexture.Width;
        }
 
        // Cater for returning the texture Height directly from the Sprite Class
        public int Height()
        {
            return mSpriteTexture.Height;
        }
 
        //Return the Bounding Box of the sprite.
        public Rectangle bounds()
        {
            return new Rectangle((int)Position.X, (int)Position.Y, mSpriteTexture.Width, mSpriteTexture.Height);
        }
 
        //Returns the color that has been masked onto this sprite
        public Color Colour()
        {
            return mColor;
        }
 
        public Sprite IsCollision()
        {
            if (collision != null)
            {
                mCollidedWith = collision.IsCollision(this);
                if (mCollidedWith != null)
                    mCollidedWith.Vector.Y = 2;
                return mCollidedWith;
            }
            return null;
        }
 
        //Initiate a controlled move with Collision detection, so that we can rebound off the sides
        //and tops of blocks.
        public bool Move()
        {
            Sprite collided = null;
            float x = Vector.X;
            float y = Vector.Y;
            float ax = Math.Abs(x);
            float ay = Math.Abs(y);
            float y1, x1;
 
            if (Math.Abs(x) >= Math.Abs(y))
            {
                x1 = x / ax;
                y1 = y / ax;
 
                float ax1 = Math.Abs(x1);
 
                for (float i = 0,j = 0; (i < ax) ; i += ax1,j += y1)
                {
                    Position.X += x1;
                    Position.Y += y1;
 
                    if ((this.Position.X + this.Width()) > Bounds.Width)
                    {
                        this.Vector.X = -this.Vector.X;
                        x1 *= -1;
                    }
                    if ((this.Position.X) < Bounds.X)
                    {
                        this.Vector.X = -this.Vector.X;
                        x1 *= -1;
                    }
 
                    if ((this.Position.Y + this.Height()) > Bounds.Height)
                    {
                        this.Vector.Y = -this.Vector.Y;
                        y1 *= -1;
                    }
                    if ((this.Position.Y) < Bounds.Y)
                    {
                        this.Vector.Y = -this.Vector.Y;
                        y1 *= -1;
                    } 
 
                    if (IsCollision() != null)
                    {
                        bool ycoll = false;
                        bool xcoll = false;
                        collided = mCollidedWith;
                        this.Position.X -= x1;
                        if (IsCollision() != null)
                        {
                            ycoll = true;
                        }
                        this.Position.X += x1;
                        this.Position.Y -= y1;
                        if (IsCollision() != null)
                        {
                            xcoll = true;
                        }
                        this.Position.Y += y1;
                        if (xcoll)
                        {
                            this.Vector.X *= -1;
                            x1 *= -1;
                        }
                        if (ycoll)
                        {
                            this.Vector.Y *= -1;
                            y1 *= -1;
                        }
                        if (!(ycoll || xcoll))
                        {
                            this.Vector.X *= -1;
                            this.Vector.Y *= -1;
                        }
 
                    }
                }
            }
            else
            {
                x1 = x / ay;
                y1 = y / ay;
 
                float ay1 = Math.Abs(y1);
 
                for (float i = 0,j = 0; (j < ay) ; i += x1,j += ay1)
                {
                    Position.X += x1;
                    Position.Y += y1;
 
                    if ((this.Position.X + this.Width()) > Bounds.Width)
                    {
                        this.Vector.X = -this.Vector.X;
                        x1 *= -1;
                    }
                    if ((this.Position.X) < Bounds.X)
                    {
                        this.Vector.X = -this.Vector.X;
                        x1 *= -1;
                    }
 
                    if ((this.Position.Y + this.Height()) > Bounds.Height)
                    {
                        this.Vector.Y = -this.Vector.Y;
                        y1 *= -1;
                    }
                    if ((this.Position.Y) < Bounds.Y)
                    {
                        this.Vector.Y = -this.Vector.Y;
                        y1 *= -1;
                    } 
 
                    if (IsCollision() != null)
                    {
                        bool ycoll = false;
                        bool xcoll = false;
                        collided = mCollidedWith;
                        this.Position.X -= x1;
                        if (IsCollision() != null)
                        {
                            ycoll = true;
                        }
                        this.Position.X += x1;
                        this.Position.Y -= y1;
                        if (IsCollision() != null)
                        {
                            xcoll = true;
                        }
                        this.Position.Y += y1;
                        if (xcoll)
                        {
                            this.Vector.X *= -1;
                            x1 *= -1;
                        }
                        if (ycoll)
                        {
                            this.Vector.Y *= -1;
                            y1 *= -1;
                        }
                        if (!(ycoll || xcoll))
                        {
                            this.Vector.X *= -1;
                            this.Vector.Y *= -1;
                        }
                    }
                }
            }
 
            if (collided != null)
            {
                if (collided.mVisible)
                {
                    collided.Position.X = -50;
                    collided.Position.Y = -50;
                    collided.mVisible = false;
                }
            }
            return false;
        }
    }
}

As is evident, the largest function in the class is the Move() function. Each Sprite is given the opportunity to move based on its Vector. Because I wanted to be able to increase the speed (and therefore increase the Vector) I needed to do a few things in this function to make it rebound correctly off different objects. First, I needed to decide whether x or y was the larger Vector part, and then step by a delta of 1 on the largest of these. For example, if the Vector is (5,2) then the Delta of x is x1 = 1. This leaves the Delta of y as y1 = 2/5. By stepping with a delta of 1 as the largest value, we can see what edge collides first with any surrounding boxes. This allows the rebound to either bounce up, down, left or right, or diagonally in the event that it collides with a corner.

Most of the the code is trying to cope with continuing the delta increments after a rebound has taken place. This increases the number of calls to IsCollision, but ensures the accuracy of the Collision Detection. As we are only doing a Breakout game, it shouldn’t affect the Gameplay at all. One thing to note is that I still have the rebound working for the Bottom of the screen, rather than a game-over/life-lost event. This will be modified soon to compensate. Once a block is collided with it is moved off the screen, and it is set to non visible. The mVisible flag was added to stop the collision detection from going crazy as more blocks are piled in the same offscreen dumping ground.

The Wall class is used to generate the Random Coloured Wall, and to register each sprite with the Collision detection Class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
 
namespace Forays_in_XNA
{
    class Wall
    {
        private Sprite[][] mWall;
        private const int startx = 3;
        //private const int height = 20;
        private const int xcount = 16;
        private const int ycount = 5;
        private const int xpad = 3;
        private const int ypad = 3;
 
        public void LoadContent(ContentManager theContentManager)
        {
            mWall = new Sprite[ycount][];
            for (int i = 0; i < ycount; ++i)
            {
                mWall[i] = new Sprite[xcount];
                for (int j = 0; j < xcount; ++j)
                {
                    mWall[i][j] = new Sprite();
                }
            }
            int x = startx;
            int y = 0;
            int height = 0;
 
            foreach (Sprite[] Line in mWall)
            {
                foreach (Sprite sprite in Line)
                {
                    sprite.LoadContent(theContentManager, "whiteblock", randomColour());
                    sprite.Position.X = x;
                    sprite.Position.Y = y;
 
                    x = x + sprite.Width() + xpad;
                    height = sprite.Height()+ypad;
                }
                x = startx;
                y = y + height;
            }
        }
 
        //Draw the sprite to the screen
        public void Draw(SpriteBatch theSpriteBatch)
        {
           foreach (Sprite[] Line in mWall)
           {
                foreach (Sprite sprite in Line)
                {
                    theSpriteBatch.Draw(sprite.texture(), sprite.Position, sprite.Colour());
                }
            }
        }
 
        //Pick a random colour for this block
        public Color randomColour()
        {
            Random rnd1 = new Random();
            int rnd = (rnd1.Next(110)+1)/10;
            Color color = new Color();
            color = Color.White;
            switch (rnd)
            {
                case 1:
                    color = Color.YellowGreen;
                    break;
                case 2:
                    color = Color.AliceBlue;
                    break;
                case 3:
                    color = Color.Bisque;
                    break;
                case 4:
                    color = Color.BlueViolet;
                    break;
                case 5:
                    color = Color.Chocolate;
                    break;
                case 6:
                    color = Color.DarkGoldenrod;
                    break;
                case 7:
                    color = Color.Crimson;
                    break;
                case 8:
                    color = Color.DarkGreen;
                    break;
                case 9:
                    color = Color.DarkMagenta;
                    break;
                case 10:
                    color = Color.DarkOliveGreen;
                    break;
                case 11:
                    color = Color.White;
                    break;
            }
            return color;
        }
 
        //Register all the wall sprites with the Collision detection/sprite manager
        public void RegisterCollision(Collision collision)
        {
            for (int i = 0; i < ycount; ++i)
            {
                for (int j = 0; j < xcount; ++j)
                {
                    collision.Register(mWall[i][j]);
                }
            }
        }
    }
}

As we see the Wall class just creates the wall sprites, and passes each off to the Collision class which is being used as the Sprite Manager.

In the Game1.cs class we have a number of member variables declared

        SpriteBatch spriteBatch;
        Wall mWall;
        Sprite mBall;
        Collision mCollision;

These are each initialized in the Initialize function

            mWall = new Wall();
            mBall = new Sprite();
            mCollision = new Collision();

The loading of the Content is done in order to get the Sprites available and ready for drawing.

            mWall.LoadContent(this.Content);
            mBall.LoadContent(this.Content, "whiteball",Color.White);
            mBall.Position.Y = Window.ClientBounds.Height - mBall.Height();
            mBall.Position.X = Window.ClientBounds.Width/2;
 
            mBall.Vector.X = 2;
            mBall.Vector.Y = -4;
 
            mBall.Bounds = new Rectangle(0, 0, Window.ClientBounds.Width, Window.ClientBounds.Height);
 
            mCollision.Register(mBall);
            mWall.RegisterCollision(mCollision);

The Ball has its bounds set and a Vector given. I have then modified the Update Section to do the following

            UpdateInput();
            mCollision.Move();

The UpdateInput function has been created to handle the Keypresses within the game. At the moment it only increases or decreases the Vector that the ball travels at, but as soon as the paddle is implemented the controls will be used to move left and right. I intend on also normalising the surfaces so that the paddle can be rotated to change its reflection angle. I could probably get away with this by just modifying based on the angle of the paddle against where the paddle is struck, though the code to counter for the corners will be quite difficult to master.

One Last function that is used in the Game1.cs Class is the Draw Function

            spriteBatch.Begin();
            mWall.Draw(spriteBatch);
            mBall.Draw(spriteBatch);
            spriteBatch.End();

More to come soon… such as scoring and the paddle controller. I had hoped to complete this within the day, but unfortunately I am also fasting so my concentration levels have been extremely low. As I complete this article for posting I already have the Paddle working, as well as Scoring and the like. Tune in to see how it all pans out.

Cheers, Chris.

:, , , , ,

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!