Sign in to follow this  
acid2

About my component system implementation

Recommended Posts

Hello everyone, I'm writing this post after my reply to the post "Shall entities know how to render itself?" I spoke of a component system that I use, and received a few pms from people curious about the system, and I promised I'd write it up, so here it is! I'll start by giving a brief overview of the system for people who are not familiar with it, and then I'll show how I implemented it. At the end of this post is a link to my source code - the way I'm doing it. So first, the intro. What is this? Essentially, the component system is a way of describing objects in a game engine, in a way that is reusable. It is similar to why we use inheritance, so we can extend. The component system works on the object level, rather than the class level. What I mean by this, is instead of extending functionality of an entire class, we can extend the functionality of a single object. This brings the benefits of very minimal bloat in places, and - in my opinion clarity. Here's a quick example of how it might work:
// First, we have to create the object.
ObjectID npc = ObjectManager.CreateObject("NPC #547");
ObjectManager.CreateComponent(npc, "/Common/Entity")
ObjectManager.CreateComponent(npc, "/Graphics/AnimatableMesh")
ObjectManager.CreateComponent(npc, "/Gameplay/Communicatable")
ObjectManager.CreateComponent(npc, "/Common/ScriptedInteraction")


About the example Ok, so that might not mean a lot to you, so I'll quickly go over the code presented above. The first thing that I do is create an "ObjectID." This is merily an index to the object, and ObjectID contains only 2 properties, ID and name. I request the object manager create an object, called "NPC #547" and it returns a unique id. The name part is just for scripts to find objects, btw. The lines after that are me constructing the object. I call the CreateComponent method of my object manager, tell it the object to add the component to, and the name of the component. To quickly sumarise the components above, entity provides the location of the entity, and its orientation. AnimatableMesh does, well, what it says on the tin, Communicatable means that the player can communicate with the object, and scripted interaction means that any interaction will be handled via a script. I've purposly left a lot of the details out in that example like what script to use, setting the entity properties, and stuff like that. I left them out simply for clarity. Components Every component in my system runs of an abstract class that is:
public abstract class Component
{
	protected ObjectManager objMan;

	private ObjectID obj;
	public ObjectID Object
	{
		get { return obj; }
	}

	public virtual void Initialise(ObjectID objectId, ObjectManager manager)
	{
		...
	}

	public virtual MessageResult HandleMessage(Message m)
	{
		...
	}
}


All components inherit this class. The initialise method initializes the component, and the contents of the method in this abstract class is just to set some variables. Other classes might require resources from the manager, and such. But, how does the object manager know about these components that we create? There are a few ways I could have tackled this problem. I'm currently doing it a very manual way, which is to simply call ObjectManager.RegisterComponent(Type, string, AddComponentDelegate). The Type parameter is a .NET thing, that is the type of the component. When creating the component I use reflection invokation, but in C++ you could use a function pointer or something (I don't use C++, sorry). The string parameter is the unique name of the component. In the example above, this might be "/Common/Entity". I will touch on the AddComponentDelegate later. Another way I am considering trying is to use attributes on each component, with the same parameters. This gives me the advantage of not having to explicitly add each component to the object manager, which will make the code flow more efficient for me. Using the components So far, you've got enough information to make the basics of a component system, but when actually "using" the components, it may prove difficult. This is because there is no easy way to access them. My solution to this problem is to introduce ComponentContainers. A component container is a class that is a way to store components. The AddComponentDelegate acts like a reference to a function that should add the component to it's required store. I currently use 2 component containers in my engine, the SoupContainer and the TreeContainer. A soup container has the main goal of storing entities in a 1D list, with little order. The TreeContainer was designed specifically to address the scene graph. When I create an Entity component, it is automatically added to the root node of the Entity tree (a static property if you take a peak at the code). These component containers give a nice way to work with components. For instance, lets assume we want to render the scene. What I do at the moment is run a Update visitor on the Entity TreeContainer, that traverses the tree, pushing and popping each node onto a matrix stack appropriatly. Then, I get all of my Visibility components from a soup collection, and check the volumes against the camera view frustum, with the location specified in the entity node. I now have a list of ObjectID's that are visible to the camera, so I go through my Mesh SoupContainer, and if it's ObjectID is one of the visible ones, it is rendered. As you can see, there is a lot of interaction between components, and I'm not sure whether it's totally a good thing, but for the most part now it works, and I'm not finished with this system yet. There's one more it's worth speaking about before I wrap this post up, messaging. Messaging Often, componentss will need to know information that isn't really their responsibility. Let's take a made up example of a car. The car could be made up of a mesh component, and a damagable component, amongst others. First of all, the mesh needs to be modified as the car takes damage, or maybe start a particle system to make some sparks. You get the idea. This is where messaging is useful. The Damagable component can simlply send a message to all components of the object saying "I've taken damage, if anyone needed to know that." Components subscribe to a specific type of message (maybe DamegeTaken, in this example) and when the receive a message, they can react accordingly. I use System.Guid to represent the type of a message, and all classes that can send messages expose public static properties of there Guid. There is a chance of overlapping message ids at the moment, but it won't take much to remedy that situation. Conclusion Ok, so that's a pretty long post for what seems like a small topic, but hopefully this has given an insight into the system. I just want to say, very importantly this is not my idea. The way I store components in containers is my idea, and I've adapted the system to work well in .NET, but the original concept was invented by Bjarne Rene. You can find a nice article that beats my explanation abilities by far, in Game Programming Gems 5. I highly recommend the book! As you can see, this system works nicely in the game scenario and gives us a way to represent objects without interfering with others. It is extendable, and modular, meaning all changes should be centralised to the component, and nothing more. You can get my code from: this lovely little link. It is actually the engine I am working on at the moment. I removed a bit of stuff from it, like the material Component I am working on at the moment, because it's incomplete. I really hope that this post has given all those who were interested the information they wanted to know, and if there is anything I have missed, ask! Thanks for reading (should I submit this as an article, btw?)

Share this post


Link to post
Share on other sites
I am not able to get it to compile just yet, but I do find looking through your source code refreshing.

The way I am coding my engine right now I absolutely hate, and the way you seem to have done it is the way I previously wanted to do mine.

Thank you very much for posting this code.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this