Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Sammy The Snake: An XNA game for the Zune

Nick Gravelyn - http://www.nickgravelyn.com/

Download: http://codeplex.com/sammythesnake
Software: Visual C# Express Editions, XNA Game Studios 3.0
Difficulty:
Intermediate
Time Required: 8 hours
Cost: Free

Artwork courtesy of George Clingerman of XNADevelopment.com
Side note: XNA is c# only

In the exciting world of XNA Game Studio, the new big thing is the XNA Game Studio 3.0 Beta. This beta allows developers to glimpse what will be coming with the full release of XNA Game Studio 3.0. One of the largest new features coming with XNA Game Studio 3.0 is the new ability to create games for Microsoft's Zune media device. What we're going to cover today is how to create a full game for the Zune, specifically a clone of the classic “Snake” game.

To begin you need to make sure you are set up to start working on Zune games. For all the details and instructions you will need for this step, please visit http://creators.xna.com/en-us/3.0beta_mainpage. Once you have read and completed steps one and two, continue on with this tutorial.

Welcome back! Now that you're all set up with XNA Game Studio 3.0 Beta, we can start working on our game. Since we want to test our game as we go forward, we're actually going to create the game as a Windows game first and then simply convert it to be a Zune project when we are finished. First open up Visual Studio 2008 or Visual C# 2008 Express and create a new Windows project. Let's call it “SammyTheSnake”. Choose the location to save the project and hit Ok.

imageNow that we have the project created let's start out first by adding in the content for our game. George Clingerman of XNADevelopment.com was nice enough to create some pretty graphics for this tutorial so those are the ones we are going to add to the project. In Visual Studio you will have a panel called the Solution Explorer which will show you all of the files in your project. In that window you want to find the Content node of your project. Right click on the Content node and choose Add->New Folder. Name this new folder “Fonts”. Then add a second folder called “Sprites”. At this point your Solution Explorer should look like what is to your right:

imageLet's continue by adding our content files. The easiest way to add the sprites to our project is to simply drag and drop them in from the folder. Another method is to right click on the “Sprites” folder, select Add->Existing Item. Then you can navigate to the directory where the graphics are, select them, and click Ok. When completed you will see all five images are copied into the Sprites directory:

Next we'll create the three fonts that we will be using for the game. To create a font, right click on the “Fonts” folder and select Add->New Item. In the pop up window you will see an item called a “Sprite Font”. Select that item, name your file “MiniFont.spritefont”, and then press Ok. When created it will open up in Visual Studio. The .spritefont file is simply an XML document describing which font you want to use. For our purposes there are only two nodes we need to change. First find the node that looks like this:

<FontName>MiniFont</FontName>

The FontName node specifies which font we wish to use. Let's change our font to Arial:

<FontName>Arial</FontName>

Next we need to change the size of the font. Let's find the Size node which, by default, is set to 14:

<Size>14</Size>

And let's change our font to size 10:

<Size>10</Size>

Now that you know how to create fonts, create two more fonts: “MediumFont.spritefont” and “TitleFont.spritefont”. Both fonts should use the Arial font. MediumFont should be size 14 (the default value) and TitleFont should be size 18.

imageNow you have all the content our game needs. Let's take a look at the Solution Explorer one last time to make sure we have all the files in the correct locations:

Now that all of the content is in place, we can begin working on the game. Let's start writing that code!

To begin we want to make sure our Windows game looks just like the Zune version will. Since the Zune screen has a resolution of 240 pixels by 320 pixels, we want to make our Windows game run in a window set at 240x320. To do this we just need to add two lines of code to our Game1 class's constructor:

public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    graphics.PreferredBackBufferWidth = 240;
    graphics.PreferredBackBufferHeight = 320;
}

imageThe next thing to consider is our game's flow. Since we are making a simple game, we are only ever going to have three states in which the game can be in as illustrated by this diagram:

From the diagram we see that our game will have a title screen, some game play, and a game over screen. The arrows represent the changes in game state. From the title screen we can only progress to the in-game screen. From the in-game screen we can either quit, going back to the title screen, or we can finish the game and move to the game over screen. From the game over screen we then return to the title screen and the process repeats until the user quits.

Now that we have an idea of how our game will work we will start by creating the framework that will manage our game's state and make sure that we are executing the appropriate code based on the state of the game. First we'll create a new enumeration type to represent the three states of our game. In the Game1.cs file right above the declaration for the Game class, add this new enumeration:

public enum GameState
{
    Title,
    InGame,
    GameOver
}

Now in the actual Game1 class, we need to add a variable to hold the game's state:

private GameState state = GameState.Title;

Now we need to create some new methods which will be used to handle updating and drawing our various states. First let's add three new methods to our Game1 class:

private void UpdateTitleScreen()
{ }

private void UpdateInGame(GameTime gameTime)
{ }

private void UpdateGameOver()
{ }

From the names you can probably see that we have one for each of our three game states. Our UpdateInGame method takes in the GameTime as a parameter because it is used for moving our snake around. The other two game states don't need that value, so they have no parameters. Now let's make some changes to the Update method to handle calling each of these methods based on the state of our game:

protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    if (state == GameState.Title)
        UpdateTitleScreen();
    else if (state == GameState.InGame)
        UpdateInGame(gameTime);
    else if (state == GameState.GameOver)
        UpdateGameOver();

    base.Update(gameTime);
}

