We may encounter the problem of managing multiple player types when developing a game. Various types of players, such as offline players, bots, and online players, exist in the game. Each player has their own way of handling the same job.
Approach A: Classic Method
For example, let us consider the task of choosing a card to throw in a card game. This will be handled by different player types:
- The offline player will get a card from user input.
- Bots will run some type of logic to find the right card
- Similarly, an online player may need to wait for a message from the server to know which card to throw.
Let us take an example game and implement the player system in it. This will be a turn-based game where the player needs to play a number in their turn.
Start by making a
Game class that will handle the player turn. When a player plays a number, this class will find the next player and start his turn.
We will make a
Game class as follows:
Next, we have to make a player class. This player class should be able to handle all kinds of players.
So, we create a player class without inheritance. We will handle different player types using
if() statements and perform the action accordingly. This method has issues that will be discussed below.
Problem in Approach A
The code is not scalable with this approach. Suppose we want to add a new type of bot player for tutorial levels. Using this approach, we will have to add a new
if() statement in start turn as:
//do tutorial bot logic and produce a number
Considering our player script has multiple methods that do player type checks, we will have to change all those places.
The next issue we will encounter using this approach is that all player types handle problems differently. For example, the human player waits for user input in the
Update() method to get numbers, bots may implement their logic inside the coroutine, the online player may wait for messages from the server in the callback function, and so on.
With this approach, we must implement all those codes in a single class and check player types everywhere in the code. The
if(playerType==“something”) code checks unnecessarily long, unreadable, and unscalable codes.
This problem can be solved using inheritance. We need to break the functionality of each type of player in the player class into individual child classes of a player class.
Approach B: Solution Using Inheritance
To implement inheritance in the player system, we need a base class.
Player class that will be inherited by the child class to make different types of players. This base class will have methods and variables that are common for all types of players. We can use the previous
Game class to implement this class.
EndTurn methods are called by the
Game class when the player's turn starts and ends. Notice that they both are virtual methods, so they can be overridden by the child class. This means each type of player can override these in their way to perform player type-specific action.
TakeAction(int) method is called by the child class when it gets a number to play. This method is a
protected type. This method can be called from class and its child class only. Also, our base class is of
abstract type. This means the object of this class cannot be made directly. It must first be inherited by the child class, and the object of that child class can then be made.
We finished setting up our base class. Now let's start making different types of players for our game.
We can similarly add other player types. As all the player-specific code remains in a separate class, implementing complex player types or other types of players doesn't complicate game logic.
As the base class for every player type is the same, every player type is referenced using
Player type, and the game uses base methods without needing to know the type of player. We can also simply drag and drop players into game class without taking care of player type.
The example above shows that our game system can handle multiple player types without including code in a single class.
This pattern is not only limited to the player system. It can be used for making systems such as multiple types of animation, player class and variants in-game, enemy types, and weapon systems.
Make a base class and derive child class from producing its multiple types. This same pattern can be used using an interface, but Unity doesn't allow drag and drop, so inheritance is preferable.
Thank you for reading this blog! Please leave a comment below