Lessons Learned – Hobby Game Development
|
About five years ago I started a fairly ambitious project: I was to create a game engine that will allow me to quickly prototype the many random game ideas I have in the back of my head. It did not need the best of graphics, but it needed to be flexible enough to let me make any game I dreamed of. I opted to create the engine myself, instead of using a commercial one, as I wanted it to be a learning experience as well.
Five years have now passed and I have an engine to show for it! Even better, I recently finished a small AI Tank game that runs on top of my engine. While the programmer art leaves plenty to be desired, the game runs well and is quite a bit of fun. Furthermore, it proved that my engine is, indeed, a functional game engine. A proud moment indeed! Along the way, however, I learned a great plethora of lessons; some from having been told repeatedly, others from making rather painful mistakes. So, I’ve decided to write them down so that perhaps others can learn from them as well, without making aforementioned painful mistakes! Therefore, without further ado – herewith the various lessons I learned while building my game engine. They are listed in no particular order – no one lesson is more important than the other. If you have any comments, please reach me at grafalgar@gmail.com. I look forward to any feedback! Lesson: Forethought goes a long wayBefore starting to write code, spend a little time planning your code. Think about what you’re going to do, how you’re going to do it, what impact it makes on your project and how it will affect your code in the long run. This is as opposed to following your first instinct, coding as fast as you can, and hoping for the best. While the latter may get you results sooner, you will end up throwing away or rewriting most (if not all) of your code when new projects come along. Forethought includes maintaining a general structural overview of your game/engine/etc, and whenever you want to create a new system, really paying attention to where you want to put it. Do you want the Render engine to manage GUIs? Or do you want the GUI to sit on top of the Render engine? Where do Physics fit in, and how does it hook into game objects? What about multithreading? There’s no right answer here – it all depends on your goals and what you aim to achieve. It may take you a little longer to get to those goals by not taking shortcuts, but you’ll thank yourself later! So do spend a few moments to carefully consider what you’re going to do, make sure you’re aware of the pros and cons, and that you can live with the cons! When you add a bit of thought to what you're going to be building, your code generally results in a more stable, coherent codebase that will be much easier to maintain in the long term. Case: Before I started the engine, I drew a simple diagram that showed how I was going to architect my game, engine, tools, etc. It showed exactly where I wanted physics, GUI, rendering, AI, etc. That same diagram has helped me decide how to insert new systems so that the engine still "makes sense." Since that time I've added new systems as the need for them arose and I still referred back the original design, modifying as necessary, keeping my goals in mind and staying true to the original architecture as much as possible. Sure, there have been times I needed to modify my original design where I found it to be insufficient. But again, planning and forethought comes into play once more: Am I sure I want to change the design? What are the pros and cons? Are there any alternatives? Will this benefit me, and my engine, in the long term? Anyhow, I now have a fairly elaborate engine with networking, physics, messaging, AI, GUIs, and so on. The best part? They're all happily working together because of the aforementioned forethought. Lesson: Too much forethought will kill youSo with all of that talk of forethought you also have to be careful of too much of it. At the end of the day, there is no such thing as the “perfect system” – so don't kill yourself trying to invent it. As I’ve said already, be aware of the pros and cons of your system, learn to accept the bad with the good, and move on. If you just can't tolerate the bad then don't be afraid to rewrite. Sometimes, many times, it helps to just take your best guess at the problem and run with it. Maybe you’ll learn that your shot-in-the-dark was actually spot on and you couldn’t have dreamed of a better outcome! Maybe you’ll learn you just wasted a week’s worth of work and need to start all over again. In either case, you did something and you learned from it. Many new programmers spend countless hours over-engineering a problem and being driven crazy by not finding the best solution with no flaws. Just take the plunge already! Case: I spent hours, days, weeks trying to figure out how to architect the perfect network layer in my game, without ever writing any code. Consider also that I had never written any network code to begin with. But, I was determined to come up with most robust, flexible, and fast network layer if it was going to kill me. Finally, out of frustration of having wasted lots of paper and having no code to show for it, I just took my best approach from the bunch and ran with it. Now, granted, what I ended up with was very far from ideal. In fact, I can tell you that it's pretty crappy. But.. I wrote something, learned from the mistakes, and moved on. If I was adamant about finding the perfect solution, I would still be banging my head against paper and have nothing to show for it. Instead, I have a networked tank game that taught me many good lessons about network layers! ![]() A tank game made to demonstrate AI called "tAInk" Lesson: Don't be afraid to rewrite systems, delete code, or start overSometimes people don't want to rewrite systems because they spent months, if not years, developing them. Us programmers, man, we get very attached to our code. It’s not surprising – code isn’t easy to write, and heck if you’re going to abandon all your hard work! It’s a moment of pride when you realize that your tens of thousands of lines all magically work together! The problem is, if you grow too attached to your code, it won't get a chance to grow. You'll end up dealing with crappy systems you wrote years before, along with their frustrations and annoyances. By adding more crappy code to said crappy code, supporting the resulting crappier code with even crappier code, eventually you’ll end up with a big pile of crappy code that you end up abandoning as a whole out of frustration and starting anew, just in order to start “fresh” and “clean.” If you think abandoning one small system is heartbreaking, try it with a full-blown engine that took years to develop and shipped several commercial games. Yes, many game companies go through the exact same motions: eventually the engines become atrocious, and they spend large clumps of cash to build new technology from scratch. Don't be afraid to rewrite systems if you're not happy with them. Even if the system is "good enough", still rewrite it if you come up with something better. There's a nice word for this: "Iterative" Programming. Don't fear it! Case: The networking layer I just mentioned? Yeah, after a few months of struggling with my rather poor solution, I scrapped it, and started over with what I learned in the meantime. My solution is still not perfect. Hell, it's nowhere near professional network layers, but it's better than before! In addition to rewriting the network layer, I rewrote my editor, game-logic systems, object-hierarchies, physics, messaging, GUI, Rendering, and so on several times. I have no idea which iteration of what system I’m on, just suffice to say that with every rewrite I grow progressively happier with the results. Lesson: Separate your game logic from your engineBy separating your game logic from your engine code, your engine remains relatively "pure" and reusable. If you don't keep them separate, you end up with an "engine" (emphasis on quotes) that must be stripped and cleaned up before every new project. Each time you start a new project, the process of stripping and cleaning becomes more and more difficult. It's always surprising to see how much game code makes its way into an engine, and how difficult it is to get rid of it. What do I mean by mixing engine code and game code? Well, if your rendering system checks an object’s health to determine whether or not to render a particular mesh red, then you’ve mixed engine code and game code. When you try to create a new game that doesn’t involve health, you’ll have to figure out how to strip that code from the render system. Remember - programmers are lazy, and never trust yourself (or anyone) to not take a shortcut if it's easy to take. You'll be much happier if you can just reuse your engine without any modification/cleanup in your next project. Case: When I wrote my first game I went into it not knowing what a game engine was. After many months of struggling, I finally had something to show but everything was intermingled in the worst possible way. Game objects knew about levels, levels knew about game objects, sprites knew about input (don’t ask), level tiles know about monsters, oh it went on. When the game was finished, there was no way to separate the engine from the game. It was all or nothing. Needless to say, that was a whole lot of abandoned code simply because I could not reuse it for a new project. As a result, separating the game from the engine (and keeping it that way) was one of the first rules I made before writing or even touching code. I was going to keep the engine and the game logic code separate even if it killed me! To enforce this, I put my game in a separate project than the engine, and made the game project statically link against the engine lib. In this manner, even if I wanted to include game logic from the engine, the linker would complain (circular link dependencies). So, not trusting myself, I actively blocked a shortcut. As a result, my engine is 100% game-code free.
|
|