Now we'll repeat those steps to make three methods for rendering each state and updating the Draw method to call each of them as appropriate. While we're at it, we're also going to change the background from the default of CornflowerBlue to Gray to make it a little easier on the eyes with the white text we're going to add later:

private void DrawTitleScreen()
{ }

private void DrawInGame()
{ }

private void DrawGameOver()
{ }

protected override void Draw(GameTime gameTime)
{
    graphics.GraphicsDevice.Clear(Color.Gray);

    if (state == GameState.Title)
        DrawTitleScreen();
    else if (state == GameState.InGame)
        DrawInGame();
    else if (state == GameState.GameOver)
        DrawGameOver();

    base.Draw(gameTime);
}

With that our game now has a fully functioning state system. Of course our game only ever has one state since we don't currently change it, and none of the game states do anything, but the functionality is there. Let's move on to create the code that will power the title screen.

First we need to add a few strings to our game which will be used on the title screen. Towards the top of your Game1 class, add these string constants:

private const string gameTitle = "Sammy the Snake!";
private const string playInstructions = "Press Play to Begin";
private const string quitInsructions = "Press Back to Quit";

Now we need to add a few variables to the Game1 class to hold our fonts which we'll be using to render these strings:

private SpriteFont titleFont;
private SpriteFont mediumFont;
private SpriteFont miniFont;

Next we'll go to our LoadContent method and add in the code that will load those .spritefont files we created earlier and store them in these three variables:

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    titleFont = Content.Load<SpriteFont>("Fonts/TitleFont");
    mediumFont = Content.Load<SpriteFont>("Fonts/MediumFont");
    miniFont = Content.Load<SpriteFont>("Fonts/MiniFont");
}

After that we also need to add two more variables. The first will store the current frame's input state. The second will store the previous frame's input state. By storing these two values, we will be able to test for when a button gets pressed by verifying that the button was down the frame before and is pressed in the current frame.

private GamePadState gamePadState;
private GamePadState lastGamePadState;

Now let's go ahead and make a method which we'll use to test for new button presses for us. We'll take a parameter for which button to test and then check for that button being down in gamePadState but up in lastGamePadState.

private bool IsNewButtonPress(Buttons button)
{
    return (gamePadState.IsButtonDown(button) && lastGamePadState.IsButtonUp(button));
}

We'll go ahead, now, and modify the game's Update method to handle updating those GamePadState variables we created above. We get the current state right away and then, at the end of the Update method, we store the current state in our last state variable for next frame. We also want to remove the default code in there for exiting when the Back button is pressed because we will handle this in our UpdateTitleScreen method:

protected override void Update(GameTime gameTime)
{
    gamePadState = GamePad.GetState(PlayerIndex.One);

    if (state == GameState.Title)
        UpdateTitleScreen();
    else if (state == GameState.InGame)
        UpdateInGame(gameTime);
    else if (state == GameState.GameOver)
        UpdateGameOver();

    lastGamePadState = gamePadState;

    base.Update(gameTime);
}

With that in place we can move on to filling in our UpdateTitleScreen method. For our simple game, we're going to simply detect the Play/Pause button to start the game and the Back button to exit. These buttons map to the GamePadState's B and Back buttons, respectively.

private void UpdateTitleScreen()
{
    if (IsNewButtonPress(Buttons.Back))
        Exit();

    if (IsNewButtonPress(Buttons.B))
    { }
}

For now we'll leave the inside of our second if statement empty because we have not implemented the in-game game state at this point.

Let's now move on to rendering our title screen. We know we are going to have to render three different strings to the screen, so what we're going to do first is write a helper method that will help us write text centered on a point. This will make it easier for us so that we don't have to try and calculate where to draw each string individually; we can simply use this one method everywhere we need to draw text.

private void DrawText(SpriteFont font, string text, Vector2 position)
{
    Vector2 halfSize = font.MeasureString(text) / 2f;
    position = position - halfSize;

    position.X = (int)position.X;
    position.Y = (int)position.Y;

    spriteBatch.Begin();
    spriteBatch.DrawString(
        font,
        text,
        position, 
        Color.White);
    spriteBatch.End();
}

This is our first complex code so we're going to take a look at it line by line. The method itself takes in three parameters: the SpriteFont to use for rendering, the text we want to render, and the point at which we want the text centered.

First we use the font's MeasureString method to calculate how big the text will be (in pixels) if rendered with that font. We then divide this size by two and subtract it from the position. Why do we do all of this? In the XNA framework (and most 2D graphics APIs) positions of objects are specified using the top-left corner of the object. So by getting half of the size of the object and offsetting the position by it, we are able to set the center point and know that the text will be positioned properly.

