Page 1 of 1

XNA 3.0 Game Tutorial - Part 1 Making an Asteroids Clone

#1 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 945
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 20 April 2009 - 01:06 PM

Getting Started with XNA 3.0 - Asteroids Clone Part 1
Attached File  Sprites.zip (6.01K)
Number of downloads: 1637
In this tutorial series I will walk you through the process of creating an Asteroids clone. It will take quite a few tutorials. I'm assuming for these tutorials that you have a good grip on C# and a basic understanding of how the XNA Framework works. I'm not going to be explaining things like what a method is or what a variable is. I will be using Visual C# 2008 Express Edition. I don't have an XBOX controller so I won't be able to add t

So, let's get started. Create a new Windows Game project, I called mine AsteroidsClone. If you copy and paste the code for these tutorials you will have to make sure you are in the right namespace. To this project you will want to add a new class and call it Sprite. This is the code for the Sprite class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace AsteroidsClone
{
    class Sprite
    {
        Texture2D texture;

        Vector2 position;
        Vector2 center;
        Vector2 velocity;

        float rotation;
        float scale;

        bool alive;

        int index;

        public Sprite(Texture2D texture)
        {
            this.texture = texture;

            position = Vector2.Zero;
            center = new Vector2(texture.Width / 2, texture.Height / 2);
            velocity = Vector2.Zero;

            Rotation = 0.0f;
            Scale = 1.0f;

            alive = false;

            index = 0;
        }

        public Texture2D Texture
        {
            get { return texture; }
        }

        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }

        public Vector2 Center
        {
            get { return center; }
        }

        public Vector2 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }

        public float Rotation
        {
            get { return rotation; }
            set 
            { 
                rotation = value;
                if (rotation < -MathHelper.TwoPi)
                    rotation = MathHelper.TwoPi;
                if (rotation > MathHelper.TwoPi)
                    rotation = -MathHelper.TwoPi;
            }
        }

        public float Scale
        {
            get { return scale; }
            set { scale = value; }
        }

        public bool Alive
        {
            get { return alive; }
        }

        public int Index
        {
            get { return index; }
            set { index = value; }
        }

        public void Create()
        {
            alive = true;
        }

        public void Kill()
        {
            alive = false;
        }
    }
}



You will notice that all of the variables in the class are private and I've written properties to access them. I did this because eventually you may want to add validation so that improper values can't be set in the variables. You will also notice that you had to add two using statements to the class. One for the XNA Framework and one for the Graphics.

The constructor for the class takes one parameter, the texture of the sprite. It sets most of the other fields to zero, except for center which is set to the center of the texture and how the sprite should be scaled, which is set to 1.0f.

Most of the properties are get and set. I made the Center, Texture and Alive properties as get only as the center and texture of the sprite shouldn't change. I could have made Alive get and set but I decided to write two methods Create and Kill. One other thing I did is with the Rotation property. I made it so that the degree of the rotation, in radians, is between Pi * 2 and -Pi * 2. This isn't really a necessary step but since the trigonometric functions used are cyclical there is no need to exceed these values.

Before you can continue you will have to add some assets to the project. I will be attaching the sprites that I will be using for this project to this tutorial. Just remember, I will never claim to be a graphics artisit. :P Unzip the images to a folder. If you look at the images your will see that there is a lot of magenta in them. I did this because by default magenta is transparent in XNA. You may wonder why I made two ships and nine asteriods. I thought it would be easier to have a ship and a thrusting ship and three different asteroids and three differenct sizes. You could easily make four or five asteroids and scale them when they blow up, like I did with the UFO that will be introduced later.

Once you have the sprites, create a new folder in the Content folder and call it Sprites. Then right click the Sprites folder and select Existing Item.... Find where you unzipped the sprites and select them all.

In this step I will help you write the code to draw the ship, rotate it and fire bullets. I will cover moving the ship in another tutorial.

