Jump to content
  • Advertisement

Myopic Rhino

GDNet Emeritus
  • Content count

  • Joined

  • Last visited

Everything posted by Myopic Rhino

  1. Download this week's code files: cotc6src.zip (74k) Be sure to read the license agreement before using the code. "To be thrown upon one's own resources, is to be cast into the very lap of fortune" - Benjamin Franklin Happy returns! This week's article is going to be a bit of a short one, but the topic is nonetheless important to consider in any game project: resource management. I've probably mentioned the term "resource management" several times over the past few articles, but never actually explained what I meant by it. In a nutshell, a resource management subsystem is a blend of memory and file handling that helps you load and use resource data files (like bitmaps, sounds, models, textures, etc) in a more carefree manner. You tell the resource manager what resources you might need, and it makes sure they're there when you need them. Kind of like a "file butler". Resources these days take up a lot of space, so you generally can't afford to load every possibly-used resource file ahead of time unless you feel like wasting a ton of memory. For instance, say you're writing a strategy game and one of the units is capable of shooting a certain type of missile, with a certain missile image bitmap. Now when you're playing, that image may never be needed (for example if there are none of those units on the map, or those units are never called upon to shoot, etc). But if that image is ever needed, it needs to be loaded so it can be used. You could try and put in explicit checks for all these scenarios so the resources could be loaded when not present, but that would be pretty redundant and error prone. What's needed is a more general facility for "on-demand" or "lazy" loading of resources, and that's what our resource manager will handle for us. If we think we might need a resource file, we "register" it to the resource manager, and it gives us a resource ID to represent that resource. Later on, if we actually need that resource, we can use the ID to get the resource data itself (which will be cached in as necessary). If you're familiar with on-demand loading and/or "proxy" design patterns, this stuff shouldn't be anything new (and if you're not, don't worry... it isn't that complicated). The Details I added two more files to the project, res_man.h and res_man.cpp, which hold the resource manager subsystem. For those of you who are confused, looking at the interface will probably make things a bit clearer. The whole thing really revolves around just two important functions. [Look at res_man.h] In addition to only loading a resource when necessary, our resource manager has three different caching types it can consider. The first is a temporary resource, which only lasts until the next frame. This is for resources that you need now but won't care about anytime after. The second is a level resource, which caches out after the current level is over; most in-game resources end up being of this type. The third is a game static resource, for data that should never be cached out like menu backgrounds, font images, system sounds, etc. If you look at the interface functions, the two real functions of importance are the RES_Register and RES_PtrForId functions. The first is called whenever you want to get an ID for some resource file (a bitmap for example). You tell it the filename, the caching type (temporary, level, or game static), and optional callbacks to control how the resource is loaded and cached out. It then gives you an ID for the resource, which is tied to that filename (if you register the same filename twice, you'll get the same ID). This ID is used in place of wherever one might otherwise need a pointer to that loaded resource file. When a data pointer is actually needed, you call the second function, RES_PtrForId, and it gets it for you. The benefit here is that until you call RES_PtrForId, the resource exists nowhere in memory so it doesn't take up space. It can also get cached out according to its cache type without hurting anything depending on it, since RES_PtrForId will be more than happy to load the resource back up again. The implementation for the resource manager in res_man.cpp is pretty small, and shouldn't be difficult to understand. Most of it is built directly above the zone memory allocation routines that were added in the last article, since each cache type has its own zone that it works with to bring in resources. Take a dive into it and see if you can figure out what's going on; there's less than 400 lines of code in the implementation, so you shouldn't have too much trouble. I kept the code simple and sacrificed efficiency in a couple places, since I want to leave some possible improvements as an exercise to you readers. For example, the routine to find an existing entry based on a name goes through the entry list linearly, and only uses what I call a "lame hash" integer value to speed up rejections. Obviously there are other more efficient data structures that could be used, such as a binary tree, or a real hash table based on string length, checksum or some other hash function, etc. What do you think would be most effective in this case? Experiment with it. Also, the level cache scheme should try and preserve resources that are going to be loaded immediately in the next level, so they're not needlessly cached out. What are some of the ways you could modify the code to do this with a minimum of zone memory fragmentation? Once again, experiment with it. You may end up surprising yourself. That's It? Yup, that's it for this week. I told you it was a short one, didn't I? Hey, consider it a breather. We'll need it, because next time we'll be diving into graphics and DirectDraw! Until next time, Chris Hargrove - Chris"Kiwidog" Hargrove is a programmer at 3D Realms Entertainment working on Duke Nukem Forever. Code on the Cob is © 1998 Chris Hargrove. Reprinted with permission.
  2. Let the Madness Begin Source code for Part 2: cotc2src.zip Well, it looks like the day you've been waiting for has finally arrived! Okay maybe not, but let's pretend it is, okay? Today's the day we start building our game, starting with the usual project setup stuff and some core system code we want to use to get things off the ground. This article will be the first to have the project code with it, and every successive article will have the most recent version of the project. We'll be starting off small, but it's going to get bigger and bigger. Some of the articles are going to be a bit on the large side, others will be pretty tiny, depending on my schedule, as there's this other game I'm working on that kinda takes precedence, ya know. But I'll try to keep the content rolling regardless. I've already got tons of positive feedback in response to the first article, thanks for all the great words guys...keep 'em coming. I've also got a bunch of suggestions on what type of game to make, as diverse a set as there are games out there. So making a decision that pleases everyone isn't easy, perhaps it's impossible. But a choice had to be made somewhere. After a bit of hard thinking, I finally came up with the general idea of what this game is going to be. My goals have been simple; keep it fairly small (but big enough to still be indicative of a real project) and keep it simple, but make it fun enough to warrant actually playing. Originality was high on my list but not a requirement; I would settle for a clone if it got the point across. Fortunately I don't think we'll have to settle. The overall goal is mostly to create something that demonstrates the interconnections of many game-related programming components without a lot of junk beyond that. So here's the plan... This is something myself and a couple friends thought up a while back but never had time to embellish on, so it's about time to do so. The basic idea is this. Picture billiards, AKA pool, your typical bar variety. Now take the balls off the flat table, and put them inside a cube. Then take that cube and put it in outer space, in zero gravity. Cut pockets in the inside corners of the cube. Now grab a cue, and hop inside. Zero-gravity Pool, basically, of the extremely disturbed variety. The kind of virtual pool that the little green men might like to play. Maybe we'll even throw some planet textures on the pool balls for a cosmic twist. Sound different? Sound fun? I think so, and I hope you do too. This is what we'll be writing over the next many articles in this column. En route to this end goal, we'll also be implementing a couple "sub-games" during development... one (taken from the sheer number of people who suggested this) will likely be a small 3D Pong implementation. After that, possibly a simplistic 3D form of Breakout. The reasons for making these smaller sub-games in addition to the final one are understandable: widen the programming audience appeal a bit, and demonstrate how a common framework can make developing several similar games easier. For example very little of the Pong implementation will be unusable by the final game, and Breakout shares most of the common material as well. All three games involve the same solid interior world space, collision detection against basic primitives, and so forth. The game logic for the first two games is surprisingly minimal. So using them as midstream "test" apps doesn't seem too far-fetched a thing to do. For example, in the model skinning tool called "Cannibal" which I wrote for the Duke team's artists, I wrote a quickie implementation of "Nibbles" in an environment subwindow just to see how long it would take. How long? Around 15 minutes. You can do a lot of stuff with a good framework in place, and we're going to make sure we create a solid, working framework for this project. A couple mini-games here and there will put that to the test. So for you all rooting for 3D Pong, you have your wish. You'll just get a bit more in addition. Anyway, back to the end project. To avoid having to think up some cheesy name like "Galactic Pool" or whatever, I'm going to suspend creating an actual title for the game at this point, and stick with a project name. In a small tribute to the page I'm writing this column for, as well as the Shakespearean quote at the top, we'll call the project "Madness". Later we'll come up with a real title, but for now, it's Madness through and through. The Big Picture Now that this game has some direction, I should probably address a few platform issues first: Since the game will be 3D (what good pool game isn't these days), we have the rendering choice of going both hardware and software, or hardware only. For simplicity (and the near-obsolescence of software rendering), I'm going to shoot for the latter. Even though I like OpenGL much more than Direct3D, the stable driver support simply isn't there for OpenGL on Win9x machines, hence I'm going to go with Direct3D here as a primary target. I'll still do OpenGL in addition if the demand is great enough. Regardless, we'll also throw in some direct 3dfx Glide support, just for kicks. I want to support multiple APIs to demonstrate how important (and useful) writing unified interfaces is. User input isn't going to need to be ridiculously fast and furious, so a whole lot of tweaking work in that department isn't necessary. There also isn't much of a need for joystick support in this game, so mouse and keyboard will suffice. I'll use DirectInput as the low level for these. A full sound suite isn't really necessary for this example, so we'll stick with basic sound effects via DirectSound (for the ball collision sounds etc.). I'll leave adding Redbook audio support or whatnot as an exercise for the public (it's not difficult). Pool is meant to be played by more than one person, so we'll get into some rudimentary multiplayer network support through Winsock. I'll probably be using a simplistic client/server model for programming simplicity. The foundation for this will exist below any kind of single-player/multi-player differences, so the network's existence can be transparent. You never want to try and "toss in" multiplayer at the end of a project... it'll only screw you if you weren't planning for it from the start. Anything in the above that refers to Windows-specific stuff will be isolated in the code as much as possible to improve portability and reuse. None of those topics are going to be delved into in great detail; I could spend fifty articles discussing 3D alone if I wanted, but I don't (I tried before, and that got real tedious). The same goes for sound, networking, and so on; the details of any of these topics would take much more time to cover in full depth than a small column like this has to offer. But there are a ton of resources (both in books and online) that deal with all these things. If you need specifics for any particular topic we go over, there's more than enough places to find it (on occasion I'll recommend some specific books as well). Once again, these things are not my focus. My focus is putting it all together; getting all these smaller sub-topics connected in the big picture. If I find there's a topic covered that gets too many questions about the internals then I might go into greater detail if possible, but I'd like to avoid it. Hopefully the code itself will help explain things. I realize that I can't assume everyone already knows about half the stuff I'm going to talk about... that'd be a misguided assumption. But I have to assume at least some stuff, or this series will never get done. At the very least, I'll try to point you all in the direction of some good related books and other material when I can. With that in mind, it's time to get down to the meat: Setting Up NOTE: You don't actually have to follow the steps below to set up the project we're making; you can download the entire project in its current form with each new article. The steps I'm explaining are just what I've used, and what you might use if you redid the process yourself. It'll be VERY helpful if you download the project for an article before reading the rest of the article, so you can follow along. I'll put notes about what to look at during the explanations, written in brackets like [look at thisfile.cpp] Getting the project started is a quick thing; we just create a new project in Microsoft Visual C++ 5 (I'll use MSVC for short from now on) configured as a "Win32 Application" project type. This will start us off with a new blank project to work with. Since the project name is simply "Madness", we'll use that. Whatever directory we want the project "root" to be in (where all the MSVC project files go, and Debug/Release subdirectories are formed), we'll also want to make two more subdirectories, "Src" and "Dist". Src is where we'll put all the source code, to keep it separate from the root of the project. The Dist directory will be our "Distribution" directory, where the executable will be run from. If the game were to be released as executable & data files only, this is the directory we'd give to the public. All data files we'll put underneath this Dist directory. Once we've done that, the project's all set. Theoretically you'd be sitting in MSVC in front of a blank project right now. Before we dive into the first of the source files, one thing that's very handy to have is a set of source file template macros. If you use an IDE of any sort, it likely has support for some form of keystroke macros or another macro language. For example you can create many types of macros in MSVC using VBScript. When you start getting into lots of source files and you want to keep your code organized, having a somewhat standardized layout is a quick and easy way to help. Even if you don't use an editor that supports these kinds of macros, you can still create some template .C/.CPP/.H/etc files to do the same thing (albeit with a bit more work each time you use them). It's still very much worth doing though. You might not follow these layout templates to the letter all the time, but they give you a great place to start whenever you need to add new code. [Look at m_layout.dsm] I've created a small pair of layout macros in the file m_layout.dsm included with the project, which are what I'll use when adding a new header or source body file. You may not end up needing them as is, but they'll give you a place to start should you want to create your own layout templates in VC5. If you want to use these macros as-is, it's helpful to customize your menu so you have quick access to them. Now that our environment is the way we want it, time to add the first of many source files to come. We'll start off with some system glue. This will be part of our first "subsystem" or "package" (choose any name you like), which we'll just call "System". Down With The System Since Win32 applications begin at WinMain, we'll want to bring WinMain into the picture pretty quickly. But at the same time, we want to lock as little of our code into Windows as possible, right? So we'll want some separation very early on as to what part of our system glue is Windows, and what part isn't. To keep things simple at this point, we'll split the subsystem into two pieces, the "Main" part and the "Windows" part. Regardless of the platform we're running on, our "main" block can be broken into three key functions... initialization (one-time startup), the primary frame-by-frame loop, and shutdown. This is what the "Main" part of the system package in sys_main deals with, so it can safely assume little to nothing about the underlying Windows platform work going on. Back in sys_win, WinMain will take care of making sure these functions get called appropriately. I name my source files (and their corresponding headers) to reflect the subsystem they're in and some suffix indicative of their role in the subsystem. Some subsystems only have one file, so the suffix might just be "main", or "man" (for manager), but a one-file subsystem is still a subsystem. We'll probably have at least 20 or so subsystems/packages in the project by the time we're through with it. [Look at the sys_win and sys_main .h and .cpp files] So we'll start out with two source body files, sys_main.cpp and sys_win.cpp, along with two corresponding headers, sys_main.h and sys_win.h. Right now the source files will only contain a few functions (enough for us to be able to build the thing) as well as a few odds and ends. You certainly don't have to follow this naming scheme in your own work. There are a ton of different naming conventions out there. What's important is that you pick one and stick with it, and make sure it gets the job done. Personally I believe subsystem isolation through file and variable/function/etc. prefixing (often called "package prefixing") is one of the simplest yet most effective naming schemes out there, but everybody has their preference. The primary criteria are that your convention should clearly identify the locale and general intent of your code and data, and that subsystem encapsulation is protected as much as possible. The use of the "static" prefix for implementation (not interface) global variables and functions is an example of this, since static vars/funcs cannot be linked to from outside a source file and hence cannot cause external linkage conflicts. This is the kind of stuff you have to consider when working on projects of a larger scale. [Look at sys_defs.h, in the Interface Definitions block] Next, I'm tossing in a common definitions/includes file named "sys_defs.h". This header has some macros for a few common types and so forth. The type macros could have just as easily been typedefs, but since the names are fairly common it's nice to have them as macros so they can be undefined when a few conflicts come up (some Windows DirectX headers end up using a couple of these same types). A few of you may be asking, why a whole bunch of type macros? What's wrong with "int" etc? The answer is a platform issue. The "int" type for example may default to either signed or unsigned depending on the compiler, and while most compilers have options to force the default to one or the other, it's not always wise to assume anything. We're writing for MSVC which defaults to signed, but who knows if we may theoretically want to port the game in the future, right? In addition, the default size of "int" has caused lots of problems for many people over the past several years. In the 16-bit days, "int" defaulted to a 16-bit value, i.e. a short. But in the 32-bit days, "int" now defaults to a 32-bit value, a long. Worrying about compiling for a 16-bit compiler is not really an issue to us, but this is just another case of when assumptions can hurt you. So I follow a general rule of thumb; "int" and other generic integral types are fine to use provided you don't assume a size or default sign. When either of those are important to a specific piece of data, use a type that forces the data in the direction you want. Hence the macros. The only data type assumption I'm making in this program deals with "float" and "double", which I'm assuming are IEEE 32- and 64-bit floating point values respectively. I only make this assumption because I don't think I'll be using doubles anywhere in this app (or very sparingly), and I doubt there will be any code written that assumes a strict 32-bit float size (any exceptions will be clearly noted). I'll warn you now, there will probably be a few cases where I slip and violate this rule of thumb. I don't expect this program to be perfectly portable, but what I can do to make it an easier process, I'll do as much as I can. [sys_defs.h: Look at the Interface Required Headers and Interface Trailing Headers blocks] Anyway, before and after the type macros and a few others, are a bunch of initial C library includes and the few common ones from the project thus far. I have no problem including C library headers in virtually every source file for a couple reasons, A) I use that stuff all the time, B) They don't slow down compile time significantly (pre-compiled headers assist this), and C) Duplicating such functionality needlessly only hurts inlining potential. As for the headers from our code, we'll be putting many of the early/common headers for the project in this block. As code gets more diversified, however, we'll be leaving the more specialized headers out on their own to be included only as necessary (to assist compile time). To clearly identify Windows-dependent code, a macro is required in order to include Windows-specific headers. This must be defined by any source file before it includes sys_defs if it wants the Windows code. Initially several files will have this macro, but fewer and fewer will need it as the project goes on. [Look at the m_misc .h and .cpp files] Finally we'll start one more "mini-subsystem", for some miscellaneous utility stuff. These will go in m_misc.cpp/m_misc.h. Right now this will just hold a few functions for some string operations I'll probably refer to every now and then, but we'll likely add to it later. This subsystem also has a general "current time" function, but since it ends up dealing directly with Windows, the function will be in sys_win instead and be cascaded from m_misc (you'll see me doing this every now and then). Even if Windows can't be completely isolated in all aspects of the application, the naming references to it should be whenever possible. A Glimpse Ahead The rest of the code should be pretty self-explanatory thus far, and very little of the functional content itself will be new to many of you. When the material gets more complicated, I'll be explaining things at the function-by-function level a bit more, but that's not necessary yet. What's important here is the coding trend we're working toward. Take the time to look over the layout of the code so far and see the direction we're heading. Note the use of the SYS_, SYSW_, and M_ package prefixes on every interface function, for example. The system package is pretty small right now (and it will likely stay pretty small), but we'll have several "offshoot" subsystems that will augment the core considerably. We'll start getting some memory management and file management code out of the way next time, to get our system stuff in a good working state. After that, we'll move on to user input, graphics... ...and all the Madness that follows. Until next time, Chris Hargrove - Chris"Kiwidog" Hargrove is a programmer at 3D Realms Entertainment working on Duke Nukem Forever. Credits: Code on the Cob logo illustrated by and is © 1998 Dan Zalkus. Code on the Cob is © 1998 Chris Hargrove. Reprinted with permission.
  3. Introductions "Code on the cob". Not a very technical name for a column on programming, but since it's for a site named "loonygames", I figured it was only appropriate. I suppose I should introduce myself. My name is Chris Hargrove (like you hadn't read that in the byline already), I'm currently a programmer for 3DRealms Entertainment, working on Duke Nukem Forever. I've only officially been a programmer in the industry for around two years or so now, but I've been programming "this type of stuff" for a few years longer. Regardless, I get enough people asking me for information on game programming (or game coding; I'll be using the terms "programming" and "coding" interchangeably throughout this column... some people treat the name difference religiously but I don't) that when loony brought up the idea of me possibly writing a column, it sounded like a cool idea. You'll note that I go by the nickname of "Kiwidog" while online (long story behind that nick...don't ask), so you'll know what I'm talking about when I refer to one of my rants as "kiwidogma" or whatnot. Anyway, this column is obviously about programming, game programming in particular. I'll be talking about coding for larger projects and the considerations it requires, interactions of various subsystems in a typical game project, and in general just writing good game code that helps get the project done, and with decent code clarity to boot. Overview I know all too many coders who start on a project, work on it until it's "75% done" or whatever, and then abandon it, giving excuses why the project was dropped. Often the real reason isn't lack of interest or motivation, but that the project hit a snag that made the coder(s) go "oh shit" and realize they were screwed by a design flaw (If this has happened to you, don't be ashamed, it's happened to all of us). This is a problem that professional coders don't run into, yet beginner and intermediate coders run into all too often when working on a game. Where's the difference come in? It's all about solid code design. This is what I'll be stressing in this column, since to me it makes all the difference between a beginner and a professional... more than all the graphics algorithms, networking schemes, AI routines and such combined. Once you know how to write good, solid code, everything else becomes secondary. To help reinforce this, there will be a lot of code to go along with these columns, and not in the form of tiny little example programs. Instead, we'll work our way toward making an actual game. Something completed and playable. It may be small, and it may be cheesy, but it'll be done... and you'll be able to walk away from it with some insight into how to start (and complete!) your next game project. Which game, you ask? I don't know yet. That's intentional, though... a lot of code, especially some core functionality, can be used for a great many different projects. All the code we create initially will be general towards almost any game we want to make, keeping our game-specific stuff until later. This is a good thing. All the code design principles we apply to this game will still hold true for a larger endeavor, so don't be fooled by the size. I'll be spending a lot of time talking about what makes "good" code. Even though you might not agree with some of the style or philosophy used, you should be able to identify that the code is usable in a large, and real, programming project. That's all that matters to me... the code style you work with beyond that basic premise is entirely up to you (and your team). What You Need I'm going to assume that you have some programming experience, i.e. that you understand concepts like pointers, basic data structures like linked lists, and so on. At the very least, you should have enough experience to identify what you don't know of these things, and know where to get the information (because I won't be teaching it here). The best scenario is if you've written or tried to write a game before and ran into problems, solvable or not, when it started getting large. My language of choice is C++, and I'll be using it for the code examples throughout the column, even though the code might often be compliant with pure C. Many people are religious about using purely C or purely the object-oriented constructs of C++, and not mixing them. I'm not a religious pundit on this matter. I mix and match whenever I see fit, using functional programming and object-oriented programming at various points in a project. Languages are tools, and you use what tools work for the job... that might include both design philosophies working together. As far as I'm concerned, code that's well designed shouldn't have a problem with this combination, as long as the mixture makes sense. The platform I'll be writing the example code for is Microsoft Visual C++ 5.0 (make sure you have service pack 3 installed), under Windows 95. The code would likely be easy to adapt to another Win32 compiler, but I'm not going to bother with that directly... all my project files will be for MSVC++. If you want to use another compiler, by all means do so. As for porting the code to another platform altogether, much of it will be portable, with the exception of some Windows interface stuff (including DirectX code) which will be clearly identified. If you want to port this code to another project, these modules will need to be rewritten, but the others should remain virtually intact. Once again I'll leave such a task in the hands of the public. We'll be writing this game incrementally with each column, so the project will continue to grow as time passes. Don't be surprised if the output executable ends up looking useless when certain subsystems are still being developed. Okay, enough overview stuff... time to get this thing on the road. Since the column will be heavily code-design oriented, perhaps a better explanation is in order regarding what I think good code design is. I'll be doing a lot of rambling about this kind of stuff over the first few articles. Pandora's Boxes Code is evil. Let me rephrase that... code is chaotic (and to many people that may imply that it's evil). Lots of coders have heard of John Carmack's phrase "Fight code entropy", and I agree with that. You need to fight something with as much tendency for disorder as code has (considering all the effort that goes in to stop that tendency). My method of fighting it isn't necessarily starting from a blank slate though. You do often have to rewrite a lot of stuff, that's just the way of things...but many pieces of code don't need the blank slate treatment. And the less you have to rewrite, the better. So how do you keep your code around and still fight code entropy? Simple... don't give the code enough room for entropy to be a factor. It doesn't matter how disorderly your code gets on the inside, if it's contained in a nice, tiny little box where nobody gets to see what's in it. A chaotic system can only get disorderly within the environment it's contained in, after all. As long as the box stays intact, and it does what it's supposed to do, then what's inside can be full of demons and evil things unmentionable. All we see from the outside, is the box. This little premise is the basis for nearly all of software engineering... a solid, well designed system treats all of its key components as isolated (or "encapsulated") boxes connected together and nested within other larger boxes. How these connections are made is the subject of a great many software design books, and there is certainly no one solution. But the common factor is that the boxes that make up the system, whatever they may be, are intact. You work inside the box to write what it does, and work outside the box to connect it to the rest of the system... but you never, ever, open the box. Doing so lets out a whole lot of evil, by breaking encapsulation. Once you've opened a box, it will do as much as possible to infect every piece of code it can, and that can turn your project into a big mess. Even if you're not an object-oriented programmer, the idea of encapsulation is important (just because your code's content isn't object-oriented doesn't mean the code itself isn't). Let's see how this applies to a game. In a very very simplified form, games fall into subsystems like this: Graphics Sound User Input Game Logic / AI Networking etc. These break down into further sub components, i.e. graphics may break down into higher-level rendering components, a lower-level video layer, the video layer's interface into the platform you're writing for, and so on. All this is nothing new, and pretty much every game programmer is aware of these divisions. What not every game programmer is aware of is just how important keeping these divisions is. Once you know where the separations among modules should be, obey those separations religiously. That includes making a conscious effort to keeping the code isolated, using naming conventions that facilitate the separations, and so forth. Every subsystem is an empire looking to extend its sphere of influence well beyond where it should, and if you don't watch out, they'll succeed. This is not a small matter; it can make or break a large project. When you get to that point on the project where the code is just "too messy" to continue, the code has won. Don't let it. To me, good code is not a matter of specific naming conventions, the algorithms used or other tricks. That's clever code, not necessarily good code. Good code is all in the connections, how well the code is structured and organized. And in the end, that all boils down to these little Pandora's Boxes. Interface and Implementation This idea of code isolation is nothing new, and has been known as a "good" way to code for quite a long time. Many programmers just don't seem to realize it. Pascal has units with "interface" and "implementation" sections. Java has a similar layout, along with package encapsulation. C and C++ have header files and source files, used for the interface and implementation respectively. C++ classes have public and private sections... the language list goes on and on. So what are "interface" and "implementation"? To use the Pandora's Box analogy, "interface" is the box, and "implementation" is what's in the box. The interface (or "public") part of a subsystem is the legal connection that the outside world has to the subsystem. It's the set of function prototypes, structures, classes, variables (hopefully not many variables), and other definitions that the subsystems outside the box can use to connect with the implementation. The implementation is the meat of the subsystem... it's all the functions, class methods and so forth (public and private) that the subsystem uses to get its job done. While many subsystems can have massive and complicated implementations, the best interfaces are those that are simple and to the point. And they should be clear what they're interfaces to. In a language like C, the global namespace can get very cluttered with tons upon tons of functions. If you don't name those functions to clearly identify them, you won't know what subsystem they're for nor that they're the primary interface for that subsystem. You also need to take steps to make sure that private implementation functions are not available for use outside the subsystem (or you'll open the box, which is bad). That means keeping them as private class methods, or for C using the "static" keyword in front of local, internal-linkage-only functions, etc. One way or another, the separation has to be made. We'll be following this very closely as we write our game. All our C/C++ files will belong to a subsystem, (even if that subsystem is the core system "glue" code) all subsystems will have header files, with only those functions that belong in the subsystem interface declared there. Several modules may belong to the same subsystem, and they'll be named accordingly, as will all subsystem functions. We'll be following this more or less to the letter, and to many beginner/intermediate programmers, it may seem a bit anal. But as the project grows more and more complicated, you'll see exactly why we did what we did. Another "anal" thing we'll be doing is structuring our code around the idea that we're not starting with any absolute target platform. While the code may not be completely portable as-is (due to issues I'll get into as they come up), the code will still be structured to lend portability a hand. That means not locking anything into Windows, DirectX, Glide or whatever. There will be very few assumptions, and the few that are made will be identified as such so they could be easily remedied later. The key here is isolation... don't let any one part of your code "bleed" into any other part of the code. The more isolation and encapsulation you have, the less code entropy you have to worry about. Be Prepared Next time we'll start our project with some core system material and utility code, getting us set up for our project (regardless of what it ends up being). We'll build up some common subsystems over the next several articles, working our way through user input, graphics (2D or 3D, I haven't decided yet but probably 3D since that's my focus), sound and other stuff. What we create won't be that complicated, so you shouldn't have trouble following along. What matters is the structure, and you should watch it closely. Because when the project is done, that's the stuff you'll want to take with you, not the code itself. If you have reasonable suggestions of anything specific you'd like to see in this column, (don't say make a Quake clone...that's way beyond the scope of this) send me feedback. If the request is appropriate, I'll see what I can do to work it into the project. Or if you have any other questions or comments, let me know and I'll do what I can to respond. Until next time, Chris Hargrove - Chris"Kiwidog" Hargrove is a programmer at 3D Realms Entertainment working on Duke Nukem Forever. Credits: Code on the Cob logo illustrated by and is © 1998 Dan Zalkus. Code on the Cob is © 1998 Chris Hargrove. Reprinted with permission.
  4. An RPG in a week, starting from scratch? How hard could it be? I did it because of a dare. There are many great resources available to small, budget-conscious independent game developers today. On a public forum, in a counter-rant, I expressed this fact by bragging that if you gave me a week, a fresh install of Windows, and a good Internet connection, I could build a halfway decent game with no budget whatsoever. No, it wouldn't be able to compete with Halo 2 or anything (if I could create that kind of game in a week, I'd quit my day job), but it would be reasonably amusing and playable. Tom Bampton, who runs the monthly Game-In-A-Day 'competition' (www.gameinaday.com ), said "You're on!" He then added an extra contingency - I had to do it without the benefit of one of the (free) game engines out there. I could only use a basic library / API. At first, I dismissed the idea. I didn't have time to take a week off of work and my current game development project to do something like this. But then I thought: What is a week? Unless you work for EA, a work-week is 40 hours. How about taking 40 hours to create a game? I was intrigued - but I didn't want to just create a space-invaders clone. How about a role-playing game - one of the most complicated genres to create games for? Would it be possible? I didn't know. I knew it would be extremely difficult. But I accepted the challenge. On top of that, I documented what I was doing as I worked, which I expected would be a little like going through an entire development cycle on fast-forward. I thought it might be interesting to game developers - or at least an entertaining record of how I fell on my face if I failed. The end result was a long, rambling, stream-of-consciousness record of my hourly activities. I've tried to edit it down to something a little less yawn-inducing here. So here is how I created a game in a single week from scratch, with no budget. If you want to skip to the end and see what the final product looked like, in all it's buggy, imperfectly-realized glory, you can download the Windows version of the game at: http://www.rampantga...ackenslash.html The Plan The Goal Create an "old-school" RPG in the style of the old, early 80's "top-down" RPGs like The Temple of Apshai, Ultima III, and Telengard. The player will move through rooms in a stereotypical dungeon, doing battle with various monsters with magic and combat. Along the way he'll improve his capabilities through gaining "levels" of experience, and magical equipment. It won't all be about combat, however. The player will also have the ability to sneak past or negotiate with monsters. There will be locked and trapped doors and chests, and unique dungeon features that may have strange effects. The game will not be long on plot, characterization, or dialog - it's mainly a hack & slash affair. You go up the level treadmill until you are powerful enough to face the final boss, retrieve a great quest item, and bring it back safely home (your 'starting room'). The Rules Rule #1: A limit of one work-week (defined as 40 hours) Game Development time should be restricted to 40 total hours. These will be actual game development or research hours. Breaks of longer than ten minutes won't count towards the total time. This will be an "ideal" workweek of 40 highly productive hours. The 40 hours only includes development to a feature-complete "alpha test" stage. Debugging and packaging the game for distribution and won't count towards the development time, but no new features should be implemented. Documentation of the process doesn't count. Rule #2: All Free Tools Except for the software that comes with a Windows install, only free / open-source software tools are used. The point of this whole exercise is to show how you don't need expensive (or even not-so-expensive) tools to develop a game. Hardware such as a scanner, microphone, and digital camera are exempted from this rule - if you don't have these, you can probably borrow them from someone. Rule #3: No Engines, only basic libraries / APIs The game must be created "from scratch" without the benefit of a fully-featured Game Engine. No cheating and creating a game using some kind of "click-and-play" game-maker software to throw together a game. The Tools Code: Python 2.3 (http://www.python.org/) PythonWin PyGame (http://www.pygame.org/) Py2EXE to compile this into an executable for distribution (http://starship.pyth...theller/py2exe/) Art: Gimp 2.0 (http://gimp-win.sourceforge.net/) MS Paint (which comes with Windows) - for pasting up screen shots grabbed by hitting the PrintScreen key (GiMP doesn't like these for some reason) Free textures available from places like Toob's Tiled Textures (http://www.texturear...tures/index.htm) and Mayang's Free Textures (http://www.mayang.com/textures/) Sound: Audacity (http://audacity.sourceforge.net/) plus my microphone or free sound samples The (Intended) Schedule Schedules are made to be broken, but it's important to have them as a baseline to compare your progress with and make corrections as necessary. Hour 1-10: Basic Architecture Design the "engine" and the main components. Get the world displaying on the screen. I should be able to move a 'test player' around the world to look at things. In fact, I should allow the "test player" to be turned into a full-on editing tool if I can swing it. Hour 11-20: Player Interaction Implement all core interactivity for the player - moving around, attacking things, opening doors, dying, picking up and using inventory. Bare-Bones representative objects in the environment will be created to test the interactivity Hour 21-30: Making the World Active Add the AI, game "events", traps, and special effects. By the end of this period, the game should be a pretty complete tech-demo of all of the game's major features. Hour 31-40: Adding Content and Rules Take the project from "tech demo" to game. Add all additional content. Complete and balance the game-play mechanics. Apply polish where time permits - adding special effects, animation, etc. Post-Hour 40: Testing and Game Release Fix bugs (no adding features!) Package up the game and release it. Finish documentation. The Development Diary of Hackenslash: A Game In A Week Hour 1 - Wild Freeform Design and Base Classes I spend this hour creating some basic classes for the game - and using these to help guide my design. The world is represented as a series of rooms, connected by portals. Everything in the world is room-relative, similar to how old Adventures and MUDs are designed. Most objects in the game are represented by a "GameObject," which has a position and contents (which include other objects - a map might contain rooms, a room contains a box, a box contains a sword... and I guess the sword could contain more rooms, but we won't go there.) I create a "creature" and "player" object I generate a set of "attributes" off the top of my head for creatures, and put this in a class. Apparently I'm a game geek who has played way too many RPGs. I don't know exactly how the game mechanics will work, yet. This really is seat-of-the-pants game development! I make a "room" object, derived from GameObject. Rooms have width, height, and walls - and not much else right now. I figure out how things will work and make corrections as I go. I don't even have PyGame linked in at this point - I don't even have anything other than a console for output. But I feel like I've made great progress! Hour 2 - PyGame 101 The goal this hour is to initialize PyGame, and start putting things on the screen. Actually, I spend most of my time going through the PyGame documentation and figuring out how to do things, since I have almost no experience with PyGame or SDL. I end the hour bringing up a blank screen filled with black. So far, it's not very impressive. Actually, there's quite a bit going on behind that black window - sort of like the Black Triangle story. There's a functional game-loop, page-flipping, the calling of several classes, and a lot of stubbed behavior. But that doesn't make the black screen any more impressive. Hour 3 - If the Walls had Ears, I'd be cussing at them This hour's goal is to get a room's walls displaying on that black screen. To make this happen, I need a room, and I need graphics. I spend a lot of time in GiMP touching up some textures downloaded from Mayang's Free Textures, so that they tile. I create a texture manager class. And I fill out a sample room structure. I also spend a bit more time looking through PyGame's docs to see if there's anything else I can use that might make the job easier. At the end of the hour, I still don't have walls on the screen. Hour 4 - The Inn Now Has Room After fighting some syntax errors, I finally get the walls to appear on screen. Not displaying correctly - they are in the wrong positions, with gaps between segments. It's horrible. But with a little bit of tweaking and bug-fixing, I have something resembling a 10-square by 10-square room on the screen. Without a really detailed project plan, it's really easy to get lost when you get to one of those points where you wonder, "What next?" It's very easy to get stuck chrome-plating what you've already done without moving on to the next task. I decide that if drawing one room is good, drawing two rooms is better, and move forward towards this goal. I create a "minidungeon" file to handle the generation and storage of these rooms. I start adding logic for "portals" - holes in the wall that lead to other rooms (and provide all the offset information necessary to draw the adjoining rooms). Hour 5 - Hackenslash Gains More Rooms I Change the title of the window to be "Hackenslash!" Just because it was cool. I create a Map object to contain rooms, and a MapMaster class to hold multiple maps. I add a second room connected to the first via a portal. Neighboring rooms connected to the current room via portals are now displayed. I fix some clipping bugs where the walls aren't displaying correctly when partially outside the viewport. Hour 6 - Wherein We Practice Our Mad Drawing Skillz I add a door class, and set up the maps to accommodate doors (since these need to be shared by two rooms). (Editorial Note: Too bad I never got to actually use these!) I create 3 more wall tiles, combined them together into one "sheet" Walls graphic changes based on type. I make a simple top-down player-graphic. Hour 7-8 - Rotations and Exclamations! I figure out how to have PyGame rotate bitmaps. I have the test-player spin slowly in a circle. Lots of tweaks are necessary to correct for size changes as he rotates. I learn how to use fonts in PyGame, and I build some classes to display and animate text. I add a manager class to automatically handle all this animated text. Having them be "fire and forget" means they'll be much easier to maintain, and I'll be more likely to use them in the future. Hour 9-11 - Feature-ific! Once again, I face a troublesome "What next?" decision. Rooms need more interesting features, so a "feature" element goes onto the list. I don't know what sort of behaviors features should have, so I decide to go from specific to general. I decide on three static features that could be found in a dungeon room: A rug (visual only), a pillar (blocks movement and line-of-sight, like a wall), and a staircase (blocks movement from some directions along some tiles, and "teleports" you to a new location at the end.) I determine that features can be larger than a single square, and should be able to be rotated any ninety-degree step. (Editorial Note: In retrospect, a very dumb decision - I spent way too much time on this feature and derived very little benefit from it.) I end up spending about three hours working on "features," between graphics and fighting some poorly designed code. Hour 12 - 13 - We Want Loot! I create art and code for items. It's amazing how much time can get sucked away doing artwork. It's particularly annoying when it still ends up looking like dumb programmer art no matter how hard you work on it. I add a lot of details for items, including their value, size, equipment slot, graohics image data, and so forth. They aren't interactive yet, but at least they are being drawn in their correct locations in the room. Hour 14 - Carpets I'm clearly behind schedule, and what do I do? I decide that the black background is too ugly, and I put in floors. After working on the floor graphics, I discover that I'd forgotten to put a transparent background on the player and item graphics. So I spend more time fixing them. But the floors look cool. Cooler than black, at least. Hour 15-16 - Click! Click! I implement mouse control and event handling. Add player responses and movement to mouse events. The movement is blocky, not yet smooth-scrolling. The player isn't yet leaving the rooms or checking for collisions I fix several bugs I create a prettier-looking staircase in GiMP. By now, I've already crossed the threshold of Hour 17, so I start to get a little bit nervous. I should now be 2/5ths of the way done with this game - the second "working day" of development. While what I've got up and running is pretty impressive thus far, I recognize just how much I've got left to do. I now have 4 more working hours to finish up basic player interaction according to the schedule. It's going to be tight... but I still don't regret putting those floors in! Hour 17 - Smooth Move, Until You Hit the Wall More time is spent cleaning up graphics and fixing bugs. Add collision detection and smooth-scrolling to player movement. Player can now move multiple steps (turns) on a single mouse-clock. Hour 18 - Crossing the Threshold The player now goes through portals into other rooms. This exposes a cosmetic bug with overlapping walls and floors between adjoining rooms that is really jarring. More bug-fixing on rotated portals not allowing / prohibiting movement. Hour 19 - Stairway to Heaven, Menu Hell My brother volunteered to do some music for the game. He did the music for Void War, and is really good. This reminds me that I need to do sound (and now music) for the game. This looks pretty simple in PyGame, so it shouldn't take too long (Editorial Note: I never did get around to this, sadly. Hackenslash ended development as a silent game.) My next goal is to handle interactions with creatures and items in Hackenslash. I'm really fond of how The Sims and Neverwinter Nights did context-sensitive pop-up menus that appeared when you wanted to interact with a game object. I'm thinking of doing something very similar here. I get stairways to properly 'teleport' the player to a new room. I do some research on the Internet and through PyGame docs to see if anyone already has some kind of menu system in PyGame already done with an open license that I can use. I don't find anything. I start work on the menu system. Hour 20 - 21 - What's On the Menu? I continue work on menus. Menus can be associated easily with the object that spawned them, making it easy to 'call back' to the originating object with a callback to handle the player's selection. I start work on item menus. They simply pop up and allow a selection at this point - clicking on an option does nothing but close down the menu. Hour 22 - Falling Asleep at the Wheel I continue work on items - trying to make them functional and have them respond to menu commands - this includes putting more contextual information about the menu in the player's "action" queue. Right now there's still not much that the item actually does, except create an animated 'bark' reflecting command information. I improve how movement is calculated for performing an action, which allows more flexibility. It's getting very late - I'm finding myself "zoning out" while working on this. If I wasn't paying attention to the total time - sure, I'd work through it. Since I'm on a limited number of "work-hours" for the development of this game, having a less-productive hour is really bad news. It's interesting how your priorities shift when you consider time a limited resource. So I go to bed. Hour 23 - Stat Attack! I modify (and actually start USING) some of the attributes class that I created in Hour 1. I create a window in the upper-right corner to actually display those stats. I optimize said window so that it is just a bitmap that quickly blits on screen, rather than writing out font instructions every frame. I update the bitmap only when I detect a change in the stats. Hour 24 - Player Menus I wrap up optimization on the stats window. I create a pop-up menu that appears when the player clicks on his avatar. I create sub-menus for potion usage, casting spells, etc. I fix some bugs in the menu response handling. Hour 25 - I Take A Saw To Floors and Walls This morning I had an idea in the shower (why is it that the shower is such a great place to have ideas?) for eliminating the overlap problem for walls shared by rooms (see the Hour 18 entry). What if I only draw half of the exterior walls (walls 0-3) in a room - the half facing into the room? That way there'll be no overlap with walls of neighboring rooms, and I don't have to add some complicated logic to detect and resolve overlaps. I begin work (yet more engine / foundation work) on this "quick fix." Unfortunately, it really complicates my room drawing routing (for floors as well), and turns out to not be as quick a fix as I had hoped. It takes about an hour to create and debug this system to make rooms more seamless. But the results are much nicer. While debugging the code, I discover a few more movement bugs related to crossing portal thresholds with negative offsets, and fix that. Intermission - Crisis Management! At this point I realize I'm more than 3/5ths of the way through my schedule, and that I have less then 15 hours left to finish the game. I go through my list of Things To Do, and assuming one hour per feature, it'll take around twenty-five hours to finish it all. That's ten hours over my limit I'm halfway through the time I'd allocated to getting the environment active, and I've not really started on it yet. The project is officially in jeopardy. Going overtime is really not an option I'm allowing myself. Hiring additional help or buying some more code / resources isn't an option, either. Since I'm only counting real "development" time for my 40 hours, I'm already pretty maxed out on productivity - I can't think of any way to be more efficient. Aside from spending a lot of time on the Internet looking for magical free solutions to my problems, I really have no choice here but to cut features, and see what I can do to simplify my design. Doors: CUT! I REALLY want doors in this game. This is the most painful feature to cut - especially since I've already spent some time working on them already. But there is too much work to be done on them - especially considering the AI has to deal with them. I probably need 2-3 hours to get them working, and I don't have the time. Inventory: SIMPLIFIED! Forget having a "back inventory" of usable items you don't currently have equipped. Anything you aren't equipped with gets converted into money immediately. Traps: SIMPLIFIED! I wanted to have all kinds of nasty traps with interesting, debilitating effects. Not gonna happen. Traps will have a simple visual when they go off, and just do damage and temporarily increase the chance of running into a random ("wandering") monster Bows (Missile Weapons): CUT! It's going to be just melee and spells in this game. Saving / Loading the Game: SIMPLIFIED! Only your character will be saved and loaded, not the state of the world. (Editorial Note: I didn't even do this!) Particle Effects: BACK-BURNERED! These are getting dropped to the bottom of the priority list. I really doubt I'll get to them. I wanted some cool particles for animations for spell effects... but that is unlikely to make it. Spells: SIMPLIFIED! I had a concept for spells where you could "find" spells in the game via scrolls, and that there'd be over a dozen spells you could use. Well, as much as it pains me... I don't see that happening. I'm going to have only a handful of spells available now: Heal, Damage, Debilitate, Buff, and Recall. To deal with increased level, I'm going to allow the player to "beef up" the spells by increasing the number of magic points that go into the spells Monster and Player Animations: CUT! I don't have the artistic talent to do a great job, anyway. While deciding what I won't do (or what I'm dropping down in priority to highly-unlikely status), it's equally important to decide what I absolutely must do - what needs to be made top priority. There are a lot of elements to the planned game that I consider very important - like searching for traps, finding secret doors (well, secret portals now), and unlocking chests. But the core of the game is combat. If that's not there, nothing else matters. So I decide I must focus on that, and getting combat working is top priority. I set a goal that by hour 30, monsters will be working - at least enough that the player can kill them. With my adjusted priorities in place, I move on to continue development. Hour 26 - The Roll of the Dice I work out the core "dice" mechanic, something I've had in mind for about the last week --- how the random chances come into play. Since we're not limited to using real dice, we can make a random number of any range we want. Like 1-33, or 6-17. So what I do for a "standard" roll is a weighted, random roll against the total of the attacker's score plus the defender's score. If the number is above the defender's score, the attacker wins. For example, let's say I have a total attack rating of 15. I'm attacking a monster with a total defense rating of 10. My odds are 15:25 (25 is 15 + 10), or 3:5. So the game will generate a number between 1 and 25, and if it's above a ten, I win. Damage uses a slightly different type of roll. I add the defender's "armor" rating together with the attack's "damage" rating. I generate a random number between 1 and this total, and then subtract the armor rating. If the total is less than one, the defender takes no damage. Otherwise, he takes the amount indicated. So if a monster with a damage rating of 10 attacks the player with an armor rating of 5, the game will roll a number between 1 and 15, and then subtract 5 for the damage. This explanation took more time to document here than it did to write. The dice-rolling code looks like this: from random import randint def StandardRoll(attackerScore,defenderScore): if (attackerScore<1): # If the attack rating is 0, the attack always fails return 0 if (defenderScore<1): # Otherwise, automatic success on defense rating of 0 return 1 roll = randint(1,attackerScore+defenderScore) if (roll>defenderScore): return 1 return 0 def DamageRoll(attackAmount, defenseAmount): if (attackAmount<1): # 0 attack rating? No damage will be done return 0 if (defenseAmount < 1): # Man, don't screw with us with negative numbers defenseAmount = 0 total = randint(1,attackAmount + defenseAmount) total -= defenseAmount if (total<1): return 0 return total Filling out the hour, I decrease the window size for drawing the dungeon to get a slight framerate increase --- the section to the right will now be entirely User Interface stuff. I also make sure the player's movement is corrected for frame- rate. Hour 27 - Building A Monster I have a lot to worry about to get monsters working. I have to change the game update system to make it turn-based. The player needs a variety of ways to interact with creatures (bribery, combat, spell-casting). The monster has to interact back. There's AI and pathfinding considerations. And graphics! DO I display the monster with the same top-down perspective? I can't worry about all these at once - I need to start small. Just putting a monster in the room and displaying in the correct position on the screen is a good start. I create a "monster" class, derived from creature. I create an "ActiveAI" list for the main game loop to deal with activated monsters. I work on art for a monster - which takes me the rest of the hour (and it still doesn't look very good- I should have just used a smiley-face). Hour 28 - The Monster Appears, and the Player Has to Change His Armor I get the display routines working for monsters I make the monster block the player's movement. The perspective on the monster is totally different from the player, but I don't care. But the player graphic looks more terrible by comparison, so I re-do the player graphics. Hour 29 - Time to Come Out Swinging I create a menu and menu-responder for the monster. Player attacks are working Main loop responds to the monster dying. Experience points are awarded for killing the monster. Hour 30 - With this Sword, I Thee Slash I create the first of true equippable "items" (a '+1 sword') Monsters drop their items when they die. Game loop update gets broken into turn-based and non-turn-based updates. Massive changes to player movement / actions based on the turn-based architecture. Hour 31 - Entering the Final Stretch Monsters get emotional - I give them three 'attitudes' towards the player, which can be adjusted by player actions. Money is added to the game, in the form of silver I get the "negotiate" action working, where you can bribe the monsters to leave you alone if you haven't attacked them yet. Monsters attack a player if the player is right next to them (and they've not yet been bribed). I fix a bug where the player decides to treat a monster like a chest, and runs up to them to begin the negotiation. This gives the monster a free attack before the negotiation happens. That kinda ruins the point, but it's funny. Hour 32 - Why Can't We All Just Get Along? Finish negotiation logic Add monster hunt / chase routines Monster chooses between spellcasting attacks (stubbed out) or melee combat In order to save time, I cheat on monsters chasing players through a portal. I just assign a random chance of the monster going through the portal each round. This gives the player a better chance to escape, anyway. Hour 33 - Wandering Artwork Monsters now wander around if they aren't actively attacking the player. Artwork for some items and potions I add potions as items - they don't work yet, but they appear Hour 34 - 35 - Drink Me! Potions get added to the player's potion inventory Potions have the appropriate effect on the player's stats. Healing Potions restore hit points, Essence potions restore mana points. I add pure treasure (which gets converted immediately into silver) as an item. I start working on equipment items. Equipment items are a little tricky. Since I don't have an "inventory" of unused equipment, you can't carry a spare sword around with you. If you pick up an item and you already have another item in the "slot", the game asks you if you want to replace the old item, or cash in the new item for treasure. For items where only a single slot is permitted (like a weapon, or armor), that's not too hard. Wands and rings are more complicated, since those can be in one of several slots. After almost an hour of working on this, I realize that I've got the player running through two menus (one to pick up an item, and another to choose what to replace) for items is really ugly. It's far better to have the game be "smart" about it and reflect these options in a single menu. I end up overhauling some of what was already done, throwing away a little bit of code, to make this change to a simpler system. Hour 36 - HACKENSLASH! I create a "Hackenslash" title panel with a sword graphic, and link this bitmap into the right UI panel display Add the logic for the "monster display" panel to display the information on whatever monster last had the mouse pass over it. Hour 37 - We Don't Need No Stinkin' Word-Wrap! I create a scrolling "text box" class to display game mechanics information. This is useful both for player feedback and for debugging purposes. Without any user controls (like scroll bars), development goes extremely quickly. It actually takes more time to send it lines of text from various events in the game (picking up items, attack results, etc) Some of my preliminary work back in the first 20 hours is FINALLY paying off - there's a good framework in place for calculating the bonuses for skills based on equipment. All items have a 'dictionary' (a great Python construct) of bonuses and penalties to SOMETHING (the name of the roll). So when I get ready to make a roll, say, an attack roll, I just run through the equipment list and apply all adjustments with an "attack" key. I'll do something similar for spell effects. Next, I work on the menu for opening chests - and decide to simplify it significantly. Why not just assume the player is ALWAYS going to check it for traps, and will ALWAYS attempt to unlock it if it's locked? The extra steps in there are tedious. No sense in having trivial choices floating around. Hour 38 - A Little Bit of Magic I'm frantic now. Three hours left - I have to work on ONLY what MUST be in the final game. I generate five types of spells - three that are cast on the player, two on monsters. I add extra "levels" for the spells to give them slightly more variety. The levels vastly increase the power (and cost) of the spell, and are only available as the player's "magic" skill increases. I make the player-menu non-static, so I can add menu options that become available only under certain circumstances. I add the ability to "level up" through a set of menus that become available from the player menu when the player's experience points exceed a certain level-based threshold. Hour 39 - Crisis Management, Part II What I'm missing: Searchable secret doors and items Quest Items - and a place to return the quest item to win the game Sound Effects Music A merchant from whom to purchase equipment A place to rest Random equipment with different bonuses and names Random monsters Graphics for the new equipment, monsters, and merchant. Random chest traps & locks Creatures casting spells Wands A larger dungeon with a quest item (and maybe boss monster) at the end. The ability to sneak around (?) to avoid being attacked by the monsters. There is no way I can get these all in with a little over 100 minutes to go. I can handle maybe three or four of these if I'm fast. What is the minimal set of items needed to make a playable game? I decide on the following The player must be able to rest to regain health and magic points Monsters and treasure should respawn while the player is resting Random equipment types and abilities should be generated with the treasure, and should gradually scale up by player level Random monsters should be created with abilities and numbers that scale up to the player level. The initial room (the one up the stairs) becomes the the "rest" room (pay no attention to the awful pun - aw, heck, it's part of the charm). There will be no monsters in this room, ever. I add a new dynamic entry into the player menu when he's in a rest area - the "rest option." Then I create a routine to "re-initialize" the dungeon while the player is resting. Right now it's a stub. I add the remaining equipment graphics in GiMP. I have no time to make them look any good. I simply draw them in 32 x 32, blur them in areas to hide my awful artwork, and then sparingly add some highlighting so they don't just look like a blurry mess. Now they look like a touched-up blurry mess. For the random equipment generator, I have the game pick one or two random abilities and slap them onto the item, with a possible range based on the player's "level" (Editorial note: Unfortunately, the player's level is never actually displayed on the screen anywhere. D'oh!). Certain items have mandatory effects - a sword has to improve either your attack or your damage rating, armor must improve your armor rating, and a shield improves your defense rating. I add these constraints... and we're now already well into hour 40. Hour 40 - The End Is Near I have no time to create actual unique monster types - so I frantically work on just changing the names and stats of the one monster I have - the goblin. I throw some silly-sounding adjectives in front of the goblin names, and I have them "level up" similarly to the player based on the player's level. I finish up the "re-initialization" routine to repopulate the dungeon while the player rests. I finish up some routines to deactivate monsters that are far away from the player, and to make them become active when the player enters the room. I add a couple more rooms to the dungeon, and then I'm out of time. Post-Development Work: Wrapping It Up With a Bow After talking with a friend, I'm convinced that I shouldn't work on fixing any bugs except the ones that crash the game. Since the purpose of this project is to show off what can be done in 40 hours, I leave it in that state. There are lots of non-critical bugs: stats aren't being displayed properly in the right-hand panel; I don't think goblin hit points are increasing with level; I'm not convinced monsters are properly de-activating; monsters are ignoring collision detection; treasures are appearing inside interior walls; monsters are ignoring magic attacks from long range... I leave them, and focus on the crash bugs. This means playing the game quite a bit. It's actually pretty fun. Not play-this-game-for-hours fun, but certainly an amusing diversion. Once the crash bugs appear to be mostly quashed, I work on the distribution. Py2exe is supposed to make things ridiculously easy, allowing you to package a Python program as a native Windows executable, including everything you need so that users don't need to have Python installed to run the program. It's not quite so easy as all that. First, it tells me there are missing modules, and the executable crashes in the font code. I check on the Internet, and find that the 'missing modules' are red herrings, and what I'm missing is a font file that needs to be copied over manually. After I do this, I get another crash in the executable. This is much harder to track down. It turns out that I need an icon for the window - something that didn't seem required in the non-compiled version. Once I have that in place, everything works. I run a few more tests - and I seem golden. WHEW! I create a zip file of the distribution (I'm not going to bother with an installer / setup program), and I'm DONE. My week-long project actually took two-and-a-half weeks of real-world, part-time effort. It's definitely not everything I hoped it would be, but I'm pretty proud of what I accomplished: http://www.rampantga...ackenslash.html Aftermath: The Post-Mortem Interestingly enough, the lessons I "learned" from working on a game with such impossible constraints are very applicable to the development of any game, on any budget and schedule. A lot of the lessons Hackenslash taught me weren't completely new to me - but these sorts of things often bear repeating. Without further ado, here are the Top Ten Lessons Hackenslash taught me: Lesson 10: Doing something like this really was worthwhile I didn't think I'd have time to do something like this when I first found myself challenged. But now that it's done, I found it not only taught me a few things, but it increased my enthusiasm for continuing work on the project I put on hold for this. You wouldn't think that working on Yet Another Game would feel like a vacation, but it did. And after all, I didn't lose that much time - only forty hours. This was a fun experience - one I'd be happy to repeat again in the future. Lesson 9: Cutting features isn't always free Some of the last-minute changes to Hackenslash really blew the game balance out of whack. The inability of monsters to cast spells, and the lack of need to for the player to 'conserve resources' as he pushes deeper into the dungeon trivialized some of the challenge to the game. If those features were going to stay 'gone,' the game needed another design pass to re-balance it and improve the modified gameplay. In other words, cutting features introduced an additional cost to the development of the game. This made me wonder how many retail games were released in a terrible state because the development team didn't have time to re-visit the game design after features were cut to meet schedule. Lesson 8: Do the important stuff first I found I tended to be more productive, efficient, and make more progress on the game when I was in 'crisis mode," realizing how tight my deadline was and making a conscious decision to (usually) only work on the pieces that made the biggest difference in the game. I think I may run through a similar exercise with all of my future projects: I'm going to try to break my development time into, say, 8-hour segments, and play a little game with myself: If I pretend that I only have those 8 hours to 'finish' the game, what could I do that would make the biggest difference in those 8 hours? I don't know if it would pay off as well at the end of the project as the beginning, but it's worth trying. Lesson 7: Scope will expand to exceed your budget and schedule Every programmer I've ever met tends to underestimate the time required for him or her to complete a feature. Add to that the dreaded 'feature creep,' and you can guarantee your project is going to be way over schedule. I'm not one of those guys who believed that "feature creep" is always a bad thing. I think some of the most killer features - the ones that turned games into hits - often came about as a form of "feature creep." But new features are seldom free. You will have to make room for them in your budget - and that often means cutting other, less worthy features. This project taught me a little bit about being ruthless in cutting features. In truth - if I had the option, I should have added an extra ten hours to make the game truly "work" - but only after cutting all the fat (like doors, magic wands, etc.) Lesson 6: Get the Game Playable as Fast As Possible The sooner you are able to get things on screen and start playing around with it, the sooner you can revise and improve your design, weed out the things that don't work, and come up with great ideas for making the actual game better. It also helps you catch bugs (especially those ugly design bugs) earlier, which makes them much easier (cheaper) to fix. It also aids you in sorting through priorities. Lesson 5: It's sometimes much faster to throw away old code and start over I only ended up completely throwing away some code and starting over once in this project. While I can't know with a certainty if I really saved time by doing this, I suspect I would have been fighting the design flaws of the original method all the way to the end of the project. On the flip side - throwing away the old rotating feature code and starting over with a better design might have saved me some trouble. Lesson 4: Python Rules! I can't believe how quickly many features came together using Python as opposed to, say, C++ or even Java. Things like typeless variables, dictionaries, and extremely easy-to-declare lists (allowing a mixing and matching of object types) made it very easy to implement content lists, attribute handling, spell effects, and so forth. I was already a fan of the language, but now the prospect of using Python, tied into a high-level 3D engine, has become extremely appealing to me. Lesson 3: Don't underestimate the art requirements Looking up source art, drawing, tweaking, testing, and re-tweaking artwork... even little 32 x 32 bitmaps - consumed a great deal of my time - and the results weren't nearly as satisfying as what I'd have gotten if I had devoted those hours to programming. Would an experienced artist taken less time? Undoubtably - though the difference probably wouldn't have been too extreme. Would their results have been better? Absolutely. Be careful not to trivialize the effort it takes to generate art for your game, whether you are doing it yourself or getting someone else to do it for you. It can suck up a great deal of time if you aren't careful. Lesson 2: I need to be more efficient in my use of time A night where I'd devoted four hours to working on the game often ended with only two hours (or less) of actual development time taking place. Some of the time went to documenting what I was doing, but I also found myself losing focus, taking extended breaks, not immediately returning to work after a minor interruption, surfing the 'Net, playing (quick) games, or whatever. Now, taking breaks during long stretches of development is a good and healthy thing. But by recording my actual productivity, I was pretty surprised at how inefficient I was with my usage of my development time. I'm not getting paid by the hour. Better use of my time means I can get more done AND have more 'free time' to do other things. So I'm going to be making a concentrated effort to improve my use of time when I am "on the clock." Lesson 1: IT CAN BE DONE While Hackenslash in its current, 40-hour incarnation is hardly a poster-child for high production values, I think it demonstrates how much can be done - on a fairly complex game - with no budget and very little time by a single developer. Given more time - and even a fairly insignificant budget - or the help of a few friends - who knows how much better it could become? The bottom line is this: If you want to develop games, nothing is stopping you. You can find the time. You don't need a big budget or fancy tools. You don't need a team of specialists. You don't need years of training. All you need is the will to make it happen. And that's the most important lesson of all.
  5. Myopic Rhino

    I'm dreaming too high?

    I don't know of any companies that would even entertain letting them pitch you an idea, unless you're willing to fund it. If they rejected it, but happened to develop something similar, they'd be opening themselves for a lawsuit.
  6. Clearly, you just need a better CPU
  7. Myopic Rhino

    New to the forum, Hello everyone!

  8. Happy Father's Day to both of my dads! See you both in a few weeks.
  9. Happy half-birthday to my best friend and love of my life!
  10. On the train, I overheard a group of people talking about the NSA getting phone records from Verizon. One woman stated that once her contract is up, she's canceling her service with Verizon. Another said that we shouldn't worry about it because NPR said it isn't a big deal. /facepalm. This is why we can't have nice things.
  11. I think I just found my birth mother...
  12. Dave Foley tonight :D
  13. I just received a robo-spam (i.e. our sites cover similar topics) from a speech pathology site because GameDev.net has a thread about LISP...
  14. Talking to marketing people is making me feel stabby
  15. Can't sleep, clown will eat me
  16. Thanks for all of the birthday wishes! I had one of the weirdest birthdays ever, but it dampened the blow of turning 40, and I had a good time. Plus, I got my suitcase back :)
  17. Useless coworkers are useless
  18. Just tuned in to the debates to see the "analysts" demonizing Ron Paul for his views on Iran. What a trio of loathsome trolls.
  19. Me: Are you mad at me? Tyler: That depends. What did you actually do? Me: I just summoned Cthulhu. Tyler: ...
  20. Nate: "How do you spell everything?" Evan: "That's going to take a while, Nate"
  21. Should I be concerned by a resume from a guy with an interest in "human-computer interaction" and experience working with "vibro-tactile interfaces"?
  22. I'm watching Conan get his ordination as a minister from the same website I got mine. Awesome! :)
  23. I am looking for people to do tools development for mobile graphics. These are full-time, permanent positions with excellent compensation and benefits. If you are interested or know someone who might be, get in touch with me. I'll post a link to an actual job listing soon.
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!