The initial design was to produce all of the interface-related items we would need: a health bar, score, map transitions, and a main menu.. We’ll need to implement scoring and map transi
Trang 1flags = new String[size];
for (int i = 0; i < flags.Length; i++)
Trang 2With our map in place using its brand-new script, run ZombieSmashers You’ll see
some-thing like Figure 9-7
Figure 9-7 Final product of this chapter
Conclusion
We’ve covered some fairly varied ground in this chapter The main goal was to introduce map
scripting, but we ended up adding quite a bit of functionality to our characters: blood sprays,
initialization and death, and rudimentary AI We know that we say this every chapter, but we’re
a lot closer to having a fleshed-out game
To recap, we accomplished the following:
• Added all sorts of blood sprays and spurts to the character editor
• Created zombie attack, decap, and bloodsplode animations
• Added initialization scripts
• Implemented the new blood effects in our game
• Implemented character initialization and death
• Added AI
• Defined a map scripting language
• Added script-editing functionality to the map editor
• Introduced map level and global level flags
• Added fog
Trang 3In the next chapter, we’ll introduce player death, map transitions, a HUD, and game menus
As we near the completion of our game, it is important to keep our head in the game and not get distracted by the want or need to change things that are done One of the biggest prob-lems that plagues independent developers is their constant desire to fix old code, no matter how well it works
The best thing to do at this point is finish the game first, and then go back
Trang 4■ ■ ■
Menus, a HUD, and Deployment
At Last, the Coveted Xbox 360
Deployment
We’ll warn you right off the bat: this is going to be another odds-and-ends chapter The
initial design was to produce all of the interface-related items we would need: a health bar,
score, map transitions, and a main menu However, since these tie in with the constraints we’ll
be dealing with on the Xbox 360, and because thus far we haven’t touched the thing, it seemed
like a perfect time to introduce the concept of deployment
If you don’t have an Xbox 360 and/or an XNA Creators Club membership, you can
bliss-fully skip the deployment section of this chapter But, honestly, if you’ve gotten this far in the
book and still don’t have any intention of playing around with an Xbox 360, you’ve got some
issues Granted, they might be good issues (thriftiness?), but they are issues nonetheless
Here’s the rundown of what we’ll be doing in this chapter:
• Add a player HUD to display the health and score We’ll need to implement scoring and
map transitions, and add scripting functionality to support map transitions
• Add a menu system
• Implement player death We’ll need to have a menu system in place to deal with this
• Dive into Xbox 360 deployment
There will be a bit of “we need to do A, but to get there we need to have B and C in place,”
so bear with us
Adding a HUD
Our HUD will consist of a row of five hearts for our health and a score, as shown in Figure 10-1
The hearts will just be a visual representation of our integer health value, not some sort of atomic
unit of health For instance, if you have 82/100 HP, you’ll have 4.1 hearts We just thought
hearts would look cute
Trang 5Figure 10-1. Health and score display
Creating the HUD Class
The HUD class will deal with all things related to the interface: updating the health display and fading transitions, and drawing the health, score, and transitions Just like everything else so far, we’ll be calling the Update() and Draw() functions from Game1
We’ll be using a new object, scoredraw, to draw numbers “But wait,” you may be saying,
“why not just use a text class and ToString() to draw text?” The long and the short of it is that ToString() generates a ton of garbage when used every frame; this kills performance on the Xbox 360 We’ll use a more efficient algorithm that you may have seen in a Computer Science
101 class:
ScoreDraw scoreDraw;
We will talk more about ScoreDraw in the next section
We’ll use the field heartFrame to let our hearts sort of waver in a classic comic, cutesy fashion We’re using the fHP field (think floating health points) for a sort of catch-up health bar When
we take damage or get health, we want our health bar to smoothly transition from the previous value to the current value When we call HUD.Update(), we’ll have the floating bar try to get to
Trang 6where the real bar is Then when we Draw(), we’ll draw more prominently at the floating
posi-tion than the real posiposi-tion This technique is infinitely more professional-looking than just
drawing the current health value
float heartFrame;
float fHP;
For the constructor, we’ll just send it all of the objects it will need: the ever-present
SpriteBatch, some textures, and the Character array and Map
public HUD(SpriteBatch _sprite, Texture2D _spritesTex,
As promised, the Update() function increments our heartFrame and tries to get fHP to
match with our goal HP
public void Update()
Our Draw() method will first draw the score, then some black background hearts (our
floating health hearts), and finally our real health hearts
We’re using the same sprites texture we previously used it for smoke, flame, and muzzle
flashes, but we’ll add some hearts and numbers to it The new image is shown in Figure 10-2
Trang 7Figure 10-2. Updated sprites texture
The hearts all start at 0, 192 and are 32 ✕ 32
public void Draw()
float prog = character[0].HP / character[0].MHP;
Trang 8float r = (float)Math.Cos((double)heartFrame * 2.0 +
(double)i) * 0.1f;
First, we draw the dark background hearts:
sprite.Draw(spritesTex, new Vector2(
Next, we compute how much of a heart is shown, by getting the difference between the
progress value and the current heart index, and draw the floating health heart:
float ta = fProg - (float)i;
new Rectangle(i * 32, 192, (int)(32f * ta), 32),
new Color(new Vector4(1f, 0f, 0f, 75f)),
new Rectangle(i * 32, 192, (int)(32f * ta), 32),
new Color(new Vector4(.9f, 0f, 0f, 1f)),
r, new Vector2(16f, 16f), 1.25f,
SpriteEffects.None, 1f);
}
}
Lastly, we’re implementing some fade-to-black functionality We’ll have transition values
in the map soon, for entering and exiting segments, as well as for when we first start a game
Based on where the map is transition-wise, we’ll draw our nullTex over the entire screen with
an appropriate alpha value
Trang 9(int)Game1.ScreenSize.Y), new Color(
new Vector4(0f, 0f, 0f, a)));
}
sprite.End();
}
}
Drawing the Score
We referred to a ScoreDraw class earlier Let’s define it here:
public enum Justification
Trang 10• Draw 1, shift left a bit
• Divide 1 by 10 = 0
• Fini !
Computer science professors love to use this problem as an introduction to modulus
arithmetic
public void Draw(long score, Vector2 loc,
Color color, Justification justify)
{
int place = 0;
The obnoxiously ugly part is in left-justified text Drawing and shifting left as necessary is
fine, but if we can’t draw and shift right, we would get a reverse score Instead, we apply our
divide-by-10 loop to the score to determine the entire string width, shift our draw position
right by that much, and proceed as normal.
Trang 11And that does it for our HUD We have updating and drawing functionality for health, score, and map transitions That leaves quite a bit of implementation to do Let’s start with map transitions!
Creating Map Transitions
As we described in Chapter 4, our game world will be made up of map segments When the
player walks all the way to the left or right on a map, the previous or subsequent segment will load An example of a segmented map is shown in Figure 10-3 For the hero in segment 1 to get
to the house in segment 4, three map transitions are necessary
Figure 10-3. Four map segments
Designating Segment Transitions
To designate segment transitions, we’ll add some scripting to our functionality to define exits and entrances We’ll use the terminology as follows:
Exit: This is the destination map we’ll transition to upon hitting a boundary For instance,
if leftexit is map1, we’ll transition to map map1 if our player hits the map segment’s left boundary If rightexit is "", there is no right exit
Entrance: This is the vector for our player when we transition to a new map For example,
if we’ve just transitioned to map1 by exiting map2 to the left, we’ll place our player at the vector defined as rightentrance
At this point, we’ll be using only leftexit, rightexit, leftentrance, rightentrance, and initentrance We’ll use initentrance to define an entrance vector for starting a new game
New Script Commands
We need to add the new script commands to our MapScript and MapScriptLine classes First, let’s define them in our MapCommands enumeration:
Trang 13In our Map class, we’ll use an array of enumeration TransitionDirection to hold transition destinations We’re also referring to fields that we’ll add to Map We’ll get to the Map updates in the next section.
Let’s move on to our entrances:
When we set the entrances, we’re not actually setting anything We’re checking to see what
type of transition has just occurred leading up to this latest map initialization If the transition matches whichever one we’re checking for, we’ll set the player to the appropriate location, complete with the proper animation, airborne state, and so on
Trang 14Map Transition Enum and Fields
Now, a bit out of order, we’ll define some of the fields we just talked about at the class level of
Map But first, let’s define our enumeration:
public enum TransitionDirection : int
The fields transInFrame and transOutFrame will be used to operate the map transitioning:
public float transInFrame = 0f;
public float transOutFrame = 0f;
public string[] transitionDestination = { "", "", "" };
public TransitionDirection TransDir;
When we first trigger a transition, transOutFrame will be set to 1, and as transOutFrame
decreases to 0, the screen will fade to black When transOutFrame hits 0, we’ll load the new map
(which will in turn set the player to the right entrance location), and set transInFrame to 1 As
transInFrame decreases to 0, the screen will fade in from black
Remember the GetTransVal() function we use in HUD to black out the screen? Here it is:
public float GetTransVal()
We’re just returning a value between 0f and 1f, based on transInFrame and transOutFrame
Checking for Transitions
Now we’ll define a function to check if a transition should be triggered We’ll check for
transi-tions based on player index 0’s location If it’s at the left boundary, we’ll transition left; if it’s at
the right boundary, we’ll transition right We have also started to correct a mistake that was
made in a previous chapter As we add more and more code that is dependent on the size of the
map, we want to move that value into a private variable or two Here, we define two variables:
xSize and ySize, both with a value of 20
Trang 15public void CheckTransitions(Character[] c)
Trang 16Uh-oh, looks like we snuck another new method in there We added Reset() to our
ParticleManager class It just iterates through all particles, setting each to null
Now we have the map transition functionality in place However, at this point in
develop-ment, there’s only one map!
Trang 17The initialization script for our start map is as follows:
Now we need to add some transition scripting to map, as shown in Figure 10-5
Figure 10-5. Our original map with transitions
We’ll add a bit to the initialization script for this one:
Trang 18So, start has map defined as the right exit, and map has start defined as the left exit Tedious?
We got used to it There are most likely better ways to do it, such as by incorporating adjoining
map data into the map definition, rather than the script However, for a quick solution, this
works fairly well It handled the more than 100 map segments in The Dishwasher: Dead
Samurai game easily enough
Remember that setintroentrance command? We’re getting to that main menu!
Adding Menus
We’ll create a big fat Menu class to deal with all things menu-related Our class will have menu
functionality in levels—a main level, options level, quit-are-you-sure? level, and so on—where
we can be on one level or transitioning from one level to another
We also want to be able to use our Menu class as a pause menu and a you-are-dead menu
Hitting start while in a game will bring up the menu in pause mode; dying will bring up the
menu in dead mode Each mode will carry certain restrictions For instance, hitting Start in the
main menu is functionally the same as pressing A, but hitting Start in the pause menu will
return the player to the game, because Start is meant to work as a pause toggle
Designing the Menu
We want our menu to look excellent Figure 10-6 shows a rough sketch of the look we have
in mind
Figure 10-6. Main renu, rough draft
Trang 19Our menu options are at left, slightly staggered Our hero is at right in a prominent, wielding pose In the foreground, we have a zombie hand underfoot
wrench-We’ll use a series of layers to do this Our hero will be one image, as shown in Figure 10-7
Figure 10-7. Our hero poses in pose.png
The black foreground will pan in a slightly more exaggerated way than the hero pose, giving an illusion of depth (it’s that crazy parallax again!) Also, we can draw some fog between our hero and the foreground Figure 10-8 shows the foreground image
Figure 10-8. Pose foreground in posefore.png
On the left side of the menu will be some buttons While it would be more robust to use a text-drawing class for these buttons, it’s a lot easier (and perfectly acceptable) to use images
We put together a sprite sheet, shown in Figure 10-9
Trang 20Figure 10-9. Game options (more to come) in options.png
We’ll set it up to have a layer of smoothly animated fog, a hero, the buttons, another layer
of fog, and the foreground, with everything slowly panning left and right in parallax Figure 10-10
shows what we’re going for
Figure 10-10. The main menu final product
Let’s get to creating the class
Trang 21Creating the Menu Class
We’ll start with a rather lengthy list of class-level declarations, dealing with both game logic and render logic
public class Menu
{
When transitioning from one level to another, sometimes we want just the buttons to fade out, and sometimes we want everything to fade out The Trans enumeration takes care of that: public enum Trans : int
public enum Level : int
Trang 22public enum MenuMode : int
With our enumerations out of the way, let’s use them for something!
We’ll use transFrame for level-to-level transitions, transType to define the type of transition
going on, and transGoal to specify the destination menu level Obviously, level is our current
menu level Finally, selItem is the currently selected menu item
public float transFrame = 1f;
public Trans transType = Trans.All;
public Level transGoal;
public Level level = Level.Main;
public int selItem;
While we’re using selItem to indicate which item is currently selected, we can keep track
of which item had been currently selected on all levels with levelSel[]
int[] levelSel = new int[32];
We’ll use a method called PopulateOptions(), which we’ll define later (in the “Option
Popu-lation” section), to figure out the available options for the particular menu level For instance, the
Main level could have the options New Game, Continue, Options, and Quit; the Dead level will
have End Game and Quit PopulateOptions() will populate option[] and totalOptions We’ll use
optionFrame[] to smoothly transition to the selected option
Option[] option = new Option[10];
float[] optionFrame = new float[10];
int totalOptions = 0;
For our fog particles, we’ll use fog[] We can calculate each fog particle’s location, size,
and speed by its index and some snazzy modulus arithmetic
Vector2[] fog = new Vector2[128];
We’ll use frame for our slow parallax panning
float frame;