Skip to main content
GameDev.net gamedev.net
🔒 Locked

component-based game object framework

Started by Raveler Jan 19, 2009 at 5:11 PM 28 replies 10.2k views
Original Post
Raveler
Raveler
Hello guys, I have a fairly simple question for you guys, but I have not managed to find the answer on the internet anywhere. Has anyone if you succesfully implemented and used a component-based framework in a of your reasonable sized game project? Please bear with me, I'll explain myself. I am talking about a component-based system as described by some of the following links: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ http://www.gamearchitect.net/Articles/GameObjects1.html http://www.drizzle.com/~scottb/gdc/game-objects_files/frame.htm When you read the pages, it all makes perfect sense. It's also not so difficult to implement a good framework for creating objects, creating components, attaching them to objects and have them communicate with each other. I have opted, as have most people who use this approach it seems, for a message-system to communicate between components. So one component sends (or broadcasts) a message, and other components who are listening pick up the message and respond to it. So far so good... everything still seems to make sense. But when I start to actually USE this system in a game, I get completely stuck. I completely and utterly fail to convert my seasoned inheritance-based thinking to the new paradigm. To make this more concrete, here are some examples I am struggling with: 1. In my game, I am using a quad tree to manage game objects, and to increase performance in expensive operations such as drawing and collision detection. Implementing this in an inheritance system is very straight forward, but I can't find a way to translate this to a component-based system for the life of me. 2. Performing actions on objects in a particular order. For example, I want to draw sprites on screen starting from the one with the lowest z-value up to the sprite with the highest z-value, so that sprites "closer" to the player are drawn on top of previous ones. How to do this neatly and efficiently in a message-based, component-based system, I don't know. I'm sure that anyone who has ever used a component-based system with success must have tackled these problems, since both are pretty much essential tasks in every 2D game. So if you can give me some insight in how to solve these problems, and how to actually USE such a component-based framework in general, it would be greatly appreciated. Thanks!
Telastyn
Telastyn
I have not, and tend to think that current languages make such a thing very unwieldy. That said, this comes up every so often and there are people who have used such things in commercial games here.


But to your examples:

1. Have a class that defines a leaf in the tree. It can then talk with a component that holds position (either by reference or messages or whatever) in order to reposition itself in the tree (on events or messages triggered by the position component) or otherwise provide the tree access to the object.

2. Same way you'd do it in a 'normal' system. Sorted container of sprites, for each -> draw.

Just because everything is an entity doesn't mean you can't still refer to parts of the entity directly. That is part of the reason behind designing things that way; make interfaces smaller so that the different parts of code use what they need and no more.
Raveler
Raveler
I'm sorry, I'm not with you. I think you're being too fast for me.

First, this leaf in the tree, is it a component itself? Or is it something outside of the component system? Is the tree a component, or an object containing components? If the leaf is a component, then how does it re-organize itself in the tree? By sending messages to other leaf components? Or the tree component/object?

Your answer to point 2 is even more puzzling. How do you even sort something if there's no communication but through messages/events/...?

I think I'm interpreting the entire idea totally wrong.
jpetrie
jpetrie
I've done so, in various iterations. I just finished refactoring the system I've been using for a while now, actually, and I planned to start writing a blog post or two on it -- particularly aiming at covering the kinds of problems you're running into. There's a lot of (obvious-in-hindsight) sort of articles out there on how component-oriented object systems should be conceptualized... objects as an implicit or explicit aggregation of components, components that provide isolated behavior and/or data, et cetera.

But most such articles (while they are quite good) stop at those high level issues and don't get into the practical nitty-gritty of various sort of implementation problems, such as how you initialize a newly-formed aggregate object with runtime-determined properties, how you deal with inter- and intra-component communication and dependencies without invalidating the benefits of the system, and along what axes you break your former rigid types down into components.

It's not that these problems are hard or that you've never tackled them before, it's than in a component-based system you're kind of approaching them... sideways... at least relative to what you might be used to.

The trick, also, is to remember that you don't need -- or want -- to convert everything to components! Some of your components can be little more than lightweight stand-ins, proxies for the aggregate object within an external subsystem. Rendering is a good example, to address one of your specific questions.

