Tuesday, December 16, 2014

Component Based Design

Today I am going to talk about using Components for your game objects. 

Wow, that one came out of nowhere, wasn't I just talking about my new alpha version game and the design contest I have going for it at adventuregamequest.azurewebsites.net/#/contest? Yes, but let us take a break from Text-RPG games and focus on some exciting 3D Games.

As I have mentioned before, I am currently working on two projects. Both of these projects are a 3rd person adventure/action game of sorts. From various phases of the design process, before implementing any code, I have come to the conclusion that components are great. In fact, I would venture to say its the next best thing to inheritance.

To keep this in scope, I am not talking about the Component-Entity design pattern per say, rather, the component systems implemented in both Unreal Engine 4 and Unity 4. Not coincidentally, these are two of the largest game engines I can think of that are still of relevance and readily available to the public. Both of these engines incorporate a component model, where you have a base object within the world that has objects attached to them.

In Unity these are the MonoBehaviour scripts. They can be attached to any GameObject within the engine and readily used. In Unreal Engine 4, these are ActorComponents that can be attached to Actors.

Why is this such a good system?

I have several answers to that question. These answers are as follows: modularity, prototyping, less limitations.

The first point is the best: modular components make life awesome. I would venture to say that, for games, components are the next best thing after object oriented programming languages/design. By allowing a designer/programmer to take an unspecified object with no real value other than existing as an entity - GameObject in Unity and Actor in Unreal - on can put together pieces to design a complex system or entity within the game. The best part is that traits of the entity can be replicated and attached to other entities that are not necessarily an instance of that entity. For example, one of the problems I had with a game I was making in UE4 was as follows:

An Actor has several different methods to attack. They can have a weapon, or use an unarmed attack. This means the Actor (we will refer it to creature from now on) will need to have some way to cause damage without a weapon. Simple enough, sounds like a simple property named damage. And, again, simply, the weapon now needs a property called damage. Nice, simple, easy to the point, use some logic to see if the creature has a weapon, if not, use it's damage.
But wait, now we want ranged weapons. Well, a ranged weapon is a weapon, so we will just inherit from weapon, also inheriting the damage property. But a ranged weapon doesn't do damage, it shoots things that cause damage. Well, we could give that projectile a damage property too, sounds good, and ignore the ranged weapon's damage property when the projectile hit an enemy. Another route would be to have a pointer to the firing weapon, and if the projectile hits anything, use the ranged weapons damage to cause damage.
Nice, that is all wired up now. So for each actor that has this damage property, add a callback event to apply damage on the other Actor hit by the creature, the creature's weapon, or the creature's ranged weapon's projectile.
Awesome done.
But wait, now we want spells, okay, just copy all logic from the other four classes...
 Clearly, you can see where this is going. And it is a long an miserable road to travel. This is what your (prototyped) files would begin to look like:

class Creature : AActor
{
public:
float Damage;
Weapon* MyWeapon;
float GetDamage()
{
if(MyWeapon == NULL)
return this->Damage;
else
return MyWeapon->Damage;
}
}
class Weapon : AActor
{
public:
float Damage;
}
//Notice the circular dependencies in this awful design not using components
class RangedWeapon : Weapon
{
public:
Projectile* WhatIShoot;
}
class Projectile : Actor //You could even have projectile inherit from weapon if you would like... but is that really any better?
{
public:
float GetDamage()
{
return WhatShotMe->Damage;
}
RangedWeapon* WhatShotMe;
}


Sure, you could argue, "Well, why not have a DamageActor that has all these stats?" As an exercise to the reader, I will let you argue that point.

So here is the alternative:

Make an ActorComponent called DamageComponent, add a damage property (and any other awesome damage like properties like critical hit, critical chance, critical multiplier, etc.) and have a get method that calculates damage.
Slap that component onto any actor you want and implement whatever event that causes damage (overlap, hit, fall, etc.)
Done, Need spells? Awesome, create a spell actor and slap that puppy on there.

Seems pretty obvious why the whole modularity thing comes in handy now, doesn't it? This also helps create a nice, distinct, seperation of concerns when it comes to your software. Rendering components render, collision components collide, gameplay components do game stuff, and so on and so forth.

Next is how easy this modularity makes prototyping. Let us go back to the first example of making attack power/damage where we created a file for each entity that could possibly cause damage. Better, yet, let us take that a step further and say we used some good OOP and have a DamageActor inherited from Actor that all of these classes inherit from. Now, we just add all the extra properties we need for this. Seems like a good solution.

Well, the Game Designer came in and changed something. The player can now transform into any one of the enemy AI you see before you. You already created all of these awesome combat mechanics using inheritance, and you created the player with a controller, as well as some stats of his own (since this is a stealth game all of a sudden, the player is not a DamageActor because, unlike every other protagonist in the world of games, he does not kill people). So, do we turn this docile, sneaky player into a Damage Actor? Do we just create and inherited class for each possible AI in the game into a PlayerXXXActor and have a bunch of unneeded data implemented?

No. That is bad.

This is a tough one, but my approach would be to find a reference to the base class and copy over the components into the component array, exclude the ones that are type or super type of DamageComponent. One line of code, done. Call it a day, and no crunch time.

Who loves components, modularity, and prototyping? We do.

Finally, this brings to the point of less limitations. In the world of programming, refactoring is a way of life. It can be fun, it can be tedious, but it is also the result of lots of prototyping. With components we can swap in and out common and uncommon responsibilities between common and uncommon entities within the game. Let us, for instance look at a Unity example.

You have a multi player game and there is a single player mode. Do you want to write a script called AIScript where you have if statements for every single action? For instance:

public class AIScript : MonoBehaviour
{
private bool multiplayer;
private ClientRunner client;
OnUpdate()
{
if(multiplayer && client != null)
{
client.Send("Position", new object[]{position.x, position.y, position.z});
}
else
{
//Do nothing, so we have a stupid if/else statement here (or we could hide it with out putting the else, but does that really make you feel good                                 inside?)
}
if(multiplayer && client != null)
{
//Receive a whole slew of data to update
}
else
{
//Um.... do nothing, perhaps go out into the local game world and get that //same data
}
//A plague of if/else blocks
if(multiplayer && client != null)
{
}
else
{
}
if(multiplayer && client != null)
{
}
else
{
}
if(multiplayer && client != null)
{
}
else
{
}
if(multiplayer && client != null)
{
}
else
{
}
}
}
Looks okay. I mean, if the AI is on the network it replicates, if not it uses the game world. Well, that is a lot of conditional branching in an update loop - kind of sucks for performance. And now the AI is responsible for its own Client/Server or Local Gameplay management. Might as well as throw ALL information that requires that knowledge into those blocks of data, or do a lot more copying and pasting.

I have your solution from this awful life: Have a CommonAI, NetworkAI, and LocalAI, have an AI manager detect gamemode and when creating AI, attach the appropiate script. No need to go in and out of if/else blocks to change little things, you can still use inheritance by having common AI routines go in Common AI and have the Network* or Local* inherit from that. Not bad, components win the day again.

In the end I would like to say that I love the component systems used in modern game engines. I know this may seem obvious, but some schools teach the importance of OOP in schools to the point where one can focus on only using OOP in game engines and gameplay classes. When there is a better solution out there, it should be taught, and I hope this helped you to see the light or help solve a difficult case of multi-inheritance messes.
 

No comments:

Post a Comment