This is the code for the the Game1.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace AsteroidsClone
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Sprite ship;
        int ScreenWidth, ScreenHeight;

        KeyboardState oldState;

        Sprite bullet;
        List<Sprite> bullets = new List<Sprite>();

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferHeight = 500;
            graphics.PreferredBackBufferWidth = 500;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            base.Initialize();

            SetupGame();
        }


        private void SetupGame()
        {
            ScreenHeight = graphics.GraphicsDevice.Viewport.Height;
            ScreenWidth = graphics.GraphicsDevice.Viewport.Width;
            SetupShip();
        }

        private void SetupShip()
        {
            ship.Position = new Vector2(ScreenWidth / 2, ScreenHeight / 2);
            ship.Create();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            ship = new Sprite(Content.Load<Texture2D>("Sprites/ship"));
            bullet = new Sprite(Content.Load<Texture2D>("Sprites/bullet"));

        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            KeyboardState newState = Keyboard.GetState();

            if (newState.IsKeyDown(Keys.Escape))
                this.Exit();

            if (newState.IsKeyDown(Keys.Left))
            {
                ship.Rotation -= 0.05f;
            }

            if (newState.IsKeyDown(Keys.Right))
            {
                ship.Rotation += 0.05f;
            }

            if (newState.IsKeyUp(Keys.Space) && oldState.IsKeyDown(Keys.Space))
            {
                FireBullet();
            }

            oldState = newState;

            UpdateBullets();
            base.Update(gameTime);
        }

        private void UpdateBullets()
        {
            foreach (Sprite b in bullets)
            {
                b.Position += b.Velocity;
                if (b.Position.X < 0)
                    b.Kill();
                else if (b.Position.Y < 0)
                    b.Kill();
                else if (b.Position.X > ScreenWidth)
                    b.Kill();
                else if (b.Position.Y > ScreenHeight)
                    b.Kill();
            }

            for (int i = 0; i < bullets.Count; i++)
            {
                if (!bullets[i].Alive)
                {
                    bullets.RemoveAt(i);
                    i--;
                }
            }
        }

        private void FireBullet()
        {
            Sprite newBullet = new Sprite(bullet.Texture);

            newBullet.Velocity = new Vector2((float)Math.Cos(ship.Rotation - MathHelper.PiOver2), 
                                             (float)Math.Sin(ship.Rotation - MathHelper.PiOver2)) * 4.0f
                                              + ship.Velocity;
            
            newBullet.Position = ship.Position + newBullet.Velocity * 1.75f;
            newBullet.Create();

            bullets.Add(newBullet);            
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

            spriteBatch.Draw(ship.Texture,
                    ship.Position,
                    null,
                    Color.White,
                    ship.Rotation,
                    ship.Center,
                    ship.Scale,
                    SpriteEffects.None,
                    1.0f);

            foreach (Sprite b in bullets)
            {
                if (b.Alive)
                {
                    spriteBatch.Draw(b.Texture,
                        b.Position,
                        null,
                        Color.White,
                        b.Rotation,
                        b.Center,
                        b.Scale,
                        SpriteEffects.None,
                        1.0f);
                }
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}



Now, I will explain the code. First I created six variables. A variable for the ship, variables to hold the ScreenWidth and ScreenHeight, a variable to hold the old state of the keyboard, a variable to hold the bullet information and a list of bullets. I made a variable to hold the keyboard state because when the player presses and releases the space bar I want a bullet to be shot, I don't want an autofire game.

In the constructor of Game1 I set the height and width of the window. I thought 500 x 500 was a good size. The field should be square, but it doesn't have to be. You can change these to whatever you like.

In the Initialize method I made a call to a method called SetupGame that will be written next. The SetupGame method sets the ScreenWidth and ScreenHeight variables and calls the method SetupShip to set the ship in the start position and make the ship to be alive.

The next method is the LoadContent method. This is where you load the content for your project. Right now all you need for now are the ship and the bullet.

Now is when things start to get interesting. There are two methods in XNA, the Update and the Draw method. The Update method is where you perform the logic of the game. The Draw method is where you render your scene.

I will tackle the Update method first. XNA creates some code for you in the Update method to give a way for the game to end using a XBOX controller. After that is where I added the logic to rotate the ship and fire off a bullet. I also added a feature that will end the game if the player presses Escape. The code for rotating the ship is simple. If you are holding down the Left key the rotation of the ship is decreased and the other way around for the Right key.

To tell if the Spacebar has been pressed I did things a little different than the way it is usually done. Usually you check to see if the current state of the key/controller button is down and the last state of key/controller button is up. I like to do it the other way around. You don't have to for a simple game like this but when you get to doing more advanced things it does help. If the Spacebar has been pressed and released I call a method to fire a bullet called FireBullet. Then I store the current state of the keyboard and call a method to update the bullets, UpdateBullets.

The FireBullet method does something interesting. First, a creates a new bullet that will be added to the list of bullets. Finding the position and velocity if the bullet takes a little bit of calculations. Velocity is a little harder than position. To get the direction of the bullet is travelling is a little complicated. You need to find the cosine of the ship's rotation and the sine of the ships rotation. The way I set up the game, you have to subtract Pi/2 from the rotation angle. If you don't the bullet will shoot 90 degrees to the right.

The position of the bullet took a little figuring out. If you just use the position of the ship, it will start in the middle of the ship. I tried just adding the velocity of the bullet to the ship's position. It still started inside the ship. So I messed around with different multipliers and found one that I liked,
1.75f. After finding the position of the bullet I create the bullet and add it to the list of bullets.

The code in the UpdateBullets method is a little interesting. I used a foreach and a for loop in this method. The foreach loop goes through all of the bullets in the list of bullets. It adds the velocity of the bullet to the bullet's position. If the bullet has travelled outside of the boundries of the screen it kills the bullet.

The for loop goes through the list. If it finds a dead bullet it removes it from the list of bullets. When going through a list and removing items from the list inside a for loop if you remove an item from the list, you need to decrease the counter by 1, otherwise you will get an exception.

Now that the ship and bullets are created and updating it is time to draw the ship and the bullets. To draw a 2D object you use a SpriteBatch object. When you are drawing sprites, you do it inside a Begin and End method call of the SpriteBatch object. In the Begin method call I used SpriteBlendMode.AlphaBlend because there are transparent parts of the images. The overload of the Draw method I used has nine parameters. I will explain them. The first one is the texture that is being drawn. Next there is the position you want to draw the texture. The third is the source rectangle you want to use. If you have all of your images in one bitmap you would use the source rectangle to find where the texture is in the image. I used seperate images so I just set it to null to say I don't want to use one. The next is the tint color that you want to use. If you don't want to tint the texture just set it to Color.White. Next is the angle of rotation you want to use, in radians. After rotation comes origin. You can choose different points to rotate your texture around with different effects. I chose the center of the texture. Next is the scale you want to use to draw the texture. Then is the SpriteEffects that you want to use, in this case SpriteEffects.None. Finally there is the layer depth you want to draw at. In this game it is not important so just set it to 1.0f all the time.

I think that is more than enough for one tutorial. I will try and have the next part up soon which will be adding asteroids to the game.

Is This A Good Question/Topic? 4
  • +

Replies To: XNA 3.0 Game Tutorial - Part 1

#2 jglamere  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 20-June 09

Posted 20 June 2009 - 01:17 AM

I can't get the ship to load in the center of the game window. It loads in the upper left corner. Any ideas what I could be doing wrong?
Was This Post Helpful? 0
  • +
  • -

#3 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 945
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 20 June 2009 - 06:14 AM

View Postjglamere, on 20 Jun, 2009 - 02:17 AM, said:

I can't get the ship to load in the center of the game window. It loads in the upper left corner. Any ideas what I could be doing wrong?


I just found an oops in the Sprite class. Change the Position property to this:

		public Vector2 Position
		{
			get { return position; }
			set { position = value; }
		}



Thanks for finding the mistake and pointing it out. :)
Was This Post Helpful? 0
  • +
  • -

