Your younger brother has
become adept at balancing the cheese on the bat, but he wants something
to aim at, so now’s the time to provide some targets. You decide to use
tomatoes for this, so you need to add them to your program. You want to
have lots of tomatoes, so you need to create an array of GameSpriteStruct instances to hold all of them:
Texture2D tomatoTexture;
GameSpriteStruct[] tomatoes;
int numberOfTomatoes = 20;
These are the fields
that you have to create to hold tomato information. Note that although
I’ve created an array reference called tomatoes, I haven’t yet created the array itself. You’ll load the tomato texture from your image into a single Texture2D object which will be loaded with the rest of the content for the game:
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
cheese.SpriteTexture = Content.Load<Texture2D>("Images/Cheese");
bread.SpriteTexture = Content.Load<Texture2D>("Images/Bread");
tomatoTexture = Content.Load<Texture2D>("Images/Tomato");
setupSprites();
}
Textures are classes, and
are managed by reference, not value, so each of your tomatoes contains a
reference to the same tomato texture:
void setupSprites()
{
setupSprite(ref cheese, 0.05f, 200.0f, minDisplayX, minDisplayY);
setupSprite(ref bread, 0.15f, 120.0f, displayWidth / 2, displayHeight / 2);
tomatoes = new GameSpriteStruct[numberOfTomatoes];
float tomatoSpacing = (maxDisplayX - minDisplayX) / numberOfTomatoes;
for (int i = 0; i < numberOfTomatoes; i++)
{
tomatoes[i].SpriteTexture = tomatoTexture;
setupSprite(
ref tomatoes[i],
0.05f, // 20 tomatoes across the screen
1000, // 1000 ticks to move across the screen
minDisplayX + (i * tomatoSpacing), minDisplayY);
}
}
The setupSprites method creates the tomatoes array and contains a for
loop that works through each tomato sprite and sets its size and
position. Your first version of the game has the tomatoes evenly spaced
in a line along the top of the screen. To make this work, the method
uses a local variable called tomatoSpacing
that’s set to the width of the display divided by the number of
tomatoes that you’re using in the game. Note that you’re following the
advice of the Great Programmer in that it is very easy to change the
number of tomatoes in the game; you need only change the value of one
variable.
At the moment, you won’t be making the tomatoes move, so the Update method only needs to copy the X and Y positions of the tomato into the rectangle for that sprite:
for (int i = 0; i < numberOfTomatoes; i++)
{
tomatoes[i].SpriteRectangle.X = (int)tomatoes[i].X;
tomatoes[i].SpriteRectangle.Y = (int)tomatoes[i].Y;
}
The last thing you need to do is add the code to draw all the tomatoes. This is placed in the Draw method as follows:
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(cheese.SpriteTexture, cheese.SpriteRectangle, Color.White);
spriteBatch.Draw(bread.SpriteTexture, bread.SpriteRectangle, Color.White);
for (int i = 0; i < numberOfTomatoes; i++)
{
spriteBatch.Draw(tomatoes[i].SpriteTexture,
tomatoes[i].SpriteRectangle, Color.White);
}
spriteBatch.End();
base.Draw(gameTime);
}
The Draw method contains another for loop that draws each of the tomatoes in turn. Figure 1 shows the display produced with your 20 tomatoes along the top.
1. Zune Image Sizes
The Zune will run XNA
games quite happily, but it does not have as much available memory as
the Xbox 360 or Windows PC. Up until now I have been using quite high
resolution images of the bread, tomatoes, and cheese. However, these
images have been so large that if we add too many the Zune is unable to
hold them. (And the Zune screen is so small that images made up of fewer
pixels are quite acceptable anyway.) For this version of the game I
have re-sized the textures so that they look acceptable on the Xbox and
Windows PC, but will also fit into the Zune memory. When you create game
resources you must be careful to make sure that the images you produce
are of the appropriate size and resolution.
2. Tomato Collisions
The idea of the game is
that when the cheese hits a tomato, the tomato vanishes. This means
that you need a way of making the tomatoes disappear. You can’t make
them vanish as such, but you can decide not to draw them.
2.1. Controlling Sprite Visibility
The
game must have some way of deciding when a particular sprite shouldn’t
be drawn. This turns out to be easy; you need only add an extra field to
the GameSpriteStruct structure:
struct GameSpriteStruct
{
public Texture2D SpriteTexture;
public Rectangle SpriteRectangle;
public float X;
public float Y;
public float XSpeed;
public float YSpeed;
public float WidthFactor;
public float TicksToCrossScreen;
public bool Visible;
}
The Visible field is set to true if the sprite is to be drawn on the screen.
2.2. Setting the Initial Visibility State
The initial value of Visible can be set by the setupSprite method, which is now given an additional parameter that is used to set the initial visibility of the sprite:
void setupSprite(
ref GameSpriteStruct sprite,
float widthFactor,
float ticksToCrossScreen,
float initialX,
float initialY,
bool initialVisibility)
{
// original setup code here
sprite.Visible = initialVisibility;
}
Initially, it’s set to true for all the tomatoes, the cheese, and the bread in the setupSprites method:
void setupSprites()
{
setupSprite(ref cheese, 0.05f, 200.0f, 200, 100, true);
setupSprite(ref bread, 0.15f, 120.0f, displayWidth / 2, displayHeight / 2, true);
tomatoes = new GameSpriteStruct[numberOfTomatoes];
float tomatoSpacing = (maxDisplayX - minDisplayX) / numberOfTomatoes;
for (int i = 0; i < numberOfTomatoes; i++)
{
tomatoes[i].SpriteTexture = tomatoTexture;
setupSprite(
ref tomatoes[i],
0.05f, // 20 tomatos across the screen
1000, // 1000 ticks to move across the screen
minDisplayX + (i * tomatoSpacing), minDisplayY,
true // initially visible
);
}
}
This setupSprites
method also sets the initial position of the cheese a bit further into
the screen so that it does not initially collide with any tomatoes.
2.3. Using the Visible Field When Drawing
You use the value of the Visible field when you draw the sprites in the Draw method:
for (int i = 0; i < numberOfTomatoes; i++)
{
if (tomatoes[i].Visible)
{
spriteBatch.Draw(tomatoes[i].SpriteTexture,
tomatoes[i].SpriteRectangle, Color.White);
}
}
Only tomatoes that have the Visible field set to true are drawn on the screen. To make a tomato vanish, you simply set its Visible property to false. You do this in the Update method:
for (int i = 0; i < numberOfTomatoes; i++)
{
if (tomatoes[i].Visible)
{
if (cheese.SpriteRectangle.Intersects(tomatoes[i].SpriteRectangle))
{
tomatoes[i].Visible = false;
cheese.YSpeed = cheese.YSpeed * -1;
break;
}
}
}
The for
loop looks through all the tomatoes and tests to see if any of the
tomato rectangles intersect with the cheese. If it finds an
intersection, it sets the Visible property of the tomato to false
and then reverses the direction of the cheese movement to make it
"bounce" off the tomato it has just destroyed. Once it has removed one
tomato, it stops looking for any more because the break statement causes the for
loop to end at that point. This is important because otherwise, the
cheese might collide with and destroy more than one tomato at a time,
making the game too easy.