Archive for November 2011

XNA Snake

So I was going through the App Hub forums, randomly locking and deleting threads like normal (well, maybe not so randomly and not really that often relatively despite what everyone says :D) when I came across a post asking how to do the classic Snake game. As so often happens, I thought “This might be interesting to see how quickly I could do this” and went off and started hacking together a basic snake game.

If you don’t know this game, read about it here.

I thought about explaining everything, but it’s relatively simple and you should be able to puzzle everything out rather quickly. If not, post a comment. 🙂

I will say that the one thing that’s likely to trip up someone that hasn’t done rotated graphics is the code to draw the snake sections. I’ll briefly talk about that portion of code.

I decided to have the snake head have eyes because a blind snake is a bit silly:

So if the snake is heading north, nothing special needs to be done. If the snake (or any part for that matter) is heading in a direction other than north, the texture needs to be rotated. This means using one of the overloaded SpriteBatch Draw calls that takes a rotation parameter:

SpriteBatch.Draw (Texture2D, Vector2, Nullable<Rectangle>, Color, Single, Vector2, Single, SpriteEffects, Single)

The parameters for this overload are: texture, screen position, optional source rectangle, color tint, rotation, origin, scale, effects, and sort depth. The last three we really aren’t going to worry about and will just get default values. The source rectangle will get a null since we’re always drawing the entire thing. The Color will, of course, be Color.White. The screen position, rotation, and origin are the tricky pieces here.

The screen position will depend on whether we’re drawing the texture rotated:

part.Location * 32 + new Vector2(GetOffset(part.PartDirection), GetOffset(part.PartDirection))

The Location member of the SnakePart class we use for each part is a Vector2. Each square of the area the snake can run around is 32 pixels square so we can just multiply the member by 32 and both X and Y values will be multiplied by 32. To that result we then add a Vector2 that will either have both X and Y values as 0 or 16 (the middle of the texture):

       private int GetOffset(Direction dir)
        {
            if (dir != Direction.North)
                return 16;
            else
                return 0;

        }

The rotation parameter is done with a simple function call:

        private float GetRotation(Direction dir)
        {
            float ret = 0.0f;

            switch (dir)
            {
                case Direction.North:
                {
                    ret = 0.0f;
                    break;
                }
                case Direction.East:
                {
                    ret = MathHelper.PiOver2;
                    break;
                }
                case Direction.South:
                {
                    ret = MathHelper.Pi;
                    break;
                }
                case Direction.West:
                {
                    ret = MathHelper.PiOver2 * 3;
                    break;
                }
            }

            return ret;
        }

The MathHelper class gives us an easy way to get the value for the 3 compass points other than the default North.

The origin point of the texture is another direction dependent value:

part.PartDirection != Direction.North ? _vecOrigin : Vector2.Zero

_vecOrigin is a Vector2 with the X and Y members set to 16.

The complete draw Method of the Snake class that I use looks like this:

        public void Draw(SpriteBatch sb)
        {
            foreach (SnakePart part in _parts)
                sb.Draw(_partGraphics[(int)part.PartType], part.Location * 32 + new Vector2(GetOffset(part.PartDirection), GetOffset(part.PartDirection)), null, Color.White, GetRotation(part.PartDirection),
                    part.PartDirection != Direction.North ? _vecOrigin : Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);

        }

 

A little unintuitive maybe, but that’s how it is when you’re dealing with rotated sprites. The big thing to remember is that if you’re rotating the sprite the origin needs to be the center of the texture otherwise the upper left corner. Likewise, the destination also has to be offset by half the width and height of the texture.

Here’s my result of a couple of hours of work. One thing I haven’t implemented is checking if the snake head collides with a part of itself. I had to leave something for you guys to do, right? 🙂 Also, a check should be done to see if the game is over and, if so, the Initialize method of the Game class should be called.

Both Gamepad and Keyboard (DPad and arrow keys respectively) can be used. The snake also increases speed every other fruit that’s eaten. I did this just for testing and it should increase speed a bit slower than that. See if you can figure out how to do that. It should be quickly obvious.