Inheritance and polymorphism in object-oriented programming

Tutorial

·

foundational

·

+10XP

·

20 mins

·

(183)

Unity Technologies

Inheritance and polymorphism in object-oriented programming

In this tutorial, you’ll learn about inheritance and polymorphism, two closely related pillars of OOP.

  • Explain how inheritance is used to share functionality between a parent and child class
  • Define the relationship between a parent and child class, including what a child class can and cannot do with respect to its parent class.
  • Recognize opportunities where inheritance could be used to simplify code.
  • Describe how polymorphism is used to modify parent class functionality in a child class
  • Describe how polymorphism can be applied at compile time (method overloads) and run time (method overrides).
  • Recommend a high-level system architecture for a given project

Languages available:

1. Overview

The next two pillars of object-oriented programming, inheritance and polymorphism, are deeply intertwined. Inheritance, much like the name implies, focuses on parent-child relationships between different objects. Polymorphism is a result of inheritance, and refers to the process of a child class modifying what it inherits from a parent class. Used together, inheritance and polymorphism can reduce the amount of code you have to write in an application.


2. What is inheritance?


Inheritance is the process of creating a primary class (also known as a parent class) from which other classes (called child classes) can be created. A child class takes on, or inherits, all of the features of the parent class automatically. It’s common to have different classes share similar features in an application. For example, a video game may feature many different types of enemy classes, but they are likely to share the same core features, such as managing their own health and the ability to deal damage to the player. With inheritance, the need to write that health and damage functionality for each individual enemy class is eliminated, so that you can focus on writing functionality that’s unique to each class.



You’ve already been making use of inheritance with every script you’ve written in Unity so far. By default, whenever you create a new class, it inherits from MonoBehaviour:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

MonoBehaviour is the base class from which all core Unity scripting functionality inherits. Without MonoBehaviour, you wouldn’t be able to call OnTriggerEnter, GetComponent, or even use Start or Update!


In the diagram above, it might appear that all of the child enemy classes would lose their ability to access Unity functionality, because they exchanged their inheriting class from MonoBehaviour to Enemy. Fortunately, since the Enemy class inherits from Monobehaviour, the children of the Enemy class are also considered children of MonoBehaviour!


3. What is polymorphism?


Although inheriting core functionality from a parent class can be helpful, there are many situations where you don’t want the child class to perform exactly the same action as the parent class. Polymorphism allows you to change the functionality of what an object inherits from its parent class.


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

In the above example, the Enemy class has a DealDamage method that removes 10 points from the Player’s health whenever it’s called. The Thief class, a child of Enemy, can call this method without declaring it in the class.


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

This is fine if you want the Thief to deal exactly the same amount of damage as the Enemy class, but what if you wanted it to be a different value? These changes are accomplished through the process known as method overriding.


The method that you want to override in the parent class must first be marked for overriding. This is done by making it a virtual method:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Identifying a method as virtual indicates that it can, but doesn’t have to, be overridden. This is ideal for the current example, because while the Thief child class may need to modify the DealDamage method, another child class, such as the Scoundrel class, may not.


Once DealDamage is set to virtual, the Thief class can override it by creating its own method for DealDamage. Here, instead of virtual, we’ll use the override notation. You can now add new functionality into the method specifically for the Thief class:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

The Thief class now deals a smaller amount of damage than the parent Enemy class, and also calls one of the Thief-specific methods. Now, when DealDamage is called in Update by a Thief object, the customized DealDamage method will be called instead of the parent method.


4. Create a new unit type

In the project brief, you’ll see that one of the items to be built is a productivity unit. This unit should increase the productivity of any resource type that the user has selected in the scene. The user will select the resource the same way as the forklift: left-click to select the unit, and right-click to select the resource to move to. The productivity of a resource should only be increased while the productivity unit is actively working on it, and if the unit leaves the resource, it should return to a normal production rate.


Here’s what that might look like in your project:




The forklift is managed by the TransporterUnit script, which itself is a child of the Unit class. If you look at the Unit class, you’ll see that all of the functionality we need for movement is there, so it makes sense that our productivity unit should become another child class of Unit.


1. In the Prefabs folder, locate the ProductivityUnit Prefab and add it to the scene. This is the worker that will improve the quality of your resource piles!



2. Create a new MonoBehaviour script and name it ProductivityUnit.


3. Double-click to open it in Visual Studio. Delete the Start and Update methods.


4. To make the ProductivityUnit class inherit from Unit, remove MonoBehaviour from the class declaration and replace it with Unit.


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

An error will appear in your script that says: ‘ProductivityUnit’ does not implement inherited abstract member ‘Unit.BuildingInRange()’. Don’t worry – we’ll fix this right now.


5. If you look in Unit.cs, you will see the following code:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Unlike virtual methods for which overriding is optional, this method uses the abstract notation, which indicates that it must be overridden. Abstract methods are useful when you recognize that all child classes will need a certain type of functionality, but that functionality should be coded separately in each child class. In this case, BuildingInRange is meant to manage everything that happens when a unit is interacting with a resource pile (which is a child of the building class), but what occurs will change based on which child class calling the method.