The 'visual component' in my system is little more than a stand-in. The component holds a 'render instance' (which is basically the primitive drawable entity my renderer consumes) and understands how to modify its properties in response to certain messages sent from elsewhere the component system.

But the renderer also holds a reference to the render instance and it is responsible for all those heavy-duty tasks like sorting based on material and all that.
Raveler
Raveler
Thank you, I appreciate your information greatly. I also felt that the high-level concepts are quite easy and well-explained on the internet, but the details are left unexplained too much.

So if I understand you correctly, your rendering system is something like this. You have a class Renderer (could be a singleton), which works with Render Instance components, which are part of every object that wants to be rendered. These Render Instances contain access to information such as current location, bounding box information, sprite/texture details, etc. These components are part of the object, but they are also "sent" (via messages?) to the Renderer when they are initialized. The Renderer does not bother with the fancy component/object architecture for the rest, and just works with the Render Instance component instead of the object directly.

is this a correct interpretation?
aaron_ds
aaron_ds
I'm using to great effect in an XNA project a la C#.

It has been a while since I last touched this code but I'll give a summary.
The engine as a whole operates on the MVC principle. The scenegraph is the model. The graphics rendering, sound, input, timer are views. Animation, physics, menu logic, game logic are all controllers.


The scenegraph and scenenodes implement the visitor pattern. Visitors can visit all the nodes in the scenegraph or a node and all of its children.

Nodes components - here named NodeProperties - stored in Dictionary. A node can have several or no components attached to it. A NodeProperty stored with the key Animatable will always implement the IAnimatable interface. Same goes for rendering and physics, et. all.


