Tags: gamedev endless wolf_engine
Game development is complex (like any big piece of software). Take any screen you want from a video game, and try to list all the major elements in it. For example, from Legend of Dragoon:
Let's just worry about the 2D/UI elements for simplicity. The UI has two major sections: the controls, and the character status box. The character status box has a backing and contains three characters : each having a picture, a name, an hp label (+value), an mp label (+value, an sp label when needed (+value), etc. The controls each has a image, a highlight circle, a piece of text, and also animates when you hover over it. So even not considering the 3D battle itself, we've probably got 40+ elements depending on how its split up. Just like any big project, managing this complexity is key to being able to develop it.
In Wolf (my custom game engine), the main tree structure is a Node. Nodes have very few properties themselves : they can have children nodes, a set of components, and a custom tag (for searching/debugging). They represent the structure of the game from a dev standpoint : the same way we did earlier.
A component represents a certain 'view' of a component. For example, in a 3D game, a character has several different properties. It has a transform, which says where the character is and how its rotated. It has the ability to render, which says how to draw it. It might have a physics node, which represents how it reacts with the rest of the world. And maybe it has an animation controller which controls the rendering so animations play correctly. Probably also it has a game specific component which has values such as its HP, attack power, or anything else needed for the game. While a character might have all of these, something like a particle effect will only need a few types.
For a quick example, take the node that represents the main character of Endless. He has a position, renders a texture, and can be animated. In my debug output, this shows up like this:
[Node tag='EntityNode' components=T trans=[-32,1272] rot=0 nodeVisible=true guiZPosition=1 (local= 1) ] | [Node tag='Sprite' components=TRS trans=[0,0] rot=0 nodeVisible=true guiZPosition=2 (local= 1) ]
The first node is the container for an entity, the second is the actual rendering. (The reason they're separate is a character can have other visual pieces, like an HP bar). The 'components' part tells you which components each has : the EntityNode itself just has a Translation (T), in which he's at (-32, 1272), while the sprite has a Translation (T), a RenderComponent (R) (in this case TextureRenderComponent which draws an image), and a SpriteComponent (S). The SpriteComponent handles animations and controls the TextureRenderComponent, so if you tell the sprite to 'walk forward', it'll switch the frames on the texture correctly.
This is the first useful piece of components : every node can pick and choose what it needs. Just because some node's need the ability to render, doesn't mean every node has to waste space for it (especially for more obscure components such as SpriteComponent or CameraComponent). The other major feature is the separation of the components from the node. For example when trying to draw a scene, you generally want to sort the objects - whether to draw them back to front, or try to combine things. This requires a very different tree than the normal Node tree, but since they are separate, the rendering system can easily reorder them without messing up the tree. As a simplified example of this, here's a part of the UI being displayed.
[Node tag='ItemsViewCell' components=T trans=[250,-25] rot=0 nodeVisible=true guiZPosition=100 (local= 0) ] | [Node tag='ItemName' components=TR trans=[-1,-1] rot=0 nodeVisible=true guiZPosition=101 (local= 1) ] | [Node tag='BackingRectangle' components=TR trans=[0,0] rot=0 nodeVisible=true guiZPosition=100 (local= 0) ]
The rendering system in Wolf basically takes every render component (R) in the tree that is visible and sorts by guiZPosition. Then it renders from smallest to biggest. So in this case, even though the ItemName is behind the BackingRectangle in the tree, the guiZPosition makes sure it draws correctly. The render system also doesn’t have to walk the entire tree when it renders : it just has to know all of the render objects to use. If you've ever worked with a physics system, its the same idea : the physics world has its own hierarchy separate from your own.
So components are really useful.
They help you modularize your code minimizing duplication, and help manage the complexity in games. As a final example, here's the in-game menu for Endless : I have no idea how i'd manage it otherwise.
(warning: 3MB image)