Rocket Science: Acceleration

Building the Game

The Black Hole game is based on most of the code we’ve developed in each of the previous hours of the book, and there really is no single previous hour that gets more credit than others since each hour has built upon the hours that came before. Let’s dig into the game by going over the major sections and get something up and running fairly quickly. Then we’ll continue our work on the game into the following hour, where it will get some polish and fine-tuning of the fun factor.

This game is based on the theories of Stephen Hawking. If you’re interested in black hole physics, be sure to read his popular books for more information! The Universe in a Nutshell is one of my favorites.

Gravity Well Regions

There are three regions around the black hole that affect game objects. The outer gravity well affects objects passing by, drawing them toward the black hole with relatively light force. This force is increased by an equal amount in the next two inner regions, with each region generating an equivalent gravity “tug” on objects. But the cumulative effect of all three gravity wells in the inner region of the black hole will cause objects to become hopelessly trapped.

The third and innermost region might be thought of as the event horizon, that region of a black hole where things disappear into the void, never to be seen again. It is this region that mathematics cannot penetrate, so while it appears that gravity increases toward infinity in the middle of a black hole, the truth of the matter is, there may be nothing at the very center of a black hole! The gravity might be so powerful that matter just rotates around the center of mass and no matter actually exists at that center point, which would be quite small at the center of a black hole. Then again, there might be a superdense material like a neutron star. It is at this point that physics just cannot explain it, because we don’t actually have a black hole nearby to study. Even if we did, it’s not like a spacecraft could be sent to investigate!

Figure 23.1 shows an illustration of the gravity well of the black hole in the game. The outer gravity well is quite large and draws most game objects inward at a steady “weight” or “pull,” with some help from the inner core of the black hole, also exerting force. The inner gravity well is the region where energy can be mined by the “Hawking” satellites. At any rate, that’s one of the enjoyable aspects of this game, pondering the infinite possibilities!

The code to simulate the gravitational pull of the black hole is coming up. Now let’s just take a look again at some of our earlier helper methods used in this game. All the collision code in the Black Hole game is based around this RadialCollision() method and its overloaded friend:

[code]
public bool RadialCollision(Sprite A, Sprite B)
{
float radius1 = A.image.Width / 2;
float radius2 = B.image.Width / 2;
return RadialCollision(A.position, B.position,
radius1, radius2);
}
public bool RadialCollision(Vector2 A, Vector2 B, float radius1,
float radius2)
{
float dist = Distance(A, B);
return (dist < radius1 + radius2);
}
public float Distance(Vector2 A, Vector2 B)
{
double diffX = A.X – B.X;
double diffY = A.Y – B.Y;
double dist = Math.Sqrt(Math.Pow(diffX, 2) +
Math.Pow(diffY, 2));
return (float)dist;
}
[/code]

The gravity well of the black hole covers most of the Windows Phone screen.
FIGURE 23.1 The gravity well of the black hole covers most of the Windows Phone screen.

Enhancing MassiveObject

Some minor changes need to be made to MassiveObject to support some new features needed in the game that were not in the example in the preceding hour. Following is what the class now looks like, with the new variables and updated constructor:

[code]
class MassiveObject : Sprite
{
public string name;
public bool captured;
public double mass;
public Vector2 acceleration;
public float radius, angle;
public int lifetime, startTime;
public MassiveObject(ContentManager content,
SpriteBatch spriteBatch)
: base(content, spriteBatch)
{
name = “object”;
mass = 1.0f;
acceleration = Vector2.Zero;
radius = 50.0f;
angle = 0.0f;
captured = false;
lifetime = 0;
startTime = 0;
}
// . . . note: some code omitted here
}
[/code]

Game1.cs

There are no changes to be made to Game1.cs, because the main source code file is now PlayingModule.cs. In the final hour coming up, we will again use the game state modules for a more polished gameplay experience.

Gameplay Source Code

The most significant code of the game is found in PlayingModule.cs. If you skipped ahead, you may have missed Hour 21, “Finite State Gameplay,” which explained how to use states to improve a game in many ways. The PlayingModule class is the primary gameplay class where the bulk of the game code will be found. The first lines of code in the class declare all the variables, including the key objects variable, defined as a List of MassiveObjects. We also see the black hole, the super core gravity well, and the player’s ship here, among other things. Figure 23.2 shows the game as it is just getting started, and Listing 23.1 shows the source code for the class.

The Black Hole game soon after startup.
FIGURE 23.2 The Black Hole game soon after startup.

LISTING 23.1 Source Code for the PlayingModule Class

[code]
public class PlayingModule : IGameModule
{
Game1 game;
SpriteFont font;
Random rand;
Sprite background;
MassiveObject blackHole;
MassiveObject superCore;
MassiveObject ship;
float energy = 100.0f;
int startTime, lifetime;
List<MassiveObject> objects;
public PlayingModule(Game1 game)
{
this.game = game;
rand = new Random();
startTime = 0;
lifetime = 4000;
}
public void LoadContent(ContentManager content)
{
font = content.Load<SpriteFont>(“WascoSans”);
background = new Sprite(game.Content, game.spriteBatch);
background.Load(“space”);
background.origin = Vector2.Zero;
blackHole = new MassiveObject(game.Content, game.spriteBatch);
blackHole.Load(“blackhole”);
blackHole.position = new Vector2(500, 240);
blackHole.scale = 2.0f;
blackHole.mass = 40;
blackHole.color = new Color(255, 100, 100, 200);
blackHole.velocityAngular = 0.1f;
superCore = new MassiveObject(game.Content, game.spriteBatch);
superCore.image = blackHole.image;
superCore.position = new Vector2(blackHole.position.X,
blackHole.position.Y);
superCore.scale = blackHole.scale * 0.4f;
superCore.mass = 60;
superCore.color = new Color(200, 100, 100, 180);
superCore.velocityAngular = 4.0f;
superCore.origin = new Vector2(64, 64);
//create objects list
objects = new List<MassiveObject>();
//create player ship
ship = new MassiveObject(game.Content, game.spriteBatch);
ship.Load(“ship”);
ship.position = new Vector2(200, 240);
ship.mass = 100f;
ship.scale = 0.2f;
ship.rotation = MathHelper.ToRadians(90);
}
[/code]

The Update() method is a bit monolithic at this stage, but the code is easier to follow this way than if it had been divided into several smaller methods. I usually divide a method like this when it grows too large to be easily maintained, but since the gameplay code in PlayingModule is only 300 lines long, there isn’t too much to consume here at once. There’s a lot going on here in Update(), but we won’t break up the code listing and break the flow of the code, which can be distracting.

First of all, the blackHole and superCore objects are updated. Then we go into a foreach loop that processes all the MassiveObject objects in the objects list (there’s a tongue twister!). Each object is updated, rotated, and animated. Within the foreach is where the bulk of the code is found for the game.

When one of the satellites grows to a certain size (from collecting energy), that triggers a subset of code here in the foreach block where the player’s ship actually attracts the satellite toward it, using the same code used to simulate the gravitational pull of the black hole on the same objects. The slight gravity “tug” causes the satellites to veer toward the ship and increase the chances of their being caught by it, without making it too easy for the player. After all, the ship doesn’t move yet, it only rotates in place!

Next up is the code that tugs objects inward toward the black hole, if they are in range. Figure 23.3 shows another screenshot of the game, this time with a lot of satellites in orbit. Note the addition of animated asteroids in the scene. The asteroids serve no purpose, but just fill in some detail to make the scene look more interesting. A new asteroid is added every few seconds at a random direction and velocity, and over time they do tend to add up to quite a large mass of rotation around the black hole, which only increases the fun factor. Now, there is also potential use for these asteroid sprites beyond just “for looks.”

A large number of objects are orbiting the black hole, and they tend to fall in quite frequently.
FIGURE 23.3 A large number of objects are orbiting the black hole, and they tend to fall in quite frequently.

Listing 23.2 contains the source code for the Update() method.

It’s quite a challenge to come up with mass values for the black hole, the super core, and each of the objects that not only result in a realistic simulation of gravity’s effect on objects of mass but also make the game fun. Fun is more important than realism, but we want to have a little of both if possible. But when a trade-off is required, always go with that which helps the game to sell: the fun factor.

LISTING 23.2 Source Code for the Update() Method

[code]
public void Update(TouchLocation touch, GameTime gameTime)
{
int time = gameTime.ElapsedGameTime.Milliseconds;
blackHole.Update(gameTime);
blackHole.Rotate();
superCore.Update(gameTime);
superCore.Rotate();
foreach (MassiveObject obj in objects)
{
if (!obj.alive) continue;
obj.Update(gameTime);
obj.Rotate();
obj.Animate(time); //frame animation
obj.Animate(); //mod animation
//allow ship to collect energy satellites for bonus energy
if (obj.scale > 3.0f && obj.name == “satellite”)
{
obj.Attract(ship);
obj.color = Color.White;
if (game.RadialCollision(obj.position, ship.position,
obj.size.X, 40))
{
obj.alive = false;
energy += obj.scale;
}
}
if (!obj.captured)
{
//attract when object is near the black hole
if (game.RadialCollision(obj.position,
blackHole.position, 10, 500))
{
obj.Attract(blackHole);
obj.Attract(superCore);
//is object touching the outer edges of the black hole?
if (game.RadialCollision(obj.position,
blackHole.position, 10, 120))
{
obj.color = Color.Red;
if (obj.name == “satellite”)
{
obj.scale += 0.1f;
energy += 0.5f;
}
obj.Attract(blackHole); //outer black hole
obj.Attract(superCore); //inner black hole
//oh no, object is caught by the black hole!
if (game.RadialCollision(obj.position,
superCore.position, 16, 60))
{
obj.captured = true;
obj.lifetime = 3000;
obj.startTime = (int)
gameTime.TotalGameTime.TotalMilliseconds;
OrbitalMovement anim1 = new OrbitalMovement(
blackHole.position, 10 + rand.Next(40),
obj.rotation, -0.8f);
obj.animations.Add(anim1);
}
}
else
{
obj.color = Color.White;
}
}
}
//when captured, time runs out
if (obj.lifetime > 0)
{
if (obj.startTime + obj.lifetime <
gameTime.TotalGameTime.TotalMilliseconds)
obj.alive = false;
}
//see if object has gone too far out of bounds
if (obj.position.X < -200 || obj.position.X > 1000 ||
obj.position.Y < -200 || obj.position.Y > 700)
obj.alive = false;
}
//update ship
ship.Update(gameTime);
ship.Rotate();
ship.Animate(time);
if (energy <= 0)
{
ship.Attract(blackHole);
//object is caught by the black hole
if (game.RadialCollision(ship.position, superCore.position,
64, 40))
{
ship.captured = true;
ship.lifetime = 3000;
ship.startTime = (int)
gameTime.TotalGameTime.TotalMilliseconds;
OrbitalMovement anim1 = new OrbitalMovement(
blackHole.position, 10 + rand.Next(40),
ship.rotation, -0.8f);
ship.animations.Add(anim1);
}
//done being squished?
if (ship.lifetime > 0)
{
if (ship.startTime + ship.lifetime <
gameTime.TotalGameTime.TotalMilliseconds)
ship.alive = false;
}
}
else
{
energy -= 0.05f;
ship.velocityLinear.X = 0.0f;
}
//check user input
if (touch.State == TouchLocationState.Released)
{
if (touch.Position.X > ship.position.X)
{
CreateSatellite();
}
else
{
if (touch.Position.Y < 200)
ship.velocityAngular = -0.01f;
else if (touch.Position.Y > 280)
ship.velocityAngular = 0.01f;
else
ship.velocityAngular = 0;
}
}
//time to add another random asteroid?
if (startTime + lifetime < gameTime.TotalGameTime.
TotalMilliseconds)
{
startTime = (int)gameTime.TotalGameTime.TotalMilliseconds;
CreateAsteroid();
}
//clean out the dead objects
foreach (MassiveObject obj in objects)
{
if (obj.alive == false)
{
objects.Remove(obj);
break;
}
}
}
[/code]

The Draw() method is next, with its source code in Listing 23.3. This is a rather small method because the gameplay objects are managed.

LISTING 23.3 Source Code for the Draw() Method

[code]
public void Draw(GameTime gameTime)
{
background.Draw();
superCore.Draw();
blackHole.Draw();
ship.Draw();
foreach (MassiveObject obj in objects)
{
if (obj.alive)
{
obj.Draw();
}
}
string text = “Ship rot “ + MathHelper.ToDegrees(
ship.rotation).ToString();
game.spriteBatch.DrawString(font, text, new Vector2(0, 0),
Color.White);
text = “Objects “ + objects.Count.ToString();
game.spriteBatch.DrawString(font, text, new Vector2(0, 20),
Color.White);
text = “Energy “ + energy.ToString(“N0”);
game.spriteBatch.DrawString(font, text, new Vector2(650, 0),
Color.White);
}
[/code]

Finally, we have two helper methods, CreateAsteroid() and CreateSatellite(), that generate a random asteroid and random satellite, respectively. These two methods, shown in Listing 23.4, are quite important to the gameplay because they determine whether the objects will actually move reasonably on the screen. I say reasonably rather than realistically because, again, we don’t want absolute realism; we want some realism with gobs of fun gameplay. The asteroids aren’t important to the gameplay, because they are just for looks, but we do want them to start off in such a way that they end up rotating around the black hole. Likewise, the satellite must be launched in such a way that it moves reasonably well. At this stage, our satellites move at a constant speed, but in the next (and final) hour, we will add GUI controls that allow the player to adjust the power.

LISTING 23.4 Source Code for the CreateAsteroid() and CreateSatellite() Methods

[code]
public void CreateAsteroid()
{
MassiveObject obj = new MassiveObject(game.Content, game.spriteBatch);
obj.Load(“asteroid”);
obj.columns = 8;
obj.totalFrames = 64;
obj.scale = 0.1f + (float)rand.NextDouble();
obj.size = new Vector2(60, 60);
obj.radius = 80;
//randomly place at top or bottom of screen
obj.position = new Vector2(rand.Next(100, 800), -100);
obj.velocityLinear = new Vector2(4.0f, (float)(rand.NextDouble() *
6.0));
if (rand.Next(2) == 1)
{
obj.position.Y = -obj.position.Y;
obj.velocityLinear.Y = -obj.velocityLinear.Y;
}
obj.scale = 0.4f;
obj.mass = 1;
obj.velocityAngular = 0.001f;
obj.lifetime = 0;
obj.name = “asteroid”;
objects.Add(obj);
}
public void CreateSatellite()
{
MassiveObject obj;
obj = new MassiveObject(game.Content, game.spriteBatch);
obj.Load(“plasma32”);
obj.position = ship.position;
obj.mass = 1;
obj.scale = 0.5f;
obj.lifetime = 0;
obj.name = “satellite”;
//calculate velocity based on ship’s angle
float accel = 4.0f;
float angle = ship.rotation – MathHelper.ToRadians(90);
float x = (float)Math.Cos(angle) * accel;
float y = (float)Math.Sin(angle) * accel;
obj.velocityLinear = new Vector2(x,y);
//load energy to launch
energy -= 1;
objects.Add(obj);
}
}
[/code]
This all sounds like fun, but is there even a way to lose the game? Certainly! If the player runs out of energy, the ship will fall into the black hole! At this stage, the ship just loses its “traction” or station-keeping thrusters and is drawn into the black hole, only to be whipped around by the acceleration code. Some sort of fantastic animation will have to be added so that the ship gets sucked into the black hole like the other objects—a task for the next hour! Figure 23.4 shows what happens now if the player runs out of energy. Another improvement to be made in the next hour is an energy bar rather than just a text display. We have a lot of work yet to do on this game, but it’s already showing promise.