Once we have offset the position, we then cast each of the components to integers. We do this to avoid unnecessary blurring of our text. Since all text is rendered as whole pixels (because there's no such thing as a fractional pixel), having a position that is not an integer value will cause the device to try and filter the text to get it to look right, or simply render it incorrectly. So when drawing in 2D, it's always best to cast your positions to integers before using them to render.

Lastly our method uses a simple SpriteBatch routine to draw the text at the desired position using the desired font using the White color.

With that helper method out of the way, we can add three simple lines to the DrawTitleScreen method to draw our title screen:

private void DrawTitleScreen()
{
    DrawText(titleFont, gameTitle, new Vector2(120f, 25f));
    DrawText(mediumFont, playInstructions, new Vector2(120f, 200f));
    DrawText(mediumFont, quitInsructions, new Vector2(120f, 225f));
}

imageNow if you run your game, you should see a window like this:

If you have an Xbox 360 controller hooked to your computer, you could press the Back button to quit, but otherwise you can simply use the close button of the window to exit the game.

From this point we're going to start working on the gameplay. I am only going to be showing GamePad controls for this (since we are targeting the Zune), so if you don't have an Xbox 360 controller connected to your PC, you are either going to have to deploy the game to the Zune each time you want to test or implement keyboard controls. You can skip down to the end of this tutorial where we convert the project to be a Zune game or use other references for that process.

Next we need to add in the game play code itself. Our game is rather simple. At any given time our game will have a single orange on screen, the snake, and a display of the number of oranges consumed. We're going to start with the orange, then handle the snake, and finally the scoring system.

To begin any of our rendering we need to get setup with a system for easily rendering our game. Since the whole game is grid based, we are going to make a class whose only purpose is to handle rendering a sprite at a given point using grid coordinates. We'll start by creating a new static class called Grid:

 

 

 

 

 

 

public static class Grid
{ }

Next we will define a few constants. The first sets the size of each grid cell. The second is one half of the grid cell size. We will be using a 16x16 pixel area for our grids. This number has been chosen because both 240 and 320 are even divisible by it, thus giving us a perfect grid for the resolution. We then have values to represent the largest row and column values in our grid:

Next we'll define a method called PointToVector2. The whole purpose of this method will be to take a Point value representing cell coordinates and converting them into the pixel coordinates needed to render a sprite. To do this we simply multiply the point's coordinates by the scale and add in the half scale value. We add the half scale so that the resulting Vector2 is at the center of the cell. As I said before you always want to convert to integer based coordinates, but since the Point only contains integer values, and all our constants are integers, we don't need to explicitly cast the values to ints:

public static Vector2 PointToVector2(Point p)
{
    return new Vector2(
        p.X * Scale + HalfScale,
        p.Y * Scale + HalfScale);
}

The only other method we're going to add to our Grid class is the DrawSprite method. DrawSprite will handle rendering a given texture at a specific point with a given angle of rotation.

public static void DrawSprite(
    SpriteBatch spriteBatch, 
    Texture2D texture, 
    Point point, 
    float rotation)
{
    float spriteSize = (float)Math.Max(texture.Width, texture.Height);
    Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f);
    spriteBatch.Draw(
        texture,
        PointToVector2(point),
        null,
        Color.White,
        rotation,
        origin,
        Scale / spriteSize,
        SpriteEffects.None,
        0);
} 

Let's walk through what this method is doing so that we're all clear as to how it works. We first calculate the largest dimension of the texture given to us. We do this so we know how much to scale the sprite to make it fit into a single cell of the grid. Then we compute the origin we'll use for rendering. I'll explain the origin below as we go through the parameters of our Draw call.

Next we make a large call to spriteBatch.Draw, where we have lots of variables going in. Let's just explain each one briefly:

  • texture – This represents the texture object we want to draw.
  • PointToVector2(point) – Here we leverage the Grid.PointToVector2 method we wrote before to convert our grid coordinate to pixel coordinates.
  • null – This parameter represents the source rectangle used to render. The source rectangle is used to specify a subsection of the area of the texture to use for rendering. Since we want to draw the entire texture, we use null here.
  • Color.White – Using white here simply causes the sprite batch to render the texture with no color modifications.
  • rotation – The rotation (specified in radians) instructs the sprite batch to rotate the texture as desired.
  • origin – Here we use the origin value we calculated earlier. This value specifies how to place, rotate, and scale the texture. What we've done is specified the pixel value of the center of our sprites. The sprites created for us are 32x32 pixels making their center point 16x16. Rather than hard coding in that value, we simply divide the width and height by two. By using the center of the texture, our sprite will be positioned such that the second parameter tells the sprite batch where we want the center of our texture. It also means that the sprite will be rotated and scaled about its center.
  • Scale / spriteSize – Here we use our grid's Scale value and divide it by the size of the sprite. This will cause sprite batch to scale the texture so that it fits completely within a single grid cell.
  • SpriteEffects.None – SpriteEffects are used for flipping the texture over the horizontal axis, vertical axis, or both. We don't want to flip the texture, so we specify none.
  • 0 – The last parameter is the layer depth. This can be used to specify the order in which sprites drawn in the same batch are layered when rendered with zero being the topmost drawn sprites and one being the bottom most drawn sprites. We just use zero here because we will be handling the rendering order by hand so that sprites we want lower will be drawn first. In addition we will rarely ever have overlapping sprites, so ordering is hardly an issue for us.

With the Grid class complete, we can move on to create the Orange class:

public class Orange
{
    Position = new Point(Grid.MaxColumn / 2, Grid.MaxRow / 2);
}

Now let's think about what data our orange will need. The two obvious ones are a position and the texture. But we also need a third value: a random number generator which will be used to place the orange in a random place on the screen:

public Point Position;
private Texture2D texture;
private Random rand = new Random();

Now we're going to add three short methods to our Orange class that will handle just about all the functionality we will need for this game:

public void Reposition()
{
    Position = new Point(rand.Next(Grid.MaxColumn), rand.Next(Grid.MaxRow));
}

public void Load(ContentManager content)
{
    texture = content.Load<Texture2D>("Sprites/Orange");
}