Here is a sample of code in the animation controller.
        public void Update()        {            //visit each scenenode and see if it has any animation properties. Apply them            AnimationNodeVisitor animationnodevisitor=new AnimationNodeVisitor(gameTime);            sceneGraph.Visit(animationnodevisitor.AnimateNodeDelegate);        }        private class AnimationNodeVisitor        {            private GameTime gameTime;            public AnimationNodeVisitor(GameTime gameTime)            {                this.gameTime = gameTime;            }            public void AnimateNodeDelegate(ISceneNode node)            {                if (node.Properties.ContainsKey(AnimatableProperty.PropertyType))                {                    ((AnimatableProperty)node.Properties[AnimatableProperty.PropertyType]).Animate(gameTime, ref node);                }            }        }


The AnimatableProperty wraps the IAnimatable and serves as a component to be attached to a scenenode:
    public class AnimatableProperty: NodeProperty, IAnimatable    {        private IAnimatable animatable;        public AnimatableProperty(IAnimatable animatable)        {            this.animatable = animatable;        }        public void Animate(GameTime gameTime, ref ISceneNode sceneNode)        {            animatable.Animate(gameTime, ref sceneNode);        }    }


Finally, we can make things like an AnimatedSkinnedMesh that implemented interfaces IRenderable and IAnimatable. It works with both the RenderableProperty and the AnimatableProperty to be attached to scenenodes.
    public class AnimatedSkinnedMesh : IRenderable, IAnimatable    {        //we can get which animation clip is playing and start a new animation clip using this property.        public string Animation        {            get {                 return animationPlayer.CurrentClip.ToString();            }            set            {                SkinningData skinningData = model.Tag as SkinningData;                if (skinningData.AnimationClips.ContainsKey(value))                {                    AnimationClip clip = skinningData.AnimationClips[value];                    animationPlayer.StartClip(clip);                }                else                {                    animationPlayer.StartClip(skinningData.AnimationClips.Values.GetEnumerator().Current);                }            }        }        protected Model model;        protected Effect effect;        protected Texture texture;        protected string technique;        protected AnimationPlayer animationPlayer;        public AnimatedSkinnedMesh(Model model, Texture texture, Effect effect)        {            this.model = model;            this.texture = texture;            this.effect = effect;            this.technique = "SkinnedModelTechnique";            // Look up our custom skinning information.            SkinningData skinningData = model.Tag as SkinningData;            if (skinningData == null)                throw new InvalidOperationException                    ("This model does not contain a SkinningData tag.");            // Create an animation player, and start decoding an animation clip.            animationPlayer = new AnimationPlayer(skinningData);            AnimationClip clip = skinningData.AnimationClips["Take 001"];            animationPlayer.StartClip(clip);        }        public void Animate(GameTime gameTime, ref ISceneNode node)        {            animationPlayer.Update(gameTime.ElapsedGameTime, true, Matrix.Identity);        }        public void Render(Camera camera, GraphicsDeviceManager graphics)        {            if (model != null && texture!=null && effect!=null)            {                //render the model using the texture and effect from the camera's perspective using the graphicsdevicemanager            }        }    }



Views like the Render view, take advantage of the visitor pattern as well and traverse the scenegraph when the model is changed and needs to be re-rendered.
jpetrie
jpetrie
Quote:
Original post by Raveler
So if I understand you correctly, your rendering system is something like this. You have a class Renderer (could be a singleton)

I'll pretend you didn't say that. :(

Quote:

which works with Render Instance components, which are part of every object that wants to be rendered. These Render Instances contain access to information such as current location, bounding box information, sprite/texture details, etc. These components are part of the object, but they are also "sent" (via messages?) to the Renderer when they are initialized. The Renderer does not bother with the fancy component/object architecture for the rest, and just works with the Render Instance component instead of the object directly.

is this a correct interpretation?

More or less, although the render instances are not themselves components, but rather there are VisualComponent objects that contain RenderInstance objects and a handful of other relevant data.
Trefall
Trefall
For me it helps to divide my components into Model-, View- and Controller-Components, that each hold a list of components.

I like to think of the MVC in game programming as following:
- The Model holds world rules that knows nothing about View or Controller.
- The View can render stuff based on the Model.
- The Controller knows about the Model (so that a controller can manipulate a rule) and the View (so that a controller can query it when using a mouse to manipulating on-screen objects).
- Using events, I also allow inter-model, inter-view and inter-controller events.

Check out deWitter's article on MVC for further reading:
http://dewitters.koonsolo.com/gamemvc.html

Using this approach, it has been easier for me to divide my code into components with the rules applied by the MVC.

An example (from deWitters, for the lazy, ported to component-based with event handling):
- We have an entity named Racecar.
- The Racecar holds following components:
- ModelComponents:
- - CarHandlingModel
- - CarPhysicsModel
- - CarStateModel
- ViewComponents:
- - Car3DView
- ControllerComponents:
- - CarHandlingController
- - CarPhysicsController

The CarHandlingController is a component that could be plugged into any car entity to allow the user to control the car (one could probably branch this component to allow a user or an AI to control the CarHandlingController also).

The user presses the left button, which causes the controller to fire the event Event_SteerLeft. The CarHandlingModel subscribes to this event, catches it and handles the rules.

The CarPhysicsController also subscribes to the Event_SteerLeft, so that it may control the physical behavior of such an event. It issues the event Event_WheelRotate that the CarPhysicsModel subscribes to. The model then handles the rules, like friction, forces, velocity and acceleration vectors, etc for the car. The CarPhysicsModel migh change the state of the model through the CarStateModel, if the PhysicsModel notice that the friction might damage the wheels, or that the car crashes or whatever, this also happens through events.

Every time the engine finds it well to do another frame event update, the Car3DView Viewer, which subscribes to the event, will extract the latest information from the modelcomponents and use that to render the car. An AudioView Viewer would've done the same thing to render audio.

That's how I like to divide up my components.
Zipster
Zipster
For the better part of last year I took part in converting a large codebase over to a component-based object system. The conceptualization is relatively easy, but like jpetrie said the real challenge is in the implementation.

How do you cleanly make the entire thing data-driven? How do you automate a lot of the tedious steps involved in authoring new components so that programmers can concentrate on writing the component logic itself and not on writing and maintaining easy-to-screw-up-and-forget boilerplate glue code? How do you efficiently reference and communicate with other components (and handle dependencies) in an order-independent way? How do you make it easy for designers to create slight variations on object definitions (essentially create "base" definitions and types, mimicking inheritance) without forcing them to write redundant data? How do you design your component system to be compatible with C++ inheritance? How do you design a generic interface that allows programmers to easily expose per-instance editing functionality to tools, such as a level editor? How do you design the system to work across multiple project and library boundaries with all their interwoven dependencies? How do you design the system so that you can leverage the same data across multiple creation contexts (i.e. on the server versus the client versus the level editor)? How do you properly handle static (per object definition) and dynamic (per object instance) and pseudo-static (anything not in the other two categories) data? How do you design components to be as independent as possible? How do you design components to be event-based as opposed to service- or update-based?

These are just a few of the major issues we had to address. Unfortunately I can't really discuss any implementation details, and even if I could there's simply too much information to divulge all at once; after all it does represent over a year of design and implementation by a core team of programmers. My suggestion is to start slow and implement features one at a time prioritized by need, as per usual. Start with getting your framework to a place where you can define an object in data and instantiate it in code. Next I would work on getting it integrated into your level editor. That should cover the most important features, which is establishing a basic data-driven pipeline even if it doesn't have a lot of features or requires a lot of manual labor for programmers to get new components implemented. The good news is that a lot of the topics above would probably be considered "advanced" features that a basic functional framework won't need, and when you put the thing together one small piece at a time, you'd be surprised at how quickly you have a cool system up and running.
Rainweaver
Rainweaver
Hello all,
My two cents on the subject. I think Zipster nailed some of the major issues, and they couldn't have been worded better.

Another issue to consider is object persistence. I apologise if it has been already mentioned, but it's something really worth taking in account as early as possible in the design.

I think the T=Machine blog mentions the fact that entity systems and relational databases share many similarities. And in my humble opinion, this is a fundamental aspect.

As a final note, I for one found enlightening trying to remodel the way buffs and debuffs work in World of Warcraft. If you're interested in some quick considerations about it, I have a blog with some entries at arainsong.blogspot.com.
m0nkfish
m0nkfish
I too find this a very interesting approach to system design - conceptually it seems the most 'complete' in the sense that it is both modular and non-redundant. I'd really like to see a small example - perhaps a simple entity of two components - realised in code.
MightyDog
MightyDog
We have used this system in both our MMOs here (one in dev and the other has shipped). I'm the lead client programmer so I'm heavily involved in architecture. Both (very different) projects share core components. I was an OO inheritance guy, but I have now seen the light. ;)
Nohup
Nohup
A quick question for those who have/will be implementing this sort of system.

I'm curious about the "messages" which are passed between the components and subsystems. Are these messages serialized chunks or data? Which can be buffered, and passed around in a conventional manner?

Or are we talking about a signal-slot, or a callback/functor type of system?

I imagine that both methods would be possible, but the choice would impact the performance and extensibility of the system.

It would be great to know how people have implemented this, and how it worked.
Raveler
Raveler
Hi guys! Here's a quick follow-up on my progress.


First and foremost, I have to thank you guys, because you finally put me in the right direction. After three iterations of trying to program a object/component manager and communication system that I can wrap my head around, I have finally come up with a very lightweight solution that really does the job.

Basically, the main method of communication between components in my system is the request of other components. Each component can register a "component request" for a particular type of component, and whenever such a component is created or destroyed (either locally, in the same object, or in any object), the interested component is warned about it.

This way, I can still let components interact with other components in a traditional (read: non-message) way, while still maintaining a high level of modularity, since components only need to know about components they work with directly. This is a huge improvement over my previous system, which had boiled down to a big blob object class which contained tons of logic.


I'm only about 1/3 in converting my code base to the new system, but everything seems to port rather nicely. However, there is one particular issue that I can't find a good solution for.

In my system, a game object can have several components, and game objects are assembled on the fly from an XML file. Some of these components are:
- light source (if an object emits light)
- collision box (if an object can collide with other objects)
- sprite (if an object is drawn on the screen)
- movement controller (if an object can move around)

There are quite a few more, but they all share one basic property: they all rely on the same small set of basic object properties that every object has. I am talking about, for example, the location or size of the object (objects can be scaled dynamically in my engine). I have put these in a separate component, but the problem is that I have to include an interface to this component in EVERY other component, which is kind of annoying. But it even gets more annoying if I, for example, have two collision boxes resolve a collision. Because collision box 1 needs the location and size of collision box 2, which it can only access through the base component. So I have to provide functions in the collision box component that just forwards the location and size from the interface to the base component it has.

So I have many functions looking like this:

Vector2 GOLocalComponent::getBoundingBoxSize() const {
return fParent->getBoundingBoxSize();
}


This annoys me a great deal, but I see no better solution at the moment. How do you guys go about solving this problem?
m0nkfish
m0nkfish
One approach is to just compromise that one part of your framework and store a plain old position vector in your entity/game object. There was a thread made a few years back about component-based architectures (I think the title may have included the word "off-board") where this issue was raised a few times.

Ninja: aha

(it was "outboard" ;) )
mb108
mb108
Quote:
Original post by Nohup
A quick question for those who have/will be implementing this sort of system.

I'm curious about the "messages" which are passed between the components and subsystems. Are these messages serialized chunks or data? Which can be buffered, and passed around in a conventional manner?

Or are we talking about a signal-slot, or a callback/functor type of system?

I imagine that both methods would be possible, but the choice would impact the performance and extensibility of the system.

It would be great to know how people have implemented this, and how it worked.


I've been iterating a ton on the messaging / event-handling aspect of my system. I use a std::map. An event is sent by it's KeyType (template-defined), and the accompanying arguments are bound to corresponding signal functor with boost::bind. The resultant boost::function objects are inserted in the event queue. This affords me the advantage of being able to pass whatever I want to event handling routines. There's some overhead with all the functors flying around, but I prefer it to the overhead of having a bunch of event classes derived from a BaseEvent.

It was a bit of a hack to make it type-safe, but it works. =P

I can post the code if there's interest, but gotta run for now.
Raveler
Raveler
Quote:
Original post by m0nkfish
One approach is to just compromise that one part of your framework and store a plain old position vector in your entity/game object. There was a thread made a few years back about component-based architectures (I think the title may have included the word "off-board") where this issue was raised a few times.

Ninja: aha

(it was "outboard" ;) )