Running out of energy spells doom for the poor ship and its crew!
FIGURE 23.4 Running out of energy spells doom for the poor ship and its crew!

Creating a Graphical User Interface

Creating the GUI Controls

A graphical user interface (GUI) is absolutely essential for a game to be successful, even if that means using nothing more than labels and buttons on the screen that the user can click on.

Sprite Class Improvements

Modifying the Sprite Class

To make the GUI controls more effective, the Sprite class must be tweaked just a little.

  1. We need to change the definition of p_content and p_spriteBatch from private to protected so that they will be accessible to classes that inherit from Sprite. This way, we can load assets and draw without creating new reference variables in every subclass. Open the Sprite class and make the change:
    [code]
    protected ContentManager p_content;
    protected SpriteBatch p_spriteBatch;
    [/code]
  2. Just to be sure we are on the same page despite the changes made to this class in the past, here is the Load() method. Ignore past changes and just note this current version, which shows that the size and origin properties have been moved out of the try block:
    [code]
    public virtual bool Load(string assetName)
    {
    try
    {
    image = p_content.Load<Texture2D>(assetName);
    }
    catch (Exception) { return false; }
    size = new Vector2(image.Width, image.Height);
    origin = new Vector2(image.Width / 2, image.Height / 2);
    return true;
    }
    [/code]
  3. Add an error-handling line to the Draw() method so that it won’t crash the program if the image is null. This is a common verification. Since our GUI controls will be using a few images in interesting ways, we just want to ensure that any image that is not loaded correctly won’t crash the program—instead, it will just not show up.
    [code]
    public virtual void Draw()
    {
    if (!visible) return;
    if (image == null) return;
    . . .
    }
    [/code]

GUI Base Class: Control

All the GUI classes will be found in the GUI.cs source code file for the sake of convenience. Within that file, the classes will be wrapped inside the GameLibrary namespace (the same namespace used by Sprite and Animation).

[code]
namespace GameLibrary
{
. . .
}
[/code]

The base GUI class is called Control, and it is primarily used to create a reference to the ContentManager, SpriteBatch, and SpriteFont objects used in a game—all of which are needed by the GUI. Control inherits from Sprite, so it supplies GUI controls (declared as subclasses of Control) with all the features of Sprite, including loading and drawing. Methods are declared as virtual or override so they can be used and overridden in each subclass. There are certainly more services the base class could provide, such as touch input, but it turns out (during development) that most of that code must reside in each individual class. Listing 20.1 contains the source code for the Control class.

LISTING 20.1 Source Code for the Control Class

[code]
public abstract class Control : Sprite
{
protected SpriteFont p_font;
public Control(ContentManager content, SpriteBatch spriteBatch,
SpriteFont font)
: base(content, spriteBatch)
{
p_font = font;
}
public override bool Load(string filename)
{
return base.Load(filename);
}
public virtual void Update(TouchLocation touch)
{
}
public override void Draw()

{
base.Draw();
}
}
[/code]

Label Control

A Label is the most fundamental type of GUI control, with the simple task of displaying a text message on the screen. This is more important than it might at first seem, because a Label control can be moved anywhere on the screen without affecting the call to Label.Draw() from the main program. This Label class is rather basic, providing a shadow feature with customizable Color properties for the text and shadow. Two Labels will be used in the sample project later in this hour. Listing 20.2 contains the source code for the Label class.

LISTING 20.2 Source Code for the Label Class

[code]
public class Label : Control
{
public string text;
public Color shadowColor;
public Color textColor;
public bool UseShadow;
public Label(ContentManager content, SpriteBatch spriteBatch,
SpriteFont font)
: base(content, spriteBatch, font)
{
text = ““;
color = Color.White;
textColor = Color.White;
shadowColor = Color.Black;
UseShadow = true;
}
public override void Update(TouchLocation touch)
{
base.Update(touch);
}
public override void Draw()
{
if (UseShadow)
{
p_spriteBatch.DrawString(p_font, text,
new Vector2(position.X – 2, position.Y – 2), shadowColor);
}
p_spriteBatch.DrawString(p_font, text, position, textColor);
}
public Vector2 TextSize()
{
return p_font.MeasureString(text);
}
}
[/code]

Button Control

A Button is the second most common type of control needed for a rudimentary GUI system. Our Button class will load a 64×64 bitmap file called button.png (which must be in the content project). The great thing about this is that you can replace the image with one of your own. Due to the way the class works, I recommend using an image with the same dimensions but with your own “skin” theme. The button used in the example this hour is a gray box with a white outline. An important feature for a Button control is to display text and respond to user tap events. Our Button goes further by allowing its background and text colors to be changed independently for a customized look. Listing 20.3 contains the source code for the Button class.

LISTING 20.3 Source Code for the Button Class

[code]
public class Button : Control
{
public string text;
public Color shadowColor;
public Color textColor;
public bool UseShadow;
public bool Tapped;
public Button(ContentManager content, SpriteBatch spriteBatch,
SpriteFont font)
: base(content, spriteBatch, font)
{
text = ““;
color = Color.White;
textColor = Color.White;
shadowColor = Color.Black;
UseShadow = true;
Load(“button”);
}
public override void Update(TouchLocation touch)
{
base.Update(touch);
Tapped = false;
if (touch.State == TouchLocationState.Pressed)
{
Rectangle rect = Boundary();
Vector2 pos = touch.Position;
Point point = new Point((int)pos.X, (int)pos.Y);
if (rect.Contains(point))
{
Tapped = true;
}
}
}
public override void Draw()
{
base.Draw();
Vector2 size = TextSize();
Vector2 pos2 = new Vector2(position.X + 2, position.Y + 2);
Vector2 pivot = new Vector2(size.X / 2, size.Y / 2);
p_spriteBatch.DrawString(p_font, text, position, shadowColor,
0.0f, pivot, 1.0f, SpriteEffects.None, zindex);
p_spriteBatch.DrawString(p_font, text, pos2, textColor, 0.0f, pivot,
1.0f, SpriteEffects.None, zindex);
}
public Vector2 TextSize()
{
return p_font.MeasureString(text);
}
}
[/code]

Horizontal Slider Control

A slider control makes it possible to adjust a setting or to control some aspect of a game directly by the user, and resembles a movable sliding lever on the screen. There are two types of slider: horizontal and vertical. Although one common class could be used for both slider orientations, it would be more coding work, so it is more effective to just separate them into HSlider and VSlider controls. This is definitely a complex type of control compared to Label and Button. HSlider loads three images, so these bitmap files must all be found in the content project for the GUI code to run properly:

  • hslider_bar.png
  • hslider_end.png
  • button.png

Remember, when you are creating your own game using these GUI controls, that you can skin the controls to your own liking. The slider button needn’t be a circle at all! It can be any shape, including a custom image or a picture of a dragon—it doesn’t matter, and it’s up to you!

The left and right end images are shared, so if you create a custom skin for the control, be sure that the end images are interchangeable. The middle piece is a line one (1) pixel wide, scaled to the width of the control (set with the HSlider.Limit property). If the limit is 100, the one-pixel-wide image is scaled by 100 times to reach the edge! The scale as well as other properties are borrowed from the base Sprite class embedded in Control, inherited by HSlider. There isn’t much error handling, so if you try to set Limit to a negative number, it just will not work right or will crash. Listing 20.4 contains the source code for the HSlider class.

LISTING 20.4 Source Code for the HSlider Class

[code]
public class HSlider : Control
{
public bool Moving;
public Vector2 start;
private int p_value;
private int p_limit;
Sprite sprLeftEnd, sprRightEnd, sprBar;
public HSlider(ContentManager content, SpriteBatch spriteBatch,
SpriteFont font)
: base(content, spriteBatch, font)
{
scale = 1.0f;
start = Vector2.Zero;
Load(“slider_tab”);
sprLeftEnd = new Sprite(content, spriteBatch);
sprLeftEnd.Load(“hslider_end”);
sprLeftEnd.origin = new Vector2(3, 16);
sprRightEnd = new Sprite(content, spriteBatch);
sprRightEnd.Load(“hslider_end”);
sprRightEnd.origin = new Vector2(0, 16);
sprBar = new Sprite(content, spriteBatch);
sprBar.Load(“hslider_bar”);
sprBar.origin = new Vector2(0, 16);
Limit = 100;
}
public int Value
{
get { return p_value; }
set
{
p_value = value;
if (p_value < 0) p_value = 0;
if (p_value > p_limit) p_value = p_limit;
position.X = start.X + p_value;
}
}
public int Limit
{
get { return p_limit; }
set
{
p_limit = value;
sprBar.scaleV = new Vector2((float)
(p_limit + this.image.Width+1), 1.0f);
}
}
public override void Update(TouchLocation touch)
{
base.Update(touch);
Moving = false;
if (touch.State == TouchLocationState.Moved)
{
Rectangle rect = Boundary();
Point point = new Point((int)touch.Position.X,
(int)touch.Position.Y);
if (rect.Contains(point))
{
Vector2 relative = Vector2.Zero;
relative.X = touch.Position.X – position.X;
position.X += relative.X;
Value = (int)(position.X – start.X);
if (position.X < start.X)
position.X = start.X;
else if (p_value > p_limit)
position.X -= relative.X;
Moving = true;
}
}
}
public override void Draw()
{
//draw ends
sprLeftEnd.position = new Vector2(start.X – 16, start.Y);
sprLeftEnd.color = this.color;
sprLeftEnd.Draw();
sprRightEnd.position = new Vector2(start.X + 16 + p_limit, start.Y);
sprRightEnd.color = this.color;
sprRightEnd.Draw();
//draw middle bar
sprBar.position = new Vector2(start.X – 16, start.Y);
sprBar.color = this.color;
sprBar.Draw();
//draw sliding circle
base.Draw();
//draw value text
Vector2 size = p_font.MeasureString(p_value.ToString());
p_spriteBatch.DrawString(p_font, p_value.ToString(), position,
Color.Black, 0.0f, new Vector2(size.X/2, size.Y/2), 0.6f,
SpriteEffects.None, 1.0f);
}
public void SetStartPosition(Vector2 pos)
{
position = pos;
start = pos;
}
}
[/code]

Vertical Slider Control

The Vertical Slider control, or VSlider, shares all the same functionality as HSlider, but calculations are shifted 90 degrees in a vertical orientation. So, all the “X” properties used in the HSlider’s functionality become “Y” properties in VSlider in order for it to work properly. Here are the bitmaps required by the control (and note that button.png is shared):

  • vslider_bar.png
  • vslider_end.png
  • button.png

Listing 20.5 contains the source code for the VSlider class.

LISTING 20.5 Source Code for the VSlider Class

[code]
public class VSlider : Control
{
public bool Moving;
public Vector2 start;
private int p_value;
private int p_limit;
Sprite sprTopEnd, sprBottomEnd, sprBar;
public VSlider(ContentManager content, SpriteBatch spriteBatch,
SpriteFont font)
: base(content, spriteBatch, font)
{
scale = 1.0f;
start = Vector2.Zero;
Load(“slider_tab”);
sprTopEnd = new Sprite(content, spriteBatch);
sprTopEnd.Load(“vslider_end”);
sprTopEnd.origin = new Vector2(16, 3);
sprBottomEnd = new Sprite(content, spriteBatch);
sprBottomEnd.Load(“vslider_end”);
sprBottomEnd.origin = new Vector2(16, 0);
sprBar = new Sprite(content, spriteBatch);
sprBar.Load(“vslider_bar”);
sprBar.origin = new Vector2(16, 0);
Limit = 100;
}
public int Value
{
get { return p_value; }
set
{
p_value = value;
if (p_value < 0) p_value = 0;
if (p_value > p_limit) p_value = p_limit;
position.Y = start.Y + p_value;
}
}
public int Limit
{
get { return p_limit; }
set
{
p_limit = value;
sprBar.scaleV = new Vector2(1.0f, (float)
(p_limit + this.image.Height + 1));
}
}
public override void Update(TouchLocation touch)
{
base.Update(touch);
Moving = false;
if (touch.State == TouchLocationState.Moved)
{
Rectangle rect = Boundary();
Point point = new Point((int)touch.Position.X,
(int)touch.Position.Y);
if (rect.Contains(point))
{
Vector2 relative = Vector2.Zero;
relative.Y = touch.Position.Y – position.Y;
position.Y += relative.Y;
Value = (int)(position.Y – start.Y);
if (position.Y < start.Y)
position.Y = start.Y;
else if (p_value > p_limit)
position.Y -= relative.Y;
Moving = true;
}
}
}
public override void Draw()
{
//draw ends
sprTopEnd.position = new Vector2(start.X, start.Y – 16);
sprTopEnd.color = this.color;
sprTopEnd.Draw();
sprBottomEnd.position = new Vector2(start.X, start.Y + 16 + p_limit);
sprBottomEnd.color = this.color;
sprBottomEnd.Draw();
//draw middle bar
sprBar.position = new Vector2(start.X, start.Y – 16);
sprBar.color = this.color;
sprBar.Draw();
//draw sliding circle
base.Draw();
//draw value text
Vector2 size = p_font.MeasureString(p_value.ToString());
p_spriteBatch.DrawString(p_font, p_value.ToString(), position,
Color.Black, 0.0f, new Vector2(size.X / 2, size.Y / 2), 0.6f,
SpriteEffects.None, zindex);
}
public void SetStartPosition(Vector2 pos)
{
position = pos;
start = pos;
}
}
[/code]

Demonstrating the GUI Controls

The initialization code for a GUI demo or a game using GUI controls will always be much more involved and code-intensive than the processing code where the controls are updated and drawn, because there are so many properties involved in creating and customizing a nice-looking, interactive GUI. Our example this hour demonstrates a GUI with Labels, Buttons, HSliders, and VSliders, and is quite functional, as you can see in Figure 20.1. The source code for the GUI Demo program is found in Listing 20.6.

The example demonstrates labels, buttons, and sliders.
FIGURE 20.1 The example demonstrates labels, buttons, and sliders.

On the left is a vertical slider used to adjust the background color. Why? Just to show that the slider works and does something interesting. Maybe in a game a VSlider would be used to adjust the power level of a catapult or an artillery gun. Really, the use for these controls is up to the game’s designer and is just implemented by the programmer (or team). On the right side are three buttons labeled RED, GREEN, and BLUE. Beside each button is a slider.

Clicking a button changes the color component to a random value from 0 to 255, and automatically moves the slider to that location. The slider can also be moved manually, and this in turn will change the button’s color to reflect the change to that color component. The end result of all this color manipulation is seen in the small, unassuming Exit button at the lower-right corner of the screen. Note that the Limit property of both HSlider and VSlider changes its overall size and defines the limits of the sliding button. The three color sliders have a range of 0 to 255, whereas the smaller vertical slider has a range of 0 to 100.

LISTING 20.6 Source Code for the GUI Demo Program

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
SpriteFont font;
Random rand;
TouchLocation oldTouch;
Label lblTitle, lblColor;
Button[] buttons;
HSlider[] hsliders;
VSlider vslider;
Color bgcolor;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
oldTouch = new TouchLocation();
rand = new Random();
bgcolor = Color.CornflowerBlue;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>(“WascoSans”);
lblTitle = new Label(Content, spriteBatch, font);
lblTitle.text = “Graphical User Interface Demo”;
lblTitle.position = new Vector2(400 – lblTitle.TextSize().X / 2, 0);
//create buttons
buttons = new Button[4];
buttons[0] = new Button(Content, spriteBatch, font);
buttons[0].text = “RED”;
buttons[0].position = new Vector2(400, 150);
buttons[0].textColor = Color.Red;
buttons[0].color = Color.DarkRed;
buttons[0].scaleV = new Vector2(1.5f, 1.0f);
buttons[1] = new Button(Content, spriteBatch, font);
buttons[1].text = “GREEN”;
buttons[1].position = new Vector2(400, 230);
buttons[1].textColor = Color.Green;
buttons[1].color = Color.DarkGreen;
buttons[1].scaleV = new Vector2(1.5f, 1.0f);
buttons[2] = new Button(Content, spriteBatch, font);
buttons[2].text = “BLUE”;
buttons[2].position = new Vector2(400, 310);
buttons[2].textColor = Color.Cyan;
buttons[2].color = Color.DarkCyan;
buttons[2].scaleV = new Vector2(1.5f, 1.0f);
buttons[3] = new Button(Content, spriteBatch, font);
buttons[3].text = “Exit”;
buttons[3].position = new Vector2(750, 450);
buttons[3].scaleV = new Vector2(1.2f, 0.8f);
//create horizontal sliders for color editing
hsliders = new HSlider[3];
hsliders[0] = new HSlider(Content, spriteBatch, font);
hsliders[0].SetStartPosition(new Vector2(500, 150));
hsliders[0].color = Color.Red;
hsliders[0].Limit = 255;
hsliders[1] = new HSlider(Content, spriteBatch, font);
hsliders[1].SetStartPosition(new Vector2(500, 230));
hsliders[1].color = Color.LightGreen;
hsliders[1].Limit = 255;
hsliders[2] = new HSlider(Content, spriteBatch, font);
hsliders[2].SetStartPosition(new Vector2(500, 310));
hsliders[2].color = Color.Cyan;
hsliders[2].Limit = 255;
//create vertical slider for bg color editing
vslider = new VSlider(Content, spriteBatch, font);
vslider.SetStartPosition(new Vector2(140, 170));
vslider.color = Color.Yellow;
vslider.Limit = 100;
//create label for slider
lblColor = new Label(Content, spriteBatch, font);
lblColor.text = “Background Color”;
lblColor.position = new Vector2( 140 – lblColor.TextSize().X/2,
100);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
TouchCollection touchInput = TouchPanel.GetState();
if (touchInput.Count > 0)
{
TouchLocation touch = touchInput[0];
oldTouch = touch;
lblTitle.Update(touch);
UpdateButtons(touch);
UpdateSliders(touch);
vslider.Update(touch);
lblColor.Update(touch);
}
base.Update(gameTime);
}
void UpdateButtons(TouchLocation touch)
{
//update buttons
int tapped = -1;
for (int n = 0; n < buttons.Length; n++)
{
buttons[n].Update(touch);
if (buttons[n].Tapped) tapped = n;
}
//was a button tapped?
int c = rand.Next(256);
switch (tapped)
{
case 0:
buttons[0].color = new Color(c, 0, 0);
hsliders[0].Value = c;
break;
case 1:
buttons[1].color = new Color(0, c, 0);
hsliders[1].Value = c;
break;
case 2:
buttons[2].color = new Color(0, 0, c);
hsliders[2].Value = c;
break;
case 3:
this.Exit();
break;
}
}
void UpdateSliders(TouchLocation touch)
{
//update horizontal sliders
int moving = -1;
for (int n = 0; n < hsliders.Length; n++)
{
hsliders[n].Update(touch);
if (hsliders[n].Moving) moving = n;
}
switch(moving)
{
case 0:
buttons[0].color = new Color(hsliders[0].Value, 0, 0);
break;
case 1:
buttons[1].color = new Color(0, hsliders[1].Value, 0);
break;
case 2:
buttons[2].color = new Color(0, 0, hsliders[2].Value);
break;
}
//colorize Exit button based on colors
buttons[3].color = new Color(hsliders[0].Value,
hsliders[1].Value, hsliders[2].Value);
//update vertical slider
if (vslider.Moving)
{
bgcolor = Color.CornflowerBlue;
bgcolor.R -= (byte)vslider.Value;
bgcolor.G -= (byte)vslider.Value;
bgcolor.B -= (byte)vslider.Value;
}
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(bgcolor);
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.AlphaBlend);
lblTitle.Draw();
foreach (Button b in buttons)
b.Draw();
foreach (HSlider hs in hsliders)
hs.Draw();
vslider.Draw();
lblColor.Draw();
spriteBatch.End();
base.Draw(gameTime);
}
void print(int x, int y, string text, Color color)
{
var pos = new Vector2((float)x, (float)y);
spriteBatch.DrawString(font, text, pos, color);
}
}
[/code]

