We’ll put in two buttons to swap the current part with the one on the previous or next layer, using a function named Game1.SwapParts: if DrawButton700, y, 1, mouseState.X, mouseState.Y,
Trang 1So far, we’re iterating through all images for each texture, getting the source and
destina-tion rectangles so that we can draw them in a neat row on the bottom of the screen Of course,
the special case with weapons is coming right up:
With the correct source and destination rectangles, we draw the image But since we have
the destination rectangle, we might as well check if the mouse location is within the rectangle
Assuming we add a call to DrawPalette() and DrawCursor() at the end of Game1.Draw()
somewhere, we’ll be treated to the result shown in Figure 5-6 Also, be sure to set mouseClick to
false at the end of the Draw() method
Trang 2Figure 5-6 Icon palette
The Parts List
We’ll use the icon palette to specify which image index each part uses The parts list will allow
us to manipulate our composited character in a way similar to a layer-heavy image-editing approach We’ll be able to select a part to manipulate, move parts up and down the list (like Send to Bottom and Bring to Top in layer ordering), and delete parts We do this in a method called Game1.DrawPartsList(), as follows:
Trang 3We’ll put in two buttons to swap the current part with the one on the previous or next
layer, using a function named Game1.SwapParts():
if (DrawButton(700, y, 1, mouseState.X, mouseState.Y, mouseClick))
We’ll put some makeshift buttons next to the swap buttons to modify the parts One of these is
to mirror parts For the mirror button, we’ll use an (n) for normal and an (m) for mirrored
Part part = charDef.Frames[selFrame].Parts[selPart];
Because scaling leaves all sorts of openings for things to go terribly wrong in artistic
consis-tency, we’ll put in a button next to the selected part to reset the scale, denoted with an (r) We’ll
also add a part delete button, marked with an (x)
Trang 4Earlier in Draw(), add a line to draw the character:
DrawCharacter(new Vector2(400f, 450f), 2f, FACE_RIGHT, selFrame,
Trang 5Figure 5-7 Parts list
It’s the classic swap algorithm, t = i; i = j; j = t, but it’s applied to two objects, so we store the
references temporarily, rather than storing the values themselves
Moving, Rotating, and Scaling Parts
Now we can specify part icons, but we can’t move them, so we can only end up with a head,
arms, and legs in a heap on the floor, which isn’t what we’re really going for We need to be able
to manipulate parts We allow that with the following code:
int xM = mouseState.X - preState.X;
int yM = mouseState.Y - preState.Y;
Trang 6We’re now able to move, rotate, and scale parts, so we can finally get a look at what we’re shooting for with this character format Take a look at our guy in Figure 5-8, which should give you a much better idea of what we’re creating.
Trang 7Figure 5-8 Our hero (assembled)
The Frames List
The character you see in Figure 5-8 is one frame If we’re going to have animation, we’ll need a
series of frames Figure 5-8 could be idle1 Then we would need idle2, idle3, and so on
Let’s create a frames list in Game1.DrawFramesList():
for (int i = frameScroll; i < frameScroll + 20; i++)
Remember how we edited text in the map editor? We’re using a similar system here We
use the class-level variable editingMode to keep track of which field we’re editing, and then
Trang 8from Game1.Update(), we call UpdateKeys(), which may call PressKey() We can basically copy the code over from MapEditor, with a few changes, which we’ll get to soon
Next to the selected frame, we’ll draw a little add frame button, denoted with an (a) Clicking this button will add a reference to this frame to the selected animation
if (text.DrawClickText(720, y, "(a)",
mouseState.X, mouseState.Y, mouseClick))
{
Animation animation = charDef.Animations[selAnim]; for (int j = 0; j < animation.KeyFrames.Length; j++) {
KeyFrame keyFrame = animation.KeyFrames[j]; if (keyFrame.FrameRef == -1) {
keyFrame.FrameRef = i; keyFrame.Duration = 1;
break; }
}
}
}
else {
if (text.DrawClickText(600, y, i.ToString() + ": " + charDef.Frames[i].Name, mouseState.X, mouseState.Y, mouseClick)) {
When selecting a frame, two things happen If the frame’s name was empty, we copy the previously selected frame to the current frame This isn’t very intuitive, but it works Also, we make the currently selected frame’s name editable if (selFrame != i) {
if (String.IsNullOrEmpty(charDef.Frames[i].Name)) CopyFrame(selFrame, i); selFrame = i; editingText = EDITING_FRAME_NAME; }
}
}
}
}
Trang 9Finally, we allow our list to be scrolled.
We can now create several frames of animation, as shown in Figure 5-9
Figure 5-9 The frames list
Let’s take a little look at Game1.PressKey() In MapEditor, we would evaluate editMode, copy
an appropriate string into a temporary string, work with that temporary string, and then copy
the string back All that we change here is where we’re copying that string to and from:
Trang 10Also, we use a nonintuitive method for copying frames: if the user selects a frame that has
a blank name (that is, a fresh, unused frame under typical circumstances), the previously selected frame will be copied onto the new one using the CopyFrame() method Here’s Game1.CopyFrame():private void CopyFrame(int src, int dest)
{
Frame keySrc = charDef.Frames[src];
Frame keyDest = charDef.Frames[dest];
keyDest.Name = keySrc.Name;
Trang 11for (int i = 0; i < keyDest.Parts.Length; i++)
{
Part srcPart = keySrc.Parts[i];
Part destPart = keyDest.Parts[i];
We iterate through the source frame’s part array, copying all part fields to the destination
frame’s part array
Next, we’ll implement our animations and keyframes lists
The Animations List
The animations list will be fairly simple We’ll draw a list of all animations at the top-left side
of the window If the user clicks an animation, its name becomes editable and it becomes
the selected animation The list is scrollable as well, so we use the variable animScroll
There’s not much more to it than that As usual, we put this in its own function named
Trang 12if (DrawButton(170, 5, 1, mouseState.X, mouseState.Y, (mouseState.LeftButton == ButtonState.Pressed)) && animScroll > 0)
animScroll ;
if (DrawButton(170, 200, 2, mouseState.X, mouseState.Y, (mouseState.LeftButton == ButtonState.Pressed)) && animScroll <
charDef.Animations.Length - 15) animScroll++;
The Keyframes List
The keyframes list is more of the same, with a bit of a hassle thrown in for good measure We’ll implement functionality to allow the user to modify keyframe durations, allowing us to fine-tune animation pacing We put this in a function called Game1.DrawKeyFramesList()
for (int i = keyFrameScroll; i < keyFrameScroll + 13; i++)
Trang 13if (DrawButton(170, 250, 1, mouseState.X, mouseState.Y, (mouseState.LeftButton ==
ButtonState.Pressed)) && keyFrameScroll > 0)
keyFrameScroll ;
if (DrawButton(170, 410, 2, mouseState.X, mouseState.Y, (mouseState.LeftButton ==
ButtonState.Pressed)) && keyFrameScroll <
charDef.Animations[selAnim].KeyFrames.Length - 13) keyFrameScroll++;
An Onionskin Effect
Moving back to our character-drawing call, we can implement a really simple onionskin effect
with our editor An onionskin effect is where you can see a translucent version of neighboring
frames of animation layered over the current frame However, ours won’t be exactly correct,
because the effect will operate only on neighboring frames as they appear in the frames list
Trang 14Figure 5-10 Animations list, keyframes list, and onionskin
Playback Preview
It’s finally time to implement that preview character we’ve been talking about
We’re using a class-level integer, curKey, for the current keyframe However, keyframes point to frame references from our frames list, and if keyframes are blank, their frame reference will be -1 We’re going to try to account for all of this here
Trang 15int fref = charDef.Animations[selAnim].KeyFrames[curKey].FrameRef;
Animation animation = charDef.Animations[selAnim];
KeyFrame keyframe = animation.KeyFrames[curKey];
Trang 16We’re using an arbitrary time value for duration ticks, where one tick equals one-thirtieth
of a second This is because it’s easier to work in small, standard units when every change of duration involves clicking a tiny + or –
Figure 5-11 shows our preview in action
Figure 5-11 Animation preview
Loading and Saving
If you’ve been following along, you probably aren’t too happy with the fact that each time you debug CharacterEditor, you must create a new character from scratch That means it’s time to implement loading and saving We’ll create a Read() and Write() function in CharDef Here’s the Write() function:
Trang 17public void Write()
String[] scripts = keyframe.Scripts ;
for (int s = 0; s < scripts.Length; s++)
Trang 18string[] scripts = keyframe.Scripts;
for (int s = 0; s < scripts.Length; s++)
Trang 19Now we just need to make some buttons in Game1.Draw() Fortunately, we brought
DrawButton() over from MapEditor, so it’s a pretty simple implementation:
if (DrawButton(200, 5, 3, mouseState.X, mouseState.Y, mouseClick))
We now have saving and loading functionality in a Spartan-yet-functional interface, all
shown in Figure 5-12 We haven’t implemented keyframe script editing, but we’ll get to that
once we start fleshing out the rest of the game
Trang 20Figure 5-12 Save, load, and path
Conclusion
In this chapter, we put together a robust, ugly character editor in a hurry We discussed our hierarchical character format, created some classes to implement the structure, and built an editor around it
We now have a fairly functional character editor in place, to go with our fairly functional map editor, so we can finally start working on the actual game And as we’ve said before, the nice aspect of these crazy tools is that there won’t be too much to actually do to create the game now that we have them
Trang 21■ ■ ■
Bringing It to the Game
The Payoff
Well, here we are Six chapters in, and we’re finally making something we can play And isn’t
that the whole reason we got into this? (It certainly wasn’t for the money!)
In this chapter, we’ll get a rough idea of the game and also implement a scripting system
to afford deeper animation interactivity
We’re taking this in two chunks: first create our rudimentary game engine, and then create
scripting By the time you’re finished with this chapter, you’ll have a very slick-looking start to
Zombie Smashers XNA
Building the Game
To put together our game, we’ll need to set up our environment, and then get to coding
Specif-ically, we’ll do the following:
• Create a new project in our game’s solution
• Copy over and change the map and character classes
• Load our map and character
• Create a new character class to control game functionality for an individual character
• Implement simple movement and collision detection
So, let’s get started with a new ZombieSmashers project
Trang 22Creating a New Project: ZombieSmashers
Create a new Windows Game (2.0) called ZombieSmashers in our ever-growing ZombieSmashers solution Again, by putting most of our code into a single solution, we can leverage the quick-access capabilities of Visual Studio to easily reference and use code in adjacent projects (Although,
in this case, we’re going to make enough changes to our map and character classes to warrant copying them over to the project, rather than referencing them.)
■ Tip Referencing, rather than copying, code makes your projects more flexible One way you can do this is
to link files across projects and solutions To do this, right-click the project in Solution Explorer, select Add Existing Item, and browse to the file you wish to link to Instead of clicking the Add button, select the down arrow next to it and select Add As Link This way, the file will show up and compile in the project, but it won’t actually be copied over This is a great way to manage files for projects that are cross-platform, because it allows you to change code based on the platform for which it is being developed
Copy the MapClasses folder from the MapEditor solution and the Character folder (renaming it CharClasses) from CharacterEditor to the ZombieSmashers solution folder In ZombieSmashers
in Solution Explorer, include the MapClasses and CharClasses folders
Next, manually change the namespaces in all of the copied classes from MapEditor and CharEditor, respectively, to ZombieSmashers Also, create a Character class in the CharClasses folder We’ll use this class to encapsulate all logic and drawing functionality for a game char-acter This is where we’ll be doing the majority of the work for this chapter
From here, copy all of the art assets into the Content project From MapEditor, we’re taking maps1.png From CharacterEditor, we’re taking legs1.png, torso1.png, head1.png, and weapon1.png We’ll also take 1x1.bmp, Arial.spritefont, and icons.dds for good measure
We also need the data files we created: guy.zmx and map.zmx Move those to the ZombieSmashers project, in the folders data/chars and data/maps, respectively Make sure to include these files
in your solution and specify Copy If Newer
Finally, we need the map segment definition data Copy maps.zdx from MapEditor into the data folder You should end up with a project organized as shown in Figure 6-1
From here, we can start coding by modifying our map classes to work with the new game Unfortunately, we’ll be doing quite a bit of jumping around; hopefully, nothing will get lost in the shuffle!
Trang 23Figure 6-1 ZombieSmashers project
A Random Numbers Class
We’re going to be using a ton of randomization from the game, so let’s make a nice class, in the
project root, to help us out We need only a class-level static Random object, which we’ll use from
within various classes we create, and some methods that we can throw some number ranges at
and expect random values in return
public static class Rand
{
private static Random random;
public static Random Random{
get { return random; }
private set { random = value; }
}
Trang 24public static Vector2 GetRandomVector2(float xMin, float xMax,
float yMin, float yMax)
instan-Modifying the Map Functionality
We will now make some changes to the map functions, so the map is better suited for game play We’ll adjust its look and feel, and add some helper functions
Map Look and Feel
We need to change some of how the map moves and renders, because in a game, a map looks and feels quite different than in an editor In an editor, you want to see a quick, rough overview
of how the game will look, with the ability to change as much on the map as possible For instance, in the editor, we drew the map in a zoomed-out fashion to give a wide view on the map We don’t want to do this for the players, because we want to get them close in on the action For now, we will draw the map at two times the zoom level of the map editor This is simple enough Just remove this line from the Map.Draw() function: