Making the Zune HD Sparkle with XNA Game Studio 3.1 – Part 2
Introduction
This is part two of a two-part tutorial dealing with touch and accelerometer input on the Zune HD. It’s assumed that readers have already read the first part, which provides a full discussion of building the application to this point, including required prerequisites. If you haven’t read it already, see Making the Zune HD Sparkle with XNA Game Studio 3.1 – Part 1.
This part of the tutorial adds a number of GUI elements to the application, making it more complete by adding an instruction screen and a menu to manipulate the behavior of the display.
As before, the source code can be downloaded here.
Adding an Instructions Screen
As I mentioned at the end of part one, the application, as it is, doesn’t give the user any indication of what to do: it initially starts with a black screen.
We’ll now add an instructions screen that will appear when the app is first started, and then will fade into the background, allowing the user to continue to create sparkles without the instructions visible.
I’ll do this as simply as possible: the instructions screen is simply another texture that will be displayed and faded. The instructions look like this:

Add this file (also in the source zip, if you downloaded it) to your content project. The Asset Name should be “Instructions” and the Build Action, as before with the flare image, should be “Compile”.
The Instructions Class
We’ll add a new class to handle the instructions screen, to avoid mucking up Game1′s code. In Visual Studio, add a new class called Instructions.cs. If you create the class by right-clicking the InputToyZuneHD project and selecting Add…, then New Item, and then Class, you’ll be provided with some skeleton code for your class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace InputToyZuneHD
{
class Instructions
{
}
}
Replace all the using lines after System with the following Xna.Framework namespaces :
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
We’ll be making use of these namespaces in our code, but won’t be needing System.Collections.Generic, System.Linq, or System.Text.
Now, begin filling in the class, adding some class data members:
private const float TIMETOSHOW = 6000.0f;
private const float FADETIME = 2000.0f;
private State state;
private float timeRemaining;
private Texture2D image;
private Color color;
private Vector2 pos;
The TIMETOSHOW and FADETIME members will be used to control how long to display the instructions, and are in milliseconds. There is also a State enumeration, made public so it can be seen outside the class, and related data member to track the state of the instructions display.
The next four members: timeRemaining, image, color, and pos, are used to update and display the instructions. Their use, if not already obvious, will be shortly.
Add a constructor, initializing the state and color members:
public Instructions() { state = State.HIDE; color = new Color(255,255,255,255); }
Also, add a loadContent public method that will be used to fill the image and pos members:
public void loadContent(ContentManager cm, GraphicsDevice gd)
{
image = cm.Load<Texture2D>("Instructions");
pos = new Vector2(
(gd.Viewport.Width - image.Width) * 0.5f,
(gd.Viewport.Height - image.Height) * 0.5f);
}
We’re placing the image in the middle of the display area (provided by GraphicsDevice.Viewport), based on the image dimensions.
Next, add a public method to show the instructions, and another to check to see if the instructions are visible or not. We’ll call these show and isVisible. Yes, these methods practically name themselves:
public void show()
{
state = State.DISPLAY;
color.A = 255;
timeRemaining = TIMETOSHOW;
}
public bool isVisible()
{
return (state != State.HIDE);
}
As long as the instructions are not in the HIDE state, they are visible.
The core of the Instruction class is really the update method, which serves to set the state and alpha-channel of the color, based on the time remaining:
public void update(float etms)
{
if (state == State.HIDE)
{
return;
}
timeRemaining -= etms;
if(timeRemaining < 0)
{
state = State.HIDE;
return;
}
else if (timeRemaining < FADETIME)
{
// fading out
state = State.FADEOUT;
color.A = (byte)(255.0f * timeRemaining / FADETIME);
}
}
You may recall that etms, the elapsed time in milliseconds since the last frame, was a value that we used in the first part of the tutorial. It’s used here to reduce the time remaining.
Lastly, we’ll add a method to draw the instructions image, using a SpriteBatch:
public void draw(SpriteBatch sb)
{
if (state == State.HIDE)
{
return;
}
sb.Draw(image, pos, color);
}
Rather self-explanatory: If the state of the instructions is HIDE, don’t draw it. Otherwise, draw it with the image, pos, and color members. Notice that this Draw call is far simpler than the one we used to draw the sparkles. That’s because we’re not applying any rotation to the image. If you’re simply drawing an unscaled, non-rotated image on the screen, this version of Draw is the way to go.
That’s it for the Instructions class! Next, we’ll hook it in to our game loop.
Adding the Instructions Class to the Game
Open the Game1.cs class that we worked with in the first part of the tutorial. We’ll add a new data member to Game1, and then initialize it in Game1‘s constructor:
List<Sparkle> sparkles; Instructions instructions; public Game1() { graphics = new GraphicsDeviceManager(this); sparkles = new List<Sparkle>(); instructions = new Instructions(); Content.RootDirectory = "Content"; ...
In Game1.LoadContent, add code to load the instructions content and show it (since we want the instructions to show as soon as the application starts):
flareImage = this.Content.Load<Texture2D>("Flare");
flareOffset = new Vector2(
flareImage.Width * 0.5f, flareImage.Height * 0.5f);
instructions.loadContent(this.Content, this.GraphicsDevice);
instructions.show();
In Game1.Update, update the instructions screen right after getting the elapsed time in milliseconds. There’s no need to update instructions if it’s not visible, however:
// elapsed time will be used in updating movement
float etms = gameTime.ElapsedGameTime.Milliseconds;
// update the instructions state if needed
if (instructions.isVisible())
{
instructions.update(etms);
}
The last thing we need to do is add the instructions to the draw code. Since we want the player to be able to draw sparkles on the screen even while the instructions are showing, we’ll draw the instructions first, right after the call to SpriteBatch.Begin, and before drawing any sparkles. In Game1.Draw, add:
spriteBatch.Begin();
// draw stuff in the background
if (instructions.isVisible())
{
instructions.draw(spriteBatch);
}
// draw the sparkles
foreach(Sparkle s in sparkles)
...
You should be able to build and deploy the application. Now, when you run it, you should be able to see the nifty instructions screen show as soon as the application loads, and watch it fade out after about six seconds.
Adding a Menu
There’s still one more part of the GUI that would be nice to have: the ability to show the instructions screen at will while the game is running, and also the ability to stop and re-start the sparkle fading, so the user can spend more time using the accelerometer to play with existing sparkles.
We’ll now add an interactive menu to accomplish this. It will be small and unobtrusive so that it won’t interfere with sparkle creation, but large enough that the player can actually use the menu. Our menu will consist of two buttons, one of which (the pause/play button for sparkle fading) will have two states.
The buttons look like this:
![]()
Add these files to the InputToyZuneHD content project. As before, they should have Asset Names that correspond to their filenames (“btnHelp”, “btnPause”, and “btnPlay”, respectively) and the Build Action of each should be set to “Compile”.
The Menu Class
As with the instruction screen, we’ll create a new class to handle the menu. It’ll be called Menu.cs. The using block should look like this:
using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Input;
Declare the following private data members at the beginning of the new Menu class:
private const int BUTTONMARGIN = 4;
private Texture2D pauseBtnImg;
private Texture2D playBtnImg;
private Vector2 pauseBtnPos;
private Texture2D helpBtnImg;
private Vector2 helpBtnPos;
private bool paused;
The BUTTONMARGIN represents the distance we’ll place each button from the edges of the screen, and the rest of the variables hold the texture and position of each button, and whether fading is paused or not. You’ll note that we only need one position for the pause/play buttons, since they’ll appear in the same position on the screen.
Now, add Menu’s constructor, setting paused to false:
public Menu()
{
paused = false;
}
As we did with the Instructions class, we’ll add a loadContent method to load the class content:
public void loadContent(ContentManager cm, GraphicsDevice gd)
{
pauseBtnImg = cm.Load<Texture2D>("btnPause");
playBtnImg = cm.Load<Texture2D>("btnPlay");
pauseBtnPos = new Vector2(
0, gd.Viewport.Height - pauseBtnImg.Height);
helpBtnImg = cm.Load<Texture2D>("btnHelp");
helpBtnPos = new Vector2(
gd.Viewport.Width - helpBtnImg.Width,
gd.Viewport.Height - helpBtnImg.Height);
}
The pause/play button is placed on the bottom-left part on the screen, and the help button is placed on the bottom-right. This will provide a minimal, but useful, user interface.
We’ll also add a public isPaused method that the Game1 class can use to determine whether or not to fade the sparkles:
public bool isPaused()
{
return paused;
}
Now, we’ll get to the core of this class. Unlike the instructions screen, the menu is interactive, so it must handle user input. We’ll create a handleInput method that takes a TouchLocation, so it can determine if the TouchLocation intersects one of the buttons, and a reference to the Instructions class, so we can show the instructions screen if the help button is pressed:
public bool handleInput(TouchLocation tl, Instructions instructions)
{
if (tl.Position.Y > (pauseBtnPos.Y - BUTTONMARGIN))
{
if (tl.Position.X < (pauseBtnImg.Width + BUTTONMARGIN))
{
// toggle the pause state.
paused = !paused;
}
else if (tl.Position.X > (helpBtnPos.X - BUTTONMARGIN))
{
// show the game instructions.
instructions.show();
}
return true;
}
return false;
}
The function returns true if it handled the input, and false if it didn’t. We’ll use this in Game1 to determine whether or not to draw a new sparkle for this TouchLocation.
Finally, there will be a draw method for the menu, so it can be displayed:
public void draw(SpriteBatch sb)
{
// draw the buttons.
sb.Draw((paused ? playBtnImg : pauseBtnImg), pauseBtnPos, Color.White);
sb.Draw(helpBtnImg, helpBtnPos, Color.White);
}
It’s time to hook the menu to the game loop, and see how it all comes together!
Adding the Menu Class to the Game
Open Game1.cs again, and add a new member variable for the Menu class:
List<Sparkle> sparkles;
Instructions instructions;
Menu menu;
In Game1′s constructor, initialize the menu:
instructions = new Instructions();
menu = new Menu();
Load the menu’s content as we did for the instructions. In Game1.LoadContent, add:
flareOffset = new Vector2(
flareImage.Width * 0.5f, flareImage.Height * 0.5f);
menu.loadContent(this.Content, this.GraphicsDevice);
instructions.loadContent(this.Content, this.GraphicsDevice);
Next, we’ll need to send touch events to the menu, to see if any of the buttons were pressed. Add the following lines of code to Game1.Update:
foreach (TouchLocation tl in touchCollection)
{
if ((tl.State == TouchLocationState.Pressed)
|| (tl.State == TouchLocationState.Moved))
{
if (tl.State == TouchLocationState.Pressed)
{
if (menu.handleInput(tl, instructions))
{
break;
}
}
// add sparkles based on the touch location
sparkles.Add(new Sparkle(tl.Position.X,
tl.Position.Y, ttms));
...
You might be wondering why, if tl.State == TouchLocation.Pressed was already tested for, we’re testing for it again?
The reason is this: I made a design choice to not allow the menu to be activated if the player simply drags his or her finger over the menu area during a swipe meant to create sparkles. Similarly, I didn’t want the pause/play button to be turned on and off if the player swipes the screen a little after pressing the button. Only a new touch will activate the menu.
Another design choice has been made here: if a menu button was touched, we use break instead of continue in the foreach loop. This causes all further touch-points to be ignored.
As an experiment, you might like to see what happens when you allow TouchLocationState.Moved to activate the menu, or use a continue instead of a break if a menu button was activated. I find that experimenting with design choices such as the above really gives me an better idea of how to design my user interface elements to respond as the user expects them to.
There’s only one thing left to do here, and that’s to draw the menu! In Game1.Draw, add a line to draw the menu, just before SpriteBatch.End is called:
// draw the foreground (AKA the HUD)
menu.draw(spriteBatch);
spriteBatch.End();
The menu is drawn after the sparkles so it will always appear over the sparkles. This type of display is often called a heads-up display, or HUD, since it is drawn over any game action. Health-bars, mini-maps and other such game interface elements are often drawn this way.
That’s all. Build, deploy, and run the application again to see its final form (at least, for now). You should be able to display the instructions again by pressing the help button, and you should be able to toggle the fading off and on by using the pause and play buttons.
I hope that you enjoyed the tutorial. If you have any questions about its content, feel free to contact me.
Tags: touch, UI, user interface, XNA Game Studio Docs & Resources, Zune HD