Scaling an image
Controlling the size of images is a basic technique in Windows Phone 7 XNA programming. Mastering this technique will help you implement many special visual effects in 2D. In this recipe, you will learn how to zoom an image in and out for a special visual effect. As an example, the image will jump up to you and go back. In the jumping phase, the image will fade out gradually. When the image becomes transparent, it will fall back with fading in.
How to do it…
The following mandatory steps will lead you to complete the recipe:
- Create a Windows Phone Game in Visual Studio 2010 named ImageZoomInOut, change Game1.cs to ImageZoomGame.cs. Then add the Next.png file from the code bundle to the content project. After the preparation work, insert the following code to the field of the ImageZoomGame class:
[code]
// Image texture object
Texture2D texImage;
// Image position
Vector2 Position;
// The scale factor
float scale = 1;
// The rotate factor
float rotate = 0;
// Alpha value for controlling the image transparency
float alpha = 255;
// The color of image
Color color;
// Timer object
float timer;
// Bool value for zooming out or in.
bool ZoomOut = true;
[/code] - In the Initialize() method, we define the image centered in the middle of the screen and the color:
[code]
Position = new Vector2(GraphicsDevice.Viewport.Width / 2,
GraphicsDevice.Viewport.Height / 2);
color = Color.White;
[/code] - Load the Next.png file in the LoadContent() method with the following code:
[code]
texImage = Content.Load<Texture2D>(“Next”);
[/code] - Add the code to the Update() method to control the size transparency and rotation of the image:
[code]
// Accumulates the game elapsed time
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
// Zoom out
if (ZoomOut && alpha >= 0 && timer > 50)
{
// If alpha equals 0, zoom the image in
if (alpha == 0.0f)
{
ZoomOut = false;
}
// Amplify the image
scale += 0.1f;
// Rotate the image in clockwise
rotate += 0.1f;
// Fade the image out
if (alpha > 0)
{
alpha -= 5;
}
color.A = (byte)alpha;
// Reset the timer to 0 for the next interval
timer = 0;
}
// Zoom in
else if (!ZoomOut && timer > 50)
{
// If alpha equals 255, zoom the image out
if (alpha == 255)
{
ZoomOut = true;
}
// Scale down the image
scale -= 0.1f;
// Rotate the image counter-clockwise
rotate -= 0.2f;
// Fade the image in
if (alpha < 255)
{
alpha += 5;
}
color.A = (byte)alpha;
// Reset the timer to 0 for the next interval
timer = 0;
}
[/code] - To draw the image and effects on the Windows Phone 7 screen, add the code to the Draw() method:
[code]
spriteBatch.Begin(SpriteSortMode.Immediate,
BlendState.NonPremultiplied);
spriteBatch.Draw(texImage, Position, null, color, rotate, new
Vector2(texImage.Width / 2, texImage.Height / 2), scale,
SpriteEffects.None, 0f);
spriteBatch.End();
[/code] - Now, build and run the application. The application runs as shown in the following screenshots:
How it works…
In step 1, texImage holds the image for controlling; the Position variable represents the image position; scale will be used to control the image size; as the rotation factor, the rotate variable will store the value for rotation; alpha in color controls the image transparency degree; timer accumulates the game elapse milliseconds; the last variable ZoomOut determines whether to zoom the image out.
In step 2, we define the Position of the image in the center of the screen and set the color to white.
In step 4, the accumulated timer is used to control the period of time between the two frames. The next part is to check the direction of scale. If ZoomOut is true, we will increase the image size, decrease the alpha value and rotate the image in a clockwise direction, then reset the timer. Otherwise, the behavior is opposite.
In step 5, we set the BlendState in SpritBatch.Begin() to NonPremultiplied because we change the alpha value of color linearly, then draw the images with scale and rotation factors around the image center.
Creating a Simple Sprite Sheet animation in a 2D game
In Windows Phone 7 game programming, it is an obvious performance cost if you wanted to render a number of images. You need to allocate the same quantity of texture objects for the images in your game. If this happens in the game initialization phase, the time could be very long. Considering these reasons, the talented game programmers in the early days of the game industry found the solution through the Sprite Sheet technique, using a big image to contain a set of smaller images. It is very convenient for 2D game programming, especially for the sprite animation. The big advantage of using the Sprite Sheet is the ability to create character animation, complex effects, explosions, and so on. Applying the technique will be performance sweet for game content loading.
The Sprite Sheet has two types:
- Simple Sprite Sheet, where all the smaller images have the same dimensions
- Complex Sprite Sheet, where all the images in the sheet have different dimensions
In this recipe, you will learn how to create the Simple Sprite Sheet and use it in your Windows Phone 7 game.
In Simple Sprite Sheet, every subimage has the same size, the dimension could be defined when the Sprite Sheet was initiated. To locate the designated sub-image, you should know the X and Y coordinates, along with the width and height, and the offset for the row and column. For Simple Sprite Sheet, the following equation will help you complete the work:
[code]Position = (X * Offset, Y * Offset)[/code]
For convenience, you can define the size of the subimage in digital image processing software, such as Adobe Photoshop, Microsoft Expression Design, Paint.Net, GIMP, and so on.
In this recipe, you will learn how to use the Sprite Sheet in your own Windows Phone 7 Game.
Getting ready
In the image processing tool, we create a Sprite Sheet as the next screenshot. In the image, every subimage is surrounded by a rectangle 50 pixels wide and 58 pixels high, as shown in the left-hand image in the following screenshot.
In a real Windows Phone Game, I am sure you do not want to see the border of the rectangle. As part of the exportation process, we will make the rectangles transparent, you just need to change the alpha value from 100 to 0 in code, and the latest Sprite Sheet should look similar to the right-hand image in the following screenshot:
We name the Sprite Sheet used in our example SimpleSpriteSheet.png.
Now the Sprite Sheet is ready, the next part is to animate the Sprite Sheet in our Windows Phone 7 game.
How to do it…
The following steps will give you a complete guidance to animate a Simple Sprite Sheet:
- Create a Windows Phone Game project in Visual Studio 2010 named SimpleSpriteSheetAnimation, change Game1.cs to SimpleSpirteSheetGame.cs, adding the lines to the field of the SimpleSpriteSheetGame class:
[code]
// Sprite Texture
Texture2D sprite;
// A Timer variable
float timer = 0f;
// The interval
float interval = 200;
// Frame Count
int FrameCount = 4;
// Animation Count
int AnimationCount = 2;
// Current frame holder
int currentFrame = 0;
// Width of a single sprite image, not the whole Sprite
int spriteWidth = 50;
// Height of a single sprite image, not the whole Sprite
int spriteHeight = 58;
// A rectangle to store which ‘frame’ is currently being
Rectangle sourceRect;
// The center of the current ‘frame’
Vector2 origin;
// Index of Row
int row = 0;
// Position Center
Vector2 screenCenter;
[/code] - Load the Sprite Sheet image. Put the code into the LoadContent() method:
[code]
sprite = Content.Load<Texture2D>(“SpriteSheet”);
screenCenter = new Vector2(GraphicsDevice.Viewport.Width / 2,
GraphicsDevice.Viewport.Height / 2);
[/code] - This step is to animate the Simple Sprite Sheet. Insert the code in to the Update() method:
[code]
// Increase the timer by the number of milliseconds since
// update was last called
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
// Check the timer is more than the chosen interval
if (timer > interval)
{
//Show the next frame
currentFrame++;
//Reset the timer
timer = 0f;
}
// If reached the last frame, reset the current frame back to
// the one before the first frame
if (currentFrame == FrameCount)
{
currentFrame = 0;
}
// React to the tap gesture
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed)
{
// Change the sprite sheet animation
row = (row + 1) % AnimationCount;
}
// Compute which subimage will be rendered
sourceRect = new Rectangle(currentFrame * spriteWidth,
row * spriteHeight, spriteWidth, spriteHeight);
// Compute the origin position for image rotation and scale.
origin = new Vector2(sourceRect.Width / 2,
sourceRect.Height / 2);
[/code] - Draw the Sprite Sheet on the screen. Add the code in the Draw() method:
[code]
spriteBatch.Begin();
//Draw the sprite in the center of an 800×600 screen
spriteBatch.Draw(sprite, screenCenter, sourceRect,
Color.White, 0f, origin, 3.0f, SpriteEffects.None, 0);
spriteBatch.End();
[/code] - Now, build and run the application. When you tap on the screen, the animation will change, as shown in the following screenshots:
How it works…
In step 1, the sprite object will hold the Sprite Sheet image; the timer variable will accumulate the game elapsed time; the interval variable defines the period of time between two frames in milliseconds; AnimationCount shows the number of animations that will be played; currentFrame indicates the playing frame, and also presents the column in Sprite Sheet image; spriteWidth and spriteHeight define the width and height of the currently rendering subimage. In this example the width and height of the Sprite subimage is 50 and 58 pixels respectively; the sourceRectangle lets the SpriteBatch.Draw() know which part of the Sprite Sheet image will be drawn; the row variable shows which animation will be rendered, 0 means the animation starts from the first.
In step 3, we accumulate the game elapsed time. If the accumulated time on the timer is greater than the interval, the current frame goes to the next frame, and then sets the timer to 0 for the next interval. We then check whether the current frame is equal to the FrameCount. If yes, it means that the animation ended, then we set the currentFrame to 0 to replay the animation. Actually, the currentFrame represents the current column of the Sprite Sheet image. Next, we should make sure which animation we want to render, here, the react to the tap gesture to set the row value within the AnimationCount to change the animation. When the currentFrame for the column and the row are ready, you can use them to locate the sub-image with the rectangle object sourceRect; the last line in this code will compute the origin position for the image rotation and scale, here we set it to the center of the Sprite Sheet subimage.
In step 4, the Draw() method receives the texture parameter; position of texture drawn on screen; the rectangle for defining the part of texture that will be drawn; the color White means render the texture in its original color; rotation angle set to 0 stands for no rotation; the scale factor will let the rendering device know how big the texture will be, 3 here means the final texture size will be three times larger than the original; SpriteEffects means the effects will be applied to render the texture, SpriteEffects.None tells the application not to use any effect for the texture; the final parameter layerDepth illustrates the texture drawing order.
Creating a Complex Sprite Sheet animation in a 2D game
The Complex Sprite Sheet contains subimages with different sizes. Moreover, for every Complex Sprite Sheet, it has an additional description file. The description file defines the location and size of every subimage. It is the key difference between a Simple Sprite Sheet and a Complex Sprite Sheet. For a Simple Sprite Sheet, you can compute the location and size of the sub-image with the same width and height; however, for a Complex Sprite Sheet, it is harder, because the subimages in this kind of Sprite Sheet are often placed for the efficient use of space. To help identify the coordinates of the sprites in the Complex Sprite Sheet, the description file offers you the subimage location and size information. For the Sprite Sheet animation use, the description file also provides the animation name and attributes. The following screenshot shows an example of a Complex Sprite Sheet:
Getting ready
In the Sprite Sheet, you can see that the subimages have different sizes and locations. They are not placed in a regular grid, and that’s why we need the description file to control these images. You may ask how we can get the description file for a designated Complex Sprite Sheet. Here, we use the tool SpriteVortex, which you can download from Codeplex at http://spritevortex.codeplex.com/.
The following steps show you how to process a Complex Sprite Sheet using SpriteVortex:
- When you run SpriteVortex, click Import Spritesheet Image, at the top-left, as shown in the following screenshot:
- After choosing MegaManSheet.png from the image folder of this chapter, click on Alpha Cut, at the top bar of the main working area. You will see the subimages in the Complex Sprite Sheet individually surrounded by yellow rectangles, as shown in the following screenshot:
- Next, you could choose the subimages to create your own animation. In the left Browser panel, we change the animation name from Animation 0 to Fire.
- Then, we choose the sub-images from the Complex Sprite Sheet. After that, we click the button—Add Selected to Animation. The selected images will show up in the Animation Manager from the first image to the last one frame-by-frame.
- Similar to these steps, we add other two animations in the project named Changing and Jump.
- The final step is to export the animation definition as XML, the important XML will be used in our project to animate the subimages, and the entire process can be seen in the following screenshot:
The exported animation XML file named SpriteDescription.xml can be found in the project content directory for this recipe.
In the XML file, you will find the XML element—Texture, which saves the Sprite Sheet path. The XML—Animation—includes the animation name and its frames, the frames contain the subimages’ location and size. Next, you will learn how to use the Complex Sprite Sheet and the XML description to animate the sprite.
How to do it…
The following steps will show you how to animate a Complex Sprite Sheet:
- Create a Windows Phone Game project named ComplexSpriteSheetAnimation, change Game1.cs to ComplexSpriteSheetAnimationGame.cs. Then add the exported Sprite Sheet description file SpriteDescription.xml to the content project, changing the Build Action property of the XML file from Compile to None because we customized the content processing code for XML format, and the Copy to Output Directory property to Copy Always. This will always copy the description file to the game content output directory of the application.
- The description file is an XML document; we need to parse the animation information when loading it to our game. Subsequently, we add some description parsing classes: Frame, Animation, SpriteTexture, AnimationSet, and SpriteAnimationManager in the SpriteAnimationManager.cs of the main project. Before coding the classes, one more reference System.Xml. Serialization must be added to the project reference list as we will use the XML Serialization technique to parse the animation. Now, lets define the basic classes:
[code]
// Animation frame class
public class Frame
{
// Frame Number
[XmlAttribute(“Num”)]
public int Num;
// Sub Image X positon in the Sprite Sheet
[XmlAttribute(“X”)]
public int X;
// Sub Image Y positon in the Sprite Sheet
[XmlAttribute(“Y”)]
public int Y;
// Sub Image Width
[XmlAttribute(“Width”)]
public int Width;
// Sub Image Height
[XmlAttribute(“Height”)]
public int Height;
// The X offset of sub image
[XmlAttribute(“OffSetX”)]
public int OffsetX;
// The Y offset for subimage
[XmlAttribute(“OffsetY”)]
public int OffsetY;
// The duration between two frames
[XmlAttribute(“Duration”)]
public float Duration;
}
// Animation class to hold the name and frames
public class Animation
{
// Animation Name
[XmlAttribute(“Name”)]
public string Name;
// Animation Frame Rate
[XmlAttribute(“FrameRate”)]
public int FrameRate;
public bool Loop;
public bool Pingpong;
// The Frames array in an animation
[XmlArray(“Frames”), XmlArrayItem(“Frame”, typeof(Frame))]
public Frame[] Frames;
}
// The Sprite Texture stores the Sprite Sheet path
public class SpriteTexture
{
// The Sprite Sheet texture file path
[XmlAttribute(“Path”)]
public string Path;
}
// Animation Set contains the Sprite Texture and animation.
[XmlRoot(“Animations”)]
public class AnimationSet
{
// The sprite texture object
[XmlElement(“Texture”, typeof(SpriteTexture))]
public SpriteTexture SpriteTexture;
// The animation array in the Animation Set
[XmlElement(“Animation”, typeof(Animation))]
public Animation[] Animations;
[/code] - Next, we will extract the Animation information from the XML description file using the XML deserialization technique:
[code]
// Sprite Animation Manager class
public static class SpriteAnimationManager
{
public static int AnimationCount;
// Read the Sprite Sheet Description information from the
// description xml file
public static AnimationSet Read(string Filename)
{
AnimationSet animationSet = new AnimationSet() ;
// Create an XML reader for the sprite sheet animation
// description file
using (System.Xml.XmlReader reader =
System.Xml.XmlReader.Create(Filename))
{
// Create an XMLSerializer for the AnimationSet
XmlSerializer serializer = new
XmlSerializer(typeof(AnimationSet));
// Deserialize the Animation Set from the
// XmlReader to the animation set object
animationSet =
(AnimationSet)serializer.Deserialize(reader);
}
// Count the animations to Animation Count
AnimationCount = animationSet.Animations.Length;
return animationSet;
}
}
[/code] - Now, from this step, we will begin to use the parsed AnimationSet to animate the Complex Sprite Sheet and switch the animations. So, add the code to the field of ComplexSpriteSheetAnimationGame class:
[code]
// A Timer variable
float timer;
// The interval
float interval = 200;
// Animation Set stores the animations in the sprite sheet
// description file
AnimationSet animationSet;
// Texture object loads and stores the Sprite Sheet image
Texture2D texture;
// The location of subimage
int X = 0;
int Y = 0;
// The size of subimage
int height = 0;
int width = 0;
// A rectangle to store which ‘frame’ is currently being shown
Rectangle sourceRectangle;
// The center of the current ‘frame’
Vector2 origin;
// Current frame holder
int currentFrame = 0;
// Current animation
int currentAnimation = 0;
[/code] - Read the Complex Sprite Sheet to the AnimationSet object. Add the line to the Initialize() method:
[code]
animationSet =
SpriteAnimationManager.Read(@”ContentSpriteDescription.xml”);
[/code] - In this step, we animate the Complex Sprite Sheet using the parsed animation set. Add the following lines to the Update() method:
[code]
// Change the animation when tap the Touch screen
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed)
{
// make the animation index vary within the total
// animation count
currentAnimation = (currentAnimation + 1) %
SpriteAnimationManager.AnimationCount;
}
// Accumulate the game elapsed time
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
// If the current frame is equal to the length of the current
// animation frames, reset the current frame to the beginning
if (currentFrame ==
animationSet.Animations[currentAnimation].Frames.Length – 1)
{
currentFrame = 0;
}
// Check the timer is more than the chosen interval
if (timer > interval && currentFrame <=
animationSet.Animations[currentAnimation].Frames.Length – 1)
{
// Get the location of current subimage
height = animationSet.Animations[currentAnimation].
Frames[currentFrame].Height;
width = animationSet.Animations[currentAnimation].
Frames[currentFrame].Width;
// Get the size of the current subimage
X = animationSet.Animations[currentAnimation].
Frames[currentFrame].X;
Y = animationSet.Animations[currentAnimation].
Frames[currentFrame].Y;
// Create the rectangle for drawing the part of the
//sprite sheet on the screen
sourceRectangle = new Rectangle(X, Y, width, height);
// Show the next frame
currentFrame++;
// Reset the timer
timer = 0f;
}
// Compute the origin position for image rotation and scale.
origin = new Vector2(sourceRectangle.Width / 2,
sourceRectangle.Height / 2);
[/code] - Draw the animation on the Windows Phone 7 Touch screen, add the code to the Draw() method:
[code]
spriteBatch.Begin();
spriteBatch.Draw(texture, new Vector2(400, 240),
sourceRectangle,
Color.White, 0f, origin, 2.0f, SpriteEffects.None, 0);
spriteBatch.End();
[/code] - Now, build and run the application. When you tap on the Touch screen, the animation will change as shown in the following screenshots:
How it works…
In step 2, the Frame class corresponds to the XML element: Frame and its attributes in the XML file. The same applies for the Animation class, SpriteTexture class, and the AnimationSet class. The XmlSerializer can recognize the XmlElement and XmlAttribute for every member variable unless the XmlAttribute or XmlElement attribute is defined above it. If the class type is customized, we also need to use the typeof() method to let the serializer know the object size. For identifying the array member variables—Frames and Animations—we should use the XmlArray attribute with array root name in the XML file and the XmlArrayItem with its element name. Additionally, the XmlRoot will help the XmlSerializer to locate the root of the whole XML file, here, it is Animations.
In step 3, since the loading of this data occurs independently of the instance of the manager class (in this case it deserializes an instance of a class from XML), we can declare it static to simplify our code, as an instance of the manager is not necessary as it contains no instance data. The Read() method creates an XmlReader object to read the Sprite Sheet XML description file. The XmlReader class supports reading XML data from a stream or file. It allows you to read the contents of a node. When the XmlReader for the description file is created, we instance the XmlSerializer object to read the AnimationSet type data. We use the XmlSerializer.Deserialize() method to decode the description XML file to meet the XML attributes we defined for the AnimationSet class, then the XmlSerializer will recursively read the subclasses with the XML attributes. After the XML deserialization, the code will set the count of the parsed animations to the AnimationCount variable. We will use the AnimationCount to control the index of animations for animation playing in the game.
In step 4, the timer variable will accumulate the game elapsed time; the interval variable defines the period of time between two frames; the animationSet variable will store the animations from the Sprite Sheet description file; the texture object holds the Complex Sprite Sheet image, with X,Y for the location and height, width for the size. The sourceRectangle variable will read the location and size information of the to create the region for drawing the subimage on screen; currentFrame defines the current animation playing frame; currentAnimation indicates the currently playing animation.
In step 6, the first part of the code is to react to the tap gesture for changing the current playing animation. The second part is to increase the currently playing frame, if the current frame reaches the maximum frame count of current animation, we will replay the current animation. The last part will get the location and size of the current subimage according to the current frame and use the location and size information to build the region of the current subimage for playing. Then, increase the frame to next before resetting the timer to 0 for a new frame. This information is controlled by the XML file that was generated from the Codeplex tool we exported in step 1.
Creating a text animation in Adventure Genre (AVG) game
Adventure Genre (AVG) game uses the text or dialog to describe game plots. With different branches, you will experience different directions in game underplaying. In most AVG games, text presentation is the key part, if you have ever played one or more of the games, you might have found the text was rendered character-by-character or word-by-word. In this recipe, you will learn how to work with sprite font and how to manipulate the font-related methods to compute the font and properly adjust the character and word positions in real time.
How to do it…
The following steps will lead you to complete text animation effect in Windows Phone 7:
- Create a Windows Phone Game in Visual Studio 2010 named AVGText, change Game1.cs to AVGTextGame.cs, and add the following code to the AVGTextGame class as field:
[code]
// SpriteFont object
SpriteFont font;
// Game text
string text = “”;
// The text showed on the screen
string showedText = “”;
// The bound for the showedText
const int TextBound = 20;
// Game timer
float timer = 0;
// Interval time
float interval = 100;
// Index for interate the whole original text
int index = 0;
[/code] - Initialize the original text and process it for wrapped showing. Add the initialization and processing code to the Initialize() method:
[code]
// The original text
text = “This is an AVG game text, you will find the text is
+ “showed character by character, I hope “
+ “this recipe is useful for you.”;
// Split the original string to a string array
string[] strArray = text.Split(‘ ‘);
// Declare the temp string for each row, aheadstring for
// looking ahead one word in this row
string tempStr = strArray[0];
string aheadString = “”;
// Declare the StringBuilder object for holding the sliced
// line
StringBuilder stringBuild = new StringBuilder();
// Iterate the word array, i for current word, j for next word
for (int i = 0 ,j = i ; j < strArray.Length; j++)
{
// i is before j
i = j – 1;
// Check the temp string length whether less than the
// TextBound
if (aheadString.Length <= TextBound)
{
// If yes, check the string looks ahead one more word
// whether less than the TextBound
if ((aheadString = tempStr + “ “ + strArray[j]).Length
<= TextBound)
{
// If yes, set the look-ahead string to the temp
// string
tempStr = aheadString;
}
}
else
{
// If not, add the temp string as a row
// to the StringBuilder object
stringBuild.Append(tempStr.Trim() + “n”);
// Set the current word to the temp string
tempStr = strArray[i];
aheadString = tempStr;
j = i;
}
}
[/code] - Load the SpriteFont content in the LoadContent() method:
[code]
font = Content.Load<SpriteFont>(“gameFont”);
[/code] - Update for drawing the showedText character-by-character. Add the lines to the Update() method:
[code]
// Accumulate the game elapsed
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
// Show the text character by character
if (timer > interval && index < text.Length)
{
// Every interval add the current index character to the
// showedText.
showedText += text.Substring(index, 1);
// Increse the index
index++;
// Set the timer to 0 for the next interval
timer = 0;
}
[/code] - Draw the AVG text effect on the Windows Phone 7 Touch screen. Add the code to the Draw() method:
[code]
// Draw the string on screen
spriteBatch.Begin();
spriteBatch.DrawString(font, showedText, new Vector2(0, 0),
Color.White);
spriteBatch.End();
[/code] - Build and run the application. It should look similar to the following screenshots:
How it works…
In step 1, the font object will be used to render the AVG game text; the text variable holds the original text for processing; the showedText stores the latest showing text; TextBound limits the bound for the showing text; the timer is used to count the game elapsed time; the interval variable represents the period of time between two frames; the index variable indicates the current position when showing the original text.
In step 2, we use the text.Split() method to split the original text to a word array, then declare three objects tempStr, aheadString, and stringBuild. tempStr stores the current row, composed of the words from the original text, aheadString saves one more word ahead of the tempStr for preventing the actual length of tempStr from becoming greater than the TextBound. In the for loop, we declare two iterating indices i and j: i is the index from the beginning of the original text, j goes after i. In the loop step, if the length of aheadString is less than the TextBound, one more word following the tempStr will be added to it. If the new look-ahead string length is still less than the Textbound, we will assign the aheadString to tempStr. On the other hand, if the length of aheadString is greater than Textbound, we will do line breaking. At this moment, the tempStr is appended to the stringBuild object as a row with a line breaking symbol—n. Then we set the current word to the tempStr and aheadString, and also set the current i to j for the next row.
In step 4, the first line accumulates the game elapsed milliseconds. If the elapsed time is greater than the interval, and the index is less than the processed text length, we will append the current character to showedText, after that, move the index pointer to the next character and set the timer to zero for the next interval.
Creating a text-based menu—the easiest menu to create
Menu plays the key role of a complete game; the player could use the menu to navigate to different parts of the game. As guidance to the game, the menu has a lot of appearances depending on the game type, animated or static, and so on. In this chapter, you will learn how to work with three kinds of menu, text-based, image-based, and model-based. As a beginning, this recipe shows you the text-based menu.
Getting ready
The text-based menu is made up of texts. Every menu item is an independent string. In this example, when you tap the item, the reaction will be triggered with the text color changing and text visual effects popping up. OK, let’s begin!
How to do it…
The following steps will show you how to create a simple text-based menu:
- Create a Windows Phone Game project named TextMenu, change Game1.cs to TextMenuGame.cs, and add gameFont.spriteFont to content project. Then add TextMenuItem.cs to the main project.
- Open TextMenuItem.cs file, in the field of the TextMenuItem class, add the following lines:
[code]
// SpriteBatch
SpriteBatch spriteBatch;
// Menu item text font
SpriteFont font;
// Menu item text
public string Text;
// Menu Item position
public Vector2 Position;
public Vector2 textOrigin;
// Menu Item size
public Vector2 Size;
// Bool tap value shows whether tap on the screen
public bool Tap;
// Tap event handler
public event EventHandler OnTap;
// Timer object
float timer = 0;
// Alpha value of text color
float alpha = 1;
Color color;
// The scale of text
float scale = 1;
[/code] - Next, we define the Bound property of the text menu item:
[code]
// The Bound of menu item
public Rectangle Bound
{
get
{
return new Rectangle((int)Position.X,
(int)Position.Y, (int)Size.X, (int)Size.Y);
}
}
[/code] - Define the constructor of the TextMenuItem class:
[code]
// Text menu item constructor
public TextMenuItem(Vector2 position, string text, SpriteFont
font, SpriteBatch spriteBatch)
{
Position = position;
Text = text;
this.font = font;
this.spriteBatch = spriteBatch;
// Compute the text size
Size = font.MeasureString(Text);
textOrigin = new Vector2(Size.X / 2, Size.Y / 2);
color = Color.White;
}
[/code] - Then, we implement the Update() method:
[code]
// Text menu item update method, get the tapped position on
screen
public void Update(Vector2 tapPosition)
{
// if the tapped position within the text menu item bound,
// set Tap to true and trigger
// the OnTap event
if (Bound.Contains((int)tapPosition.X,(int)tapPosition.Y))
{
Tap = true;
OnTap(this, null);
}
else
{
Tap = false;
}
}
[/code] - The last method in the TextMenuItem class is the Draw() method, let’s add the code:
[code]
public void Draw(GameTime gameTime)
{
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
// Draw the text menu item
if (Tap)
{
// Draw text visual effect
if (alpha >= 0 && timer > 100)
{
// Decrease alpha value of effect text
alpha -= 0.1f;
color *= alpha;
// Increase the effect text scale
scale++;
// Draw the first layer of effect text
spriteBatch.DrawString(font, Text, Position,
color, 0, new Vector2(Size.X / 2, Size.Y / 2),
scale, SpriteEffects.None, 0);
// Draw the second layer of effect text
spriteBatch.DrawString(font, Text, Position,
color, 0, new Vector2(Size.X / 2, Size.Y / 2),
scale / 2, SpriteEffects.None, 0);
// Reset the timer for the next interval
timer = 0;
}
// Draw the original text
spriteBatch.DrawString(font, Text, Position,
Color.Red);
}
else
{
// Reset the scale, alpha and color value of original
// text
scale = 1;
alpha = 1;
color = Color.White;
// Draw the original text
spriteBatch.DrawString(font, Text, Position,
Color.White);
}
}
[/code] - When the TextMenuItem class is done, the following work is about using the class in our game class. Add the code to the TextMenuGame field:
[code]
// SpriteFont object for text menu item
SpriteFont font;
// Menu collection of text menu item
List<TextMenuItem> Menu;
// Random color for background
Random random;
Color backgroundColor;
[/code] - This step is to initialize the variables—Menu, random, and backgroundColor; add the code to the Initialize() method:
[code]
Menu = new List<TextMenuItem>();
random = new Random();
backgroundColor = Color.CornflowerBlue;
[/code] - Load the game sprite font and text menu items in Menu, and add the following code to the LoadContent() method:
[code]
font = Content.Load<SpriteFont>(“gameFont”);
// Initialize the text menu items in Menu
int X = 100;
int Y = 100;
for (int i = 0; i < 5; i++)
{
TextMenuItem item = new TextMenuItem(
new Vector2(X, Y + 60 * i), “TextMenuItem”, font,
spriteBatch);
item.OnTap += new EventHandler(item_OnTap);
Menu.Add(item);
}
[/code] - Define the text menu item event reaction method item_OnTap():
[code]
void item_OnTap(object sender, EventArgs e)
{
// Set random color for back in every valid tap
backgroundColor.R = (byte)random.Next(0, 256);
backgroundColor.G = (byte)random.Next(0, 256);
backgroundColor.B = (byte)random.Next(0, 256);
}
[/code] - Get the tapped position and pass it to the text menu items for valid tap checking. Insert the code to the Update() method:
[code]
// Get the tapped position
Vector2 tapPosition = new Vector2();
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed)
{
tapPosition = touches[0].Position;
// Check the tapped positon whether inside one of the text
// menu items
foreach (TextMenuItem item in Menu)
{
item.Update(tapPosition);
}
}
[/code] - Draw the Menu, paste the code into the Draw() method:
[code]
// Replace the existing Clear code with this to
// simulate the effect of the menu item selection
GraphicsDevice.Clear(backgroundColor);
// Draw the Menu
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.
AlphaBlend);
foreach (TextMenuItem item in Menu)
{
item.Draw(gameTime);
}
spriteBatch.End();
[/code] - Now, build and run the application, and tap the first text menu item. It should look similar to the following screenshot:
How it works…
In step 2, the spriteBatch is the main object to draw the text; font object holds the SpriteFont text definition file; Text is the actual string shown in the text menu; Position indicates the location of the text menu item on screen; textOrigin defines the center of the text menu item for scale or rotation; the Size, Vector2 variable returns the width and height of Text through X and Y in Vector2; Tap represents whether the tap gesture takes place; the event handler OnTap listens to the occurrence of the tap gesture; timer object accumulates the game elapsed time for text visual effects; alpha value will be used to change the transparency of the text color; the last variable scale stores the scale factor of text menu item size.
In step 3, Bound returns a rectangle around the text menu item, and the property will be used to check whether the tapped position is inside the region of the text menu item.
In step 4, notice, we use the font.MeasureString() method to compute the size of the Text. Then set the origin position to the center of the Text.
In step 5, the Update() method receives the tapped position and checks whether it is inside the region of the text menu item, if yes, set the Boolean value Tap to true and trigger the OnTap event, otherwise, set Tap to false.
In step 6, the first line is to accumulate the game elapsed time. When the Tap is false—which means no tap on the text menu item—we set scale and alpha to 1, and the color to Color.White. Else, we will draw the text visual effects with two texts having the same text content of the text menu item; the size of the first layer is two times the second. As time goes by, the two layers will grow up and gradually disappear. After that, we draw the original text of the text menu item to Color.Red.
In step 7, the font object holds the game sprite font for rendering the Text of the text menu item; Menu is the collection of text menu items; the random variable will be used to generates random number for creating random backgroundColor.
In step 10, because the range for red, green, and blue factors of Color is from 0 to 255, the random numbers generated for them also meet the rule. With the random number, every valid text menu item tap will change the background color randomly.
Creating an image-based menu
The image-based menu is another menu-presentation approach. Unlike the text-based menu, the image-based menu uses 2D picture as the content of every menu item and makes the game navigation user interface more attractive and innovative. It gives graphic designers a much bigger space for conceiving ideas for a game. An image-based menu easily stimulates the designers’ and programmers’ creativities. The image menu item can swipe in and swipe out, jump in and jump out, or fade in and fade out. In this recipe, you will learn how to create an image-based menu system and use it in your own Windows Phone 7 game.
Getting ready
As an example, the image menu items are placed horizontally on the screen. When you tap one of them, it will grow up and the current item index shows at the top-left of the screen; once the tapped position is outside of its bound, the menu will restore to the initial state. Now, let’s build the application.
How to do it…
Follow these steps to create your own image-based menu:
- Create a Windows Phone Game project in Visual Studio 2010 named ImageMenu, change Game1.cs to ImageMenuGame.cs, and add the ImageMenuItem.cs in the main project. Then add Imageitem.png and gameFont.spriteFont to the content project.
- Create the ImageMenuItem class in the ImageMenuItem.cs file. First, add the code to the ImageMenuItem class field:
[code]
// SpriteBatch object
SpriteBatch spriteBatch;
// Menu item text font
Texture2D texture;
// Menu Item position
public Vector2 Position;
// Menu Item origin position for translation and rotation
public Vector2 Origin;
// Bool tap value shows whether tap on the screen
public bool Tap;
// Timer object
float timer = 0;
// The scale range from MinScale to MaxScale
const float MinScale = 0.8f;
const float MaxScale = 1;
// The scale of text
float scale = 0.8f;
// Image menu item index
public int Index = 0;
[/code] - Next, add the Bound property:
[code]
// The Bound of menu item
public Rectangle Bound
{
get
{
return new Rectangle(
(int)(Position.X – Origin.X * scale),
(int)(Position.Y – Origin.Y * scale),
(int)(texture.Width * scale),
(int)(texture.Height * scale));
}
}
[/code] - Then, define the constructor of the ImageMenuItem class:
[code]
// Text menu item constructor
public ImageMenuItem(Vector2 Location,Texture2D Texture,
SpriteBatch SpriteBatch)
{
Position = Location;
texture = Texture;
spriteBatch = SpriteBatch;
Origin = new Vector2(texture.Width / 2,
texture.Height / 2);
}
[/code] - The following method is the Update() method of the ImageMenuItem class, so let’s add its implementation code:
[code]
// Text menu item update method, get the tapped position on
// screen
public void Update(GameTime gameTime, Vector2 tapPosition)
{
// if the tapped position within the text menu item bound,
// set Tap to true and trigger the OnTap event
Tap = Bound.Contains((int)tapPosition.X,
(int)tapPosition.Y);
// Accumulate the game elapsed time
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
}
[/code] - The last method of the ImageMenuItem class is the Draw() method, so let’s add its implementation code:
[code]
public void Draw(GameTime gameTime)
{
// Draw the text menu item
if (Tap)
{
// if tap gesture is valid, gradually scale to
// MaxScale in
if (scale <= MaxScale && timer > 200)
{
scale += 0.1f;
}
spriteBatch.Draw(texture, Position, null, Color.Red,
0f, Origin, scale, SpriteEffects.None, 0f);
}
else
{
// If no valid tap, gradually restore scale to
// MinScale in every frame
if (scale > MinScale && timer > 200)
{
scale -= 0.1f;
}
spriteBatch.Draw(texture, Position, null, Color.White,
0f, Origin, scale, SpriteEffects.None, 0f);
}
}
[/code] - So far, we have seen the ImageMenuItem class. Our next job is to use the class in the main class. Add the code to the ImageMenuGame class:
[code]
// SpriteFont object for the current index value
SpriteFont font;
// The image menu item texture
Texture2D texImageMenuItem;
// The collection of image menu items
List<ImageMenuItem> Menu;
// The count of image menu items
int TotalMenuItems = 4;
// The index for every image menu item
int index = 0;
// Current index of tapped image menu item
int currentIndex;
[/code] - Initialize the Menu object in the Initialize() method:
[code]
// Initialize Menu
Menu = new List<ImageMenuItem>();
[/code] - Load the SpriteFont and ImageMenuItem texture, and initialize the ImageMenuItem in Menu. Next, insert the code to the LoadContent() method:
[code]
texImageMenuItem = Content.Load<Texture2D>(“ImageItem”);
font = Content.Load<SpriteFont>(“gameFont”);
// Initialize the image menu items
int X = 150;
int Y = 240;
// Instance the image menu items horizontally
for (int i = 0; i < TotalMenuItems; i++)
{
ImageMenuItem item = new ImageMenuItem(
new Vector2(
X + i * (texImageMenuItem.Width + 20), Y),
texImageMenuItem, spriteBatch);
item.Index = index++;
Menu.Add(item);
}
[/code] - In this step, we will check the valid tapped position and get the index of the tapped image menu item, paste the code to the Update() method:
[code]
// Get the tapped position
Vector2 tapPosition = new Vector2();
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed)
{
tapPosition = touches[0].Position;
// Check the tapped positon whether inside one of the
// image menu items
foreach (ImageMenuItem item in Menu)
{
item.Update(gameTime, tapPosition);
// Get the current index of tapped image menu item
if (item.Tap)
{
currentIndex = item.Index;
}
}
}
[/code] - Draw the menu and the current index value on the screen, and insert the lines to the Draw() method:
[code]
spriteBatch.Begin();
// Draw the Menu
foreach (ImageMenuItem item in Menu)
{
item.Draw(gameTime);
}
// Draw the current index on the top-left of screen
spriteBatch.DrawString(font, “Current Index: “ +
currentIndex.ToString(), new Vector2(0, 0), Color.White);
spriteBatch.End();
[/code] - Now, build and run the application. Tap the second image menu item, and it should look similar to the following screenshot:
How it works…
In step 2, spriteBatch renders the image of the menu item on screen; texture loads the graphic content of the image menu item; Position represents the position of every image menu item; Origin defines the center for menu item rotation and translation; Tap is the mark for valid menu item tapping; the timer variable accumulates the game elapsed time for changing the scale of the image menu item. The following two objects MinScale and MaxScale limit the range of scale changing; the scale variable indicates the current scale value of the menu item; index holds the sequential position of a menu.
In step 3, the Bound property returns a rectangle around the image of the menu item according to the menu item position and the image size.
In step 6, we will draw the visual effect for the menu item with image zoom in and zoom out. The first line accumulates the game elapsed time for changing the menu item scale. If the tapped position is inside the image menu item, the item will grow up gradually along with the increasing scale value until MaxScale; otherwise, it will restore to the initial state.
In step 7, the font object will render the current index of the image menu item on the top-left of the screen; texImageMenuItem holds the image of the menu item; Menu is the collection of ImageMenuItem; TotalMenuItems declares the total number of image menu items in Menu; index is used to mark the index of every menu item in Menu; the currentValue variable saves the index of the tapped image menu item.
In step 9, we instance the image menu items horizontally and define the gap between each of the items at about 20 pixels wide.
In step 10, after calling the ImageMenuItem.Update() method, you can get the current index of the menu item when its Tap value is true.
Creating a 3D model-based menu
Text- or image-based menus are very common in games. As you know, they are both in 2D, but sometimes you want some exceptional effects for menus in 3D, such as rotating. 3D menus render the model as a menu item for any 3D transformation. They offer a way to implement your own innovative menu presentation in 3D. This recipe will specify the interesting technique in Windows Phone 7.
Getting ready
Programming the 3D model-based menu is an amazing adventure. You can use the 3D model rendering and transformation techniques to control the menu items and control the camera at different positions, getting closer or looking like a bird. As a demo, the model menu item of menu will pop up when selected. I hope this recipe will impress you. Let’s look into the code.
How to do it…
The following steps will lead you to build an impressive 3D model-based menu:
- Create the Windows Phone Game project named ModelMenu3D, change Game1. cs to ModelMenuGame.cs. Add a ModelMenuItem.cs to the project; gameFont. spriteFont and ModelMenuItem3D.FBX to the content project.
- Create the ModelMenuItem class in ModelMenuItem.cs. Add the code to its field:
[code]
// Model of menu item
Model modelItem;
// Translation of model menu item
public Vector3 Translation;
// The view and projection of camera for model view item
public Matrix View;
public Matrix Projection;
// The index of model menu item
public int Index;
// The mark for selection
public bool Selected;
// The offset from menu item original position when selected
public int Offset; - Next, define the constructor of the ModelMenuItem class and set the default offset of the selected model menu item.
[code]
// Constructor
public ModelMenuItem(Model model, Matrix view, Matrix
projection)
{
modelItem = model;
View = view;
Projection = projection;
Offset = 5;
}
[/code] - This step is to give the definition of the Draw() method of the ModelMenuItem class:
[code]
// Draw the model menu item
public void Draw()
{
Matrix[] modelTransforms = new
Matrix[modelItem.Bones.Count];
modelItem.CopyAbsoluteBoneTransformsTo(modelTransforms);
foreach (ModelMesh mesh in modelItem.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
// Enable lighting
effect.EnableDefaultLighting();
// Set the ambient light color to white
effect.AmbientLightColor = Color.White.ToVector3();
if (Selected)
{
// If the item is not selected, it restores
// to the original state
effect.World =
modelTransforms[mesh.ParentBone.Index]
* Matrix.CreateTranslation(Translation +
new Vector3(0,0, Offset));
}
else
{
// If the item is selected, it stands out
effect.World =
modelTransforms[mesh.ParentBone.Index]
* Matrix.CreateTranslation(Translation);
}
effect.View = View;
effect.Projection = Projection;
}
mesh.Draw();
}
}
[/code] - Use the ModelMenuItem class in our game. Add the code to the field of the ModelMenuGame class:
[code]
// Sprite font object
SpriteFont font;
// Model of menu Item
Model menuItemModel;
// Camera position
Vector3 cameraPositon;
// Camera view and projection matrices
Matrix view;
Matrix projection;
// The collection of Model Menu items
List<ModelMenuItem> Menu;
// The count of model menu items in Menu
int TotalMenuItems = 4;
// The left and right hit regions for menu item selection
Rectangle LeftRegion;
Rectangle RightRegion;
// Current index of model menu item in Menu
int currentIndex = 0;
// Event handler of hit regions
public event EventHandler OnTap;
[/code] - Initialize the camera, menu, and hit regions. Insert the code to the Initialize() method:
[code]
// Define the camera position
cameraPositon = new Vector3(-40, 10, 40);
// Define the camera view and projection matrices
view = Matrix.CreateLookAt(cameraPositon, Vector3.Zero,
Vector3.Up);
projection =
Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
GraphicsDevice.Viewport.AspectRatio, 1.0f, 1000.0f);
// Initialize the Menu object
Menu = new List<ModelMenuItem>();
// Left hit region occupies the left half of screen
LeftRegion = new Rectangle(
0, 0,
GraphicsDevice.Viewport.Width / 2,
GraphicsDevice.Viewport.Height);
// Right hit region occupies the right half of screen
RightRegion = new Rectangle(GraphicsDevice.Viewport.Width / 2,
0, GraphicsDevice.Viewport.Width / 2,
GraphicsDevice.Viewport.Height);
// Define the event handler OnTap with the delegate method
OnTap = new EventHandler(item_OnTap);
[/code] - Next, define the reaction method item_OnTap of the OnTap event:
[code]
// Make the current index value change within the range of
// total menu items
currentIndex = currentIndex % TotalMenuItems;
// If the current index is less than 0, set it to the last item
if (currentIndex < 0)
{
// From the last item
currentIndex = TotalMenuItems – 1;
}
// if the current index is greater than the last index, set it
to the
// first item
else if (currentIndex > TotalMenuItems – 1)
{
// From the beginning item;
currentIndex = 0;
}
// Select the menu item, of which the index equals the
// current index
foreach (ModelMenuItem item in Menu)
{
if (item.Index == currentIndex)
{
item.Selected = true;
}
else
{
item.Selected = false;
}
}
[/code] - Load the game content and initialize the menu items of Menu. Insert the code in the Initialize() method:
[code]
// Load and initialize the model and font objects
menuItemModel = Content.Load<Model>(“ModelMenuItem3D”);
font = Content.Load<SpriteFont>(“gameFont”);
// Initialize the model menu items in Menu horizontally
for (int i = 0; i < TotalMenuItems; i++)
{
int X = -20;
ModelMenuItem item = new ModelMenuItem(
menuItemModel, view,
projection);
item.Translation = new Vector3(X + (i * 20), 0, 0);
// Set the index of menu item
item.Index = i;
Menu.Add(item);
}
// Setting the first menu item to be selected by default
Menu[0].Selected = true;
[/code] - In this step, we make the current index value react to the tap on the hit regions. Paste the code to the Update() method:
[code]
// Get the tapped position
Vector2 tapPosition = new Vector2();
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed)
{
tapPosition = touches[0].Position;
Point point = new Point((int)tapPosition.X,
(int)tapPosition.Y);
// Check the tapped position whether in the left region
if (LeftRegion.Contains(point))
{
// If yes, decrease the current index
–currentIndex;
OnTap(this, null);
}
// Check the tapped position whether in the right region
else if (RightRegion.Contains(point))
{
// If yes, increase the current index
++currentIndex;
OnTap(this, null);
}
}
[/code] - The last step is to draw the menu on screen. Insert the code to the Draw() method:
[code]
// The following three lines are to ensure that the models are
// drawn correctly
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
GraphicsDevice.BlendState = BlendState.AlphaBlend;
// Draw the Menu
foreach (ModelMenuItem item in Menu)
{
item.Draw();
}
// Draw the current index on the top-left of screen
spriteBatch.Begin();
spriteBatch.DrawString(font, “Current Index: “ +
currentIndex.ToString(), new Vector2(0, 0), Color.White);
spriteBatch.End();
[/code] - Now, build and run the application. When you tap the right region, the second model menu will pop up, as shown in the following screenshot:
How it works…
In step 2, the modelitem holds the model object of the menu item; Translation stores the world position of the model menu item; View and Projection stand for the view and projection matrices of the camera respectively; index saves the index of the menu item in the menu; Selected indicates the selection state of the model menu item; Offset is the offset value from the model menu item’s original position.
In step 4 and in the iteration of Mesh.Effects, we enable the light through calling the Effect.EnableDefaultLighting() method and set the Effect.AmbientLightColor to Color.White.ToVector3(). Notice, for popping up the model menu item, we create the translation matrix with positive 5 units offset at the Z-axis from the original position. If a menu is selected, it will pop up, otherwise, it will go back or remain in the initial state.
In step 5, font object will be used to draw the current index value on the top-left on screen; menuModel stores the model object for model menu item; cameraPosition defines the position of the camera; view and projection are the matrices for camera view and projection respectively; Menu is the collection of model menu items; TotalMenuItems indicates the total number of menu items; LeftRegion and RightRegion are the areas for item choosing.
In step 6, the first part of the code before is to define the camera; the second part is about initializing the left and right hit regions. LeftRegion takes up the left half of the screen; RightRegion occupies the other half.
In step 7, the first line is responsible for making the currentIndex value not less than -1 and greater than TotalMenuItems. Next, if the current index is less than 0, the last menu item will be selected; otherwise, if the current index is greater than TotalMenuItems minus 1, the first item will pop up. The following foreach loop checks which item is currently selected when its index is equal to the current index.
In step 8, the first two lines are to load the menu item model and the font for presenting the current index. The following for loop initializes the menu items of Menu horizontally and assigns i value to the item index. The last line sets the first selected item.
In step 9, this block of code first gets the tapped position on screen. If the tapped position is in the left region, the current index decreases by 1, else, current index increases by 1. Any valid tap on the regions will trigger the OnTap event.