So, back in ProductivityUnit.cs, all you need to do is override that method:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

6. With that error resolved, let’s explore the functionality we automatically gain by simply extending the Unit class. Save the ProductivityUnit script and return to Unity.


7. Add the ProductivityUnit script to the Prefab. Notice that because the class is a child of Unit, it’s automatically given a public float variable to control its speed.



8. Press Play. Left-click on your new worker and then right-click on one of the resource piles. Your worker will navigate to the resource pile automatically. If you right-click elsewhere in the scene, it will move there as well. All of this is accomplished without writing a single additional line of code in the ProductivityUnit class — all current functionality has been inherited from Unit.


5. Override the BuildingInRange method

The main feature of the productivity unit is to increase the production rate of the resource pile it's currently assigned to. Let’s build out that functionality.


1. To complete the BuildingInRange method, you’ll need a variable that will keep track of any resource pile that the user has selected. At the top of the class, create a ResourcePile variable named m_CurrentPile and set it to null. You’ll also need a float that will define by what amount the resource productivity should be increased:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

2. Return to the BuildingInRange method to code what happens when the productivity unit is in range of a resource pile. This code will run every frame. We want the production speed to increase during the frame when the productivity unit comes within range of a Building that is a resource pile. Then we want to prevent this code from running in the next frames, or else the production speed will keep increasing!


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

The notation “as ResourcePile” sets the pile variable to m_Target only if m_Target is a ResourcePile type. If m_Target is a Base, these types won’t match, and pile will be set to null. This is an efficient way of checking whether m_Target is a resource pile. If it is (pile != null), then m_CurrentPile is set to that resource pile, and its ProductionSpeed is doubled.


On the next frame, the if statement at the top of the method will prevent this code from running again, because m_CurrentPile will be set to a value (the resource pile).


One other interesting thing to note in this code is that you were able to access the m_Target variable, which is a “protected” variable in the parent Unit class. Protected variables are like private variables, but they can also be accessed by any child classes — you were only able to access it because ProductivityUnit.cs derives from Unit.cs.


3. Save the script and return to Unity.


4. Press Play and send your worker to a resource pile.


5. Select the resource pile before the productivity unit arrives, and note that the production rate doubles once it does.


6. Understand Overloads

To finalize the productivity unit, you need to make the resource pile’s production rate return to its previous value as soon as the unit leaves, as is described in the brief. In the Unit class, this is managed by the GoTo method — but if you look at the class, you’ll notice that there are not one, but two methods that have this name:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Methods can’t share names — right? In most cases this is true, except for this special circumstance, which is referred to as method overloading. Notice that the both GoTo methods have different parameter types, and have different functionality. Method overloading effectively makes a single method multi-purpose. When the user selects a target, this pair of overloads will handle the navigation depending on the type of object they selected.


The first GoTo method takes a Building class as a parameter, which is collected when the user right-clicks on a resource pile or on the base. This parameter is then passed to the SetTarget method in the Unit script.


The second GoTo method takes a Vector3 parameter, for situations when the user selects a random point in the warehouse rather than a resource pile.


You could write these as separate methods, but then you’d have to remember multiple calls! With method overloading, you only have to remember one call, and the data type(s) being passed will determine which code to run..


This is yet another feature you have seen in Unity’s built-in methods. Whenever you call a method from the Unity API and have many options for parameters to use, you’re taking advantage of method overloading. For example, transform.Translate has four separate overloads, a few of which you used during the first unit of Create with Code to get a car moving down a road:



[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

7. Override the GoTo methods

The productivity unit needs to check and see if it’s currently working on a resource pile and then, if it is, perform what occurs in the base GoTo method and return that resource pile’s production output back to its original value before moving away. Let’s override the GoTo methods to do just that.


1. In the ProductivityUnit script, create a new method called ResetProductivity. Both of the GoTo methods require the same functionality, so we’ll build one method to call from both.


2. Next, check to see if the m_CurrentPile variable is null. If it isn’t, then divide the m_currentPile.ProductionSpeed by the ProductivityMultiplier to return it to its original value, and then set it to null.


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

3. Create a new public override GoTo method with a Building as the target parameter:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

4. Call the ResetProductivity method you created and the base.GoTo method.


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

The base label tells the script to run the original method in addition to the new code in this override method.


5. We’ll repeat the same exact process for the other GoTo:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

These methods will run as soon as the user selects a new location for the productivity unit. Before moving, it will return the production speed of the current pile back to its original rate, if a productivity pile was currently selected.


6. Save script and return to Unity, and playtest again.


7. Send your worker to a resource pile, and select the pile before the worker reaches it.


8. Observe the increased production rate of the resource pile, and then send your worker to another pile.


9. Reselect the original pile, and note that its rate has returned to its reduced amount.


8. Summary

Inheritance and polymorphism help you create interrelationships between your classes that ultimately help to reduce the total amount of code you need to write. In this tutorial, you created a new class of your own that extended the functionality of the parent Unit class.


Complete this tutorial