#4 jglamere  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 20-June 09

Posted 20 June 2009 - 07:53 PM

That fixed it for sure. Thank you for taking the time to provide the tutorial.

This post has been edited by jglamere: 20 June 2009 - 08:04 PM

Was This Post Helpful? 0
  • +
  • -

#5 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 945
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 20 June 2009 - 09:09 PM

Hope they helped. :)
Was This Post Helpful? 0
  • +
  • -

#6 Guest_Darren*


Reputation:

Posted 07 July 2010 - 12:08 AM

Bullets come out of the side with these calculations
Was This Post Helpful? 0

#7 naveenkushwaha  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 22-September 10

Posted 22 September 2010 - 03:33 AM

Replace

newBullet.Velocity = new Vector2((float)Math.Cos(ship.Rotation - MathHelper.PiOver2),
(float)Math.Sin(ship.Rotation - MathHelper.PiOver2)) * 4.0f
+ ship.Velocity;

By

newBullet.Velocity = new Vector2((float)Math.Cos(ship.Rotation),
(float)Math.Sin(ship.Rotation)) * 4.0f
+ ship.Velocity;

bullets start coming from front...



View PostDarren, on 06 July 2010 - 11:08 PM, said:

Bullets come out of the side with these calculations

Was This Post Helpful? 0
  • +
  • -

#8 mt1  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 32
  • Joined: 27-October 09

Posted 29 October 2010 - 02:35 PM

How do you calculate where the bullet shoots from on the ship?
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1