------------------------------------------------------------ RPG Development and Design Series By jkettles16 ------------------------------------------------------------ Originally posted at http://www.venosoft.com ------------------------------------------------------------ Contents I. Preface II. Engine Development Part I III. Terminology IV. Engine Development Part II ------------------------------------------------------------ I. Preface ------------------------------------------------------------ Ok I've finally begun my promised article(s) on RPG Development and Design. I must say I'm not really an expert, but over the short three or so years I've been programming I've made two complete games. I've also developed a number of utilities to make my tasks easier, and I'm currently in the process of developing another new engine. I may not be best but it's a known fact that human beings learn new things every day. Be that as it may my techniques and processes I use when developing a game may be very useful to you. Even if they're not this series of articles may insight new ideas and processes that'll make all your engines a bit better. On the technical side: Because I've had such an incredibly hard time finding any good tutorial on programming, specifically in C++, and since it's always nice to have more than one resource when it come to other languages this series teaches concepts about C++, QB, and VB programming. Some may not realize it but besides their different syntaxes, the fundamental principles behind RPG programming can be applied to each of these. In my programming endeavors in fact, I use at least two of these languages in combination with each other on most projects. What this article(s) doesn't do (subject to change): This article doesn't teach you VB, C++, or QB. There are untold resources on those topics. This article will not try to teach you my process of story development; it would be impossible for me to explain what goes through my head when I come up with a story. This article doesn't follow strict format or deadlines. I'll do a new article when I feel up to it or enough people ask, and not all the article(s) will be about programming. Most will probably be lots and lots of theory. ...And so without further ado I give you my articles on RPG Development and Design... Written: 2003 September 21 at 20:30:54 ------------------------------------------------------------ Engine Development - Part 1 ------------------------------------------------------------ The major part of these articles begins here. One thing I've noted among my many journeys across the Internet is that there are none to few tutorials on development of an RPG specific engine. Since and engine drives your game this is a very important topic that should have many tutorials on it. You'd think that by now some major company like Squaresoft would have released some of their older game code like the producers of Quake have done. Ah well, hopefully I'll rectify this problem. This will be a step-by-step series filled with source examples on both how to achieve an engine in C++ and QB, while using VB for editor creation. Source will be available at the bottom of each article. Where to begin: Where does one begin an engine? That's really a matter of preference. It depends on what most excites you about the development of your game, and where you want to see the results most. Personally when I began Cai's Quest 3 I wanted to see battles the most. I wanted to feel the thrill of battle, to make options and watch the effects fly. In this case however we probably would want input going on first to make those options. There are extremely few tutorials on RPG engine development as I said before, and even more so there are even fewer on menu systems. I should know, I couldn't find any. Saying that, this is where I will begin the engine development in this article. Coding: All right. So you know where you want to begin. You want to create the major input outlet for your RPG engine. Menus in most case are little boxes that both display visual output as well take in internal input. The hardest part about creating menus is how you want to handle the input. There are so many ways to code display and input routines, but what is the best method for doing these tasks? Over the years I've programmed RPGs I've finally come up with two really good methods of visual and input handling of menus. Method one uses a master menu system, which takes little menu descriptors. In C++ this functionality would be wrapped up in a menu class that takes structs defining menu boxes. In QB or VB you'd put the functionality into it own module creating subs and functions to handle the menu type and the menu box type. Method two is my favorite and the one I currently am developing. Using method two menus aren't really defined as such. They are defined as states. Unlike method one where menus would be a major game process, in method two menus are defined and controlled by each of the major game processes, battle process, and map process. Seeing as I'll be using method two, let's talk about how to actually achieve this system rather than continuing to talk about it in the abstract. Menus are defined as states. A state in code would be a variable. C++ make this very easy, you could enumerate the states and set your state variable using the enums... enum MenuStates{ MENU_STATE_NONE = 0, MENU_STATE_TEXTBOX, MENU_STATE_STATMENU, MENU_STATE_CHARACTER, etc. }; For QB it'd be best for you to document what the numbers mean, and use the actual numbers. Once the state variable is set you can check it easily with an IF or even better, since the state would most likely be an integer you could use a SWITCH/SELECT CASE. Another good thing is, using a state variable; you could even manage other actions such as the character walking state. With a state variable it's extremely easy to know exactly what state your major game processes are currently in. Under the rules my engine follows I create two functions for handling the game processes. One is the Process() sub/function, and the other is Show(). By their names it's obvious that Process() handles input, and Show() handles visual display. The state definitions make it easy for you to both display and manage input. From here you could easily produce code to manage each state. Implications: Besides making menus easy to manage this sort of method makes entire game processing very easy. Say you define states for your game map process... enum MapStates{ MAP_STATE_WALKING = 0, MAP_STATE_SCRIPT, etc. }; And then let's say this is your Process() sub/function for the map process... void Process(void) { switch m_iState{ case MAP_STATE_WALKING: //Character map state walking processing here case MENU_STATE_STATMENU: //Stat menu processing here }; return; } The point of this example is only one state can be run at a time. This is really cool because you certainly don't want the character to be walking while your making selections in your stat menu. Most of the time there are rarely any cases where you need to handle more than one area of input, and if you need that sort of specialized input then the system is certainly customizable. Wrapping up: With all that said there you have it, an extremely easy way to not only handle menus, but also to handle various states. In truth an engine is nothing more than a number of states and processes handled in various ways. Through the usage of Show() and Process() you can also produce code that can be shut off at a whim such as when games are paused, and of course since these functions/subs run per frame they are very windows friendly. To end this article I've provided source code for you to play with. I create a MapProcess_TYPE to manage states and such and the game sets up various menus. In C++ I create a class, CMapProcess, to manage the map process and it create various menus. The QB version uses gslib and the C++ verison uses OpenGL in Mircosoft Visual C++ 6.0 and both are fully commented for your ease. Code Samples: QB Menu code - Rpdev1q.zip (47 KB) - http://www.venosoft.com/Rpdev1q.zip C++ Menu code - Rpdev1c.zip (Not Avalible) Written: 2003 September 21 at 20:58:21 ------------------------------------------------------------ III. Terminology ------------------------------------------------------------ Ok, in this article I'll define some of the major principles and terms, I'll be discussing from here on out. Processes: What is a process? In the case of RPG programming I consider a process to be a lengthy state of being for the engine. A process would be something that is ran most of the time in the engine and major frame code would take place around such processes. In the case of an RPG there are two major processes. One is the map process, and the other is the battle process. In individual engines I'm sure people could certainly come up with many more such as story, effect, script, and many other processes. As this series of articles progresses though I'll probably talk about Battle and Map processes the most. States: A state is quite similar to a process. The difference lies in the fact that states are temporarily processed, and then discarded. There are a number of ways to handle states. In the case of my engine I use dynamic arrays to create a queue to hold current states. The state at the top of the stack is always the first one I look at and process, and when I'm done that state is popped and destroyed. Game processes: Game processes are processes that are run most of the time during engine execution. In the case of an RPG, battles and map movement are encountered the most. As such they are the major game processes. A number of varying states will allow you to do all the common tasks related to them, as you'll soon see. Objects: Objects are little black boxes in a sense that can be used for just about anything. In the case of a map system they are mainly used as a method to implement NPCs, chests and the like through scripts. If you really wanted to get fancy they could be used for just about every freeform thing in your RPG. Wrapping up: Seeing as the bulk of the articles from here on out will have quite a bit of the terminology above strewn all over, I thought it would be a good idea to go over what I mean by some of the things I say. This page will be updated with new terminology as needed, so check back here as I create new articles. Written: 2003 September 21 at 21:20:35 ------------------------------------------------------------ IV. Engine Development - Part 2 ------------------------------------------------------------ Hello again. After a long wait I've finally brought you a new article. Today we'll talk about how to add a major process to your engine, the map system. There are many different ways to handle the creation of a map system depending upon what kind of engine you wish to create. The unanimous default for anyone wanting to achieve 2D map system would be to use tiles. However, before you begin you should note some important, but easy to implement considerations. 3D systems: How'd Final Fantasy Tactics, Breath of Fire 4 and games similar manage rotation of 2D landscapes, fog, and so many other cool effects? I'm making the assumption it was through 3D, and considering the console I could be quite right. Using a 3D library is perhaps the easiest possible way to achieve these and other awesome effects. The main reason I bring up this topic though is not to lead into teaching about creating a 3D system (yet), but rather to expose you to some things you should practice even if you're not going to use 3D. -Image and tile sizes When you begin to create your graphics you should make sure they are powers of two. This only corresponds to image size but not tile size, however, it would probably be in you best interest if your tiles were powers of two as well. In case you don't know what this means, it forces your tile/image sizes to be 4, 8, 16, 32, etc. You must do this because 3D cards of today can make your games go super fast only if the images are that size. It's also useful to note you can do some really nice optimizations in your code with sizes that confirm to a power of two (can anyone say bit shifts?). There are a few restrictions to the sizes that haven't been mentioned yet. The only really safe graphics sizes are 64, 128, 256. This lies in the fact that many old graphics cards simply can't handle images above 256, and I've heard of quite a few don't deal with any below 64. Note once again that there really are no restrictions to tile size. I'd seriously recommend you use 16 at the smallest and 256 at the biggest though. Beginning the map system: At last you've chosen your image/tile sizes (I use 256*256 images with 32*32 tiles personally, which allows for 64 tiles per image) and now your ready to develop code. Being new to using bitmaps to hold your images you'd like to know how the heck to put the tiles onscreen. Before I show you how to do that you really should decide upon how you want to deal with you map data first. Important data and data just for the heck of it: As I noted in previous articles, you'd probably want editors for handling a lot of data. By now you should have created or found a map editor, but of course you don't know what data you will need for a map do you? -Header information With Vmaped30 I included a version number to start my map file format. After that I have a header. Some of the most important and absolutely necessary information would be a map name, map x, y sizes, a npc file name, and probably a main script. I also include not so important info like the music file name, and the number of warps, scripts, regions, events, and layers. -Tile information One of the most things when planning a 2D map system is what information you need to keep track of for your tiles. Once again I'll refer to my map format. My tile structure is set up like so... struct CMapTile{ Byte byBase; Byte byBase; Byte byObject; Byte byShadow; Byte bySolid; Byte byEvent; }; ...Obviously this may look a bit odd to you so let me explain. Every tile must have a base layer (this is a no brainer) but because you have limited space due to the 256*256 image restriction you can put tile on the object layer that are used on more than one base layer tile (flowers are a great example of this). The shadow layer is used after the characters are drawn to simulate real shadows (haven't you always wondered how they got Link in Legend of Zelda (SNES) to appear as if he had real shadows from walls and such cast on him?). The solid member is true or false for whether or not the tile is passable, and event is used for storyline, moving to other maps (warps), or handling battle (more on this later). The CTileMap represents only one layer. Pile these on top of other (the purpose of # of layers in the map header) and you could create incredibly detailed environments. With the right graphics artist and level designers you could even rival some Psx games with this sort of system. Moving along we still must to deal with events. How can one byte deal with warps, regions, and storyline? This is simple; byEvent is an ID that points to an array element of these... struct CEvent{ Byte byWarp; Byte byScript; Byte byRegion; }; ...In turn these ID's point to this... struct CTileData{ int x, y; char sFile[13]; }; ...now you may wonder why I don't use CTileData directly. If you look closely you can see the CTileData struct really takes a lot of space (21 bytes to be exact). This is especially true since I'd need three for each tile on the map (one for warps, scripts, and regions (63 bytes altogether)). By accessing the data indirectly I save considerable memory. Except for warps the x and y members are unused in CTileData. For warps x, y point to the location on sFile that the player will appear. For regions and scripts sFile is used to designate a background (used in turn based battle systems) or a storyline script respectively. Now you should have a relatively good idea what you need to include in your map header... struct CMapInfo{ int iVersion; int xMax, yMax; int iNumLayers; int iNumWarps; int iNumRegions; int iNumScripts; int iNumEvents; char sTitle[31]; char sMusic[13]; char sObject[13]; char sMScript[13]; char sImage[13]; }; ...and the map data which consists mainly of a number of arrays... CMapTile Tiles[][][] CEvent Events[] CTileData Warps[]; CTileData Regions[]; CTileData Scripts[]; CObject Objects[]; Now all that all the data above is assembled in our head we should be able to create a map engine to manipulate it all. There's only one problem, what the heck is up with the object file in the header and the CObject array? Objects: For my map engine I used objects as a general idea for any freeform element on a map. When dealing with map processing in its basic sense, all freeform elements on map act the same. Thus any freeform element (characters, chests, etc.) could be considered an object. What makes objects different from each other is how they are interacted with, but they all have a x and y, image, and layer location... struct CObject{ int x, y; int iLayerOn; char sScript[13]; char sImage[13]; }; ...objects also have a script. It is through the script that a chest can give an item, or a NPC can talk to the player. Objects can be used for anything and are drawn directly after the object layer but before the shadow layer. Mainly you'd use your objects to create a main character for your map and some npcs so you could walk around and chat. Just remember that scripts (with the right system) can do some very interesting things. Thus object are little black boxes that can be customized to do anything. Finally! Let's actually create a map system: After all this discussion you see why it is very important to plan out everything before you begin serious programming. Now that we have plans set let's get into the meat and calculations involved with a map system. First off we'll start by creating a few functions to help us display tiles. We also have to figure out how to calculate which tile to use in our images... void TileLoad(char *sFile, int iWidth, int iHeight, int iTilesPerRow); void DrawTile(int x, int y, int iTileNum, int Alpha, float fScale); ...These are the basic functions. Let's take a look at them in turn. TileLoad takes the file name of the image to load, the width and the height of its tiles and the number of tiles per row in the tile set. Those four pieces of information are more than enough for DrawTile to calculate the tile to draw based only on a tile number. DrawTile takes the x and y destinations for the image onscreen and number of the tile in the set, an alpha value so we can do tile blending (used mainly for the shadow layer), and a scale value. The scale value really isn't necessary most of the time, but what if you wanted to redo you game in a higher screen mode or what if you wanted to support more than one mode? Or what if you didn't care about graphics quality but you wanted your maps to seem bigger (using 16*16 graphics but scaling them to 32*32 for instance)? These functions would be apart of your map class (C++), or a module of map functions and variables (QB, VB). Of course I do things a little bit differently since these functions may need to be used in more than one place. Hence I put them and their variables into their own class. With all that down pat now why do with look at the code necessary to display a tile when only a tile number is given... int xSrc, ySrc, xSrc2, ySrc2; int xDst2, yDst2; int Col = iTileNum / iTilesPerRow; int Row = iTileNum - (yTemp * m_iTilesPerRow); xSrc = xTemp * iTileWidth; ySrc = yTemp * iTileHeight; xSrc2 = xSrc + iTileWidth; ySrc2 = ySrc + iTileHeight; if(fScale == 1){ xDst2 = iTileWidth; yDst2 = iTileHeight; } else{ xDst2 = iTileWidth * fScale; yDst2 = iTileHeight * fScale; } ...here's the break down. I set up the coordinates of the tile in the image by first calculating what Column and Row I'm on. Assuming 32*32 tiles here then if the tile number were 16 for instance, I'd be on column two (in a 256*256 image the number of tiles you can fit on a row is 8, if the tile size is 32). After I calculate the column I then calculate the row. If I multiply Row and Col by the tile width and height then I get the x and y source of the tile in the image. I can easily calculate the x2 and y2 sources of the tile by multiplying tile width and height respectively. What you done now is created a rectangle specifying the location of your image. This makes in incredibly easy to display the image in DirectX (which takes rects directly), or OpenGL (which takes four vertices to make a quad), or any other graphics library for that matter. The only other thing you need to do is finish the rectangle for the destination of the image by calculating the scaled tile. Then use your library's stretch blit functionality with blending, and masking, color keying, or alpha testing (depending on the library) turned on to draw the tile. With tile drawing functionality in hand all we need do is display our maps, but there are a number of calculations to consider here as well. To say the least the actual display of the map is easy from here on out. We'll follow a pattern that is common sense due to all the careful planning and layout we've done up to this point. We simply loop through each layer and display the tiles using the header max x and y information. However, we also need to make sure we clip out any unnecessary stuff that doesn't need to be drawn and we should add functionality to scroll the map when it's bigger than the screen... //Draw all map layers for(int i = 0; i < MapInfo.iNumLayers; i++){ for(y = 0; y < MapInfo.yMax; y++){ //Calculate the screen y destination yDst = (y * iTileH) - yCam; for(x = 0; x < MapInfo.xMax; x++){ //Calculate the screen x destination xDst = (x * iTileW) - xCam; //Draw base layer //Draw object layer } } //Draw the freeform objects for layer for(y = 0; y < MapInfo.yMax; y++){ //Calculate the screen y destination yDst = (y * iTileH) - yCam; for(x = 0; x < MapInfo.xMax; x++){ //Calculate the screen x destination xDst = (x * iTileW) - xCam; //Draw shadow layer tile } } } ...I hope this map display loop looks as you thought it might. We loop through a layer at a time displaying all the tiles for base and object layers. To make sure the correct part of the map aligns to the top of the screen we also subtract the camera x and y. Looking at the calculations you can note that xCam and yCam work with map pixels as they are directly subtracted from fine map pixel coordinates. This sort of calculation is perfect for scrolling. If you wanted to scroll one pixel at a time this allows it. After we draw the base and object layers in one fell swoop we then draw the free form objects for the layer we're on. With this system this means object can appear on various different levels (npcs looking down from a bridge above as you pass under it is a perfect example), and then we loop though all the tiles again to display the shadows for the layer. Alpha blending would be enabled only for this layer's drawing to speed things up. Finally we move to another layer and continue the process till the whole map is drawn. Looping through every tile on the map for every layer might not be the most efficient thing in the world, but with clipping in place it doesn't reduce drawing speed at all since most of the stuff won't be drawn. It still might be useful to go ahead and only loop through what you will see though. What was just covered was MapShow(). Now to make the main character move we'll use MapProcess(). This is a real simple task and we saw how to easily do it before. By modifying the x and y Camera coordinates we can make the map appear to move/scroll, and by modifying our object's x and y we can make it seem like he's moving if the map is not supposed to be. After we do movement we can then check for events on the given tile if the player has moved. By using byEvent in our CMapTile struct we can then execute the proper actions. Wrapping up: Whew, that was a long ride wasn't it? Obviously I got a little lazy at the end and left you with not much of an idea about how to really check for events and handle your objects. You want to know how to do animation for your objects and you want to know how to make them interact. My next article will be a major discussion about those topics. In the meantime I'll let you soak this huge amount of text in. Code Samples: None available at the moment Written: 2003 September 21 at 21:23:06