The Entity-Component pattern

We will now spend five minutes wallowing in the misery of an apparently unsolvable muddle. Then we will see how the entity-component pattern comes to the rescue.

Why lots of diverse object types are hard to manage

This project design raises multiple problems that need to be discussed before we can start tapping away at the keyboard. The first is the diversity of the game objects. Let's consider how we might handle all the different objects.

In previous projects we coded a class for each object. We had classes like Bat, Ball, Snake and Apple. Then in the update method we would update them and in the draw method we would draw them. In the most recent project, Snake, we took a step in the right direction and had the object handle what happened in both the updating and the drawing phases.

We could just get started and use this same structure for this project. It would work but a few major coding nightmares would become apparent by the end of the project.

The first coding nightmare

Once we had coded the objects all we needed to do was instantiate them at the start of the game, perhaps like this:

Snake mSnake = new Snake(Whatever parameters);
Apple mApple = new Apple(Whatever parameters);

Update them perhaps like this:

mSnake.update(mFPS);
// Apple doesn't need updating

Draw them like this:

mSnake.draw(mPaint, mCanvas);
mApple.draw(mPaint, mCanvas);

It might seem like all we need to do is code, instantiate, update and draw a bunch of different objects this time. Perhaps like this:

Diver mDiver = new Diver(Whatever parameters);
Chaser mChaser = new Chaser(Whatever parameters);
Patroller mPatroller = new Patroller(Whatever parameters);
PlayerShip mPlayerShip = new PlayerShip(Whatever parameters);

Then update them:

mDiver.update(mFPS);
mChaser.update(mFPS);
mPatroller.update(mFPS);
mPlayerShip.update(mFPS);

Then draw them:

mDiver.draw(mPaint, mCanvas);
mChaser.draw(mPaint, mCanvas);
mPatroller.draw(mPaint, mCanvas);
mPlayerShip.draw(mPaint, mCanvas);

But then what do we do when we need say three chasers? Here is the object initialization section.

Diver mDiver = new Diver(Whatever parameters);
Chaser mChaser1 = new Chaser(Whatever parameters);
Chaser mChaser2 = new Chaser(Whatever parameters);
Chaser mChaser3 = new Chaser(Whatever parameters);
Patroller mPatroller = new Patroller(Whatever parameters);
PlayerShip mPlayerShip = new PlayerShip(Whatever parameters);

It will work but then the update and draw methods will also have to grow as well. Now consider the collision detection. We will need to separately get the collider of each and every one of the aliens and each and every laser too and test them against the player. Then there are all the player's lasers against all the aliens. It is very unwieldy already. What about if we have say ten or even twenty game objects? The game engine will spiral out of control into a programming nightmare.

Another problem with this approach is that we cannot take advantage of inheritance. For example, All the aliens, the lasers and the players, all draw themselves in a nearly identical way. We will end up with about six draw methods with identical code. If we make a change to the way we call draw or the way we handle Bitmaps we will need to update all six classes.

There must be a better way.

Using a generic GameObject for better code structure

If every object, player, all alien types and all lasers were one generic type then we could pack them away in an ArrayList or something similar and loop through each of their update methods followed by each of their draw methods.

We already know one way of doing this- inheritance. At first glance this might seem like a perfect solution. We could create an abstract GameObject class and then extend it with player, laser, diver, chaser and patroller classes.

The draw method, which is identical in six of the classes could remain in the parent class and we won't have the problem of all that wasted duplicate code. Great!

The problem with this approach is how varied- in some respects the game objects are. For one example, all the alien types move differently. The chasers chase after the player, the patrollers just go about their business flying left to right and right to left. The divers are constantly respawning and diving down from the top.

How would we put this kind of diversity into the update method that would need to control this movement? Maybe something like this:

void update(long fps){
   switch(alienType){
          case "chaser":
                // All the chaser's logic goes here
                Break;
          case "patroller":
                // All the patroller's logic here
                Break;
          case "diver":
                // All the diver's logic here
                Break;

   }
}

The update method alone would be bigger than the whole GameEngine class and we haven't even considered how we would handle the player and the lasers.

If you remember back to Chapter 8, Object Oriented Programming, you might remember that when we extend a class we can also override specific methods. This means we could have a different version of update for each alien type. Unfortunately, however, there is also a problem with this approach as well.

The GameEngine engine would have to "know" which type of object it was updating or at the very least be able to query the GameObject it was updating in order to call the correct update method. Perhaps like this:

if(currentObject.getType == "diver"){
   // Get the diver element from the GameObject
   diver temporaryDiver = (Diver)currentObject;
   // Now we can call update- at last
   temporaryDiver.update(fps);

   // Now handle every other type of GameObject sub-class
}

Even the part of the solution which did seem to work falls apart on closer inspection. I said that the code in the draw method was the same for six of the objects and therefore the draw method could be part of the parent class and used by all the sub-classes instead of coding six separate draw methods. Well what happens when perhaps just one of the objects needs to be drawn differently, like the scrolling background. The answer is that the solution doesn't work.

Now we have seen the problems that occur when objects are different from each other and yet cry out to be the same parent class it is time to see the solution we will use in this and the next project too.

What we need is a new way of thinking about constructing all our game objects.

Composition over inheritance

Composition over inheritance refers to the idea of composing objects with other objects. What if we could code a class (as opposed to a method) that handled how an object was drawn? Then for all the classes that draw themselves in the same way we could instantiate one of these special drawing classes within the GameObject. Then when a GameObject does something differently we simply compose it with a different drawing, updating or whatever-related class to suit. All the similarities in all our objects can benefit from using the same code and all the differences can benefit from not only being encapsulated but also abstracted (taken out of) the base class.

Notice the heading of this section is composition over inheritance not composition instead of inheritance. Composition doesn't replace inheritance and everything you learned in Chapter 8: Object-Oriented Programming still holds true but where possible compose instead of inheriting.

Note

The GameObject class is the entity and the classes it will be composed of that do things like update its position and draw it to the screen are the components. Hence the Entity-Component pattern.

When we use composition over inheritance to create a group of classes that represent behaviour/algorithms as we have here this is also known as the Strategy pattern. You could happily use everything you have learned here and refer to it as the Strategy pattern. Entity-Component is a lesser known but more specific implementation and that is why we call it this. The difference is academic but feel free to turn to Google if you want to explore things further. In Chapter 26, What next? I will show you some good resources for this kind of detailed research.

The problem we have landed ourselves with, however, is that knowing what any given object will be composed of and knowing how to put them together is itself a bit technical.

I mean it's almost as if we will need a factory to do all our object assembly.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.185.155