Windows Phone Game User Interface, Heads Up Display (HUD) #Part 2

Creating a progress bar for game content loading and value status

When playing a game, especially for some big games, at the initialization phase, a progress bar will show you the game object loading status and percents. As a time-based game, the progress bar represents the remaining time. Moreover, a Role Playing Game (RPG) often uses the progress bar to present the current life value. A progress bar is a very common control in game development and is easy to use. In this recipe, you will find the inner code for creating a progress bar.

Getting ready

In Windows Phone 7 XNA programming, two methods will let you create the progress bar. One is using the rectangle for drawing the background and forefront. This is simple but not flexible and stylish. If you want to make some innovative and unusual visual effects, the primitives will not meet your needs. The second method will give you much more space to realize your idea for the progress bar. You can use graphic design tool to draw the background and forefront images as you like, then render these images and change the size of the forefront image to comply with the on going percents; even the round or other shapes can be used for presenting the progress status. In this example, we will use the rectangle images (second method) for implementing the progress bar in Windows Phone 7.

How to do it…

The following steps give you a complete guidance to develop a progress bar in your Windows Phone 7 game:

  1. Create a Windows Phone Game project in Visual Studio 2010 named ProgressBar, change Game1.cs to ProgressBarGame.cs and insert a ProgressBar.cs in the project. Then add ProgressBarBackground.png and ProgressBarForefront. png to the content project.
  2. Add a ProgressBar class in ProgressBar.cs to the main project. Add the code to the ProgressBar class fields:
    [code]
    // SpriteBatch for drawing 2D image
    SpriteBatch spriteBatch;
    // ProgressBar forefront and background images
    Texture2D texForefront;
    Texture2D texBackground;
    // The background and forefront positon
    Vector2 backgroundPosition;
    Vector2 forefrontPosition;
    // The offset of forefront image from the background.
    float forefrontStartOffSetX;
    float forefrontStartOffSetY;
    // Current value of progressbar
    public int Value;
    // The Min and Max values of progressbar
    public int Min;
    public int Max;
    // Percent of current value around 100
    float percent;
    // the actual rendering width of forefront image
    float actualWidth;
    // The direction of progress.
    bool increase = false;
    [/code]
  3. Next, we define the Increase property:
    [code]
    // The increasing direction
    public bool Increase
    {
    get
    {
    return increase;
    }
    set
    {
    // When increasing, the Value begins from Min
    if (value)
    {
    increase = value;
    Value = Min;
    }
    // When decreasing, the Value begins from Max
    else
    {
    increase = value;
    Value = Max;
    }
    }
    }
    [/code]
  4. The next step is to define the constructor of the ProgressBar class:
    [code]
    public ProgressBar(Vector2 position, Texture2D forefront,
    Texture2D background, SpriteBatch spriteBatch)
    {
    this.spriteBatch = spriteBatch;
    texForefront = forefront;
    texBackground = background;
    backgroundPosition = position;
    // Calculate the offset for forefront image
    forefrontStartOffSetX = (texBackground.Width –
    texForefront.Width) / 2;
    forefrontStartOffSetY = (texBackground.Height –
    texForefront.Height) / 2;
    // Create the forefront image position
    forefrontPosition = new Vector2(backgroundPosition.X +
    forefrontStartOffSetX,
    backgroundPosition.Y + forefrontStartOffSetY);
    // Intitialize the Min and Max
    Min = 0;
    Max = 100;
    // Set the increasing direction from high to low.
    Increase = false;
    }
    [/code]
  5. After the constructor, the following method definition is the Update(), so add the method to the ProgressBar class:
    [code]
    public void Update(GameTime gameTime)
    {
    // If decreasing and Value greater than Min, minus the
    // Value by one
    if (Increase && Value < Max)
    {
    Value++;
    }
    else if (Value > Min)
    {
    Value–;
    }
    // Compute the actual forefront image for drawing
    percent = (float)Value / 100;
    actualWidth = percent * texForefront.Width;
    }
    [/code]
  6. The final step of creating the ProgressBar class is to define the Draw() method:
    [code]
    public void Draw()
    {
    spriteBatch.Draw(texBackground, backgroundPosition,
    Color.White);
    spriteBatch.Draw(texForefront, forefrontPosition, new
    Rectangle(0, 0, (int)actualWidth,
    texForefront.Height), Color.White);
    }
    [/code]
  7. Use the ProgressBar class in our game. First, add the code to the class field:
    [code]
    // Texture objects for background and forefront images
    Texture2D texForefront;
    Texture2D texBackground;
    // The background image position
    Vector2 position;
    // Progress bar object
    ProgressBar progressBar;
    [/code]
  8. Then insert the initialization code to the LoadContent() method:
    [code]
    // Load the background and forefront images
    texForefront =
    Content.Load<Texture2D>(“ProgressBarForefront”);
    texBackground =
    Content.Load<Texture2D>(“ProgressBarBackground”);
    // Initialize the progress bar
    position = new Vector2(200, 240);
    progressBar = new ProgressBar(position, texForefront,
    texBackground, spriteBatch);
    [/code]
  9. Next, insert the code to the Update() method:
    [code]
    // Update the progress bar
    progressBar.Update(gameTime);
    [/code]
  10. [code]
    // draw the progress bar
    spriteBatch.Begin();
    progressBar.Draw();
    spriteBatch.End();
    [/code]
  11. Now, build and run the application, and it will run as shown in the following screenshots:
    game content loading and value status

How it works…

In step 2, the texForefront and texBackground are the Texture2D objects that hold the progressBar forefront and background images. The next two variables forefrontStartOffSetX and forefrontStartOffSetY indicate the offset position of forefront from the background; Value stores the progressBar current value; the Min and Max defines the range of the progressBar; percent and actualWidth will be used to calculate and store the current width of the forefront image respectively; the last variable increase represents the direction of the progressBar value increasing.

In step 3, if the Increase value is false, which means it is decreasing, the Value begins from the right with Max. Otherwise, the Value will begin from the left.

In step 4, notice the computation for forefront image offset, we use the background image width minus the width of the forefront image, get the gap between the left sides of the two images, then use the gap value and divide it by 2, get the offset on the X-axis from the background for the forefront image. The offset on the Y-axis is similar. After getting the offset of the forefront image, we set Min to 0 and Max to 100—the value range of progressBar. The last line is to define the increasing direction. False, here, stands for the progress value that will decrease from 100 to 0, right to left.

In step 5, the first part of the Update() method is to change Value by one, according to the increasing direction. The second part is about computing the actual width of the forefront image for rendering.

In step 6, this code draws the background image and forefront image on screen. Notice the third parameter in the Drawing() method for the forefront image. This is a Rectangle parameter, which represents the part of the forefront image for rendering in every frame; it helps you to adjust the size of the forefront image for presenting the value variation of the progress bar.

In step 7, the texForefront stands for the forefront image of the progress bar; the texBackground represents the background image of the progress bar; position defines the progress bar position on the Windows Phone 7 screen; the last variable progressBar is the progress bar object which will perform the different progress behaviors.

Creating buttons in your game

In any type of game, button control is always the most basic and important part. In a GUI system, button control often plays a role in linking different parts of other controls. When you input some text in a text control, you click the button to send the message or confirm it as a command. When you are using a listbox, the up and down buttons help you look up special information that you need in the game, such as choosing weapons. In the development phase, programmers can define specific behaviors of the button events to implement the game logic or effects. To implement a button in the Windows Phone 7 XNA framework is not a hard mission. In this recipe, you will learn how to build your own button in Windows Phone 7.

How to do it…