public void Draw(SpriteBatch spriteBatch)
{
    spriteBatch.Begin();
    Grid.DrawSprite(spriteBatch, texture, Position, 0f);
    spriteBatch.End();
}

Our Reposition method is what will generate a random location in our grid at which the orange will be placed. The Load method requires a ContentManager instance and handles loading our orange texture from file. Lastly the Draw method takes in a SpriteBatch instance and handles rendering the orange using our Grid class's DrawSprite method.

Before continuing on, let's give this class a try to make sure it works like we think it should. First let's head to the Game1 class and add a new variable for the orange. We'll also set our starting game state to the InGame playing state:

private Orange orange = new Orange();
private GameState state = GameState.InGame;

Then we need to make sure call the orange's Load method in our LoadContent method:

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    titleFont = Content.Load<SpriteFont>("Fonts/TitleFont");
    mediumFont = Content.Load<SpriteFont>("Fonts/MediumFont");
    miniFont = Content.Load<SpriteFont>("Fonts/MiniFont");

    orange.Load(Content);
}

Next we'll go to our UpdateInGame method and add in some code that will call the orange's Reposition method every second:

private void UpdateInGame(GameTime gameTime)
{
    if (gameTime.TotalGameTime.Milliseconds % 1000 == 0)
        orange.Reposition();
}

Lastly we'll add to the DrawInGame method so that it call's the orange's Draw method:

private void DrawInGame()
{
    orange.Draw(spriteBatch);
}

Build and run the game. You should see the same gray window with a single orange drawn. Every second the orange will move to a new grid location on the screen.

image

Now we will be creating our Snake class:

public class Snake
{ }

The Snake class is a bit more complex. Logically let's lay out what data the class will need:

  • A list of points for each segment of the body.
  • The four textures (head, straight body piece, angled body piece, and tail) with which we will render the snake
  • A value indicating how fast the snake should be moving.
  • A value indicating the snake's current direction and the direction he will be moving next.
  • A value indicating whether or not to extend the snake's length the next time it moves.
  • A timer value so that we can control how often the snake moves from grid cell to grid cell.

A good amount of data for something that seems rather simple. We'll start by creating an enumeration for those direction values we are going to need:

public enum Direction
{
    Up,
    Down,
    Left,
    Right
}

Next we'll go ahead and add all the variables to our Snake class itself:

public const float MoveSpeed = .2f; 
private float moveTimer;
private Texture2D head, straight, angle, tail;
private List<Point> bodyPoints = new List<Point>();
private Direction currentDirection = Direction.Right;
private Direction nextDirection = Direction.Right;
private bool extending;

Everything here should be rather self explanatory. The MoveSpeed represents the number of seconds before the snake proceeds to the next grid cell and the moveTimer will be used to count up to this value. We then have our four textures, a list for the body points, and two values for the current and next direction. Lastly our bool value to tell us whether the snake should extend the next time it moves.

Now we're going to add a new method called Reset which will be called from the Snake's constructor. Reset is responsible for placing our Snake it its starting position in the game.

public Snake()
{
    Reset();
}

public void Reset()
{
    bodyPoints.Clear();

    bodyPoints.Add(new Point(2, 0));
    bodyPoints.Add(new Point(1, 0));
    bodyPoints.Add(new Point(0, 0));

    currentDirection = Direction.Right;
    nextDirection = Direction.Right;
}

In the Reset method, first we make sure to clear out the list of points for the snake's body. Next we add three points to the body. The first point sets the position of the head, followed by a single body piece, and lastly the tail. To finish we also make sure to reset both the current and next direction values to Right so that the snake begins by heading to the right.

Next we'll make a Load method which, like the Orange class, handles loading in the textures needed to render the snake:

public void Load(ContentManager content)
{
    head = content.Load<Texture2D>("Sprites/Head");
    straight = content.Load<Texture2D>("Sprites/Straight");
    angle = content.Load<Texture2D>("Sprites/Angle");
    tail = content.Load<Texture2D>("Sprites/Tail");
}

Continuing on we're going to implement the rendering for the snake. This is the most complex part of this entire game, but we'll take it one step at a time to make sure each line of code is explained. First let's add the five methods we'll be using:

public void Draw(SpriteBatch spriteBatch)
{
    spriteBatch.Begin();

    for (int i = 1; i < bodyPoints.Count - 1; i++)
    {
        DrawBody(
            spriteBatch, 
            bodyPoints[i], 
            bodyPoints[i - 1], 
            bodyPoints[i + 1]);
    }

    DrawTail(spriteBatch);
    DrawHead(spriteBatch);
    spriteBatch.End();
}

private void DrawHead(SpriteBatch spriteBatch) { }
private void DrawTail(SpriteBatch spriteBatch) { }
private bool IsAnglePiece(Point current, Point last, Point next) { }
private void DrawBody(SpriteBatch spriteBatch, Point current, Point last, Point next) { }
private float GetAngleRotation(Point current, Point last, Point next) { }

We can see that we have broken the rendering up into multiple methods to make the code a bit more organized. We have the main Draw method which will be used by our Game1 class to handle drawing the snake. The method has a single SpriteBatch instance for a parameter.

Then we loop through the bodyPoints list starting on the second item and going up to the second to last item. We intentionally do not use the first or last points because those represent the head and tail, respectively.

After we draw the body, we then draw the tail and head. The order in which we call these methods will affect the graphics to a small degree. The important part is drawing the head last. By doing this we ensure that even when the head and a body piece overlap (for instance, when the snake runs into itself) the head will be drawn on top of the body part.

Next we'll go ahead and fill in the DrawHead method:

private void DrawHead(SpriteBatch spriteBatch)
{
    Point headPoint = bodyPoints[0];
    Point nextBody = bodyPoints[1];

    float rotation;
    if (headPoint.Y == nextBody.Y - 1)
    {
        rotation = -MathHelper.PiOver2;
    }
    else if (headPoint.Y == nextBody.Y + 1)
    {
        rotation = MathHelper.PiOver2;
    }
    else if (headPoint.X == nextBody.X - 1)
    {
        rotation = MathHelper.Pi;
    }
    else
    {
        rotation = 0f;
    }

    Grid.DrawSprite(spriteBatch, head, headPoint, rotation);
}

While this looks really complex, it's rather simply once you look at it. First we get out the position of the head and then the first body point after that. What we then do is compare those points to figure out what angle to use when rendering the head. Finally we just use the Grid.DrawSprite method to render the head to the screen.

Now we'll fill in the DrawTail method. This method is almost identical to the DrawHead method so there really is no explanation needed:

private void DrawTail(SpriteBatch spriteBatch)
{
    Point tailPoint = bodyPoints[bodyPoints.Count - 1];
    Point lastBody = bodyPoints[bodyPoints.Count - 2];

    float rotation;
    if (tailPoint.Y == lastBody.Y - 1)
    {
        rotation = MathHelper.PiOver2;
    }
    else if (tailPoint.Y == lastBody.Y + 1)
    {
        rotation = -MathHelper.PiOver2;
    }
    else if (tailPoint.X == lastBody.X + 1)
    {
        rotation = MathHelper.Pi;
    }
    else
    {
        rotation = 0f;
    }

    Grid.DrawSprite(spriteBatch, tail, tailPoint, rotation);
}

We'll tackle the IsAnglePiece method next. This method is used by the DrawBody method to determine if a given point should be drawn with the angled body piece or the straight body piece. To do this we compare the positions of the current body piece, the previous body piece, and the next body piece to determine if they make up a right angle or not:

private bool IsAnglePiece(Point current, Point last, Point next)
{
    return (current.X == last.X && current.X != next.X && current.Y != last.Y) ||
           (current.X == next.X && current.X != last.X && current.Y != next.Y);
}

With that completed we can fill in the DrawBody method. This method is also pretty simple:

private void DrawBody(SpriteBatch spriteBatch, Point current, Point last, Point next)
{
    if (IsAnglePiece(current, last, next))
    {
        Grid.DrawSprite(
            spriteBatch, 
            angle, 
            current, 
            GetAngleRotation(current, last, next));
    }
    else if (current.X != last.X)
    {
        Grid.DrawSprite(
            spriteBatch, 
            straight, 
            current, 
            0f);
    }
    else if (current.Y != last.Y)
    {
        Grid.DrawSprite(
            spriteBatch, 
            straight, 
            current, 
            MathHelper.PiOver2);
    }
}

First we check to see if the three points comprise and angle. If they do we use the Grid.DrawSprite method to render the angle texture at the current position. For the angle we utilize the GetAngleRotation method which we will be filling in later.

If we don't have an angle piece, we then figure out whether the body piece is horizontal or vertical and use that to render the straight body piece with either zero or PiOver2 as the angle.

Finally we come to the GetAngleRotation method. This method takes three Points and determines the angle at which the sprite must be drawn in order to connect the three body pieces. Here's the code for this method:

private float GetAngleRotation(Point current, Point last, Point next)
{
    Point negPiOver2 = new Point(next.X + 1, last.Y - 1);
    Point negPiOver22 = new Point(last.X + 1, next.Y - 1);

    Point pi = new Point(next.X - 1, last.Y - 1);
    Point pi2 = new Point(last.X - 1, next.Y - 1);

    Point piOver2 = new Point(next.X - 1, last.Y + 1);
    Point piOver22 = new Point(last.X - 1, next.Y + 1);

    if (current == negPiOver2 || current == negPiOver22)
    {
        return -MathHelper.PiOver2;
    }
    else if (current == pi || current == pi2)
    {
        return MathHelper.Pi;
    }
    else if (current == piOver2 || current == piOver22)
    {
        return MathHelper.PiOver2;
    }
    else
    {
        return 0f;
    }
}

The method begins by calculating a set of Points. Each pair of points represents one of the combinations that will result in a particular angle. This can take a little bit to wrap your head around, so if you have trouble visualizing it, I'd recommend grabbing (or making) some grid paper and drawing the points out so you can see how they all fall into place.

We then go through and compare the current point to each set of our values. If any match up, we return that value. If none match, we don't have any rotation to apply to the sprite.

That completes the snake's rendering system. It was a pretty big hill to take, but with it behind us we can feel good knowing that the rest of the code will be quite a bit simpler. Before we move on, let's head back to the Game1 class to test out our new class and make sure it works.

First we'll add a new variable for the snake:

private Snake snake = new Snake();

Next we head to the LoadContent method and make sure we call the snake's Load method:

snake.Load(Content);

Lastly for this test we'll just add a call to the snake's Draw method in our DrawInGame method:

private void DrawInGame()
{
    orange.Draw(spriteBatch);
    snake.Draw(spriteBatch);
}

Go ahead and run the game now and you should see the little snake in the top-left corner of the screen along with the orange that we added earlier:

image

