Tying It All Together -- Object Oriented Design?

Started by
7 comments, last by Waffler 17 years, 2 months ago
One thing I haven't been able to wrap my brain around is how to handle the complex interactions between game entities in a large scale object oriented game. For example look at the picture below: This is a simplified example, but it can illustrate my problem. Pretend the chest starts invisible. Let's say the following actions are available: 1. The hero can hit the enemy with his sword. 2. The hero can be hurt by touching the enemy. 3. The hero can step on the button to cause the chest to appear. 4. The hero can open the chest and get the key if it had already appeared. 5. The hero can unlock the door if he has the key. If this was all that there is in the game it would be very easy to simply hard-code these actions in, but what if the game is larger in scope? What if there could be hundreds of types of enemies which all interact with the hero differently? Maybe some enemies hurt the player if he tries to hit them with a sword. What if I wanted the button to be able to trigger any action? Perhaps I might want the button to drop a dozen enemies from the ceiling or open a door? With a very large game having everything hard-coded into the game would not be a good idea. I'd like to have as much of the world as possible defined in files and scripts, but I'm not certain how the source structure should be to allow for great flexibility and expandability. Can someone please describe a system where a large variety of game entities could interact in such complex ways. Simple one-screen arcade style games are easy because the types of objects and their interactions are simple, but very large games there could be hundreds of thousands of entities all around the world all interacting in complex abstract ways I have a hard time figuring out. I have a good grasp of C++ and a fair understanding of Object Oriented programming but not much experience with large-scale projects. I don't want to design a game only for it to become a tangled mess of poorly structured code. I don't know if I described my source of confusion very well, but can someone try to explain how they might handle such a system?
Advertisement
Quote:Original post by Waffler
Can someone please describe a system where a large variety of game entities could interact in such complex ways. Simple one-screen arcade style games are easy because the types of objects and their interactions are simple, but very large games there could be hundreds of thousands of entities all around the world all interacting in complex abstract ways I have a hard time figuring out.

Trying to think in over-generalisations (like "entities doing anything" and "complex abstract ways") will just lead to you tieing your brain in knots. You should first sit down and think through your actual requirements and have some concrete goals to aim for. Otherwise you'll end up with something which is difficult to use and 'flexible' in the wrong way.

Sticking with the RPG theme, something like Bauldurs Gate might have thousands of monsters, but really they're all of a certain small set of core types with different statistics. Data driven methods (eg. creating instances of particular classes using stats contained within data files work well here.

When it comes to individual levels and having monsters/npcs react in individual ways (eg. plot development, specific quests) then you might want to have some kind of scripting language to add specific behaviour/logic but without having to hard code it into your actual C++ code.

Requirements first, then design. Wash, rinse and repeat.
I think that the only solution is to have more experience. I would start very small (no smaller than that) and gradually make larger and larger programs. Turning a small program into a big one is even better. Yes your design will have to be refactored and it will take forever but I suspect that that is the only way to get anywhere until you are super experienced. Despite the fact that I have spent years making some excellent code I have never finished a project. It looks like I will finish my current project but only because it is extremely layered (a compiler, so each stage is separate) and I am just taking is super slow and steady.

So in your case I would add each level of complication in bit by bit. Rediscover each level of abstraction as you go. For example hardcode the first monster. Don't even represent him with a struct, just a bunch of variables in main. Then refactor when you add a second monster. Keep refactoring but only as you need to. Start unambitious. Eventually your program will grow into that super program. Think of it as a seed or an embryo. That baby is going to need a heart and skin but it doesn't start with them.
Thanks. OrangyTang, you're right about thinking about requirements first. I think my was that I didn't really have a specific game in mind while trying to figure this out. In fact, I was trying to imagine a rather generic system which could be used on a range of game genres.

I guess I was mostly asking for suggestions on what classes might be involved and how they might be tied together. There are many ways a game could be designed, but I was wondering what the right way might be. I guess this might be a difficult question to answer.

