Part Eight - Explosions



Now that we have enemies flying around on the screen and the ability to shoot them down, it's time to blow some stuff up!

We will be handling explosions in much the same way we handle bullets, in that they will be completely self contained. Once triggered, they will play their animation until it finishes and then shut themselves off.

One thing we will be doing differently from our bullets, however, is that we will not search for "Free" explosions when we need to generate one. Since it is theoretically possible to have an explosion playing for every active enemy on the screen (ie, the player hits a super bomb when all of the enemies on the map are on-screen) we will construct our array so we have as many explosion objects as we will have enemies, plus an additional explosion object for the player's ship.

Go ahead and add a class called "Explosion.cs" to your project. As always, we will add the two Using statements we need to the top of the class:

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;


Next, we will add our declarations. We are departing a bit further from the bullet class here in that we are going to be tracking "world position" for our explosions:

        static int iMapWidth = 1920;
 
        AnimatedSprite asSprite;
        int iX = 0;
        int iY = -100;
        bool bActive = true;
        int iBackgroundOffset = 0;
        Vector2 v2motion = new Vector2(0f, 0f);
        float fSpeed = 1f;


We need to know how big the world map is, because our explosions will take on some of the momentium of the ship that is exploding (for enemy explosions) so they continue to drift in the direction that the enemy ship was moving when it exploded. This means that it is possible for the explosion to cross the "zero line", or the point where our map wraps around from the 1920th pixel to the 0th pixel.

Additionally, if we didn't track the explosions world position explosions would essentially stop on screen and move with the player's ship instead of zooming past as we fly through them.

So we have iMapWidth so our explosion class knows how big the map is. Next is our standard set of location variables and a boolean to determine if the explosion is active. We also will be tracking the game board's background offset, which will be suppled to our explosions by the Game1 class.

The motion vector and fSpeed variables will receive information when the explosion is created that allow it to float after it comes into existance. This will make more sense when we implement the explosions associated with enemies, as we will copy the direction of the enemy's current motion to the motion vector and half of the enemies speed to the fSpeed variable.

As normal, we need some public properties to access our variables:

        public int X
        {
            get { return iX; }
            set { iX = value; }
        }
 
        public int Y
        {
            get { return iY; }
            set { iY = value; }
        }
 
        public bool IsActive
        {
            get { return bActive; }
        }
 
        public int Offset
        {
            get { return iBackgroundOffset; }
            set { iBackgroundOffset = value; }
        }
 
        public float Speed
        {
            get { return fSpeed; }
            set { fSpeed = value; }
        }
 
        public Vector2 Motion
        {
            get { return v2motion; }
            set { v2motion = value; }
        }


The only really different thing to note here is that the IsActive property is read-only. Once an explosion has been started (which will be done via a method call) it will handle itself, so our external code can check to see if the explosion is still going on, but it can't alter the process.

Our constructor this time around will be fairly simple:

        public Explosion(Texture2D texture, 
                         int X, int Y, int W, int H, int Frames)
        {
            asSprite = new AnimatedSprite(texture, X, Y, W, H, Frames);
            asSprite.FrameLength = 0.05f;
        }


Here, we pass along the creation parameters to our AnimatedSprite, and we also modify the default FrameLength property of the sprite. This will slow down the explosion a bit so it doesn't fly by quite so fast. Which brings us to our Activate method:

        public void Activate(int x, int y, Vector2 motion, 
                             float speed, int offset)
        {
            iX = x;
            iY = y;
            v2motion = motion;
            fSpeed = speed;
            iBackgroundOffset = offset;
            asSprite.Frame = 0;
            bActive = true;
        }


Our Activate() method passes along the parameters sent into it to the local variables, and then sets the AnimatedSprite's frame to 0 and bActive to true. This positions the explosion and then starts it animating. We will check the bActive value during the class' Update method later.

Next we need a helper function for our Draw code:

        private int GetDrawX()
        {
            int X = iX - iBackgroundOffset;
            if (X > iMapWidth)
                X -= iMapWidth;
            if (X < 0)
                X += iMapWidth;
 
            return X;
        }


The GetDrawX() method is identical to the same method in the Enemy class and serves the same purpose. When the explosion is drawn, this method is used to translate the World Position into a Screen Position.

Lets move on to our Update() method:

        public void Update(GameTime gametime, int iOffset)
        {
            if (bActive)
            {
                iBackgroundOffset = iOffset;
 
                iX += (int)((float)v2motion.X * fSpeed);
                iY += (int)((float)v2motion.Y * fSpeed);
                asSprite.Update(gametime);
                if (asSprite.Frame >= 15)
                {
                    bActive = false;
                }
            }
        }


As normal, we pass in a GameTime so we can maintain a consistant framerate. If the explosion is active, we will set the current background offset (also passed as a parameter to Update) and then modify the postion of the explosion using the v2motion vector and fSpeed float.

Next we call asSprite.update(gametime) to potentially update the animation frame if enough game time has passed.

Finally, we check to see if the animation is over (we have reached the 16th frame (frame # 15)). If we have, we deactivate the explosion. All that is left now is for us to draw the explosion to the screen:

        public void Draw(SpriteBatch sb, bool bAbsolute)
        {
            if (bActive)
            {
                if (!bAbsolute)
                    asSprite.Draw(sb, GetDrawX(), iY, false);
                else
                    asSprite.Draw(sb, iX, iY, false);
            }
        }


You will notice we have two potential ways to call the Draw method. If we pass a "true" as bAbsolute we will draw the explosion without taking iBackgroundOffset into account (via the GetDrawX() method). If we pass false we will use GetDrawX() to convert the world coordinates into screen coordinates.

The reason behind this is actually for the explosion of the player's ship. Remember that we don't track the player's ship is world coordinates, as it is always in the center of the screen (you can think of it this way : what we are really tracking is how the world moves around a static point where the player resides.)

So now lets add our explosions into our Game code. Open up Game1.cs and add the following declarations (place them after the Enemy array declaration):

        Random rndGen = new Random();
        Texture2D t2dExplosionSheet;
        Explosion[] Explosions = new Explosion[iTotalMaxEnemies + 1];


The t2dExplosionSheet texture will hold our explosion image, while the array of Explosion objects will house all of the explosions we will be using in the game. We have one more explosion than we have enemies to allow for an explosion for the player's ship.

In LoadContent, lets add the now familiar code to initialize our Explosions:

            t2dExplosionSheet = Content.Load<Texture2D>(@"Textures\Explosions");
            for (int i = 0; i < iTotalMaxEnemies + 1; i++)
            {
                Explosions[i] = new Explosion(t2dExplosionSheet,
                                0, rndGen.Next(8) * 64, 64, 64, 16);
            }


(Note, while you are in the LoadContent routine, remove the code near the top that starts with "Explosion = new AnimatedSprite(..." which loads up the little explosion that has been playing in the corner of our screen forever. Additionally, take "Explosion.Update(gameTime);" out of the Update() method and "Explosion.Draw(spriteBatch, 0, 0, false);" out of the Draw method to eliminate it completely.

We use our random number generator to pick out one of the explosion sprite strips to use for each of the Explosion objects (by picking a random number from 0 to 7 and multiplying that by the height of the frame to get topmost position of our sprite frame).

We will, of course, need to udpate and draw our explosions. We can use the same loop we use to update the enemies to update our explosions (except for the player explosion) so change the loop in your Update method to look like this:

                    for (int i = 0; i < iTotalMaxEnemies; i++)
                    {
                        if (Enemies[i].IsActive)
                            Enemies[i].Update(gameTime,
                              background.BackgroundOffset);
 
                        if (Explosions[i].IsActive)
                            Explosions[i].Update(gameTime, 
                              background.BackgroundOffset);
 
                    }


Here we are just adding the second "if" condition to the loop. As I said above, this won't update the player's explosion, but this is one of those cases where we are missing some game structure that we will need to allow this to happen, so we will come back to that in a future segment.

In our Draw code, we can do the same. Update your enemy drawing loop to add explosions to it, ad add the lines below it to draw the player's explosion if it is active:

                for (int i = 0; i < iMaxEnemies; i++)
                {
                    if (Enemies[i].IsActive)
                    {
                        Enemies[i].Draw(spriteBatch,
                          background.BackgroundOffset);
                    }
 
                    if (Explosions[i].IsActive)
                    {
                        Explosions[i].Draw(spriteBatch, false);
                    }
                }
 
                if (Explosions[iTotalMaxEnemies].IsActive)
                    Explosions[iTotalMaxEnemies].Draw(spriteBatch, true);


Again, this should all be old hat by now. We are just adding a few calls to Draw our underlying objects. Notice, however, that when we draw the player's explosion we use the "true" value for the second parameter which causes the explosion's draw method to use screen coordinates instead of world coordinates.

The only thing left to do to get our enemies to explode is to activate explosions when they are destroyed. Update your DestroyEnemy helper function to look like this:

        protected void DestroyEnemy(int iEnemy)
        {
            Enemies[iEnemy].Deactivate();
            Explosions[iEnemy].Activate(
                Enemies[iEnemy].X-16,
                Enemies[iEnemy].Y-16,
                Enemies[iEnemy].Motion,
                Enemies[iEnemy].Speed/2,
                Enemies[iEnemy].Offset);
 
        }


Here we just added a call to the Activate() method of our Explosion class, passing it the values we derrive from the enemy that we are destroying. Notice that we offset the X and Y position of the explosion by -16 compared to the location of the enemy sprite. This is because our enemy sprite is 32x32 pixels, while our explosions are 64x64 pixels. This keeps the center of the explosion on the center of the enemy ship while allowing it to be more "impressing" than a 32x32 explosion would be.

We transfer the Motion vector directly from the enemy to the explosion and half of the enemy speed, which keeps the explosion "drifting" slowing in the same directon the enemy was moving.

If you run your game, you should be able to shoot down enemies and watch them explode! And you are still invincible!

Coming up, we will add some structure to our project to make it more of a game and less of a freeform shooting gallery.

(Continued in Part 9...)


































 

 
 
Site Contents Copyright 2006 Full Revolution, Inc. All rights reserved.
This site is in no way affiliated with Microsoft or any other company.
All logos and trademarks are copyright their respective companies.
RSS FEED