There is already one issue with our game. The orange, as it keeps repositioning, occasionally will position itself underneath the snake. We want to avoid this at all costs. To accomplish this we need to add a new method to the Snake class called IsBodyOnPoint which will tell us whether a given grid cell is occupied by one of the snake's body parts. To do this we simply leverage the list's Contains method:

public bool IsBodyOnPoint(Point p)
{
    return bodyPoints.Contains(p);
}

Next we will modify the Orange class's Reposition method to take in a snake instance and use this to make sure we generate a random point not occupied by the snake:

public void Reposition(Snake snake)
{
    do
    {
        Position = new Point(rand.Next(Grid.MaxColumn), rand.Next(Grid.MaxRow));
    } while (snake.IsBodyOnPoint(Position));
}

By using the do-while loop, we ensure that we will run that code at least once. Then if the position is found to be occupied by the snake, the code will continue looping until an unoccupied space has been found.

Let's go ahead and update our code found in the Game1 UpdateInGame method to match this new method signature:

private void UpdateInGame(GameTime gameTime)
{
    if (gameTime.TotalGameTime.Milliseconds % 1000 == 0)
        orange.Reposition(snake);
}

Now if you run the game, you'll notice the orange will never, ever position itself underneath of the snake. We now have enough of the core to begin working on making the snake move around. Let's head to the Snake class and add a few new methods:

public void Update(GameTime gameTime)
{ }

private void HandleInput()
{ }

private void MoveSnake()
{ }

These three methods will handle all of our update code for the snake. First we'll start by filling in the main Update method:

public void Update(GameTime gameTime)
{
    HandleInput();

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

    if (moveTimer < MoveSpeed)
    {
        return;
    }

    moveTimer = 0f;
    currentDirection = nextDirection;
    MoveSnake();
}

The first thing our method does is call the HandleInput. Next we add the elapsed time in seconds to our moveTimer variable. Then we see if the moveTimer is less than the MoveSpeed value. If moveTimer is less than the MoveSpeed we know that enough time has not elapsed so we call ‘return' to exit out of our update method. If moveTimer has exceeded the MoveSpeed value, we proceed on with updating. The next thing we do is reset the moveTimer. Then we assign the nextDirection as our currentDirection and call the MoveSnake method.

We'll continue by filling in the HandleInput method:

private void HandleInput()
{
    GamePadState gps = GamePad.GetState(PlayerIndex.One);

    if (gps.IsButtonDown(Buttons.DPadDown) && currentDirection != Direction.Up)
    {
        nextDirection = Direction.Down;
    }
    if (gps.IsButtonDown(Buttons.DPadUp) && currentDirection != Direction.Down)
    {
        nextDirection = Direction.Up;
    }
    if (gps.IsButtonDown(Buttons.DPadLeft) && currentDirection != Direction.Right)
    {
        nextDirection = Direction.Left;
    }
    if (gps.IsButtonDown(Buttons.DPadRight) && currentDirection != Direction.Left)
    {
        nextDirection = Direction.Right;
    }
}

All we are doing here is finding out which DPad direction is down and using that for the next direction. We also check to make sure that the next direction is not the direct opposite of the current direction. Pretty simple.

Next is the MoveSnake method. This method takes care of two things for us. First it handles updating all the body points in the snake to move him around the grid. The second thing it handles is inserting new body pieces as requested. Let's take a look at the code:

 private void MoveSnake()
{
    Point p1 = bodyPoints[0];
    switch (currentDirection)
    {
        case Direction.Up:
            bodyPoints[0] = new Point(p1.X, p1.Y - 1);
            break;
        case Direction.Down:
            bodyPoints[0] = new Point(p1.X, p1.Y + 1);
            break;
        case Direction.Left:
            bodyPoints[0] = new Point(p1.X - 1, p1.Y);
            break;
        case Direction.Right:
            bodyPoints[0] = new Point(p1.X + 1, p1.Y);
            break;
    }
    if (extending)
    {
        bodyPoints.Insert(1, p1);
        extending = false;
        return;
    }
    for (int i = 1; i < bodyPoints.Count; i++)
    {
        Point p2 = bodyPoints[i];
        bodyPoints[i] = p1;
        p1 = p2;
    }
}

Going through this little by little, what we first do is get the position of the head to store that for the next body part. We then use the currentDirection value to move the head to the next position it should be at.

Following that we see if we need to add in a new body piece. If we need to, we simply insert the point the head used to be at into the list behind the head and exit the method since we have filled the gap left by moving our head.

If we did not extend, we then loop through all remaining body parts (including the tail) and update them by moving them to position of the body part in front of them.

We'll follow this up by again testing our code to make sure it all works. Head back to the Game1 class and update the UpdateInGame method to call the snake's Update method:

private void UpdateInGame(GameTime gameTime)
{
    if (gameTime.TotalGameTime.Milliseconds % 1000 == 0)
        orange.Reposition(snake);
    snake.Update(gameTime);
}

Now when you run the game your snake will take off towards the right. You can now use the DPad of your Xbox 360 controller (or Zune if you have deployed it there) to direct the snake around.

At this point we have all the ground work in place. Let's go ahead and make our gameplay actually goal oriented. First let's increase functionality that lets our snake eat the oranges. For us to do this, we need to be able to see if the snake's head is in the same grid cell as the orange. Our orange already has a position value, so we just need to add a method to see if the snake's head is at that position. We'll add the following method now to our Snake class:

public bool IsHeadAtPosition(Point position)
{
    return (bodyPoints[0] == position);
}

All this method does is compare the first point in our array to the position given and return if the two values are the same. With this in place, let's fix up the UpdateInGame method to only reposition the orange when the snake eats it:

private void UpdateInGame(GameTime gameTime)
{
    snake.Update(gameTime);

    if (snake.IsHeadAtPosition(orange.Position))
        orange.Reposition(snake);
}

Our orange is now updated appropriately so that it only repositions itself if eaten by the snake. However our snake still doesn't actually grow like he should. So we'll add another method to the Snake class that tells our snake to extend itself:

public void Extend()
{
    extending = true;
}

We can now add this to our UpdateInGame method so that when the snake eats the orange, not only does the orange reposition itself, but the snake will also grow the next time it moves:

private void UpdateInGame(GameTime gameTime)
{
    snake.Update(gameTime);

    if (snake.IsHeadAtPosition(orange.Position))
    {
        orange.Reposition(snake);
        snake.Extend();
    }
}

Now we're making some progress. We still have a big problem: there's no way to lose. In the original game of Snake you would lose by either crashing into the wall of the window or by making the snake crash into itself. We can address both of these cases by adding two methods to the Snake class:

public bool IsLooped()
{
    for (int i = 1; i < bodyPoints.Count; i++)
        if (IsHeadAtPosition(bodyPoints[i]))
            return true;

    return false;
}

public bool IsHeadOffScreen()
{
    Point h = bodyPoints[0];
    return (h.X < 0 || h.Y < 0 || h.X >= Grid.MaxColumn || h.Y >= Grid.MaxRow);
}

The first method, IsLooped, tests all body pieces against the head to see if the head is on the same space as them. The second method, IsHeadOffScreen, checks to see if the head is out of the valid grid locations. Let's head back to the UpdateInGame method and add in these tests:

private void UpdateInGame(GameTime gameTime)
{
    snake.Update(gameTime);

    if (snake.IsHeadAtPosition(orange.Position))
    {
        orange.Reposition(snake);
        snake.Extend();
    }

    if (snake.IsLooped())
        state = GameState.GameOver;

    if (snake.IsHeadOffScreen())
        state = GameState.GameOver;
}

We now have the ability to lose the game which brings us very close to having a completed game. Let's now add some code to the start of the UpdateInGame method to let the user exit back to the title screen by pressing the Back button.

private void UpdateInGame(GameTime gameTime)
{
    if (IsNewButtonPress(Buttons.Back))
        state = GameState.Title;

    snake.Update(gameTime);

    if (snake.IsHeadAtPosition(orange.Position))
    {
        orange.Reposition(snake);
        snake.Extend();
    }

    if (snake.IsLooped())
        state = GameState.GameOver;

    if (snake.IsHeadOffScreen())
        state = GameState.GameOver;
}

We are just about done with all of the code for the UpdateInGame method. Let's finish up by adding the ability to count the oranges that have been eaten. First we need to add a new integer variable to the Game1 class to hold the score:

private int score;

We now just increment the score every time the snake eats and orange. Inside of our UpdateInGame method, update the code for the snake eating the orange to look like this:

if (snake.IsHeadAtPosition(orange.Position))
{
    orange.Reposition(snake);
    snake.Extend();
    score++;
}

We can now focus on finishing the drawing for the DrawInGame. All we need to do is display the current score on the screen. We'll start by adding a new string constant to the Game1 class:

private const string scoreFormat = "Oranges Eaten: {0}";

The “{0}” in there, if you haven't worked with format strings before, is a placeholder that specifies where we want to insert our score.

Now let's head down to the DrawInGame method and add in the code to draw the score to our screen:

private void DrawInGame()
{
    orange.Draw(spriteBatch);
    snake.Draw(spriteBatch);
    DrawText(miniFont, string.Format(scoreFormat, score), new Vector2(120f, 5f));
}

We can run the game and now see how well we're doing:

image

 

We can now quickly fill in both the UpdateGameOver and DrawGameOver screens to simply display the results of the game and await input to return to the title screen. First we'll add two more string constants to the game:

private const string gameOver = "Game Over!";
private const string gameOverInstructions = "Press Play to Continue";

And then we can fill in the code for our two methods:

private void UpdateGameOver()
{
    if (IsNewButtonPress(Buttons.B))
        state = GameState.Title;
}

private void DrawGameOver()
{
    orange.Draw(spriteBatch);
    snake.Draw(spriteBatch);

    DrawText(titleFont, gameOver, new Vector2(120f, 25f));
    DrawText(mediumFont, string.Format(scoreFormat, score), new Vector2(120f, 200f));
    DrawText(mediumFont, gameOverInstructions, new Vector2(120f, 225f));
}

You can see that we not only draw text in our state, but we also draw the orange and snake. This lets the user see where things were when the game ended and is a nice touch to add to the game.

The last thing we add is the code to make sure that the UpdateTitleScreen method makes sure to reset the snake, orange, and score when the user starts a new game:

private void UpdateTitleScreen()
{
    if (IsNewButtonPress(Buttons.Back))
        Exit();

    if (IsNewButtonPress(Buttons.B))
    {
        snake.Reset();
        score = 0;
        orange.Reposition(snake);
        state = GameState.InGame;
    }
}

And lastly we make sure the initial state is set to the title screen:

private GameState state = GameState.Title;

