XNA Game Making

Resources for making games with XNA

Chapter 9: Animation

 To get the free pdf or to buy a printed version go to the book page at  lulu.com.

One of the most important things to do in a 2D game is to create animation. If you have a character that is walking across the screen you don't want just a single from sliding across, but several frames that are animated in a walk sequence. XNA doesn't have sprite animation built in to it (not that it needs to) so we'll be making a new class here that handles the animation for us. Then we'll add the animation to our Ship Fighter game. Our goals are:

 

l  Create a class to handle sprite animation

l  Put animation in a scroller game.

l  Put animation into our ship game.

 

Changing Screen Size

Before going into animation let's see how to change the screen size for XNA. This doesn't have anything to do with animation, but is good to know. The default screen size for XNA is 800 pixels wide and 600 pixels tall. To change the screen size we change the back buffer width and height of the graphics device. The back buffer is a place in memory where the screen refreshes are originally drawn to, and then when the screen has been refreshed the back buffer is placed onto the monitor. This writing to an off-screen back buffer and then flipping it to the screen buffer is called double buffering. Here is a little function that changes the screen size, that can go anywhere in the Game1.cs file.

 

public void SetWindowSize(int x, int y)

{

graphics.PreferredBackBufferWidth = x;

graphics.PreferredBackBufferHeight = y;

graphics.ApplyChanges();

}

 

To use it we just call it after we initialize the graphics device manager:

public Game1()

{

graphics = new GraphicsDeviceManager(this);

Content.RootDirectory = "Content";

SetWindowSize(1280, 1024);

}

 

Remember if you want your game to run in full screen mode smoothly you should set the size to a standard screen size (which can be found by right clicking of the desktop of you computer, selection properties and looking at sizes in the Settings tab.) Otherwise you can pick any setting you like, as long as it is not bigger than you graphics card can handle.

 

Animation Sample

Back to animation, download and run the Animation sample from xnagamemaking.com. The player that appears onscreen can move left and right with the arrow keys and pressing A will attack. The main file of this project is Animation.cs; the Animation class works by having different cells of animation (sprites) added to it, then you can play or loop through a series of them. Here is the source code for it:

 

struct AnimationCell

{

        public Texture2D cell;

}

 

class Animation

{

        int currentCell = 0;

 

        bool looping = false;

        bool stopped = false;

        bool playing = false;

        // Time we need to goto next frame

        float timeShift = 0.0f;

 

        // Time since last shift

        float totalTime = 0.0f;

        int start = 0, end = 0;

        List<AnimationCell> cellList = new List<AnimationCell>();

 

        Vector2 position;

 

        float scale = 1.0f;

        SpriteEffects spriteEffect = SpriteEffects.None;

 

        public float Scale

        {

            set

            {

                scale = value;

            }

        }

 

        Vector2 spriteOrigin = Vector2.Zero;

 

        public Vector2 SpriteOrigin

        {

            set

            {

                spriteOrigin = value;

            }

        }

 

        public Animation(Vector2 position)

        {

            this.position = position;

        }

 

        public void AddCell( Texture2D cellPicture )

        {

            AnimationCell cell = new AnimationCell();

            cell.cell = cellPicture;

 

            cellList.Add(cell);

        }

 

        public void SetPosition(float x, float y)

        {

            position.X = x;

            position.Y = y;

        }

 

        public void SetPosition(Vector2 position)

        {

            this.position = position;

        }

 

        public void SetMoveLeft()

        {

            spriteEffect = SpriteEffects.FlipHorizontally;

        }

 

        public void SetMoveRight()

        {

            spriteEffect = SpriteEffects.None;

        }

 

        public void LoopAll(float seconds)

        {

            if (playing) return;

           

            stopped = false;

            if (looping) return;

 

            looping = true;

            start = 0;

            end = cellList.Count - 1;

 

            currentCell = start;

            timeShift = seconds / (float)cellList.Count;

        }

 

        public void Loop(int start, int end, float seconds )

        {

            if (playing) return;

 

            stopped = false;

            if (looping) return;

 

            looping = true;

            this.start = start;

            this.end = end;

 

            currentCell = start;

            float difference = (float)end - (float)start;

 

            timeShift = seconds / difference;

        }

 

        public void Stop()

        {

            if (playing) return;

 

            stopped = true;

            looping = false;

            totalTime = 0.0f;

            timeShift = 0.0f;

        }

 