Glak, I have tried starting small, but the projects seem to rapidly become a tangled mess -- often to the point where starting over would be much simpler than trying to clean up what I had. I wanted to try to avoid some of these complications by starting a project with a more sound structure from the beginning.

I have experience with programming the parts which go into a game -- graphics, sound, input, etc. It's just the dynamic content management and the interactions between objects which I need to work out, so I not too interested in defining an enemy as variables in the main and working up because I want to know more about good object oriented game design.

I've read a few articles on object oriented game design, but they seem mostly to be theory. I wanted to know how one might go about it in practice within a C++ project.
First off, I love your diagram.

Secondly, I would try to first make your requirements more solid, and then see if you can hard-code it. A lot of the uglier penalties of "hard coded" code can be avoided by simply building a game-code DLL and interfacing with your "game guts" from there.

The approach I'd take in your situation is probably to set up some sort of message passing/event system. For your monster-hurts-player example, you could send events like onHitBy(player, playerWeapon) to the monster when you whack on it with a weapon, and the monster's overridden event handler would damage the attacker instead.

You'd end up with these two situations (obviously not 100% accurate C++, but you get the idea):
class BaseMonster {  ...  virtual void onHitBy(Actor& player, Item& weapon) {    takeDamage(weapon.baseDamage); // Ow!  }  ...};


class AngryRevengeMonster : public BaseMonster {  ...  virtual void onHitBy(Actor& player, Item& weapon) {    player.takeDamage(weapon.baseDamage); // Idiot.  }};


You can set up the button object to send a particular message (onButtonPressed(button, pushingActor)) which the chest could catch and then respond to, if it's the button it's looking for. You could probably generalize this to an Unreal-style "simple trigger" system if all you wanted was to tie this event to a single property of the chest (isInvisible) and then just bind the two objects to each other somehow. Many games use this "binding" system, such as Abuse, Unreal and Doom.

This is a decent system to aim for, but there are performance penalties and it's easy to get mixed up in the details. You're also still hard-coding it, unless you went with some sort of scripting solution for the events.

In any event, make sure you cleanly separate the "core game" code from the "game play" code. Ideally, your engine should really be a platform of sorts for what's going on inside the game.
I'm gonna try to offer some concrete advice. I started writing some advice that got very large-scale and philosophical and probably isn't what you're looking for, so I'll try to keep this as pragmatic and directly useful as possible.

Don't think of all your possible game objects (individual types of ogres, potions, wizards, players, traps, chest, doors) in terms of a large and complicated inheritance tree. Instead, think of them as all variations of one (or a few) base object types. These base object types should have a very rich amount of functionality; most of the differences between actual objects should be in changing properties or small snippets of code to respond to certain events.

Mentally divide the types of behaviors your object has into categories (e.g., visual, audio, inventory, attacking/taking damage, etc.) and use that to organize how you implement your base object's API. You can organize these into sub-components in your code so you don't have one GameObject.cpp file that's 10,000 lines. Just expect that your base object class is going to have (either directly or through subcomponents) a whole lot of properties, and a whole lot of methods, and only maybe 5 or 10 are actually used to define a specific object type.

The ideal way to do this is to have the different specific object types defined by properties set in a file, maybe with the ability to use short scripts to respond to certain events. If you want to use hardcoded subclassing, try to think of subclasses of your base object (or your small set of base objects) as the equivalent of those sorts of files -- doing nothing besides setting a few properties or responding to a few events. Don't make a deep class tree where lots of functionality is distributed vertically.

This probably sounds a little counterintuitive, because OOP practice teaches you to not put any functionality on an object that doesn't need it, so making one very rich base object sounds like cheating. I do admit it's not always superficially pretty at first. However, I think it lets you reason with game objects better than a very dense class tree with functionality distributed everywhere. It also encourages more data-drivenness to be able to just set 5 or 10 properties on a list of maybe 100 possible properties supported by one base object. Another benefit is that you can't have cross-cutting issues where you have to suddenly need move functionality up the tree because someone wants an enchanted sword to be able to talk (that example comes from one of the Dungeon Siege-related presentations I link below, I forget which.)

Long story short, offset complexity towards the root of the class tree (you don't really have to go as far as having only one base object) and try to make it so producing variant object types requires as little work as possible, ideally just setting a few properties or responding to a few events.

Here's a list of some powerpoint presentations and websites I put together for a friend, that happen to refer to roughly the same issue. A lot of these go into way more detail on this issue, specifically applied to large RPGs with all sorts of complicated behavior.

http://www.drizzle.com/~scottb/gdc/game-objects.ppt
http://garage.gaspowered.com/?q=su_301
http://gamearchitect.net/Articles/GameObjectRoundtable.html
http://gamearchitect.net/Articles/GameObjects1.html
http://www.gamasutra.com/features/gdcarchive/2003/Duran_Alex.ppt
http://www.d6.com/users/checker/ObjSys.ppt

Also, when all else fails, look at how existing games do it. Lots of RPGs come with editing tools that let you get a good feel for how the world is actually architected, and how objects are actually designed. I'm not big into RPGs, but off the top of my head I know Morrowind/Oblivion, NWN, and Dungeon Siege have editors you can play around with. If you don't own them, you can still read tutorials online. For something outside the realm of RPGs, you can take a look at UnrealScript references/tutorials -- reading the documentation for their Actor class really made something click in my head the first time I saw it, although the design has some drawbacks as well.
I think you'll find it helpfull for orientations sake to look at how other games handle this. I found the Half-Life and Half-Life 2 entity systems to be a good source of inspiration.

Buttons in HL can be given various properties, that determine how they can be activated (touched, shot, used) and whether or not they will move when activated, and how long it'll take them to reset.
HL's system allows entities to trigger each other, which is sort of sending a nudge. You can give a door entity a name and have the button activate it by setting the buttons target to the doors name. When the button is activated, it'll activate the door as well.

HL2's system is a bit more complicated, but a natural extention when you take the HL system limits into account. HL2 entities can send complete messages to other entities, and aren't limited to one target only. So, a button could send the following messages when pressed:
<Open 'door1' with delay 2.0 s> <Close 'door2' with delay 2.0 s> <Delete 'button1' delay 0.0 s>
Each entity contains code to parse the messages it can receive, and of course some entities have a different range of keywords they can handle.

Anyway, I'd hard-code the base types, such as buttons, doors (= movable objects), base enemy types, items and such, but give them various options to allow the level-designers to customize them somewhat. The complexity of the system really depends on what you need. HL's system isn't too flexible - a 4-floor elevator would easily cost hundreds of entities - but it was sufficient for that game.

I hope this helps you orientate some more. :)
Create-ivity - a game development blog Mouseover for more information.
Quote:Original post by Ravuya
First off, I love your diagram.

Secondly, I would try to first make your requirements more solid, and then see if you can hard-code it. A lot of the uglier penalties of "hard coded" code can be avoided by simply building a game-code DLL and interfacing with your "game guts" from there.

The approach I'd take in your situation is probably to set up some sort of message passing/event system. For your monster-hurts-player example, you could send events like onHitBy(player, playerWeapon) to the monster when you whack on it with a weapon, and the monster's overridden event handler would damage the attacker instead.

You'd end up with these two situations (obviously not 100% accurate C++, but you get the idea):
*** Source Snippet Removed ***

*** Source Snippet Removed ***

You can set up the button object to send a particular message (onButtonPressed(button, pushingActor)) which the chest could catch and then respond to, if it's the button it's looking for. You could probably generalize this to an Unreal-style "simple trigger" system if all you wanted was to tie this event to a single property of the chest (isInvisible) and then just bind the two objects to each other somehow. Many games use this "binding" system, such as Abuse, Unreal and Doom.

This is a decent system to aim for, but there are performance penalties and it's easy to get mixed up in the details. You're also still hard-coding it, unless you went with some sort of scripting solution for the events.

In any event, make sure you cleanly separate the "core game" code from the "game play" code. Ideally, your engine should really be a platform of sorts for what's going on inside the game.


This is what I'm trying to build myself, a real basic version ofcourse. And I must say it works like a charm, but sometimes it's hard to seperate the 'core game' code and gameplay' code. That isn't that much of a problem if you're planning to create a new engine for every new game you'll make.

Best practices for Software Engineering state that you must think of a way to try and make your code reusable for new projects, that way you don't have to begin from scratch over and over again. There are 2 ways of learning this: finish projects and learn from them yourself and/or read books written by people that learned from finished projects.

Learning = Think + Act + Try Again.

Tying it all together is the hardest part, because all separate parts are discussed everywhere on the internet and in books, I mean GLSL, Skeletal Animation, Scenegraphs, Scene partitioning, etc. Bringing it all together in a workable, tidy fashion is the real art of programming.
Next time I give my advice, I'll buy some bubblegum so I won't your kick ass!
I decided to test out an idea similar to the one presented in http://www.devmaster.net/articles/oo-game-design/ in a small Python program. It's not exactly the same and there are some aspects which I would want to be different in an actual game, but it seems promising. I liked how quick and easy it was to reuse the enemy GetHurt in the Hero class -- it literally had taken only a few seconds to give the Hero energy and allow him to get hurt!

In C++ would using boost::ptr_map<string, Action*>, in a way similar to the action dictionaries I used, be inefficient in a large scale game?

import randomclass Entity:    name = "Entity"    action = {}    state = {}    def update(self):        pass    class RenderableEntity(Entity):    name = "RenderableEntity"    message = "Renderable Entity"        def draw(self):        print self.name, ": ", self.message        print    def update(self):        self.draw()class NonRenderableEntity(Entity):    passclass Action(NonRenderableEntity):    def action(self, actor, target):        passclass State(NonRenderableEntity):    passclass Health(State):    value = 100class Weapon(State):    value = 20    class GetHurt(Action):    def action(self, actor, target):        if actor.state.has_key('Health') and target.state.has_key('Weapon'):            actor.state['Health'].value = actor.state['Health'].value - target.state['Weapon'].value            if actor.state['Health'].value < 0:                actor.state['Health'].value = 0class Attack(Action):    def action(self, actor, target):        if target.action.has_key('GetHurt'):            target.action['GetHurt'].action(target, actor)        class Enemy(RenderableEntity):    name = "Enemy"    message = "I am evil!"        def __init__(self):        global enemies        self.action = {"GetHurt":GetHurt(), "Attack":Attack()}        self.state = {"Health":Health(), "Weapon":Weapon()}        enemies.append(self)    def update(self):        if self.state['Health'].value == 0:            self.message = "I am dead"        else:            if random.randint(0, 3) == 0:                print self.name, "is attacking", hero.name                self.action['Attack'].action(self, hero)        self.draw()            def draw(self):        print self.name, "- energy:", self.state['Health'].value        print self.message        printclass Hero(RenderableEntity):    name = "Hero"    message = "Alive and well"    def __init__(self):        global hero        self.action = {"Attack":Attack(), "GetHurt":GetHurt()}        self.state = {"Weapon":Weapon(), "Health":Health()}        hero = self            def update(self):        global enemies        if self.state['Health'].value == 0:            self.message = "I have been vanquished!"        else:            if len(enemies) > 0:                e = random.choice(enemies)                print self.name, "is attacking", e.name                self.action['Attack'].action(self, e)        self.draw()    def draw(self):        print self.name, "- energy:", self.state['Health'].value        print self.message        print    class World(Entity):    entity = []    def update(self):        for e in self.entity:            e.update()enemies = []w = World()w.entity.append(Hero())e = Enemy()e.name = "Bat"w.entity.append(e)e = Enemy()e.name = "Goblin"w.entity.append(e)for i in xrange(10):	w.update()	print "-----"

This topic is closed to new replies.

Advertisement