component-based game object framework

Started by
28 comments, last by Raveler 15 years, 2 months ago
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!
Advertisement
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.
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.
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.
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?
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<NodePropertyType, NodeProperty>. 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.
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.
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.
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.
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.
Rainweaver Framework (working title)

IronLua (looking for a DLR expert)



This topic is closed to new replies.

Advertisement