Skip to content
Nishanth-blag

Practical OOP - Simple Polymorphism to avoid duplicate code

Programming, C#, Unity3D, OOP, game-dev, polymorphism2 min read

While working on a 2D Platformer game I was structuring and coming up with ideas to code NPC AI. To create the AI I chose the animator StateMachineBehavior (SMB) methodology. In this way I can have a State Machine based on NPC animator states thus giving me good control. NPCs in the game, in most of the cases have similar states say Idle, Walk, Attack, Hurt, Die as behaviour. Keeping this in mind I created a MonoBehavior Script which acts as the state for each of the NPC's SMB scripts. So this will be the base script which has state as well as methods which can be used by SMB scripts.

SimpleNPCBase MonoBehavior Script - link

In order for the SMB scripts to have access to the reference of this script instance w.r.t the gameobject Unity provides a way. Communication between MonoBehaviours and StateMachineBehaviours.

Checkout the scripts in example here

1public class UndeadGreenFSMBase : DefaultBaseFSM
2{
3 [HideInInspector]
4 public UndeadNPCScript MonoScriptRef;
5}
6
7public class UndeadIdleState: UndeadGreenFSMBase
8{
9 // make use of the MonoScriptRef
10}
11
12public class UndeadNPCScript : SimpleNPCBase
13{
14 void start()
15 {
16 //.....
17
18 anim.GetBehaviour<UndeadIdleState>().MonoScriptRef = this;
19
20 //.....
21 }
22}

This sets the variable MonoScriptRef of UndeadGreenFSMBase SMB script. Note here that the type of MonoScripRef is UndeadNPCScript which inherits from SimpleNPCBase. So for every NPC I need to have a <Name\>FSMBase script which has a member Type say <Name\>NPCScript. Due to different <Name\>FSMBase script the SMB scripts cannot be reused. Then these cannot be reused and attached to the animator state as a SMB Script as MonoScriptRef Type changes from NPC to NPC. IdleState, WalkState are same for every NPC which falls under category simple and can be seen that it doesnt change at all in logic making redundant code usage all over the project.

NPC class diagram before

From the figure, Each MonoBehavior inheriting from SimpleNPCBase script in the leaf node will be attached to its NPC GameObject and the SMB scripts in the leaf node inherting from DefaultBaseFSM will be attached to the states of the animtor of the specific NPC GameObject which has animator component attached.

So we have to find a way to have Idle, Walk, etc as base scripts which can be applied to any Simple NPC. The issue which is stopping us from resusing those scripts is the reference to MonoBehavior script i.e MonoScriptRef variable in our case whose type changes from NPC to NPC i.e UndeadNPCScript, BanditNPCScript etc.

Fix and remove redundant script files

To resolve this issue I made use of polymorphism concept. Since all the Simple NPC MonoBehavior script inherit from SimpleNPCBase the DefaultBaseFSM will now have a variable MonoScriptRef of type SimpleNPCBase. This way from every script which inherits from SimpleNPCBase can set the reference it self in the SMB scripts using the following statements.

And also create default SMB scripts like Idle, Walk, Attack, Hurt.

All these can be clearly seen by comparing the above and below class diagram.

Note: The variable type of MonoScriptRef after change and before change. Before change we had to make it same as that of the derived ones. i.e UndeadNPCScript for below script.

1public class DefaultBaseFSM
2{
3 [HideInInspector]
4 public SimpleNPCBase MonoScriptRef;
5}
6
7
8public class UndeadNPCScript : SimpleNPCBase
9{
10 void Start()
11 {
12 // other init statemnts
13 // ...
14
15 // initialize the behavior scripts
16 anim.GetBehaviour<IdleSMB>().MonoScriptRef = this;
17 anim.GetBehaviour<WalkSMB>().MonoScriptRef = this;
18 anim.GetBehaviour<AttackSMB>().MonoScriptRef = this;
19 anim.GetBehaviour<HurtSMB>().MonoScriptRef = this;
20
21 }
22}

NPC Class diag after

Before vs After

Before:

For each NPC MonoBehavior script had to create a <Name\>BaseFSM with MonoScriptRef variable whose type is same as that of the MonoBehaviour script. Eg: For UndeadNPCScript its relevant SMB UndeadGreenFSMBase needs a MonoScriptRef variable of type UndeadNPCScript. Had to duplicate and create SMB script files with same logic for each of the states Idle, Walk, Attack, Hurt.

After:

Avoid duplicate SMB scripts and have default ones like IdleSMB, WalkSMB, AttackSMB, HurtSMB. From the diagram it can be seen that at first level there are default SMB scripts which can be used by any Simple NPC and in second level NPC which has additional states like AttackIdle can have a its own SMB script which can be easily added in the current setup. MonoBehavior script reference now has a type SimpleNPCBase which will be automatically casted when the reference is set using the following statement.

1anim.GetBehaviour<IdleSMB>().MonoScriptRef = this;

Also checkout the file structure before vs after.