Components have become pretty popular these days and I’d like to share some issues we had with them in our game projects. I was experimenting with component-based game engine architectures for 2 years and eventually stumbled upon functional reactive programming (FRP) to solve some of the issues of game-object components. I hope to provide some arguments from an objectoriented programming (OOP) viewpoint to why I think FRP helps to write more reusable code.
The argument for component-based game-engines usually starts like this: Game-objects should be represented like objects in reality but as the project progresses however, class hierarchies become more and more complex and thus should be split into small, reusable components. Game developers like abstract and re-usable game-engines after all. The following diagram illustrates such a monolithic class hierarchy (adopted from the book Game Engine Architecture):
Game-object components address these issues by reducing game-objects to identifiable containers of components, where each component encapsulates some reusable functionality and automatically communicates with other components. A component could be something like: a position, player movement, 3D model, health-points and so on. For the communication of components in our engine we used messages, events or let them directly search for components implementing a specified interface, which is illustrated in the following diagram:
In component-based architecture we were mostly concerned about the component intercommunication, like the player movement for example: Should the Mover-component manipulate the position directly or send movement-messages? Or should the Position-component listen to movement-events? What if we want the player movement to behave just a little differently, like being a little random? Do we implement a new RandomMover-component, or do we need a new component,… again,… just to encapsulate the random function into a component? And how does this fit into the automatic Position-Mover-communication?
In the book Component-based Software Engineering (CBSE) they actually define a component as:
A software component is a software element that conforms to a component model and can be independently deployed and composed without modification according to a composition standard. [...] A component model defines specific interaction and composition standards. [...]
In plain English: “In order to allow a new component to be added to an existing environment of components in a game-object and automatically communicate with each other, the communication protocol between the components has to be predefined“. I think the component-based game engine literature mixes-up “combining existing functionality to define game-specific logic” (= reusing functionality) and “automatic communication within unknown game-objects” (= dynamic functionality). Automatic communication is restricted to the known components and new functionality always has to be game-specific (either hard-coded, or in scripts, or any other data-driven method). Even with components you define the player game-object specifically at some point: Player = PositionComponent + VisualComponent + InputComponent.
In FRP everything is based upon time, thus time should be abstracted away into “time dependent functions” (behaviors). A Mover-”component” should just produce a translation vector over time from user-input — nothing more! And if we want it to be a bit random, we just compose the output with the random function in the game-object definition. A pure mathematical function is the most isolated, self-existing and reusable component you can get, as it depends (and only depends!) on the direct input. There is no need the encapsulate a function into a component… which is then managed by a component container (game-object) and defines a lot of messages and events to get the data to the right point.
I’m arguing that game-objects are always game-specific but with time-dependent functions you just have to combine the existing functionality in the right way for every specific game-object. For example, let the movement of a game-object by calculated by: the initial position + some translation over time from user-input + a random factor + … and so on. You may then compose the movement behavior into more complex behaviors (or complete game-objects if you like) in every way you can imagine: A movable, static image game-object? MovingImage = translation function + draw image function. A movable, animation game-object? Certainly! MovingAnimation = same translation function + draw animation function. Or just a static animation perhaps? StaticAnimation = position + draw animation function.
I’m not going into more detail about FRP for now, you can find more information on my blog or on the internet. Component-based software engineering can still be applied in this environment on higher level, like the communication between subsystems or game-objects, but not on the level of game-object functionality level! If you still think you want to implement game-object components, ask yourself the following questions:
- How can different functionality be defined in terms of relative complex, non-elementary components?
- What are the most elementary components?
- In which way are elementary components different from pure functions?
- How can existing components automatically communicate with new messages from new components?
- What is the point of ignoring a message that a component doesn’t know of?
- What happened to the input-process-output model after all?