Dev work on Kung Fu Legends has been chugging along. I’ve got the engine into a state where it’s not finished, but the important components are there. Sound, input, the user interface system and basic graphics are all implemented. You can work on the engine for the rest of your life, adding more bells and whistles, and polishing it all to a lustrous shine. But I need a game. I’ve put the game engine on hold until I get more done on the game itself.
When I first started cutting code, I approached the game in a very straightforward way. It’s written in C++, so the obvious thing to do with game entities was to make them into classes. At the top you’d have some generic Entity, then a bunch of subclasses – Humans, Items, and the like. You’d get all your specialized code via subclasses: the protagonist would be a subtype of Human, the other characters would be another subtype of Human, weapons would be a subtype of Item… So you think of all the things you want in the game and make a class hierarchy.
But then if you figure that you need to split NPCs into good guys and bad guys (because you’ll never fight a good guy, right?) you have to refactor your class hierarchy. Every addition ends up making the tree more complicated, often invalidating your design specifications along the way. For example, if you put the fighting code into the generic NPC and you don’t want that code in good guy NPCs, you either have to override it in a way compatible with generic NPCs or shift the fighting code down to bad guys. Then you remember that good guys might fight alongside you against bad guys and you have to shift it back, or duplicate it, or just patch, patch, patch.
If you have a very simple game universe (or a simple view of a regular game universe), this approach is okay. Injecting complicated behaviour or modelling tends to mess this approach up, as does iterative development like adding little features over time.
What I thought I needed was a better model of the universe. At this point I was in love with ontology modelling. Ontologies are ways of describing the known universe. It describes all the types of things in the world and how they relate to each other. This has a more general view on how entities are specified than what C++’s object model proposes. You can arbitrarily create classes of entities like LivingThing, and have subclasses of Humans, Animals and so on. You can specify that LivingThings have a gender and that information propagates through your model of the universe. A specific thing (called an instance or an entity) will be the conglomeration of properties, relations and prescribed data that you take from the ontology.
I thought working this way was awesome. I’d be using modern computer science techniques and look really sophisticated. Everything in the world could be queried or modified through some query language and it’d all be hunky dory. And then I tried to code it.
The concept isn’t too difficult and there’s standard mathematical frameworks to make sure you can ask the computer questions and get a response back in a sensible amount of time. But the coding platforms available are more focussed to either curating an ontology or doing some hardcore computer science on it. No-one does ontological engineering for fun yet. And to be honest, the overhead in pursuing this idea was totally not worth it. It was major engineering overkill. I had to devise a good way to get code to work in tandem with this ultra-flexible data structure. I also had to pay a lot of attention to how I engineered the ontology, which sounded a lot like my original generic C++ approach.
The design of the game simulation went on hiatus for a while so I could work on the engine. At least that had tangible outcomes. Meanwhile I did a lot of reading on game engine design to help prompt me in the right direction.
So what did I want from my game simulation? Here’s a short list:
- A model based on storytelling. I wanted to have the ability to wave my hands and make things happen that benefitted the story and not so much the underlying simulation. Something like Oblivion’s Radiant engine layered the storytelling on top of a physical model of where players were, their specific stats and behaviour type. This means I can treat crowds as a “crowd entity” rather than simulating all the individuals and hoping that the aggregate behaviour was what I wanted.
- Concurrent simulation of about 100-200 non-player characters. This number is mostly based off Dunbar’s Number, a rough estimate as to how many characters I could have bouncing around without you losing your mind trying to remember who was who. I’d also like to track things like injuries and skills to a fine degree. I didn’t need it to be in real-time, but it’d be nice if it were fast.
- Hefty AI subsystem. As I’ve mentioned before, I want to bring a huge amount of AI technique to bear. This included generating and managing “quests” by the Storyteller, simulating the social dynamic of the main characters (friends, enemies, lovers etc), and a strategic approach to martial arts simulation. Characters need to be able to call on nifty algorithms to get things done.
- Extensible. I’d like to keep adding to the game in a way that I don’t have to rewrite the engine every time I add something. Moreover, I’d like to be able to tweak the game without having to recompile it every time. Currently it takes a minute and a half to totally recompile the game. It’d be nice to change some of the game’s specifics and see the changes instantly because a minute’s delay is a minute for me to be distracted by something else.
An off-the-shelf solution would have a hard time matching any of these. The last bit would suggest something like a scripting language, although typically that’s best suited to a simulation with a focus on numerical details. For example, if you know an enemy is too easy, just change his health from 100 to 150. But I’m trying something different with the simulation, so this was very close to what I wanted but not quite.
The model I’ve settled on is a component-based architecture. Instead of thinking of an object in the game world as been carved from a single prototype class, we consider every object to be an empty shell that you can plug things into. The only difference between an NPC and the PC is the PC will replace the AI component with a component that links you into the input/output system. Moreover, suppose we have an NPC created that uses a simple AI system, we can test out a more complicated and hopefully better AI system without having to rewrite the entire NPC code and anything that touches it.
With a strong segregation of code between the components, you need a way for them to interact. For example, getting punched in the nose isn’t just confined to your martial arts code, but has effects on your injury code and emotional code (does you character shake it off, get angry or cry?) I’m doing this via message-passing, which satisfies a nice unity of interface for both the components, and each entity in the world can pass messages to each other in the same way. For example, take a fight. Both combatants send their messages of “I do this attack” to the central fight coordination code. It simulates the attacks and determines what happens. Suppose Combatant A ducks, but Combatant B tries a deep uppercut. The uppercut lands squarely on Combatant A’s jaw. The combat system sends messages to both fighters, saying “receive this injury” or “you connected!” The entity as a whole passes that message down to its various subsystems and it’s resolved. This may involve internal message passing like “hey emotional system, we just got punched and it really hurt!” or external message passing like “Combatant A makes a loud oof sound.” These get resolved and the scene plays out.
The good thing about this architecture is that it’s extensible and there’s a good division of concern. You can code just for one system at a time and not worry too much about all the other subsystems, but when you bring it all together you get nice emergent effects like the example above. There are pitfalls, to be sure. Like subsystem A reacting to something, sending a message to subsystem B, which sends one back to A, and you get caught in an infinite loop. There are many ways around this and we’ll have to see what works.
The messages being passed around double as an event record which I’d like to tie to English language production. That is, if we have an abstract description of events, it’d be neat to be able to convert that into a written English description. This takes a lot of work. I’ll talk about my ideas and successes with that at a later date.
All in all, I think I have a good architecture in mind. It’s powerful, extensible, and easy to work on now and in the future. It’s taken a bit of maturity as a programmer to get to this point, but it’s likely to be well worth it.