UW Game Development Certificate - Quarter 2 - AI
by Lee Wilson, March 15th, 2005
What Went Right
- The decision to separate code for individual assignments from actual engine code. This forced an architecture wherein all rendering, etc had to be done in a generic way. While this also caused a lot of headaches, I'm generally pleased with the flexibility and power that this architecture ended up having. As a small example, because of the separation of game-specific and engine code, it is very simple for me to add "levels" or scenes to a game - I simply create a new "Scene" subclass and do any special processing there, and override any base class methods as needed. Then I add the Scene to the SceneManager and all transitions work seamlessly. (ok, in my dreams...but it's mostly working =) )
- My knowledge of game and engine programming has increased exponentially since joining this class. Before I started this class, I had some experience with graphics (mostly DX8 and some old OpenGL stuff), but I wanted a catalyst to help me learn a broad overview of the skills I would really need to be a true game developer. The individual assignments have not usually been very tough, but nonetheless, they've clearly led to problems common to most games and I've spent a lot of time learning about those problems and common solutions. In that respect, this class has so far exceeded every expectation I had for it.
- Individual pieces of the engine/game architecture are noteworthy (to me, anyway) because of how elegantly they worked out. After a while, some things just seemed to "fit" and grow organically out of the base architecture
- Using XML for initial input and configuration. Basically, this makes for a very data-driven design - something I consider essential in any modern software project (whether it's a game or not). Having a basic XML parser that worked well has made a lot of the design for other classes work really easily. And tuning parameters is a breeze.
- A library of general purpose classes came in really handy - things like fast numeric tools, bitflags, a reference counter base class, a singleton, a versatile string class, an XML parser, a generic memory pool, etc. Because of the heavy use I made of them, I'm relatively confident in their stability and uses and thus have a fairly portable library of code that I can drop in and use on any future project.
- Fast Delegates - a simple, generic way of making function pointers and callbacks that can be used by any class has vastly simplified how I handle callbacks and a lot of really neat functionality has been made possible with these.
- A good math library was also very helpful to handle basic geometry - classes like Vector3 (which wraps D3DXVECTOR3 and adds a lot of extra functionality all in a OO manner). While I could have just used D3DX, I found that using wrapper classes made things a lot easier and also allowed easy extensibility.
- Early on, I made the decision to have everything handled by a GameManager class. This is a singleton that is accessible anywhere and has pointers to other manager classes (DisplayManager, ResourceManager, InputManager, SceneManager, etc) as well as handling global events like device resets, etc. This layered functionality works very well and allows good extensibility while still allowing easy accessibility to globally needed functionality.
- Because the DX9 sample framework was so easy to build on, I used it as a base, which also saved a ton of work when it came to GUI work and handling D3D infrastructure.
- A basic theme of "any instance of a rendered 3D object is a SceneObject, and all SceneObjects have a SceneObjectModel" worked out well for the most part. The model can be shared between any number of SceneObjects and is also generic enough so that the SceneObject doesn't need to know or care whether the model is a VertexBuffer, a Mesh, or an IndexBuffer. This currently needs to be extended a little bit to handle objects rendered via sprites, but that should be easy enough with a bit of refactoring.
- Another theme that goes well with SceneObjects is the concept of a GameObject. Basically, these are anything that have game-logic embedded in them. They are usually attached to a SceneObect, but not necessarily. Unlike SceneObjects, GameObjects are intended to be subclassed and thus individual item logic can live in appropriate places, but the underlying engine still has a basic block of information that it needs for most tasks.
- The DXResourceManager ended up being way more useful than I originally thought it would be. The basic premise is that the DXResourceManager is a cache for any DirectX resource that needs to have processing done when the device is reset, created, etc. Thus, it ends up being the main repository for textures, sprites, vertex buffers, meshes, etc. This allows for easy lookups and thus resource sharing between models, SceneObjects, etc. Meshes are a special case that are somewhat tricky and require some special-case coding, but overall, the Resource Cache ends up being extremely useful. In fact, I'm thinking of expanding it to things that don't necessarily need processing with device resets, etc.
- The shader system is a combination of ideas from several sources and is a definite step in the right direction with respect to .fx files. However, as neat and generic as it is now, it's still not the system that it needs to be where artists can simply choose a combination of lighting schemes and post-processing simply by clicking on checkboxes in a GUI and choosing a material for parts of a model and then having a shader built from pieces to handle their combination of choices.
- The QuadTree ended up being extremely useful in far more applications than originally intended. I'd like to genericize it a bit though and make an abstraction for space partitioning and proximity queries that I can simply plug different back-ends that make sense for different data representations (grids, 3D space, etc). Lots of ideas brewing here, but no time to implement them yet.
- The A-Star Machine architecture ended up being really neat and interesting to program. It was so neat, in fact, that I was thinking about writing an article about it. However, getting AI Wisdom 2 showed me that it's simply one way of many to do efficient pathfinding. While I still like the A-Star Machine, I need to do more research to find out how it compares to some of the other methods mentioned in AI Wisdom 2.
- Deciding to sort my rendering caused all sorts of architectural dependencies. However, the end result is that while I don't have the most efficient rendering system, I ended up doing a lot of research into how to actually do efficient rendering and thus I learned a lot and the RenderQueue sorting served its purpose well.
- State Machines just plain rock. I never want to be without them again =)
- After lots of research into camera classes, I rewrote my current camera and I'm very pleased with the result. It's a very flexible camera that still ends up pretty efficient and very flexible in how it can be used. And it taught me how to use Quaternions =)
- The incredible amount of debugging framework code that came out of the first few weeks of the AI class has been extremely useful and showed me the wisdom of building in extensive debugging abilities into anything I write from the very beginning.
- The Grid architecture is a very handy paradigm and the Grid class abstracts that to some degree. The initial Grid Editor that Bretton gave us was so useful that I spent some time expanding it and I think it really drove home for me the usefulness of custom tools in a game project.
What Went Wrong
- The decision to separate code for in individual assignments from actual engine code. While I'm ultimately fairly pleased with the result, this was a lot of extra work. I feel like mostly what I've learned is how to write a rendering engine rather than how to write a game. The extra work definitely made this decision a two-edged sword.
- The class so far has focused entirely on low level things such as pathfinding, basic rendering, collision detection, etc. While it is extremely important to understand these algorithms and how they are used, most of the real learning that I've done in this class has been at a level somewhat removed from this - i.e. overall architecture and engine design. Figuring out an overall design that easily incorporates the lower-level algorithms ended up being the vastly more difficult problem, but also the problem least talked about in class. Most of the learning I've had to do has happened outside of the class materials. I'm not sure how to solve this particular problem except to expand the class - but then it becomes more than just a night class and thus beyond the scope of the current curriculum.
- SceneObjects ended up a little too heavyweight for my tastes. Because they are so generic, they seem to need an awful lot of information. Not sure what a good solution to this is, especially since SceneObjects need to scale and be able to handle thousands of instances. Thus, the less information in this class, the better. Would like to move more information to the SceneObjectModel class. Or possibly have a base SceneObject and specialized subclasses for more information-intensive things like meshes.
- The InputManager ended up being a lot of wasted work, I think. While it is useful to have cached input data and an abstraction layer around it, I'm not sure that the payoff was really worth the initial work when just using mouse and keyboard. Maybe it will be worth it when more device types are added.
- StateMachine messages, Events, and Triggers are all similar concepts that could be combined into a generic message system, but they're just different enough that implementing it becomes something of a chore, and thus the EventManager is currently a hack that I'm very unhappy with. It could really use an overhaul and redesign.
- The SoundManager is useful, but needs some work to be REALLY useful (i.e. playing background music, etc).
- The Mesh system I have now is probably somewhat overcomplicated and is definitely not as efficient as it needs to be. Some research is needed to figure out how to simplify it while still getting the functionality of animation, multi-frame meshes, RenderMethods, etc.
- I've spent far, far too much time futzing around with collision detection. The system I have now mostly works, but is very fragile and not easy to change or add different geometries of objects to. In addition, there's far too much "resetting" (teleporting) of objects going at high speeds. I decided to get rid of it all and use NovodeX, but so far have not had time to integrate it and rip out all the code that currently depends on my custom collision detection. This is a big refactor and I'm not sure if I'll have time. This is really depressing in some ways because I know that I'll end up spending more time than I want to debugging collision detection.
- Most engine architectures allow SceneObjects to have children, etc. I decided not to do this and have SceneObjects be atomic. This worked well until I started working with Meshes and then it got very complicated. I did figure out a way to make them still atomic even with multi-frame and animated meshes, but I don't think it's all that efficient. In hindsight, I think I probably would have been better off going with the common parent/child paradigm.
- Learned the usefulness of sprites and "old-style" 2D rendering too late. Support for it at this point seems like more of a hack into the overall architecture. A rewrite of various portions is needed to make true use of sprites.