        public void GotoFrame(int number)

        {

            if (playing) return;

 

            if (number < 0 || number >= cellList.Count) return;

            currentCell = number;

        }

 

        public void PlayAll(float seconds)

        {

            if (playing) return;

            GotoFrame(0);

            stopped = false;

            looping = false;

            playing = true;

            start = 0;

            end = cellList.Count - 1;

 

            timeShift = seconds / (float)cellList.Count;

        }

 

        public void Play(int start, int end, float seconds)

        {

            if (playing) return;

            GotoFrame(start);

            stopped = false;

            looping = false;

            playing = true;

            this.start = start;

            this.end = end;

 

            float difference = (float)end - (float)start;

 

            timeShift = seconds / difference;

        }

 

 

        public void Draw(SpriteBatch batch)

        {

            if (cellList.Count == 0 || currentCell < 0 ||

                cellList.Count <= currentCell) return;

 

            batch.Draw(cellList[currentCell].cell, position, null, Color.White, 0.0f,

spriteOrigin,

                new Vector2( scale, scale), spriteEffect, 0.0f );

        }

 

        public void Update(GameTime gameTime)

        {

            if (stopped) return;

 

            totalTime += (float) gameTime.ElapsedGameTime.TotalSeconds;

            if (totalTime > timeShift)

            {

                totalTime = 0.0f;

 

                currentCell++;

 

                if (looping)

                {

                    if (currentCell > end) currentCell = start;

                }

                if (currentCell > end)

                {

                    currentCell = end;

                    playing = false;

                }

            }

        }

}

 

Like with the background code we won't go into much of the inner details, but will hit the main points. The constructor for the Animation class takes in a position, which is where on the screen the animation should be drawn:

 

public Animation(Vector2 position)

 

You can also set the spriteOrigin and change the position later, for example if playerAnimation is our object:

 

playerAnimation.SpriteOrigin = new Vector2(50.0f, 50.0f);

playerAnimation.Position.X = 50.0f;

 

are both valid lines of code. To create the animation cells we just load a texture and add it to it:

 

Texture2D animationCell = Content.Load<Texture2D>("Walk_1");

playerAnimation.AddCell(animationCell);           

 

Right now the Animation class doesn't keep an id or numbering of the cell, so when referring to the cells later it is by the number of the order it was added to the animation. The first cell you add is zero (it is zero based indexed) the second cell is one, and so on. To draw a specific cell we use:

 

public void GotoFrame(int number)

 

where number is the frame number to draw. To play an animation we have two options, to play an animation once or to play the animation and have it loop. The syntax for the two are similar:

 

public void Loop(int start, int end, float seconds )

public void Play(int start, int end, float seconds)

               

where start is the number of the first frame of the animation to play and end is the last. The seconds is how much time it should take to play the animation. If you look at the Game1.cs you'll find that the Loop is used for the walk cycle, since we want to repeat it as long as the character is walking. But for the attack animation Play is used because we want to play the animation just once during an attack. In addition to these there are LoopAll and PlayAll methods that cycle through every cell for the animation.

 

Two more methods let you flip which way the pictures are facing:

 

public void SetMoveLeft()

public void SetMoveRight()

 

If you look through the Game1.cs in the Animation sample you'll see an example of how the animation works. Pressing the left or right arrow will change the player position onscreen to the left or right and play the walk animation, but if neither are pressed the animation goes to frame zero, the standing still picture. Looking at this sample should be enough to create standard scroller games with the character walking around the screen.

 

Adding Animation to Ship Fighter

Now let's add some animation to the Ship Fighter game. What we'll do is pretty simple; we'll add in two more pictures, one of the ship rotating to the left and another rotating to the right, so when we turn left or right we'll see the ship change. The solution for this project is Player with Animation at xnagamemaking.com. Or you can follow these steps to add to the background project from last time.

 

The first thing to do is to add Animation.cs to the ship project. This is done the same as for MultiBackground.cs, copy Animation.cs to the folder with the  Game1.cs and from the solution explorer right click on the project name and select Add->Existing Item and select the Animation file. Also to the content directory copy the art files shipLeft and shipRight and add them to the project.

 

The first thing we'll do is in the Player.cs delete the single sprite that used to hold our ship picture and replace it with an Animation object. So delete the line in Player.cs that has:

 

Texture2D shipSprite;

 

and replace it with an Animation object:

 

Animation playerAnimation;

 

You'll also need to add the using xnaExtras to the Player.cs. Currently the constructor for the Player class takes in a texture and saves it and calculates the sprite origin from it:

 

public Player(GraphicsDevice Device, Vector2 position, Texture2D sprite)

{

this.position = position;

 

shipSprite = sprite;

            spriteOrigin.X = (float) shipSprite.Width / 2.0f;

            spriteOrigin.Y = (float)shipSprite.Height / 2.0f;

     windowHeight = Device.Viewport.Height;

windowWidth = Device.Viewport.Width;

}

 

Since we are no longer using a single sprite we won't pass a Texture2D in but we will need a sprite origin. So we'll change it to:

 

public Player(GraphicsDevice Device, Vector2 position, Vector2 origin )

{

// The position that is passed in is now set to the position above

this.position = position;

 

// Create the animation, this method is in Animation.cs

playerAnimation = new Animation(position);

 

windowHeight = Device.Viewport.Height;

windowWidth = Device.Viewport.Width;

 

playerAnimation.SpriteOrigin = origin;

playerAnimation.Stop();

}

 

Here we initialized the animation by giving it the start position for the player and set the SpriteOrigin for the animation. The next thing we'll do is add a method to add frames of animation to the player. So after the constructor add the following to the player class.

 

public void AddCell(Texture2D cellPicture)

{

            playerAnimation.AddCell(cellPicture);

}

 

Now we're setup to create a Player and give it the different frames of animation for the ship. We'll do that now. In the Game1.cs LoadContent method remove the current player initialization and replace it with the following:

 

Texture2D shipMain = Content.Load<Texture2D>("shipSprite");

// Create our player, besides position we pass it the graphics device

playerShip = new Player(graphics.GraphicsDevice, new Vector2(400.0f,

350.0f), new Vector2(shipMain.Width / 2, shipMain.Height / 2));

           

playerShip.AddCell(shipMain);

Texture2D shipLeft = Content.Load<Texture2D>("shipLeft");

playerShip.AddCell(shipLeft);

Texture2D shipRight = Content.Load<Texture2D>("shipRight");

playerShip.AddCell(shipRight);

 

This modification first reads in the original ship sprite, then creates a new player (getting the sprite origin from the ship sprite.) After that two more frames of animation are read in; this means our ship animation has three frames. The original ship at frame zero, left is frame one and right is frame two.

Now we have the player animation defined, we need to modify the player update and draw methods to handle the animation. Let's first modify the draw method. Erase the batch drawing routine and replace it with this:

 

public void Draw(SpriteBatch batch)

{

playerAnimation.SetPosition(position);

playerAnimation.Draw(batch);

}

 

Here we just tell the animation object to draw. But we want to make sure the animation is drawn at the correct position, so before we draw it we just update the position. This is all we need to do for the drawing modification, next update the Update loop. The first thing to do is just to add the playerAnimation update to the update loop. In the player Update method add the following line:

 

playerAnimation.Update(gameTime);

 

This is the only thing we need to do to the update loop, but we need to modify the move left and right methods to switch the animation frame. So we'll change the TurnRight() and TurnLeft() to:

 

public void TurnRight()

{

playerAnimation.GotoFrame(2);

position.X += 3.0f;

}

 

public void TurnLeft()

{

playerAnimation.GotoFrame(1);

position.X -= 3.0f;

}

 

This flips the frames when turning, but now we need a way to flip back to the original ship sprite. So we'll add the following method that resets that we're going straight:

 

public void GoStraight()

{

playerAnimation.GotoFrame(0);

}

 

Now let's modify the Game1.cs to reflect this new method. All we'll do is when testing if the left and right buttons are pressed if they're not then we'll tell the player to go straight:

 

if (keyState.IsKeyDown(Keys.Left)

                || gamePadState.DPad.Left == ButtonState.Pressed)

{

playerShip.TurnLeft();

}

else if (keyState.IsKeyDown(Keys.Right)

                || gamePadState.DPad.Right == ButtonState.Pressed)

{

playerShip.TurnRight();

}

else

            playerShip.GoStraight();

 

Now if you run the program you'll see the ship change sprites when moving left and right.

 

Summary

We now have some basic animation in our ship fighter game and have seen how to create a character with animation. In the next chapter we'll make things more complex by giving the player the ability to fire and adding enemies to the game.

NEXT