The following steps will show you the working code for creating the buttons for your Windows Phone 7 game:

  1. Create a Windows Phone Game project named Button, change Game1.cs to ButtonGame.cs. Then add the Button.cs to the main project and button_image.png and gameFont.spriteFont to the content project.
  2. Create the Button class in the Button.cs file. Add the line to the class as a field:
    [code]
    // Button texture
    Texture2D texButton;
    // SpriteBatch for drawing the button image
    SpriteBatch spriteBatch;
    // Button position on the screen
    public Vector2 Position;
    // Color alpha value
    public int Alpha = 255;
    // Button color
    Color color;
    // Timer for game elapsed time accumulation
    float timer;
    // The Tapped bool value indicates whether tap in the button
    region
    public bool Tapped;
    // Event handler OnTapped to react with tap gesture
    public event EventHandler OnTapped;
    [/code]
  3. Then, define the HitRegion property of the Button class:
    [code]
    // Get the hit region
    public Rectangle HitRegion
    {
    get
    {
    return new Rectangle((int)Position.X, (int)Position.Y,
    texButton.Width, texButton.Height);
    }
    }
    [/code]
  4. Next, give the class constructor Button() of the Button class:
    [code]
    // Initialize the button without text
    public Button(Texture2D texture, Vector2 position, SpriteBatch
    spriteBatch)
    {
    this.texButton = texture;
    this.Position = position;
    this.spriteBatch = spriteBatch;
    color = Color.White;
    }
    [/code]
  5. After the class constructor, the important Update() method that reacts to the tap gesture looks similar to the following code:
    [code]
    // Update the button
    public void Update(GameTime gameTime, Vector2 touchPosition)
    {
    // React to the tap gesture
    Point point = new Point((int)touchPosition.X,
    (int)touchPosition.Y);
    // If tapped button, set the Hovered to true and trigger
    // the OnClick event
    if (HitRegion.Contains(point))
    {
    Tapped = true;
    OnTapped(this, null);
    }
    else
    {
    Tapped = false;
    }
    }
    [/code]
  6. The final step to build the Button class is to define the Draw() method:
    [code]
    // Draw the button
    public virtual void Draw(GameTime gameTime)
    {
    timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
    // Draw the button texture
    if (Tapped)
    {
    // Flash the button through the alpha value changing
    if (timer > 100)
    {
    // If the Alpha is 255, set it to 0
    if (Alpha == 255)
    {
    Alpha = 0;
    }
    // If the Alpha value is 0, set it to 255
    else if (Alpha == 0)
    {
    Alpha= 255;
    }
    // Set the color alpha value
    color.A = (byte)Alpha;
    // Set the timer to 0 for next frame
    timer = 0;
    }
    // Draw the button image
    spriteBatch.Draw(texButton, HitRegion, null, color, 0,
    Vector2.Zero, SpriteEffects.None, 0);
    }
    else
    {
    spriteBatch.Draw(texButton, HitRegion,
    null,Color.White, 0,
    Vector2.Zero, SpriteEffects.None, 0);
    }
    }
    [/code]
  7. Use the Button class in our main game class. Insert code to the ButtonGame class field:
    [code]
    // Sprite Font for showing the text
    SpriteFont font;
    // Text object
    string textTapState = “Random Color Text”;
    // Text color;
    Color textColor = Color.White;
    // Random object for showing the random color
    Random random;
    // Button object
    Button button;
    // Button texture;
    Texture2D buttonTexture;
    [/code]
  8. Initialize the random variable in the Initialize() method, and add the following code to the method:
    [code]
    random = new Random();
    [/code]
  9. Load the button image and initialize the button object. Add the code to the LoadContent() method:
    [code]
    font = Content.Load<SpriteFont>(“gameFont”);
    buttonTexture = Content.Load<Texture2D>(“button_image”);
    Vector2 position = new Vector2(
    GraphicsDevice.Viewport.Width / 2 – buttonTexture.Width / 2,
    GraphicsDevice.Viewport.Height/2 – buttonTexture.Height / 2);
    button = new Button(buttonTexture, position, spriteBatch);
    button.OnTapped += new EventHandler(button_OnTapped);
    [/code]
  10. Next is the reaction method for the button OnTapped event:
    [code]
    void button_OnTapped(object sender, EventArgs e)
    {
    textColor.R = (byte)random.Next(0, 256);
    textColor.G = (byte)random.Next(0, 256);
    textColor.B = (byte)random.Next(0, 256);
    }
    [/code]
  11. Get the tapped position and pass it to the Button.Update() method, paste the code in to the Update() method:
    [code]
    TouchCollection touches = TouchPanel.GetState();
    if(touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    Vector2 tappostion = touches[0].Position;
    button.Update(gameTime, tappostion);
    }
    [/code]
  12. Draw the button on screen, and put the following lines of code to the Draw() method:
    [code]
    spriteBatch.Begin(SpriteSortMode.Immediate,
    BlendState.NonPremultiplied);
    button.Draw(gameTime);
    spriteBatch.DrawString(font, textTapState, new Vector2(0, 0),
    textColor);
    spriteBatch.End();
    [/code]
  13. Ok, we have done the code work. Build and run the project, and the application should run similar to the following screenshot; when we tap the button, it will flicker and generate a random color for the text on the top-left corner.
    Creating buttons in your game

How it works…

Steps 2–6 are about creating the Button class.

In step 2, the texButton is the button texture; spriteBatch will render the button texture on screen; Position specifies the location of the button on screen. Alpha represents the alpha value of the button color; timer will be used to accumulate the game elapsed time; bool value tapped will indicate the tapping state of the button; OnTap is the event handler to handle the button tap gesture.

In step 3, the HitRegion property will return the bound surrounding the button for tap validation.

In step 4, the constructor initializes the button texture, position, and color.

In step 5, within the Update() method of the Button class, the code checks the tapped position to see whether it’s inside the button HitRegion. If yes, set Tapped to true and trigger the OnTapped event, else, it will be false.

In step 6, the first line is to accumulate the game elapsed time in milliseconds. The following code draws the button image. If the button is tapped and the elapsed time is greater than 100, it will flicker. The effect is implemented by setting the Alpha value of button color. If the Alpha value equals 255 (Opaque), we set it to 0 (Transparent). Otherwise, the value will be set from 0 to 255. After that, the latest alpha value will be assigned to Color.A , the alpha factor of Color. Then, reset the timer for the next frame. The last line will render the flickering effect on screen.

Steps 7–11 are about using the Button class in the main game class.

In step 7, the font object will render the text on screen; the textTapState stores the text to be displayed; textColor specifies the text color; random will be used to generate a random color for the text; the button variable represents the Button class instance; the buttonTexture loads the button image.

In step 9, the button_OnTapped() method will run if the OnTapped event happens. In the event reaction method, we set the R, G, and B factors of text color randomly, because the RGB value is from 0 to 255, so the random value for each of them must be inside the range.

In step 10, we get the tapped position for the button hit region validation.

In step 11, notice we must set the BlendState.NonPremultiplied because we change the button image Alpha value linearly.

Creating a listbox to speed up your information management in a game

Listbox is a list-style control, which collects the information in the list. For games, list control often plays a role in information management. The items in the listbox are presented one-by-one vertically or horizontally. You can choose the information entry through the control. In this recipe, you will master the technique of building your own listbox.

Getting ready

The example will create a listbox in Windows Phone 7. When you click the scrollbar down or the down button, the listbox will show the list items from the latest index. Once you tap one of the items, the text of the item will be presented at the top-left of the screen. Now, let’s begin with building the Button class.

How to do it…

