1. Trang chủ
  2. » Công Nghệ Thông Tin

Building XNA 2.0 Games- P4 potx

30 336 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 30
Dung lượng 828,47 KB

Nội dung

CHAPTER 4 ■ THE MAP EDITOR 77 The Grid We’ll define the grid in the Map class, and then add functionality for viewing and editing it in Game1. MapSegment[,] mapSeg; int[,] col; In the constructor, initialize col[,] to be a 20-by-20 array: mapSeg = new MapSegment[3, 64]; col = new int[20, 20]; ReadSegmentDefinitions(); As usual, we add a property to gain access from the outside: public int[,] Grid { get { return col; } } Back in Game1, we make a function to draw the grid and collisions: private void DrawGrid() { spriteBatch.Begin(SpriteBlendMode.AlphaBlend); for (int y = 0; y < 20; y++) { for (int x = 0; x < 20; x++) { Rectangle dRect = new Rectangle( x * 32 - (int)(scroll.X / 2), y * 32 - (int)(scroll.Y / 2), 32, 32 ); if (x < 19) spriteBatch.Draw(nullTex, new Rectangle( dRect.X, dRect.Y, 32, 1 ), new Color(255, 0, 0, 100)); if (y < 19) spriteBatch.Draw(nullTex, new Rectangle( dRect.X, dRect.Y, 1, 32 ), new Color(255, 0, 0, 100)); 78 CHAPTER 4 ■ THE MAP EDITOR if (x < 19 && y < 19) { if (map.Grid[x, y] == 1) { spriteBatch.Draw(nullTex, dRect, new Color(255, 0, 0, 100)); } } } } spriteBatch.End(); } We’re using our 1 × 1 white texture again to draw a grid here, filling in grid spaces where the value of col is 1. To allow the user to edit the collision map, we’ll need to let the user change the drawing mode. Currently, the drawing mode allows us to place map segments; we want to be able to switch to a collision map drawing mode. Add a new class to the MapEditor project called DrawingMode. When the editor opens, change it from a class to an enumerator, adding the following values: enum DrawingMode { SegmentSelection, CollisionMap } Then in the Game1 class, add the following field: DrawingMode drawType = DrawingMode.SegmentSelection; We’ll make a drawing button sort of like our layer-selection button—ugly yet functional. In Game1.DrawText(), right next to the layer button, add the following: switch (drawType) { case DrawingMode.SegmentSelection: layerName = "select"; break; case DrawingMode.CollisionMap: layerName = "col"; break; } if (text.DrawClickText(5, 25, "draw: " + layerName, mosX, mosY, mouseClick)) drawType = (DrawingMode)((int)(drawType + 1) % 2); CHAPTER 4 ■ THE MAP EDITOR 79 Because we don’t want to see our map segment palette while we’re in collision map editing mode, modify the Game1.Draw() method to look like the following: map.Draw(spriteBatch, mapsTex, scroll); switch(drawType) { case DrawingMode.SegmentSelection: DrawMapSegments(); break; } DrawGrid(); DrawText(); DrawCursor(); Back in Game1.Update(), we’ll change the block that checks to see if you’re trying to drag a new segment so that it happens only when the user is in select mode. if (drawType == DrawingMode.SegmentSelection) { int f = map.GetHoveredSegment(mosX, mosY, curLayer, scroll); if (f != -1) mouseDragSeg = f; } Then, to allow users to edit the collision map, add this: else if (drawType == DRAW_COL) { int x = (mosX + (int)(scroll.X / 2)) / 32; int y = (mosY + (int)(scroll.Y / 2)) / 32; if (x >= 0 && y >= 0 && x < 20 && y < 20) { if (mState.LeftButton == ButtonState.Pressed) map.Grid[x, y] = 1; else if (mState.RightButton == ButtonState.Pressed) map.Grid[x, y] = 0; } } We’re computing the x and y coordinates by getting the mouse coordinates relative to scroll, and then dividing them by the grid size to get the proper collision map cells. If the left button is down, we’ll set the collision map value to 1. If the right button is down, we’ll set the value to 0. If you try playing with our current build, you’ll see that clicking the draw button will also draw a collision square below the button. We’ll need to make a more standard method for determining whether the user is drawing in a safe draw zone and not below buttons. We can define this method in Game1: 80 CHAPTER 4 ■ THE MAP EDITOR private bool GetCanEdit() { if (mosX > 100 && mosX < 500 && mosY > 100 && mosY < 550) return true; return false; } In Game1.Update(), we change every occurrence of (mosX < 500) to GetCanEdit(). We should also draw a rectangle to show users the drawing area. In Game1.DrawGrid(), add the following code segment after the drawing of the collision grid: Color oColor = new Color(255, 255, 255, 100); spriteBatch.Draw(nullTex, new Rectangle(100, 50, 400, 1), oColor); spriteBatch.Draw(nullTex, new Rectangle(100, 50, 1, 500), oColor); spriteBatch.Draw(nullTex, new Rectangle(500, 50, 1, 500), oColor); spriteBatch.Draw(nullTex, new Rectangle(100, 550, 400, 1), oColor); The current state of our build is shown in Figure 4-14. Figure 4-14. A gridded collision map CHAPTER 4 ■ THE MAP EDITOR 81 Ledges The gridded collision map will work really well for all things blocky (like our blocks), but sloping sections like grass will need a different type of collision definition. We’ll use line strips, which we’ll call ledges. We’ll define a new Ledge class in the MapClasses folder as such: class Ledge { Vector2[] nodes = new Vector2[16]; public int totalNodes = 0; public int flags = 0; public Vector2[] Nodes { get { return nodes; } } } A ledge is a series of points. For simplicity, we’ll assume these points always go from left to right. Each point is a node. We’re also throwing in a flags variable for good measure. For now, we’ll say that with flags, 0 is a “soft” ledge and 1 is a “hard” ledge, meaning that the player cannot drop below it. Now add ledges to our Map class: Ledge[] ledges; public Map() { ledges = new Ledge[16]; for (int i = 0; i < 16; i++) ledges[i] = new Ledge(); } As usual, we include a property to expose ledge functionality to Game1: public Ledge[] Ledges { get { return ledges; } } Now we need to add a new draw type to Game1 to go along with CollisionGrid and SegmentSelection. This can be done by adding another item, named Ledges, to the DrawingMode enumeration in Game1. We’ll also be using a state-based ledge drawing system, where every time the user clicks, a node will be added to the current ledge. To set this up, add the following to the Game1 class level: int curLedge = 0; int curNode = 0; 82 CHAPTER 4 ■ THE MAP EDITOR We’ll need to make sure our new draw type gets drawn and can be selected by clicking on our fantastically minimal draw button. In Game1.DrawText(), we evaluate drawType and then draw a button that the user can click to change drawType. Let’s add a new case for ledges and change the DrawClickText() line as follows: case DrawingMode.Ledge: layerName = "ledge"; break; } if (text.DrawClickText(5, 25, "draw: " + layerName, mosX, mosY, mouseClick)) drawType = (drawType + 1) % 3; Now we can switch the draw type between selection, collision, and ledge. Note that we’ve changed the DrawClickText() call modulus value to 3, because there are now three draw types. Let’s create a function in Game1 to draw all ledges. private void DrawLedges() { Rectangle rect = new Rectangle(); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); Color tColor = new Color(); rect.X = 32; rect.Y = 0; rect.Width = 32; rect.Height = 32; for (int i = 0; i < 16; i++) { if (map.Ledges[i] != null && map.Ledges[i].TotalNodes > 0) { for (int n = 0; n < map.Ledges[i].TotalNodes; n++) { Vector2 tVec; tVec = map.Ledges[i].Nodes[n]; tVec -= scroll / 2.0f; tVec.X -= 5.0f; if (curLedge == i) tColor = Color.Yellow; else tColor = Color.White; spriteBatch.Draw(iconsTex, tVec, rect, tColor, 0.0f, Vector2.Zero, 0.35f, SpriteEffects.None, 0.0f); CHAPTER 4 ■ THE MAP EDITOR 83 if (n < map.Ledges[i].TotalNodes - 1) { Vector2 nVec; nVec = map.Ledges[i].Nodes[n + 1]; nVec -= scroll / 2.0f; nVec.X -= 4.0f; for (int x = 1; x < 20; x++) { Vector2 iVec = (nVec - tVec) * ((float)x / 20.0f) + tVec; Color nColor = new Color(255, 255, 255, 75); if (map.Ledges[i].Flags == 1) nColor = new Color(255, 0, 0, 75); spriteBatch.Draw(iconsTex, iVec, rect, nColor, 0.0f, Vector2.Zero, 0.25f, SpriteEffects.None, 0.0f); } } } } } spriteBatch.End(); } Here, we have three nested for loops: • The outermost iterates through all ledges. • The middle loop iterates through all nodes within the current ledge, drawing each node. • The innermost loop iterates through a series of midpoints between every adjacent pair of nodes in the current ledge, drawing a makeshift line. We added some little color niceties as well. We draw the main nodes in yellow if the ledge is currently selected. We draw the midpoints in red if the ledge’s flag value is 1. Don’t forget to add a call to DrawLedges() in Game1.Draw(). After the DrawGrid() call, add the following: DrawLedges(); In Game1.Update(), in the block where we check for hovered segments, we put our functionality for adding ledge nodes: else if (drawType == DrawingMode.Ledges) { if(map.Ledges[curLedge] == null) map.Ledges[curLedge] = new Ledge(); 84 CHAPTER 4 ■ THE MAP EDITOR if (map.Ledges[curLedge].TotalNodes < 15) { map.Ledges[curLedge].Nodes[map.Ledges[curLedge].TotalNodes] = new Vector2(mosX, mosY) + scroll / 2.0f; map.Ledges[curLedge].TotalNodes++; } } All we’re doing is setting the node at index TotalNodes to the current location we’ll give it (we’re factoring in scroll), and then incrementing TotalNodes by one. Let’s add a ledge palette for selecting ledges and changing ledge flag values by creating a new method, Game1.DrawLedgePalette(), which we call from the Game1.Draw() method. private void DrawLedgePalette() { for (int i = 0; i < 16; i++) { if(map.Ledges[i] == null) continue; int y = 50 + i * 20; if (curLedge == i) { text.Color = Color.Lime; text.DrawText(520, y, "ledge " + i.ToString()); } else { if (text.DrawClickText(520, y, "ledge " + i.ToString(), mosX, mosY, mouseClick)) curLedge = i; } text.Color = Color.White; text.DrawText(620, y, "n" + map.Ledges[i].TotalNodes.ToString()); if (text.DrawClickText(680, y, "f" + map.Ledges[i].Flags.ToString(), mosX, mosY, mouseClick)) map.Ledges[i].Flags = (map.Ledges[i].Flags + 1) % 2; } } The currently selected ledge is drawn in lime green; unselected ledges are drawn as click- able text. After each ledge button, the number of nodes is drawn, followed by a clickable display of the ledge’s flag value. It’s all so very ugly, yet functional. Our ledge-editing functionality is shown in action in Figure 4-15. CHAPTER 4 ■ THE MAP EDITOR 85 Figure 4-15. Editing ledges Text Editing Now we need to add a way to name our map. Editing text is another bit of functionality that’s ugly to implement due to the fact that XNA does not strictly follow an event-based model, espe- cially for keyboard, gamepad, and mouse input. We need to track keyboard state changes, handle pressed keys, and handle special cases, like the Backspace and Enter keys. Much as we did with drawing, we can simplify the current editing mode with an enumer- ation in Game1. This time, we have called the enumeration EditingMode. Go ahead and create this enumeration with the following states: enum EditingMode { None, Path } At the class level of Game1, we’ll add some fields to keep track of previous keyboard state (so we know when it changes), as well as to keep track of what text is currently being edited. KeyboardState oldKeyState; EditingMode editMode = EditingMode.None; 86 CHAPTER 4 ■ THE MAP EDITOR Before we go any further, we also need to add a string in the Map class that represents its path: private string path = "maps.zdx"; public string Path { get { return path; } set { path = value; } } Back in the Game1 class, add two functions for handling keyboard input: UpdateKeys() to compare the current keyboard state to the previous to check for new key presses, and PressKey() to handle the key presses. private void UpdateKeys() { KeyboardState keyState = Keyboard.GetState(); Keys[] currentKeys = keyState.GetPressedKeys(); Keys[] lastKeys = oldKeyState.GetPressedKeys(); bool found = false; for (int i = 0; i < currentKeys.Length; i++) { found = false; for (int y = 0; y < lastKeys.Length; y++) { if (currentKeys[i] == lastKeys[y]) found = true; break; } if (!found) { PressKey(currentKeys[i]); } } oldKeyState = keyState; } private void PressKey(Keys key) { string t = String.Empty; switch (editMode) { [...]... format, drag-and-drop functionality, ledge-creation functionality, collision-map painting, and reading and writing functionality We’ve also taken our first step toward the creation of Zombie Smashers XNA! We hope it wasn’t too exhausting The next step will be to build a similarly rudimentary character editor, create a hero and an enemy, and then start right in on our game engine Of course, when we... referenced by any type of NET project To create a library, right-click the ZombieSmashers solution in Solution Explorer and choose Add ➤ New Project In the Add New Project dialog, select Windows Game Library (2.0) Name the project TextLib, as shown in Figure 5-2 CHAPTER 5 ■ THE CHARACTER EDITOR Figure 5-2 Adding the TextLib project Visual Studio sets us up with a fresh library, complete with a Class1.cs class... Paste this over the public class Class1 line in Class1, and add the public modifier, because we’ll need Text to be public now that it’s in its own library It should look like this: using Microsoft .Xna. Framework; namespace TextLib { public class Text { private float size; private Color color; This should be all we need to do to set up our text library Now we need to put it in CharacterEditor In... select Add Reference In the Add Reference dialog, click the Projects tab Select TextLib and click OK Finally, in Game1, we need to specify that we’ll be using TextLib Add the following: using Microsoft .Xna. Framework.Storage; using TextLib; 95 96 CHAPTER 5 ■ THE CHARACTER EDITOR Remember this process; reusable code can be abstracted and put in a central library When you have enough code in this central... far-too-adorable rendition of this hierarchy CHAPTER 5 ■ THE CHARACTER EDITOR Figure 5-3 Character format hierarchy This should be fairly straightforward If not, all will become clear as we work through building the character editor The Character Definition Now it’s time to start some coding We’ll organize all of our character-related class files into a Character folder A quick glance at our planning... location = part.Location * scale + loc; Vector2 scaling = part.Scaling * scale; if (part.Index >= 128) scaling *= 1.35f; if (face == FACE_LEFT) { rotation = -rotation; location.X -= part.Location.X * scale * 2.0f; } So far, we are iterating through the Part array for the frame to draw, FrameIndex, getting set up to draw each Part We’re using one of the sprite sheets in Figure 5-4 On these sprite sheets, each... FACE_RIGHT && part.flip == 0) || (face == FACE_LEFT && part.flip == 1)) flip = true; if (texture != null) { spriteBatch.Draw(texture, location, sRect, color, rotation, new Vector2( (float)sRect.Width / 2f, 32.0f), scaling, (flip ? 103 104 CHAPTER 5 ■ THE CHARACTER EDITOR SpriteEffects.None : SpriteEffects.FlipHorizontally), 1.0f); } } } spriteBatch.End(); } And that should take care of drawing the character . grid: Color oColor = new Color (25 5, 25 5, 25 5, 100 ); spriteBatch.Draw(nullTex, new Rectangle( 100 , 50, 400 , 1), oColor); spriteBatch.Draw(nullTex, new Rectangle( 100 , 50, 1, 500 ), oColor); spriteBatch.Draw(nullTex,. dRect.X, dRect.Y, 32, 1 ), new Color (25 5, 0, 0, 100 )); if (y < 19) spriteBatch.Draw(nullTex, new Rectangle( dRect.X, dRect.Y, 1, 32 ), new Color (25 5, 0, 0, 100 )); 78 CHAPTER 4 ■. spriteBatch.Begin(SpriteBlendMode.AlphaBlend); for (int y = 0; y < 20 ; y++) { for (int x = 0; x < 20 ; x++) { Rectangle dRect = new Rectangle( x * 32 - (int)(scroll.X / 2) , y * 32 - (int)(scroll.Y / 2) , 32, 32 ); if (x <

Ngày đăng: 01/07/2014, 22:20

TỪ KHÓA LIÊN QUAN

w