With that your game is complete! You now have a fully functional Snake clone. “But what about the Zune?”, you ask. Let's take care of that right now.

XNA Game Studio has always aimed to be a cross-platform tool and the new Zune support is no exception. Getting our project onto a Zune is literally just a matter of clicks. First right click on your game project in the Solution Explorer and choose the Create Copy of Project for Zune menu item. When completed you will have a complete duplicate project that targets the Zune platform. The Solution Explorer should now look like this:

image

Next we need to build and deploy the game to the Zune. This process is also rather simple. If you went through the tutorial on the download instructions page, this will already be familiar to you. If not, we'll cover the whole process here.

Head up to the Tools menu and choose the Launch XNA Game Studio Device Center option. You will be presented with a window that looks like this:

image

Click on the Add Device button and you will see the following dialog:

image

 

We want to add a Zune, so click on the Zune button. You should see your Zune appear in the window now:

image

Note: If your Zune did not appear in the window,make sure you have the latest Zune firmware on your device and that the Zune Player application is closed.

From here select your Zune and press the Next button. After a brief moment you will see the success screen:

image

Press the Finish button and then close the XNA Game Studio Device Center.

Now click on the Zune project you created earlier and go up to the Build menu and click on Deploy Zune Copy of SammyTheSnake to begin deploying the game to your Zune.

image

 

You should see the Zune screen update to show that the game is deploying. Once the text on the screen changes to ”connected” or “waiting for computer”, your game has been completely deployed.

To get to the game on your Zune, simply press the center button to exit XNA Game Studio Connect. Next navigate in your main menu to find the Games option. Inside of there will you will see your newly deployed game. Simply click Play and away you go. Enjoy the new snake game and good luck on your adventures of Zune game development with XNA Game Studio.

Tags:

Follow the Discussion

  • Mr GigglesMr Giggles

    Hey man, this tutorial is great and I had no problems with it. Keep up the good work Smiley

  • RyanRyan

    Please Help, where do i get the content files? i cant figure out where i get them!

  • Clint RutkasClint I'm a "developer"

    @Ryan:  Content files aren't at CodePlex?  The link is at the top of the article.

  • ACILACIL

    ACTUALLY I HAVEN'T READ IT YET BUT I THINK THAT IT WILL BE GREAT + IT WILL HELP ME CREATING A XNA GAME

  • FreeFree

    Very Good Man But How Can I Download This Game Or Source Code of it?

  • Clint RutkasClint I'm a "developer"

    @Free codeplex link at the top of the article

  • AndrewAndrew

    On the part of the tutorial that deals with the grid class, the tutorial doesn't show some of the statements that pass the scale and halfscale integers.  I know it is explained in the text a little, but there is no code for it - except in the source code I had to download.  Also, the tutorial doesn't tell you to where explicitly to add code sometimes, it just tells you to type it in - as in I had completely unsorted code in my game1.cs file in the beginning until I looked at the source, and the tutorial doesn't tell you to make a new class a new file either (I guess you expect us to know this already).  But minus these complaints, it is a good tutorial and it has helped me so far.  

  • ge-forcege-force

    When I ran the code for displaying the orange, it gave me a ContentLoadException.

     Message="Error loading "Sprites/mushroom". GraphicsDevice component not found."

  • ge-forcege-force

    OK, Thanks! I'll look into the codeplex stuff soon!

  • Clint RutkasClint I'm a "developer"

    @ge-force and @Andrew the tutorials are not a full blown cut and copy and you'll have a working game.  They talk through the hard parts and we provide the source code then so you can walk along with it.

  • ge-forcege-force

    Where do u create the Scale and HalfScale? I don't see where they are defined in this code?

  • ge-forcege-force

    I got 11 errors -

    Invalid token '=' in class, struct, or interface member declaration

    Method must have a return type

    Identifier expected

    Invalid token '/' in class, struct, or interface member declaration

    'SammyTheSnake.Grid' does not contain a definition for 'MaxColumn'

    'SammyTheSnake.Grid' does not contain a definition for 'MaxRow'

    The name 'Scale' does not exist in the current context

    The name 'HalfScale' does not exist in the current context

    The name 'Scale' does not exist in the current context

    The name 'HalfScale' does not exist in the current context

    The name 'Scale' does not exist in the current context

    I don't exactly know where a lot of the referenced properties are declared.

  • Clint RutkasClint I'm a "developer"

    @ge-force so you'll need to add content in properly and have it named.  right now the cs file is looking for sprites/mushroom and isn't seeing anything.  You'll need to add in a new graphic or rename the orange.png (i think the rename will work)

  • ge-forcege-force

    No, sorry, I renamed orange to mushroom.

  • Clint RutkasClint I'm a "developer"

    @ge-force, I did a glance at the project off codeplex, I don't see any reference to a mushroom.  Do you have a file and line number?  Did you attempt to add in new sprites?

  • Clint RutkasClint I'm a "developer"

    @maha if not, tell me what parts didn't translate well and I'll see if I can't get someone to do a physical translation.

  • Clint RutkasClint I'm a "developer"

    @maha best I can do:  http://www.microsofttranslator.com/BV.aspx?ref=BVNav&from=&to=ar&a=http%3A%2F%2Fblogs.msdn.com%2Fcoding4fun%2Farchive%2F2008%2F10%2F06%2F8976852.aspx

    Hoping the translation is enough to get the points across.

  • mahamaha

    very goad but can you explain in arabic

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.