The following steps will show you the complete process of creating the GUI listbox control:

  1. Create the Windows Phone Game project named ListBoxControl, change Game1.cs to ListBoxControlGame.cs. Add the Button.cs, ScrollBar. cs, and ListBox.cs files to the main project, and add gameFont.spriteFont, ListBoxBackground.png, ScrollBarDown.png, and ScrollBarUp.png to the content project.
  2. Create the Button class in Button.cs. First, insert the lines as the field of the Button class:
    [code]
    // Button texture
    Texture2D texButton;
    // SpriteBatch for drawing the button image
    SpriteBatch spriteBatch;
    // Button position on the screen
    public Vector2 Position;
    // Button color
    Color color;
    // The Tapped bool value indicates whether tap in the button
    region
    public bool Tapped;
    // Event handler OnTap to react with tap gesture
    public event EventHandler OnTapped;
    [/code]
  3. The next part is the HitRegion property:
    [code]
    // Get the hit region
    public Rectangle HitRegion
    {
    get
    {
    return new Rectangle((int)Position.X, (int)Position.Y,
    texButton.Width, texButton.Height);
    }
    }
    [/code]
  4. After the property definition, the constructor will be:
    [code]
    // Initialize the button without text
    public Button(Texture2D texture, Vector2 position, SpriteBatch
    spriteBatch)
    {
    this.texButton = texture;
    this.Position = position;
    this.spriteBatch = spriteBatch;
    color = Color.White;
    }
    [/code]
  5. Then, we define the Update() method:
    [code]
    // Update the button
    public void Update(GameTime gameTime, Vector2 touchPosition)
    {
    // React to the tap gesture
    Point point = new Point((int)touchPosition.X,
    (int)touchPosition.Y);
    // If tapped button, set the Hovered to true and trigger
    // the OnClick event
    if (HitRegion.Contains(point))
    {
    Tapped = true;
    OnTapped(this, null);
    }
    else
    {
    Tapped = false;
    }
    }
    [/code]
  6. The final method in the Button class is the Draw() method:
    [code]
    // Draw the button
    public virtual void Draw(GameTime gameTime)
    {
    // Draw the button texture
    if (Tapped)
    {
    spriteBatch.Draw(texButton, HitRegion, null,
    Color.Red, 0,
    Vector2.Zero, SpriteEffects.None, 0);
    }
    else
    {
    spriteBatch.Draw(texButton, HitRegion,
    null,Color.White, 0,
    Vector2.Zero, SpriteEffects.None, 0);
    }
    }
    [/code]
  7. Create the ScrollBar class in ScrollBar.cs. As the class field, we use the following code:
    [code]
    // SpriteBatch for drawing the scrollbar
    SpriteBatch spriteBatch;
    // ScrollBar up and down buttons
    Button scrollUp;
    Button scrollDown;
    // Textures for scrollbar up and down buttons
    Texture2D texScrollUp;
    Texture2D texScrollDown;
    // The position of scrollbar
    public Vector2 Position;
    // The positions of scrollbar up and down buttons
    public Vector2 scrollUpPosition;
    public Vector2 scrollDownPosition;
    // Event handler when scrollbar up button tapped
    public event EventHandler OnScrollUpTapped;
    // Event handler when scrollbar down button tapped
    public event EventHandler OnScrollDownTapped;
    // The ScrollBar Height and Width
    public int ScrollBarHeight;
    public int ScrollBarWidth;
    [/code]
  8. The following code is the ScrollDownBound and ScrollUpBound property of the ScrollBar class:
    [code]
    // The Bound of Scrollbar down button
    public Rectangle ScrollDownBound
    {
    get
    {
    return new Rectangle((int)scrollDownPosition.X,
    (int)scrollDownPosition.Y,
    (int)texScrollDown.Width,
    (int)texScrollDown.Height);
    }
    }
    // The Bound of Scrollbar up button
    public Rectangle ScrollUpBound
    {
    get
    {
    return new Rectangle((int)scrollUpPosition.X,
    (int)scrollUpPosition.Y,
    (int)texScrollDown.Width,
    (int)texScrollDown.Height);
    }
    }
    [/code]
  9. After the properties, the constructor of the ScrollBar class should be as follows:
    [code]
    // ScrollBar constructor
    public ScrollBar(Vector2 position, int scrollbarHeight,
    ContentManager content, SpriteBatch spriteBatch)
    {
    // Load the textures of scroll bar up and down button
    texScrollDown = content.Load<Texture2D>(“ScrollBarDown”);
    texScrollUp = content.Load<Texture2D>(“ScrollBarUp”);
    Position = position;
    this.spriteBatch = spriteBatch;
    // Get the scrollbar width and height
    this.ScrollBarWidth = texScrollDown.Width;
    this.ScrollBarHeight = scrollbarHeight;
    // The position of scrollbar up button
    this.scrollUpPosition = new Vector2(
    Position.X – ScrollBarWidth / 2, Position.Y);
    // The position of scrollbar down button
    this.scrollDownPosition = new Vector2(
    Position.X – ScrollBarWidth / 2,
    Position.Y + ScrollBarHeight – texScrollDown.Height);
    // Instance the scrollbar up and down buttons
    scrollUp = new Button(texScrollUp, scrollUpPosition,
    spriteBatch);
    scrollDown = new Button(texScrollDown, scrollDownPosition,
    spriteBatch);
    }
    [/code]
  10. Next, we define the Update() method of the Scrollbar class:
    [code]
    // Scrollbar Update method
    public void Update(GameTime gameTime, Vector2 tappedPosition)
    {
    // Check whether the tapped position is in the bound of
    // scrollbar up button
    if (ScrollDownBound.Contains((int)tappedPosition.X,
    (int)tappedPosition.Y))
    {
    // If yes, set the Tapped property of scrollbar down
    // button to true
    scrollDown.Tapped = true;
    // Set the Tapped property of scrollbar up button to
    // false
    scrollUp.Tapped = false;
    // Trigger the scrollbar down button event
    OnScrollDownTapped(this, null);
    }
    else if(ScrollUpBound.Contains((int)tappedPosition.X,
    (int)tappedPosition.Y))
    {
    // If yes, set the Tapped property of scrollbar up
    // button to true
    scrollUp.Tapped = true;
    // Set the Tapped property of scrollbar down button to
    // false
    scrollDown.Tapped = false;
    // Trigger the scrollbar up button event
    OnScrollUpTapped(this, null);
    }
    }
    [/code]
  11. Then, draw the scrollbar on screen by using the Draw() method:
    [code]
    // Draw the scrollbar
    public void Draw(GameTime gameTime)
    {
    // Draw the scrollbar down and up buttons
    scrollDown.Draw(gameTime);
    scrollUp.Draw(gameTime);
    }
    [/code]
  12. Create the ListBox class in the ListBox.cs file. We add the following code as the class field:
    [code]
    // Game object holds the listbox
    Game game;
    // SpriteBatch for drawing listbox
    SpriteBatch spriteBatch;
    // SpriteFont object for showing the listbox text items
    SpriteFont font;
    // The listbox background texture
    Texture2D texBackground;
    // The collection of listbox text items
    public List<string> list;
    // The position of listbox on screen
    public Vector2 Position;
    // The count of the listbox text items
    public int Count;
    // Scrollbar object to control the text items for showing
    ScrollBar scrollBar;
    // The Index for locating the specified item in listbox
    public int Index = 0;
    // The bounds of showed items
    List<Rectangle> listItemBounds;
    // The index of selected items
    public int SelectedIndex = 0;
    // The selected item
    public string SelectedItem = “”;
    // The selected area for highlighting the selected item
    Texture2D SelectedArea;
    // The offset from the position of listbox as the beginning of
    // drawing the text items
    Vector2 Offset;
    // The width and height of listbox
    int ListBoxWidth;
    int ListBoxHeight;
    // The total number of items presenting in listbox
    int ShowedItemCount = 0;
    [/code]
  13. As properties, the CharacterHeight and Bound look similar to the following:
    [code]
    // Get the character height o text item
    public float CharacterHeight
    {
    get
    {
    if (font != null && list.Count > 0)
    {
    // The Y value represents the character height in
    // the returned Vector2 value
    // of SpriteFont.MeasureString()
    return font.MeasureString(list[0]).Y;
    }
    else
    {
    throw new Exception();
    }
    }
    }
    // Get the bound of listbox
    public Rectangle Bound
    {
    get
    {
    return new Rectangle((int)Position.X, (int)Position.Y,
    texBackground.Width, texBackground.Height);
    }
    }
    [/code]
  14. The next block of code is the constructor of the ListBox class:
    [code]
    // Listbox constructor
    public ListBox(Vector2 position, ContentManager content,
    SpriteBatch
    spriteBatch, Game game)
    {
    this.game = game;
    this.spriteBatch = spriteBatch;
    listItemBounds = new List<Rectangle>();
    list = new List<string>();
    Position = position;
    font = content.Load<SpriteFont>(“gameFont”);
    texBackground =
    content.Load<Texture2D>(“ListBoxBackground”);
    ListBoxWidth = texBackground.Width;
    ListBoxHeight = texBackground.Height;
    // Define the scrollbar position relative to the position
    // of listbox
    Vector2 scrollBarPosition = new Vector2(
    Position.X + ListBoxWidth + 40, Position.Y);
    // Instance the scrollbar
    scrollBar = new ScrollBar(scrollBarPosition, ListBoxHeight,
    content, spriteBatch);
    scrollBar.OnScrollUpTapped += new
    EventHandler(scrollBar_OnScrollUpTapped);
    scrollBar.OnScrollDownTapped += new
    EventHandler(scrollBar_OnScrollDownTapped);
    // Define the offset for drawing the text items
    Offset = new Vector2(20, 4);
    }
    [/code]
  15. Now, we define the reaction method of the tap event of the scrollbar’s up and down buttons:
    [code]
    // The reaction method of scrollbar down button tapping event
    void scrollBar_OnScrollDownTapped(object sender, EventArgs e)
    {
    // If the current item index plus the ShowedItemCount
    // is less
    // than count of list items, increase the Index
    if (Index + ShowedItemCount < Count)
    {
    Index++;
    }
    }
    // The reaction method of scrollbar up button tapping event
    void scrollBar_OnScrollUpTapped(object sender, EventArgs e)
    {
    // If the current item index is greater than 0, decrease
    the
    // Index
    if (Index > 0)
    {
    Index–;
    }
    }
    [/code]
  16. The following important method in the ListBox class is the Update() method:
    [code]
    // Check the tapping state of scrollbar and the selection of
    // listbox items
    public void Update(GameTime gameTime, Vector2 tapposition)
    {
    scrollBar.Update(gameTime, tapposition);
    CheckSelected(tapposition);
    }
    [/code]
  17. The definition of CheckSelected() is as follows:
    [code]
    // Get the selected index and item in listbox
    private void CheckSelected(Vector2 tappedPosition)
    {
    for (int i = 0; i < ShowedItemCount; i++)
    {
    // Check whether the tapped position is in the region
    of
    // listbox and in which one of the item bounds.
    if (Bound.Contains(
    (int)tappedPosition.X, (int)tappedPosition.Y)
    && tappedPosition.Y <
    listItemBounds[i].Y + CharacterHeight)
    {
    SelectedIndex = i;
    SelectedItem = list[Index + i];
    break;
    }
    }
    }
    [/code]
  18. Before giving the definitions of the AddItem() and RemoveItem() methods, let’s give the definition of the GetListItemBound() method:
    [code]
    private void GetListItemBound(List<String> list)
    {
    // If the count of the items is greater than 0
    if (list.Count > 0)
    {
    Rectangle itemBound;
    // If the current count of item is less than the
    // ShowedItemCount, set the LoopBound to Count, else,
    // set it to ShowedItemCount.
    int LoopBound = Count < ShowedItemCount ? Count :
    ShowedItemCount;
    // Get the item bounds
    for (int i = 0; i < LoopBound; i++)
    {
    itemBound = new Rectangle(
    (int)Position.X,
    (int)(Position.Y + Offset.Y) +
    font.LineSpacing * i,
    (int)ListBoxWidth, (int)CharacterHeight);
    listItemBounds.Add(itemBound);
    }
    }
    }
    [/code]
  19. Next it’s time for implementing the AddItem() and the RemoveItem() methods:
    [code]
    // Add text item to listbox
    public void AddItem(string str)
    {
    // Add the text item to the list object
    this.list.Add(str);
    // Update total number of list items
    Count = list.Count;
    // Set the limited count for showing the list items
    if (list.Count == 1)
    {
    ShowedItemCount = (int)(texBackground.Height /
    CharacterHeight);
    }
    // Get the text item bounds
    listItemBounds.Clear();
    GetListItemBound(list);
    }
    [/code]
  20. Now, define the RemoveItem() method:
    [code]
    public void RemoveItem(string str)
    {
    // Delete the text item from the list items
    this.list.Remove(str);
    // Update the total number of list items
    Count = list.Count;
    GetListItemBound(list);
    }
    [/code]
  21. After the text item management functions, is the Selection Area creating method:
    [code]
    // Create the texture of the selected area
    private void CreateSelectedArea(Rectangle rectangle)
    {
    // Initialize the selected area texture
    SelectedArea = new Texture2D(game.GraphicsDevice,
    rectangle.Width, rectangle.Height, false,
    SurfaceFormat.Color);
    // Initialize the pixels for the texture
    Color[] pixels = new Color[SelectedArea.Width *
    SelectedArea.Height];
    for (int y = 0; y < SelectedArea.Height; y++)
    {
    for (int x = 0; x < SelectedArea.Width; x++)
    {
    pixels[x + y * SelectedArea.Width] =
    new Color(new Vector4(125f, 125f,125f, 0.5f));
    }
    }
    // Set the pixels to the selected area texture
    SelectedArea.SetData<Color>(pixels);
    }
    [/code]
  22. The final step in building the ListBox class is to draw the listbox on screen through the Draw() method:
    [code]
    public void Draw(GameTime gameTime)
    {
    // Draw the listbox background
    spriteBatch.Draw(texBackground, Position, Color.White);
    // The text items exist
    if (Count > 0)
    {
    // If current count of items is less than the
    // ShowedItemCount, show the items one by one
    // from the beginning
    if (Count <= ShowedItemCount)
    {
    for (int i = 0; i < Count; i++)
    {
    spriteBatch.DrawString(font, list[i],
    Position + new Vector2(
    Offset.X, Offset.Y + font.LineSpacing * i),
    Color.White);
    }
    }
    // If current count of items is greater than the
    // ShowedItemCount, show the items from the current
    // index.
    else
    {
    for (int i = 0; i < ShowedItemCount; i++)
    {
    spriteBatch.DrawString(font, list[i + Index],
    Position + new Vector2(
    Offset.X, Offset.Y + font.LineSpacing * i),
    Color.White);
    }
    }
    // If the SelectionArea is not created, creat a new
    // one
    if (SelectedArea == null)
    {
    CreateSelectedArea(listItemBounds[0]);
    }
    // Draw the SelectedArea texture
    spriteBatch.Draw(SelectedArea,
    listItemBounds[SelectedIndex], Color.White);
    }
    scrollBar.Draw(gameTime);
    }
    [/code]
  23. Woo! The ListBox class and its dependent classes are done. Now, we will use the ListBox class in our main project, and this is simple and easy to code. Insert the following lines to the ListBoxControlGame class field:
    [code]
    SpriteFont spriteFont;
    ListBox listBox;
    [/code]
  24. Initialize the spriteFont object and listBox. Add the lines in the LoadContent() method:
    [code]
    // Create a new SpriteBatch, which can be used to draw
    textures.
    spriteBatch = new SpriteBatch(GraphicsDevice);
    spriteFont = Content.Load<SpriteFont>(“gameFont”);
    listBox = new ListBox(new Vector2(200, 100), this.Content,
    spriteBatch, this);
    listBox.AddItem(“Item1”);
    listBox.AddItem(“Item2”);
    listBox.AddItem(“Item3”);
    listBox.AddItem(“Item4”);
    listBox.AddItem(“Item5”);
    listBox.AddItem(“Item6”);
    listBox.AddItem(“Item7”);
    listBox.AddItem(“Item8”);
    [/code]
  25. Get the tapped position on listBox. Paste the following code to the Update() method:
    [code]
    TouchCollection touches = TouchPanel.GetState();
    if (touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    Vector2 tapposition = touches[0].Position;
    listBox.Update(gameTime, tapposition);
    }
    [/code]
  26. Draw the listbox and selected text item on screen, and insert the block of code in to the Draw() method:
    [code]
    spriteBatch.Begin(SpriteSortMode.Immediate,
    BlendState.NonPremultiplied);
    listBox.Draw(gameTime);
    spriteBatch.DrawString(spriteFont,
    “ SelectedItem: “ + listBox.SelectedItem,
    new Vector2(0, 0), Color.White);
    spriteBatch.End();
    [/code]
  27. The whole project is complete. Build and run the application. It should look similar to the following screenshots:
    listbox to speed up your information management

How it works…

Steps 1–6 are about creating the Button class.

In step 2, the texButton stores the button texture; spriteBatch will render the button texture on screen; Position defines the button position on screen; the color object represents the button color; Tapped shows the tapping state of the button; OnTap is the event handler of tap gesture.

