May 2009

Volume 24 Number 05

Going Places - Gaming In The Key Of Zune

By Mike Calligaro | May 2009

Contents

Man, That C Is Sharp
Anatomy of a Game
Sprites on Pixie Dust
Of Asset Names and Color Keys
Ya Gotta Move To Play
Sprite in a Box
Bing Bang Boom
Advanced Drawing
The Rest Is Up To You

My son, who's been playing Xbox since he was three, has started showing an interest in programming—specifically writing games. This prompted me to download XNA Game Studio and dive into it myself, you know, so that I could teach him. That I've always wanted to write my own games had nothing to do with it. Really.

XNA Game Studio 3.0 added the ability to write games for Zune, which means the Zune is now a mobile development platform. So it's fair, er, game for a Going Places article. If you're already an XNA developer, I may be reviewing some concepts you already understand. But, if you're like me, a programmer who is enthusiastic about but fairly new to game development, read on.

Man, That C Is Sharp

For the majority of my fifteen year career at Microsoft, I've been a systems and drivers developer. My language of choice and necessity has been a fairly bare bones C++. I rarely get to use runtimes like MFC and the Microsoft .NET Framework. Until recently, I couldn't have even spelled STL much less made use of it.

That's my day job. But in my off time I love playing around with C#. Mostly I write little apps for my Windows Mobile devices and occasionally for my PC. C++ doesn't treat me as badly as it does many native developers, but I still I get a little giddy when I write in C#. You can get a lot done with very few lines of code. I swear, C# is so much fun they should make it a controlled substance.

In the fun category, XNA Game Studio is like C# on steroids. The team over there has done an amazing job of making game development easy. The framework is straightforward, the hard stuff is largely handled for you, and they've released a ton of samples aimed at teaching you the various aspects of game development.

Start at creators.xna.com. From there you can download the free XNA Game Studio 3.0. If you already use one of the various incarnations of Visual Studio 2008, XNA Game Studio will integrate with it. If you don't have Visual Studio 2008, don't fret. XNA Game Studio also works with the free Visual C# Express Edition. (In other words, although I mention Visual Studio, you can substitute Visual C# Express Edition if that's what you're using.)

The creators.xna.com Web site is also full of great information to get you going. Click the Education link at the top of the page to find beginner's guides, samples, and how-to's. The "Beginner's Guide to 2D Games" is especially good, as is the documentation that gets installed with XNA Game Studio. In some cases, the installation documentation has information that's not on the Web. In Visual studio, you can get to that documentation by selecting Help | Contents and setting the filter to XNA Game Studio 3.0.

XNA Game Studio lets you write one code base and deploy it to Xbox, PC, and Zune. Everything I do here will work on all three platforms. The free downloads are all you need to develop for PC or Zune, but Xbox development requires a Premium membership that costs a yearly fee.

Anatomy of a Game

Once you've got XNA Game Studio 3.0 installed, you can create a Hello World game for Zune by selecting File | New | Project. Then select Visual C# | XNA Game Studio 3.0 and click Zune Game (3.0). While the program this generates doesn't actually say Hello World, it does watch for input, paint the background blue, and exit on demand.

If you're expecting something really complicated, you're in for a pleasant surprise. Your entire game infrastructure amounts to around twenty lines of code spread over six functions. Here's a description of those functions.

The constructor is just a standard, garden variety C# object constructor. Treat it the same as you would any other one.

Initialize is called after your constructor finishes. It lets you do further initialization, especially for things involving the graphics system.

LoadContent is called after Initialize and is where you load your images and sounds.

UnloadContent is called on game exit and lets you unload your content. But, since the unloading of most images and sounds is handled for you, you generally don't need to do anything here.

Update is probably the place where game development differs most from normal app development. Instead of a message loop, games regularly poll their input, calculate what to do with it, and then draw the result of those calculations. Most of that happens in Update, so this is where most of your code will live.

Draw is called after each Update and is where you draw your images.

That's it. Even though the program is very short, it starts, paints the screen blue, reads input, and exits when you hit the back button. What's that? Did you think game development would be hard? Not with XNA Game Studio.

Sprites on Pixie Dust

That was easy, but a blue background that exits does not make for a compelling game. You need actual images if you're going to have a game. Thankfully, putting pictures on the screen isn't difficult.

In a minute I'm going to show you the code, but first you need to load an image into your project. In Visual Studio, you should have a window called Solution Explorer (if you don't, you can find it under the View menu). In the Solution Explorer, right-click on Content and select Add | Existing Item. From here you can point it at any .bmp, .jpg, .png, .tga, or .dds image file.