Wow, that's an incredibly interesting post! It answered most of the remaining questions I still had and gave me some really neat ideas on how to improve what I already have. Thank you very much!

Someone should really write a good, lengthy article which discusses these implementation/practical issues in detail. It seems this component-based architecture is used my many developers, but none have bothered to share the details so far. It's a shame because it seems like a really powerful paradigm.
Zipster
Zipster
Quote:
Original post by Raveler
Someone should really write a good, lengthy article which discusses these implementation/practical issues in detail. It seems this component-based architecture is used my many developers, but none have bothered to share the details so far. It's a shame because it seems like a really powerful paradigm.

To be fair, that's probably due to the fact that many of the implementation details are so tailored to a particular project, proprietary code base, set of requirements, etc., that divulging any of that information either comes dangerously close to breaking the NDA, or impossible to fit into anything less than a full book. I'm on that fence at the moment -- I'd love to go into detail on how we solved a lot of issues in our component-based system, but so much of it is tied to our particular projects and other proprietary software that the risk of stepping on a mine is too high, and there's simply too much that would have to be explained :/
m0nkfish
m0nkfish
I agree - there aren't that many [online] resources on this framework approach even though it's been around for ages. In particular, a gdnet article is way overdue! When I've played around with the idea a bit more I may volunteer to write one, but obviously there are far more qualified people here that could do it better :)

Edit:

If you're looking for an example of an implementation (as I was), here you go.

[Edited by - m0nkfish on January 22, 2009 8:52:41 AM]
gekko
gekko
Quote:
Original post by RavelerIt seems this component-based architecture is used my many developers, but none have bothered to share the details so far. It's a shame because it seems like a really powerful paradigm.


GP Gems 5 and 6 both have articles on it. If you only read one, I'd recommend 6.
-- gekko

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.