In step 3, the HitRegion property returns the button hit region around the button background texture.

In step 5, the Update() method gets the tapped position and checks whether it’s inside the button hit region. If yes, set Tapped to true and trigger the OnTapped event. Else, set Tapped to false.

In step 6, the code draws a button on the Windows Phone 7 screen. If the button is tapped, draw the button in red, or else, in white.

Steps 7–11 are about implementing the Scrollbar class.

In step 7, the spriteBatch will render the scrollbar background texture on screen; the scrollUp and scrollDown buttons will be used to increase or decrease the index of listbox items; Position stores the position of the scrollbar; scrollUpPosition and scrollDownPosition maintain the positions of the scrollUp and scrollDown buttons; the following two event handlers specify the tap events of the two scroll buttons when they are tapped. The two variables ScrollBarHeight and ScrollBarWidth define the height and width of the scrollbar buttons.

In step 8, the ScrollDownBound property returns the bound around the scrollDown button, similar to the ScrollUpBound property.

In step 9, the constructor initializes the two scrollbar buttons and gets the scrollbar width and height.

In step 10, the Update() method checks the tapped position to see whether it’s in the ScrollDownBound, if yes, set the Tapped of scrollDown to true and the property of scrollUp to false, then trigger the OnScrollDownTapped event; otherwise, do similar things for ScrollUpBound.

Steps 12–22 are to build the ListBox class using Button and Scrollbar classes.

In step 12, the game is the object of Game that supplies the GraphicDevice for drawing the selection area texture; spriteBatch will draw the listbox on screen; font will be used to draw the text of the listbox items; texBackground holds the listbox background texture; list is the collection of listbox text items; Position specifies the position of the listbox; Count shows the current total number of listbox items; scrollbar is the object of ScrollBar used to explore the listbox items; Index shows the current beginning index in listbox items; listItemBounds is the collection the bounds of list items. The following two variables indicate the index of the selected item in the listbox; if the item is selected, the SelectedArea will present a rectangle texture around the item; Offset is the position for drawing the text item relative to the position of the listbox; ShowedItemCount saves the maximum limitation of the number for rendering listbox items.

In step 13, the CharacterHeight returns the character height of the listbox text item. The Bound property gets the rectangle surrounding the listbox.

In step 15, when the scrollUp button is tapped, the Index will increase by 1 if the sum of Index plus ShowedItemCount is less than the amount of listbox items. While the scrollDown button is tapped and the Index is greater than 0, we will decrease the Index by one.

In step 16, first the Update() method checks the tapped position, to see whether it’s inside the buttons of the scrollbar. Then, use the CheckSelected() method to check the listbox item selection.

In step 17, because the ShowedItemCount limits the number for showing items, we just need to do the same steps for the loop. In the body of the for loop, first we check the tapped position to see whether it’s in the bound of the listbox, then we examine the lower-side of the current item bound to see whether it is greater than the Y factor of the tapped position. If so, it means the tapped position is in the region of the current item bound that is selected. Now, we set the current i to the SelectedIndex and the content of the current item to SelectedItem. The break here is important, as we only need the first selected one.

In step 18, we implement the GetListItemBound() method. When the list.Count is greater than 0 and if the current count of listbox item is less than ShowedItemCount, the loop will be equal to the Count; otherwise, the ShowedCount should be the LoopBound. In the loop, the code generates the item bound with the CharacterHeight and ListBoxWidth.

In step 19, once every new listbox item is added to the list, we will update the Count. This stands for the total number of listbox items. We get the ShowedItemCount when there is an item in the listbox. After that, we obtain the bounds of items through the GetListItemBound() method defined in step 18.

In step 21, the CreateSelectedArea() method first creates a new texture— SelectedArea which has the same size as the method parameter—rectangle. The second line defines the dimension of pixels equal to the SelectedArea. In the for loop, we set the actual color to each pixel of the new texture. Finally, the SetData() method copies the pixels to the SelectedArea for texture drawing.

In step 22, the first line of the Draw() method draws the listbox background texture. When the Count is greater than 0 and is equal to, or less than, the ShowedItemCount, the list item will be drawn one by one from the beginning of the list. Otherwise, we draw the items from the current index. After that, if one of the list items is selected, the SelectionArea will also be rendered around the selected item.

Steps 23–26 are for drawing the listbox on screen in the main game class ListBoxControlGame.

Creating a text input control to communicate with others in a game

Textbox is a very common and useful control in applications, reading the input and displaying the symbols in the main area. For multiplayer games, players love to use the control to communicate with each other for exchanging their thoughts. A textbox control can also act like a command line for controlling the game settings. With textbox control and corresponding functions, you can do a lot of things. In this recipe, you will learn how to make your own textbox control in Windows Phone 7.

How to do it…

