Data StructuresAll right, now it's time to figure out how we will represent our world. In our case, this will mean creating two data structures: one to represent a map, and one to represent an individual tile. Let's do the map structure first. You are going to want to keep track of all the sorts of data that is stored in a map file, plus one more very important thing: the camera. The camera keeps track of where on the map the player currently is. We'll see exactly how it works in just a minute. For now, let's have a look at a sample map structure: typedef struct MAPDATA_type { int xMax, yMax; // map size in tiles int xCamera, yCamera; // camera location in pixels int xMaxCamera, yMaxCamera; // max camera coordinates int nNPCCount; // number of NPCs int nScriptCount; // number of linked scripts LPNPC lpnpc; // linked NPCs LPSCRIPT lpscr; // linked scripts } MAPDATA, FAR* LPMAPDATA; And now we declare a variable of this type, along with an array of BYTEs to hold the actual tile indices: BYTE byMap[300][300][2]; // the actual tiles MAPDATA mapdata; // the other map data Depending on what kinds of things you'll be adding to the map, you might also want to include entries for items, enemies, or enemy areas. For the actual example code that I'll include with this article, I won't even be including the scripts and NPC members, since I don't want to introduce too much at once. I've included it here so you can see an example of how it might be included once you expand your engine into something you can build a game on. Now, let's take a closer look at the members of this structure. int xMax, yMax: These are the actual map dimensions. They are stored in the map file. int xCamera, yCamera: These are the current coordinates of the "camera," and so they are changing constantly. The initial values are set by whatever script loaded the map. Each frame, the map is drawn with the upper-left hand corner of the screen at the camera coordinates. Thus, the minimum camera coordinates are (0, 0). int xMaxCamera, yMaxCamera: These are the maximum camera coordinates, and are calculated based on the size of the map. Each tile in Terran is 32x32, so the width of the map in pixels is 32 * xMax, and the height is 32 * yMax. But the camera is located at the upper-left corner of the screen, so we have to subtract the screen dimensions. The maximum camera coordinates are thus calculated in this way: mapdata.xMaxCamera = (mapdata.xMax - 19) * 32; if (mapdata.xMaxCamera < 0) mapdata.xMaxCamera = 0; mapdata.yMaxCamera = (mapdata.yMax - 14) * 32; if (mapdata.yMaxCamera < 0) mapdata.yMaxCamera = 0; The 19 and 14 are subtracted from the map dimensions because the screen resolution is 640x480, and thus a full screen is 20 tiles across by 15 tiles down. This accounts for the fact that the camera is positioned in the upper-left corner of the screen. If a map is ever encountered that is smaller than the screen, the maximum camera coordinates are set to (0, 0) so we don't get negative values. int nNPCCount, nScriptCount: These are the number of NPCs and scripts currently on the map, respectively. The latter is included in the map file; the former is not. The number of NPCs will be set by the same script that is loading the map. LPNPC lpnpc: This pointer will be an array of NPC structures, each of which describes the location and behavior of a single character. I won't get into the NPC structure here. LPSCRIPT lpscr: This will be an array of SCRIPT structures, which simply hold the index of each script (used for locating the correct script file), and the tile it is linked to. So that's not so bad. Now let's consider how to hold information about our tiles. What do we need to know about tiles? For starters, we need to know where on the DirectDraw surface each tile is located. There will probably be a pattern here that you can use just as easily, but I prefer to actually include a RECT for each tile, because then if you ever want to use tiles of variable sizes, the ability to do so is there. You also need to know whether or not that tile can be walked on. This will define where a player can go on the map. That's the bare minimum you need. So let's take a look at what Terran is using: typedef struct TILE_type { RECT rcLocation; // location on DirectDraw surface int bWalkOK; // can the tile be walked on? int nAnimSpeed; // animation speed in frames DWORD dwFlags; // approach flags TILE_type *lpNext; // next tile in animation } TILE, FAR* LPTILE; Some of this stuff is pretty simple to figure out, but more of it needs explanation, so here's the member list: RECT rcLocation: This is the location on the surface, which we already talked about. int bWalkOK: This simply tells whether or not the tile can be walked on. My variable name suggests a Boolean value, but you can do other things with this as well. For instance, if you were creating a real-time strategy game, you might want to use this field to not only say whether or not a unit can move on this tile, but how quickly or efficiently as well. int nAnimSpeed: This is used for tile animations. If the tile is not animated, this member is 0. If the tile is animated, such as water or fire, then this member is the number of frames to delay before displaying the next tile in the animation. DWORD dwFlags: You can stuff just about anything you want in a parameter like this, but I'm using it for approach information. That is, when the character is walking on this tile, how does his location change? For instance, if you're walking left onto a tile depicting a staircase, you don't just walk straight onto it... you walk up the stairs! The dwFlags member is used to specify if and how a character's location changes when traversing this tile. TILE_type *lpNext: If the tile is animated, this is a pointer to the TILE structure representing the next tile in the animation. So basically, in addition to having a TILE structure for each tile, you can also string the structures together in linked lists to account for animation. To keep track of my tilesets, each of which have a maximum of 256 tiles, I have an array of 256 TILEs to keep track of the tile data itself, and an array of 256 LPTILEs that point to the corresponding structures. Then, when an animation needs to be advanced to the next frame, I can simply do this: lptile[x] = lptile[x]->lpNext; And the animation advances by one frame for every instance of that tile on the map, without even having to touch the map data itself! Is that easy or what? :) Finally, we need to have information about the player. For the purposes of showing a map on the screen and being able to wander around on it, we really don't need anything except the player's location. My player structure has all sorts of information about stats, spellbooks, inventory, etc. that is irrelevant as far as the tile engine is concerned, and actually, I think this will end up being long enough without getting into animating the player on the map, so let's not worry about that right now. Our goal is simply to get an animated map on the screen, and to be able to scroll it around with the arrow keys. For this demo, then, all we need to keep track of location are the camera coordinates, and those are already included in the map data structure. All right, ready to code this thing? Me too. :) |
|