Let's assume you have a picture of a person called player.jpg and that you just added it to Content. Figure 1 shows the code to display that image on the screen.

Figure 1 Displaying an Image

// Add these member variables Texture2D texture; Vector2 position; // Add this line to the constructor position = new Vector2(100, 100); // Add this line to LoadContent() texture = Content.Load<Texture2D>("player"); // And add these lines to Draw(); spriteBatch.Begin(); spriteBatch.Draw(texture, position, Color.White); // (Draw the rest of your sprites) spriteBatch.End();

Before I explain those lines, let's talk lingo. XNA Game Studio started as a development system for the Xbox. As such, it's all based on 3D graphics. 2D games (and, since the Zune doesn't have a 3D renderer, you need to do 2D games) are really 3D games where everything is really thin. So you'll see 3D terms sprinkled around your code. For instance, while sprite is the normal term for an image in a 2D game, the word texture comes from 3D gaming. As far as XNA Game Studio is concerned, sprites are really thin 3D rectangles with textures mapped to them.

The Content.Load function takes an Asset Name, which by default is the file name without the extension. Since I loaded an image called player.jpg, I passed the string "player" to Content.Load. In the LoadContent function, you'll find that there's already a line that allocates a SpriteBatch. All drawing is done with this SpriteBatch. In the Draw function, you tell the SpriteBatch you're about to begin, draw all the sprites, and then tell it you're finished.

The position member variable is a vector structure that holds the x and y location where you want the sprite to be drawn. In the example, I used pixel location (100, 100). The color is a tint you can add to your sprites. Using white leaves the image unchanged.

So, to get an image on the screen, you just needed to load the texture and tell the sprite batch to draw it. The first image basically takes four lines of code, and each subsequent one takes two more.

Of Asset Names and Color Keys

Hopefully, at this point you're looking at your player image on the screen and thinking that this isn't so difficult. But maybe you're not fully satisfied. What if you don't like that your code needs to use the file name of the image? And, more importantly, what if the picture you loaded is rectangular, but the player isn't? How can you draw just the player and not the background?

To answer both of these questions, you need to look at the properties for the image you loaded. Go back to Solution Explorer and right-click on the file (in my example, player.jpg). Select Properties. A properties pane will appear below the Solution Explorer, giving you a lot of useful information. First look at Asset Name. This is the name you passed to Content.Load. If you want to make it something other than the file name, change it here.

Now expand Content Processor by clicking the little + to the left of the name. The property you care about here is the Color Key Color. Any pixel whose color matches the color key will be transparent. That's how you take a rectangular image and strip out the background. So, to get rid of your sprite's background, either change its color or change the color key setting so that they match. The setting defaults to Red, Green, Blue, and Alpha values of 255, 0, 255, 255, which is magenta.

Ya Gotta Move To Play

Certainly a single sprite on a blue background isn't going to be the next big thing in gaming. Minimally, you need the user to be able to move that sprite around somehow. And, to do that, you need to know what the user wants to do. If you've looked at the Update function in your program, you probably already have an idea how to do this. The default code reads the input and exits if the back key is pressed. Unfortunately, they kind of monopolized the input there. Let's change things around a bit to let you use the input as well.

Here's what you currently see:

if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();

I suggest changing it to look like this:

GamePadState input = GamePad.GetState(PlayerIndex.One); if (input.Buttons.Back == ButtonState.Pressed) this.Exit();

Now you can use the input variable to look at user input. Note that input is a GamePadState. Think of the Zune's input as an Xbox gamepad that's missing some of the buttons. Figure 2 provides the full list of mappings for the Zune's buttons.

Figure 2 Zune Button Mappings
Button Mapping
Back GamePadState.Buttons.Back
Play/pause GamePadState.Buttons.B
DPad center GamePadState.Buttons.A
DPad edges GamePadState.DPad
Sliding on the ZunePad GamePadState.Thumbsticks.Left
Clicking on the ZunePad GamePadState.Buttons.LeftShoulder

Okay, so let's make the player move. The code in Figure 3 covers both DPad and ZunePad input, so it will work on any Zune. (The original Zunes didn't have the ZunePad functionality.)

Figure 3 Supporting Movement

// add these lines to Update() const int c_speedScalar = 100; Vector2 speed = Vector2.Zero; speed.X = input.ThumbSticks.Left.X * c_speedScalar; speed.Y = input.ThumbSticks.Left.Y * c_speedScalar * -1; if (input.DPad.Left == ButtonState.Pressed) speed.X -= c_speedScalar; else if (input.DPad.Right == ButtonState.Pressed) speed.Y += c_speedScalar; if (input.DPad.Up == ButtonState.Pressed) speed.Y -= c_speedScalar; else if (input.DPad.Down == ButtonState.Pressed) speed.Y += c_speedScalar; position += speed * (float)gameTime.ElapsedGameTime.TotalSeconds;