The following steps will help you to implement a text input control for communicating in your own Windows Phone 7 game:

  1. Create a Windows Phone Game project in Visual Studio 2010 named TextBox, and change Game1.cs to TextBoxGame.cs. Then, add cursor.png, button.png, backspace.png, TextboxBackground.png, and gameFont.spriteFont to the content project.
  2. Now, let’s develop a button class for input. First of all, in the Button.cs file we declare the class field and property:
    [code]
    // Button texture
    Texture2D texButton;
    // SpriteBatch for drawing the button image
    SpriteBatch spriteBatch;
    // SpriteFont for drawing the button text
    SpriteFont font;
    // Button text
    public String Text = “”;
    // Button text position on the screen
    public Vector2 TextPosition;
    // Button text size
    public Vector2 TextSize;
    // Button position on the screen
    public Vector2 Position;
    // The Clicked bool value indicates whether tap in the button
    public bool Clicked;
    // Event handler when tap on the button
    public event EventHandler OnClicked;
    // Get the hit region
    public Rectangle HitRegion
    {
    get
    {
    return new Rectangle((int)Position.X, (int)Position.Y,
    texButton.Width, texButton.Height);
    }
    }
    [/code]
  3. Next, we define two overload constructors of the Button class:
    [code]
    // Initialize the button without text
    public Button(Texture2D texture, Vector2 position, SpriteFont
    font, SpriteBatch spriteBatch)
    {
    this.texButton = texture;
    this.Position = position;
    this.spriteBatch = spriteBatch;
    this.font = font;
    }
    // Initialize the button with text
    public Button(Texture2D texture, Vector2 position, String
    text, SpriteFont font, SpriteBatch spriteBatch)
    {
    this.texButton = texture;
    this.Position = position;
    this.spriteBatch = spriteBatch;
    this.Text = text;
    // Compute the text size and place the text in the center
    // of the button
    TextSize = font.MeasureString(Text);
    this.TextPosition = new Vector2(position.X +
    texture.Width / 2 – TextSize.X / 2, position.Y);
    this.font = font;
    }
    [/code]
  4. In the following step, we will make the button react to the tap gesture. Add the Update() code as follows:
    [code]
    // Update the button
    public void Update(GameTime gameTime, Vector2 touchPosition)
    {
    // React to the tap gesture
    Point point = new Point((int)touchPosition.X,
    (int)touchPosition.Y);
    // If tapped button, set the Hovered to true and trigger
    // the OnClick event
    if (HitRegion.Contains(point))
    {
    Clicked = true;
    OnClicked(this, null);
    }
    // Update the button
    public void Update(GameTime gameTime, Vector2 touchPosition)
    {
    // React to the tap gesture
    Point point = new Point((int)touchPosition.X,
    (int)touchPosition.Y);
    // If tapped button, set the Hovered to true and trigger
    // the OnClick event
    if (HitRegion.Contains(point))
    {
    Clicked = true;
    OnClicked(this, null);
    }
    [/code]
  5. The final step for the Button class is to draw it on the screen. To do this, we use this block of code:
    [code]
    // Draw the button
    public virtual void Draw()
    {
    // Draw the button texture
    if (!Clicked)
    {
    spriteBatch.Draw(texButton, HitRegion, Color.White);
    }
    else
    {
    spriteBatch.Draw(texButton, HitRegion, Color.Red);
    }
    // Draw the button text
    spriteBatch.DrawString(font, Text, TextPosition,
    Color.White);
    }
    [/code]
  6. In this step, we begin to write the TextBoxControl class. In TextBoxControl.cs, add the lines to the TextBoxControl class as fields:
    [code]
    // SpriteBatch for drawing the textbox texture
    SpriteBatch spriteBatch;
    // SpriteFont for drawing the textbox font
    SpriteFont spriteFont;
    // Textbox background texture
    Texture2D texBackGround;
    // Textbox cursor texture
    Texture2D texCursor;
    // Textbox Bound for showing the text
    public Rectangle Bound;
    // Textbox position
    public Vector2 Position;
    // Textbox cursor position
    public Vector2 CursorPosition;
    // Timer used to control the cursor alpha value
    float timer;
    // Text position in the textbox
    public Vector2 TextPosition;
    // The text size of the showing text
    public Vector2 textSize;
    // The character size of the textbox text
    private float characterSize;
    // Alpha value for the cursor
    int alpha = 255;
    // The cursor color
    Color cursorColor;
    // TypedText stores the typed letters
    public string TypedText = “”;
    // ShowedText saves the text shown in the textbox
    public string ShowedText = “”;
    [/code]
  7. Next, we add the properties to the TextBoxControl class:
    [code]
    // Get the character size
    public float CharacterSize
    {
    get
    {
    textSize = spriteFont.MeasureString(TypedText);
    characterSize = textSize.X / TypedText.Length;
    return characterSize;
    }
    }
    // Get the text size
    public Vector2 TextSize
    {
    get
    {
    return textSize = spriteFont.MeasureString(TypedText);
    }
    }
    // Get the bound for showing the text
    public int ShowedCharacterBound
    {
    get
    {
    return (int)(Bound.Width / CharacterSize);
    }
    }
    [/code]
  8. The following part is about the TextBoxControl class initialization, and the constructer looks as follows:
    [code]
    // Initialize the textbox
    public TextBoxControl(Vector2 position, Texture2D texCursor,
    Texture2D texBackground, SpriteFont font, SpriteBatch
    spriteBatch)
    {
    this.Position = position;
    this.spriteBatch = spriteBatch;
    this.texCursor = texCursor;
    this.spriteFont = font;
    this.texBackGround = texBackground;
    // Set the bound of textbox control
    Bound = new Rectangle((int)position.X, (int)position.Y,
    texBackGround.Width, texBackGround.Height);
    // Set the cursor position
    this.CursorPosition = new Vector2(position.X + 10,
    position.Y + 10);
    // Set the text position
    this.TextPosition = new Vector2(position.X + 10,
    position.Y);
    // Set the cursor color with alpha value
    cursorColor = new Color(255, 255, 255, alpha);
    }
    [/code]
  9. After the initialization, the following code is the definition of the Update() method:
    [code]
    public void Update(GameTime time)
    {
    // Accumulate the game elapsed milliseconds
    timer += (float)time.ElapsedGameTime.TotalMilliseconds;
    // Every 500 milliseconds the alpha value of the cursor
    will
    // change from 255 to 0 or 0 to 255.
    if (timer > 500)
    {
    if (alpha == 255)
    {
    alpha = 0;
    }
    else if (alpha == 0)
    {
    alpha = 255;
    }
    cursorColor.A = (byte)alpha;
    timer = 0;
    }
    }
    [/code]
  10. Then we define the Draw() method :
    [code]
    public void Draw()
    {
    // Draw the textbox control background
    spriteBatch.Draw(texBackGround, Position, Color.White);
    // Draw the textbox control cursor
    spriteBatch.Draw(texCursor, CursorPosition, cursorColor);
    // Draw the textbox showing text
    spriteBatch.DrawString(spriteFont, ShowedText,
    TextPosition, Color.White);
    }
    [/code]
  11. From this step, we will use the Button class and the TextBoxControl class in the main game class. Now, add the lines to the TextBoxGame class fields:
    [code]
    // SpriteFont object
    SpriteFont font;
    // TextboxControl object
    TextBoxControl textBox;
    // Button objects
    Button buttonA;
    Button buttonB;
    Button buttonBackspace;
    [/code]
  12. Initialize the textbox control and buttons. Insert the code to the LoadContent() method:
    [code]
    // Load the textbox textures
    Texture2D texCursor = Content.Load<Texture2D>(“cursor”);
    Texture2D texTextboxBackground =
    Content.Load<Texture2D>(“TextboxBackground”);
    // Load the button textures
    Texture2D texButton = Content.Load<Texture2D>(“button”);
    Texture2D texBackSpace = Content.Load<Texture2D>(“Backspace”);
    font = Content.Load<SpriteFont>(“gameFont”);
    // Define the textbox position
    Vector2 position = new Vector2(400, 240);
    // Initialize the textbox
    textBox = new TextBoxControl(position, texCursor,
    texTextboxBackground, font, spriteBatch);
    // Initialize the buttonA
    buttonA = new Button(texButton, new Vector2(400, 350), “A”,
    font, spriteBatch);
    buttonA.OnClicked += new EventHandler(button_OnClicked);
    // Initialize the buttonB
    buttonB = new Button(texButton, new Vector2(460, 350), “B”,
    font, spriteBatch);
    buttonB.OnClicked += new EventHandler(button_OnClicked);
    // Initialize the backspace button
    buttonBackspace = new Button(texBackSpace,
    new Vector2(520, 350), font, spriteBatch);
    buttonBackspace.OnClicked += new
    EventHandler(buttonBackspace_OnClicked);
    [/code]
  13. Define the event handling code for buttonA and button, which is same for both:
    [code]
    void button_OnClicked(object sender, EventArgs e)
    {
    // Add the button text to the textbox TypedText
    // Update the position of textbox cursor
    textBox.CursorPosition.X = textBox.TextPosition.X +
    textBox.TypedText += ((Button)sender).Text;
    textBox.TextSize.X;
    // Get the textbox showed character bound
    int showedCharacterBound = textBox.ShowedCharacterBound;
    // check whether the textbox cursor goes outside of the
    // textbox bound
    if (textBox.CursorPosition.X > textBox.Bound.X +
    textBox.Bound.Width)
    {
    // If yes, set cursor positon at the right side of
    // the textbox
    textBox.CursorPosition.X = textBox.TextPosition.X +
    textBox.CharacterSize * showedCharacterBound;
    // Show the TypedText from end to the left in
    // the range for showing characters of textbox
    textBox.ShowedText =
    textBox.TypedText.Substring(textBox.TypedText.Length –
    showedCharacterBound – 1, showedCharacterBound);
    }
    else
    {
    // If not, just set the current TypedText to the
    // showedText
    textBox.ShowedText = textBox.TypedText;
    }
    }
    [/code]
  14. The next block of code is the handling code for the backspace button:
    [code]
    void buttonBackspace_OnClicked(object sender, EventArgs e)
    {
    // Get the length of TypedText
    int textLength = textBox.TypedText.Length;
    // Check whether the TypedText is greater than 0
    if (textLength > 0)
    {
    // If yes, delete the last character
    textBox.TypedText = textBox.TypedText.Substring(0,
    textLength – 1) ;
    // Get the current showed character count.
    int showedCharacterCount = (int)(textBox.TextSize.X /
    textBox.CharacterSize);
    // Check whether the current showed character count is
    less than
    // the textbox showed character bound
    if (showedCharacterCount <=
    textBox.ShowedCharacterBound)
    {
    // If yes, just update the cursor position with
    // current text size and the showedText with
    // current text
    textBox.CursorPosition.X = textBox.TextPosition.X
    + textBox.TextSize.X;
    textBox.ShowedText = textBox.TypedText;
    }
    else
    {
    // If not, show the TypedText from end to the
    // left in the range for showing characters
    // of textbox
    textBox.ShowedText = textBox.TypedText.Substring(
    textBox.TypedText.Length –
    textBox.ShowedCharacterBound,
    textBox.ShowedCharacterBound);
    }
    }
    }
    [/code]
  15. Trigger the button event. Add the code to the Update() method:
    [code]
    TouchCollection touches = TouchPanel.GetState();
    if(touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    buttonA.Update(gameTime, touches[0].Position);
    buttonB.Update(gameTime, touches[0].Position);
    buttonBackspace.Update(gameTime, touches[0].Position);
    }
    textBox.Update(gameTime);
    [/code]
  16. Draw the textbox and buttons on screen. Paste the code into the Draw() method:
    [code]
    spriteBatch.Begin();
    textBox.Draw();
    buttonA.Draw();
    buttonB.Draw();
    buttonBackspace.Draw();
    spriteBatch.End();
    [/code]
  17. Now, build and run the application. When you tap button A and button B, the textbox will show the input as shown in the following screenshot to the left. When you tap the backspace button, it will look similar to the following screenshot on the right:
    text input control to communicate

How it works…

Steps 2–5 are responsible for creating the Button class:

In step 2, the texButton stores the button texture; font will be used to render the button text, we use the button Text for the input text; the position variable tells you where the button is on the screen; the bool value Clicked indicates whether the tap gesture takes place in the button hit region; when the button is clicked, the OnClicked event will be triggered. The HitRegion property returns the bound of the button for clicking.

In step 3, the first constructor initializes the button without text. The second constructor initializes the button with text and places the text in the center of the button. The SpriteFont.MeasureString() method computes and returns the text size as a Vector2, the X value holds the text width, and the Y value holds the text height.

In step 4, the reacting code first gets the tapped position, then use the Rectangel. Contains() method to check whether the position is inside the hit region, if yes, we set the Clicked to true and trigger the OnClicked() event.

Steps 6–10 are about creating the TextBoxControl class:

In step 6, the first four variables deal with the textbox texture and font; the following Bound variable stores the textbox bound for showing text; Position indicates the location of the textbox control on the screen; the CursorPosition represents the cursor place within the textbox control bound; the timer variable will be used to control the alpha value of the cursor for the flashing effect; the TextPosition shows the text position inside the textbox control; textSize represents the size of the TypedText; the characterSize defines the size of a single character of the TypedText; the ShowedText stores the text that will be presented in the textbox.

In step 7, the CharacterSize returns the size of a single character in the TypedText, we use SpriteFont.MeasureString() to compute the size of the TypedText, then use the X value of the textSize and divide the TypedText length to get the unit character length; the TextSize returns the size of TypedText; ShowedCharacterBound returns the region for showing the TypedText.

In step 9, the Update() method checks whether the accumulated milliseconds are greater than 500 or not. If yes and the alpha value is equal to 255 (opaque), it will be set to 0 (transparent), and vice versa. After setting the latest alpha value to alpha factor of cursor color—cursorColor.A, we reset the timer for the next interval.

Steps 11–16 are about using the Button and TextBoxControl class in the main class. We will draw the button and textbox control on the Windows Phone 7 screen and perform the reactions for text input and delete.

In step 11, the textBox stands for the TextBoxControl; buttonA represents the button for input character A; buttonB is used to input character B; the buttonBackspace will delete the character of the TypedText from the end to the beginning.

In step 12, the code loads the textures for the textbox and buttons first. Then, it initializes their event handling code.

In step 13, the code reacts to the event triggered from buttonA or buttonB. The first line casts the sender to Button. Then add the Text value to the TextBoxControl.TypedText. After getting the text, the cursor position is updated following the new TypedText. The rest of the code deals with the situation when the length of the TypedText is greater than the textbox bound. If this happens, the cursor will still stay at the right-side of the textbox, the showedText will be the substring of the TypedText from the end to the left in the range for showing characters of the textbox. On the other hand, the entire TypedText will be drawn.

In step 14, as the reaction code for the backspace button, at the beginning, we get the length of the TypedText. Then check whether it is greater than 0. If yes, we delete the last character. The rest of the code works with the state when the deleted TypedText length is greater or less than the textbox bound. If greater, the showedText will range from the end of the deleted TypedText to the left about the showed character count of the textbox. Otherwise, the cursor will follow the current TypedText, which will be completely rendered on the screen.

Using the IronPython Windowed Environment

IronPython also provides access to a windowed environment, but you can’t access it from the start menu. Instead, you  must provide a shortcut to the file you want to run or open a command prompt and start the application manually. The windowed environment simply provides a GUI interface for working with IronPython, but doesn’t do anything else for you. You start the windowed environment by using IPYW .EXE. If you type IPYW and press Enter, you see the command line switch help shown in Figure 1-9.

The windowed version supports the same features as the command line version.As you can see from Figure 1-9, the windowed environment supports the same command line switches as the character mode command line version. However, you can’t use the windowed environment to run the interpreted console environment, which is a shame because many developers would prefer working in the nicer environment. To see that the windowed environment works the same way as the standard console, type IPYW WFDemo.py and press Enter.

Mobile Utility Applications

Several mobile utility applications are available for AIR developers.

Launchpad

As its name indicates, this Adobe Labs beta tool gives Flex developers a head start in creating AIR desktop and mobile applications. Launchpad is an AIR application that generates a complete Flex project that is ready to import to Flash Builder.

The process consists of four steps. The Settings option is for the application descriptor and permissions. The Configuration option is for setting up listeners of various events, such as the application moving to the background. The Samples option is for importing sample code for the APIs, such as Geolocation or Microphone. The Generate option creates a ZIP file at the location of your choice, ready to be imported. Sample icons and assets are automatically included based on your selection.

For more information on Launchpad, go to http://labs.adobe.com/technologies/airlaunchpad/.

Device Central CS5

Adobe Device Central is not available for AIR development. At the time of this writing, it is targeted at Flash Player development only. It provides useful tools such as accelerometer simulation that can otherwise only be tested on the device. ADL provides some, albeit more limited, functionality. It can simulate soft keys and device rotation.

Package Assistant Pro

Serge Jespers, from Adobe, wrote an AIR application that facilitates the packaging of AIR applications for Android. You can download the Mac OS X and Windows versions from http://www.webkitchen.be/package-assistant-pro/. Note that you need the .swf file, the application descriptor file, and the code-signing certificate.

The packager preferences store the location of the AIR SDK adt.jar (located in AIRsdk/lib/) and your code-signing certificate. The packaging process consists of a few easy steps, all accomplished via a user interface. Browse to select the required files, enter your certificate password, and choose a name for the APK file. You can choose to compile a device release or a debug version.

This tool is convenient if you use one of the tools we will discuss next, and is a nice GUI alternative to using the command-line tool.

De MonsterDebugger

De MonsterDebugger is an open source debugger for Flash, Flex, and AIR. Version 3.0 provides the ability to debug applications running on your device and send the output straight to an AIR desktop application. It lets you manipulate values, execute methods, and browse the display list. For more information, go to http://demonsterdebugger.com/.