The Entity-Component pattern along with using composition in preference to inheritance sounds great at first glance but brings with it some problems of its own. Not least of which it exacerbates all the problems we discussed in Chapter 18, Introduction to Design Patterns and much more!. It would mean that our new GameObject
class would need to know about all the different types of component and every single type of object in the game. How would it add all the correct components to itself?
It is true that if we are to have this universal GameObject
class that can be anything we want it to be whether a Diver, Patroller, PlayerShip, PinkElephant or whatever then we are going to have to code some logic that "knows" about constructing these super-flexible GameObject
instances and composes them with the correct components. But adding all this code into the class itself would make it exceptionally unwieldy and defeat the entire reason for using the Entity-Component pattern.
We would need a constructor that did something like this hypothetical GameObject
code:
class GameObject{ MovementComponent mMoveComp; GraphicsComponent mGraphComp; InputComponent mInpComp; Transform mTransform; // More members here // The constructor GameObject(String type){ if(type == "diver"){ mMoveComp = new DiverMovementComponent (); mGraphComp = new StdGraphicsComponent(); } else if(type =="chaser"){ mMoveComp = new ChaserMovementComponent(); mGraphComp = new StdGraphicsComponent(); } // etc. … } }
And don't forget we will need to do the same for each type of alien, background, player for each and every component. The GameObject
would need to know not just which components go with which GameObject
but also which GameObjects
didn't need certain components like input-related components for controlling the player GameObject
.
For example, in Chapter 19, Listening with the Observer Pattern, Multitouch and Building a Particle System we coded a UIController
class that registered with the GameEngine
class as an observer. It was made aware of the HUD buttons by passing an ArrayList
of Rect
objects to its handleInput
method each time GameEngine
received touch data in the onTouchEvent
method.
The GameObject
class would need to understand all this logic. Any benefit or efficiency gained from using composition over inheritance with the Entity-Component pattern would be completely lost.
Furthermore, what if the game designer suddenly announced a new type of alien, perhaps a Cloaker alien which teleports near to the player takes a shot then teleports away again. It is fine to code a new GraphicsComponent
, perhaps a CloakingGraphicsComponent
that 'knows' when it is visible and invisible along with a new MovementComponent
, perhaps a CloakerMovementComponent
that teleports instead of moving in the conventional manner but are we really going to have to add a whole bunch of new if
statements to the GameObject
constructor? Yes, is the unfortunate answer in this situation.
In fact, the situation is even worse than this. What happens if the game designer proclaims one morning that Divers can now cloak! Divers now need not just a different type of GraphicsComponent
. Back into the GameObject
class we go to edit all those if
statements. Then the game designer realises that although the cloaking-divers are cool, the original divers that were always visible were more menacing. We need divers and cloaking-divers. More changes required.
If you want a new GameObject
that accepts input and then things get even worse because the class instantiating the GameObject
must make sure to pass in a GameEngineBroadcaster
reference and the GameObject
must know what to do with it.
Also notice that every GameObject
has an InputComponent
but not every GameObject
needs one. This is a waste of memory and will either mean initializing the InputComponent
when it is not needed and wasting calls from the GameEngineBroadcaster
or having a NULL InputComponent
in almost every GameObject
just waiting to crash the game at any moment.
The last thing to notice in the previous hypothetical code is the object of type Transform
. All GameObject
instances will be composed with a Transform
object that holds details like size, position and more besides. More details on the Transform
class as we proceed with the chapter.
In fact, there are even more scenarios that can be imagined and they all end up with a bigger and bigger GameObject
class. The Factory pattern or more correctly in this project, the Simple Factory pattern is the solution to these GameObject
woes and the perfect partner to the Entity-Component pattern
The game designer will provide a specification for each and every type of object in the game and the programmer will provide a factory class that builds GameObject
instances from the game designer's specifications. When the game designer comes up with quirky ideas for entities then all we need to do is ask for a new specification. Sometimes that will involve adding a new production line to the factory that uses existing components and sometimes it will mean coding new components or perhaps updating existing components. The point is that it won't matter how inventive the game designer is the GameObject
, GameEngine
, Renderer
and PhysicsEngine
remain unchanged. Perhaps something like this:
GameObject currentObject = new GameObject; switch (objectType) {case "diver": currentObject.setMovement (new DiverMovement()); currentObject.setDrawing (new StdDrawing()); break; case "chaser": currentObject.setMovement (new ChaserMovement()); currentObject.setDrawing (new StdDrawing()); break; }
In the code the current object type is checked and the appropriate components (classes) are added to it. Both the chaser and the diver have a StdDrawing
component but both have different movement (update) components. The setMovement
and setDrawing
methods are part of GameObject
and we will see their real-life equivalents later in the chapter. This code is not the same as we will be using but it is not too far from it.
It is true that the code strongly resembles the code which we have just discussed and revealed to be so totally inadequate. The huge distinction, however, is that this code can exist in just a single instance of a factory class and not in every single instance of GameObject
. Also, this class does not even have to persist beyond the phase of our game when the GameObject
s are set up ready for action.
We will also take things further by coding a Level
class that will decide which types and quantities of these specifications to spawn. This further separates roles and responsibilities of game design, specific level design and game engine/factory coding.
3.131.152.166