Remember that the Draw routine is drawing the player at the location specified by the position member variable. So, to move the sprite, all you need to do is update that location.

The really fancy part here is the last line where you change the position. The Xbox has stronger graphics capabilities than the Zune, so its Update routine gets called more frequently. In other words, it has a higher framerate. This means you need to make movement be independent of the framerate. To accomplish this, use the input to calculate a speed, not a position. Then convert the speed to a position by multiplying by the amount of time that has passed since the last call to Update.

Sprite in a Box

Anyone can move. The trick is knowing when to stop.

If your game is going to be interesting, then you need to know when one sprite collides with another. The fancy game developer's term for this is collision detection, and the simplest way to do collision detection in XNA Game Studio is to use an object called a BoundingBox. Basically, you wrap invisible boxes around your objects and then check to see if any of those boxes intersect with each other.

Aside from the current game being less than exciting, the biggest problem is that the sprite can move off the screen. Let's fix that. Whenever the sprite hits an edge, we'll make it jump back to location (100, 100).

First, you'll need a bounding box for the screen:

// Add this member variable BoundingBox bbScreen; // Add these lines to Initialize() Vector3 min = new Vector3(0, 0, 0); Vector3 max = new Vector3(graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height, 0); bbScreen = new BoundingBox(min, max);

There's a bit of magic here, but it's not very complicated magic. The Hello World framework gave you a member variable called graphics that contains the width and height of the game screen. You can set the upper-left corner of the bounding box to the upper-left corner of the screen and the lower-right corner of the bounding box to the lower-right corner of the screen. BoundingBoxes require 3D vectors, so set the z coordinates to 0.

Now let's create a box for the sprite, like so:

// Add these lines to Update() after // the position has been calculated. Vector3 min = new Vector3((int)position.X, (int)position.Y, 0); Vector3 max = new Vector3((int)position.X + texture.Width - 1, (int)position.Y + texture.Height - 1, 0); BoundingBox bb = new BoundingBox(min, max);

Finally, let's see if the sprite is fully contained in the screen. If not, it must have hit an edge. Add these lines directly below the ones added previously:

ContainmentType ct = bbScreen.Contains(bb); if (ct != ContainmentType.Contains) position = new Vector2(100, 100);

Now the sprite will jump back to (100, 100) whenever it hits the edge of the screen. You, of course, should probably do something more intelligent, like stop or warp to the other side.

Colliding with the edge of the screen is a little different from colliding with another object. The normal situation is that you're inside the screen, but you're outside of other objects. So, to check for a collision with the edge of the screen, you want to see if the screen's box contains you. But if you want to check for a collision with another object, you want to see if that object's box intersects with you. To that end, the BoundingBox object has an Intersects member function. Intersects returns a bool that is true when the two boxes intersect. Use Intersects if you want to check whether two sprites hit each other.

Bing Bang Boom

You're now one technology away from writing your own games. You can draw images, move them in response to user input, and act on them running into each other. Since this is a game, you probably want a sprite collision to end in a huge explosion. You already know how to draw the explosion, but it won't be really satisfying unless you rattle the player's speakers, too. Fortunately, playing sounds is even easier than drawing sprites.

First, you'll need to add a sound to your project. The process here is similar to adding an image. Go to the Solution Explorer, right-click on Content, and choose Add | Existing Item. From there you can choose a .wav, .wma, .mp3, or .xap file. Also, like the image file, if you want a different Asset Name than the file name, you can change it in the properties.

Now let's play that sound. This code assumes that the file you loaded was explode.wav:

// Add this member variable SoundEffect sndBoom; // Add these lines to LoadContent() ContentManager contentManager = new ContentManager(Services, "Content"); sndBoom = contentManager.Load<SoundEffect>("explode"); // Add these lines to Update() if (input.Buttons.B == ButtonState.Pressed) sndBoom.Play();

The contentManager.Load call is similar to the function you used to load your sprite's texture. Then, as you see, when you want to hear the sound, you just Play it. Note that, as written, this code will try to play a new sound every frame while the B button is pressed. Depending on the length of the sound, this could result in too many copies of it playing and your game throwing an exception. You should guard against that by playing only once per button press. The classic way to do that is to store the previous state of the input and act only when the state changes.

Advanced Drawing

You've now got the basic tools you need to build your own games. I suggest you take some time to play with those tools and get comfortable with them. When you're ready for more, you can read this section for a brief introduction to advanced ideas. The help documents for these functions will give you everything you need to know to use them.

First, let's talk about the Z order. As the code currently stands, if two sprites overlap, the one that's drawn last will end up on top. That's kind of a pain. A much better method is to give each sprite a variable that states the order in which it should be drawn. To do this, you need to use more complicated versions of spriteBatch.Begin and spriteBatch.Draw, like so:

spriteBatch.Begin( SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None); spriteBatch.Draw(texture, position, null, Color.White, 0, Vector2.Zero, 1.0F, SpriteEffects.None, zOrder);

Spend some time with the SpriteBatch documentation and experiment. These functions are a bit complicated, but they're also very powerful. For instance, if you want to change the sizes of your sprites, you can use a rectangle instead of a vector for the position. If the rectangle bounds are different than the texture's size, XNA Game Studio will scale it for you automatically. If your texture has multiple images that you want to cycle between, you can pass in a source rectangle that specifies which part of the texture to draw.

If you want a translucent sprite, change the alpha value of the color you pass to draw:

Color trans = new Color( Color.White.R, Color.White.G, Color.White.B, 128);

Pass trans to draw instead of Color.White, and the sprite would be half translucent.

Finally, here's some neat code you can use to rotate the screen from portrait to landscape mode:

// Add this member variable Matrix matTransform; // Add these lines to the constructor matTransform = Matrix.Identity; matTransform *= Matrix.CreateRotationZ(MathHelper.ToRadians(90)); matTransform *= Matrix.CreateTranslation(240, 0, 0); // In Draw() change spriteBatch.Begin to this spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None, matTransform);

The Rest Is Up To You

The full code for my example is shown in Figure 4, and it should get you moving. If you run aground, the creators.xna.com Web site is full of samples that show you how to do all sorts of arcane game development things, from per-pixel bounding boxes to physics models.

Figure 4 A Simple Zune Game

public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Matrix matTransform; Texture2D texture; SoundEffect sndBoom; BoundingBox bbScreen; Vector2 position; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; position = new Vector2(100, 100); matTransform = Matrix.Identity; // Uncomment these lines to do Landscape Mode //matTransform *= Matrix.CreateRotationZ( MathHelper. ToRadians(90)); //matTransform *= Matrix.CreateTranslation(240, 0, 0); } protected override void Initialize() { Vector3 min = new Vector3(0, 0, 0); Vector3 max = new Vector3( graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height, 0); bbScreen = new BoundingBox(min, max); base.Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); texture = Content.Load<Texture2D>("player"); ContentManager contentManager = new ContentManager(Services, @"Content\Audio"); sndBoom = contentManager.Load<SoundEffect>("explode"); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { GamePadState input = GamePad.GetState(PlayerIndex.One); if (input.Buttons.Back == ButtonState.Pressed) this.Exit(); if (input.Buttons.B == ButtonState.Pressed) sndBoom.Play(); const int c_speedScalar = 100; Vector2 speed = Vector2.Zero; speed.X = input.ThumbSticks.Left.X * c_speedScalar; speed.Y = input.ThumbSticks.Left.Y * c_speedScalar * -1; if (input.DPad.Left == ButtonState.Pressed) speed.X -= c_speedScalar; else if (input.DPad.Right == ButtonState.Pressed) speed.X += c_speedScalar; if (input.DPad.Up == ButtonState.Pressed) speed.Y -= c_speedScalar; else if (input.DPad.Down == ButtonState.Pressed) speed.Y += c_speedScalar; position += speed * (float)gameTime.ElapsedGameTime.TotalSeconds; Vector3 min = new Vector3((int)position.X, (int)position.Y, 0); Vector3 max = new Vector3((int)position.X + texture.Width - 1, (int)position.Y + texture.Height - 1, 0); BoundingBox bb = new BoundingBox(min, max); ContainmentType ct = bbScreen.Contains(bb); if (ct != ContainmentType.Contains) position = new Vector2(100, 100); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None, matTransform); spriteBatch.Draw(texture, position, null, Color.White, 0, Vector2.Zero, 1.0F, SpriteEffects.None, 0.5F); spriteBatch.End(); base.Draw(gameTime); } }

I hope you find XNA game development in C# to be as much fun as I do. Maybe something you write will become the next big thing in gaming... if my son doesn't beat you to it. Happy coding.

Send your questions and comments to goplaces@microsoft.com.

Mike Calligaro is a Senior Development Lead with the Windows Mobile team at Microsoft and a contributor to the Windows Mobile team blog, which you can see at blogs.msdn.com/windowsmobile.