Enemy Inheritance

In this tutorial, you will create several types of enemies using inheritance, a fundamental concept of Object Oriented Programming (OOP).


If you want to start from a base project rather than work inside of your existing project, you can use this project: Platformer Base Project



Hazards & Enemies

Create a basic enemy that will later be used to create more advanced enemies.



Continue from the platformer game you have been creating in the previous two lessons. Obstacles (in the form of moving platforms) have already been added to provide some core game mechanics. Hazards and enemies are also commonly used for enhancing game mechanics.

A hazard is an element of the game that damages the player. It typically is stationary (like a spike or a pool of lava) but they may move as well (such as falling rocks).

An enemy also causes damage to the player, but the attack is more intentional, even if only by way of contextual clues. For instance, a falling rock would be considered a hazard where a snake falling from the sky might be considered an enemy. Enemies typically have some degree of intelligence that makes them target the player.

Create an empty GameObject named Enemies that will contain all enemies in the scene. Reset the position of Enemies to 0, 0, 0.



The first enemy you will create could be considered a simple hazard. It will not even move. However, it will serve as a template for enemies that are more sophisticated in the future.

Add a Sphere to the right side of the camera view with the Z value set to 0 and name it Enemy Check the Is Trigger of the Sphere Collider so it behaves as a trigger instead of colliding physically with other objects in the scene.



Create a script in the Scripts folder named "EnemyBehavior.cs", open the script to edit, and remove the comments.

Add a target property that will store the GameObject that the enemy will target. Use the protected access modifier instead of public or private so classes that inherit from EnemyBehavior will have access to the property. The protected access modifier will be explained in more detail later in the lesson.

The next three properties are only needed to create a delay between the time the enemy collides with the player (and both dying) and the time the game stops. This is a useful pause to prevent the game from ending abruptly and for any post-death animations you may add later. For instance, you may want to add a dying animation to the character that needs a moment to play.

Add a private dead property that will store a bool value indicating whether the enemy is dead or not, with an initial value of false (not dead).

Add a private timeSinceDeath property of type float with an initial value of 0 which will store the amount of time passed since the enemy died.

Add a public delayUponDeath property of type float with a default value of 0.8f which will store the amount of time that should pass from when the enemy dies and when it is removed from the scene. With a value of 0.8f, the enemy will remain on the screen for 0.8 seconds before disappearing.

public class EnemyBehavior : MonoBehaviour 
{

  protected GameObject target;
  private bool dead = false;
  private float timeSinceDeath = 0;
  public float delayUponDeath = 0.8f;
            
Modify the Start method to:

protected virtual void Start () 
{
  target = GameObject.FindWithTag("Player");
}
            
Adding the protected access modifier will make this available to classes that inherit from the EnemyBehavior class. The virtual keyword indicates that classes inheriting from this class will be able to override the method. Both protected and virtual keywords will be explained later in this lesson.

The code inside the Start method simply sets the value of the target property to the GameObject which has a tag of Player (i.e. the ThirdPersonController).

Modify the Update method to:

protected virtual void Update()
{
  if(dead)
  {
    timeSinceDeath += Time.deltaTime;
    if(timeSinceDeath > delayUponDeath)
    {
      Time.timeScale = 0.0f;
      Destroy(this.gameObject);
    }
  }
}
            
Again, add the protected and virtual keywords in front of the function definition. Due to the conditional statement, the code in this script only executes after the enemy dies.

After the enemy dies, the value of timeSinceDeath begins to increase. When the timeSinceDeath reaches the value of delayUponDeath, the enemy is destroyed. Using this method, the enemy is allowed to remain on the screen for a specified amount of time after dying.

At the moment the enemy is removed, the game will be paused due to the Time.timeScale being set to 0.

Under the Update method (before the closing curly brace for the class), add the following OnTriggerEnter function.

protected virtual void OnTriggerEnter(Collider other)
{
    if (target != null && other.gameObject == target)
    {
        target.SetActive(false);
        dead = true;
    }
}
            
This function simply detects if the enemy collides with its target. When this happens, the target is set to be inactive so it will disappear from the game immediately and the dead value is set to true so the enemy will also disappear (due to code in the Update method) after the specified delay.

You may wonder why the first part of the conditional statement is needed. When would the target ever be null? This could happen if no GameObjects in the scene were tagged as Player, so the Start method wouldn't be able to find the player.

Add the EnemyBehavior script to the Enemy object. Then, play the game and move the player into the Enemy. The player should disappear (die) immediately upon collision. After a short delay (0.8 seconds) the enemy will disappear as well and the game moving platforms will stop.



Drag the Enemy from the Hierarchy into the Prefabs folder to create an Enemy prefab. You can use the Enemy prefab anytime you want to add a stationary hazards in the game. For now, remove all enemies from the scene.



Falling Enemies

Use the Enemy as a base class for other enemy classes.



Create a new script named FallingEnemyBehavior and modify the code to the following:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FallingEnemyBehavior : EnemyBehavior 
{

}
            
Notice that after the class name FallingEnemyBehavior the EnemyBehavior class name is used in place of the default MonoBehaviour. What does this mean?

The : separating the class names can be read as extends. In this case, FallingEnemyBehavior extends EnemyBehavior. This means a FallingEnemyBehavior is an EnemyBehavior in that it has all the same properties and methods that are defined in EnemyBehavior.

This programming technique is called inheritance. The sub class (FallingEnemyBehavior) inherits the properties and methods of the base class (EnemyBehavior).

Create a Sphere named FallingEnemy as a child of Enemies. Move the FallingEnemy in the scene to be near the player, maintaining a Z value of 0. Attach the script and play the game. Running into the FallingEnemy should have the same result as running into the Enemy.



Of course, it would be silly to extend a class if it were going to behave the same as the base class. The idea is that you can add additional properties and methods to add additional functionality beyond what the base class already provides.

You can also override methods from the base class when the base behavior needs to be different. Add code to override the Start and Update methods.

public class FallingEnemyBehavior : EnemyBehavior
{
    protected void Start()
    {

    }

    protected void Update()
    {

    }
}
            
Play the game again and notice that colliding with the FallingEnemy no longer causes the base behaviors define in the EnemyBehavior class.



Of course, it would be uncommon to extend a class and then override all of the methods. However, in this case that is exactly what is needed. However, you still want the code from the base methods, but you just want to add more to it.

One option is to copy the code from the base class into the subclass. However, this would cause code redundancy which would then lead to maintaining two sets of the same code. Avoiding code redundancy is one of the primary reason to use inheritance.

The object-oriented solution to this is to call the base class method from the subclass method.

protected void Start()
{
    base.Start();
}

protected void Update()
{
    base.Update();
}
            
If you were to play the game now, the FallingEnemy would behave exactly the same as the Enemy. Of course, there is no reason to override a base class method if all you are going to do is call that method.

It does make sense to do this if you are going to be adding additional code to the method to provide additional behaviors to the subclass that the base class does not have. You will do this in a moment.

First, add a Rigigbody component to the FallingEnemy so that it falls when the game is played. To prevent the FallingEnemy from falling through platforms, add another SphereCollider component to it. Move the FallingEnemy upward to the top of the camera view. Play the game. Run into the sphere after it lands on a platform.



Now, the FallingEnemy has one collider which is used as a trigger and one which is used to apply physics collisions with other objects.

If you play the game and run under the FallingEnemy before it lands you will see that the new collider you added is causing a physics collision with the player. Sometimes you want an object to collide with some objects in the game, but not all objects.

How could you make the FallingEnemy enemy collide with the platforms but not the player?

Add the following code to the Start method of the FallingEnemyBehavior script.

protected void Start()
{
    base.Start();

    // do not allow phyics collisions with the target
    Component[] colliders = GetComponents(typeof(Collider));
    foreach (Collider enemyCollider in colliders)
    {
      if(!enemyCollider.isTrigger)
      {
        Physics.IgnoreCollision(enemyCollider, target.GetComponent<Collider>());
      }
    }
}
            
Play the game to ensure their are no physics collisions between the player and the FallingEnemy. Notice that OnTriggerEnter code to destroy the objects still runs.



The code here has a few elements:

Line 12 gets all the colliders on the enemy, since there are 2 colliders, the trigger and the non-trigger collider

Line 15 says that the only collider we want to check is the one which is NOT a trigger

Line 16 says that if the player's collider hits the collider on the enemy that prevents it from going through the platform, it will pretend that the player's collider does not exist

By doing this, we can ensure that the player doesn't push the enemy away before they are destroyed.


Like the Fireball from BouncyBox, a FallingEnemy should be destroyed when it falls out of the game. While this functionality is not needed in the EnemyBehavior for enemies that are not falling, it is needed for the FallingEnemy.

Add the following code to the Update method of the FallingEnemyBehavior script.

protected void Update()
{
    base.Update();

    if (transform.position.y < -20)
    {
        Destroy(this.gameObject);
        Debug.Log("Destroy " + this.name);
    }
}
            
While the base.Update() ensures that the code in the Update method of the base class is called, the following conditional statement destroys the FallingEnemy when it's position on the y axis falls below -20 units.

How do you know if this code works if the FallingEnemy is already out of view before it is destroyed? One method is to rely on the Debug.Log command which should execute immediately after the Destroy method executes. Another method is to watch the FallingEnemy in the hierarchy as the game runs.

Move the FallingEnemy along the x axis until there is no platform under it. Play the game again to check that it is being destroyed after it falls out of view.



Using Debug.Log is a great way to check whether or not a block of code is being executed and/or to check the value of variables during runtime. However, it is a good idea to delete or comment out these statements when you are not using them so the output in the console is restricted to only the output you are wanting to see.

Use forward slashes to comment out the Debug.Log once you know the code is running as intented.

if (transform.position.y < -20)
{
    Destroy(this.gameObject);
    //Debug.Log("Destroy " + this.name);
}
            
Create a prefab of the FallingEnemy and then remove it from the Scene.



You now have an enemy that can fall. Later in this lesson, you will create enemies that can drop falling enemies. First, you need an enemy that can fly.

Birds

Extend the Enemy class to create a bird that can fly.



You could use another Sphere or some other primitive GameObject to represent a Bird. However, too many primitives representing different objects could be confusing during gameplay testing. Additionally, a Bird will have a direction and a Sphere or Cube would not clearly show which direction the Bird is facing.

Of course, game programmers also do not want to spend much time creating a 3D model of a bird. They could pull something from the Asset store to use temporarily, but that would unnescarily add files to the project.

One solution is to use a few primitives to quickly create an object that game testers can quickly identify.

Create an empty game object named Bird to be a child of Enemies. If needed, reset the position to 0, 0, 0. Then, move the Bird upward on the y axis so it is near the top of the camera view.



Add a sphere named Head as a child of the Bird. Change the scale of the head as desired. Add a capsule named Body as a child of Bird and scale and position it with the head so the bird appears to be facing left.



Create a new script named BirdBehavior in the Scripts folder and attach it to the Bird.



Instead of writing code from scratch, it would be nice to extend a class that already has some of the behaviors the Bird will need.

Modify the code to extend the EnemyBehavior class.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BirdBehavior : EnemyBehavior
{

}
            
Add properties that will be used to move the Bird back and forth between a min and max position in the same manner as movingPlatforms. Override the Start method so that it calls the Start method of the base class (which sets the value of target to the player) and then initialize the value of min and max.

public class BirdBehavior : EnemyBehavior
{
  public float minFromStart = -10f; // 0 or negative
  public float maxFromStart = 10f;  // 0 or positive
  public float speed = 6f;      // negative for left, positive for right

  private float min, max;

  protected void Start () 
  {
    base.Start();

    min = transform.position.x + minFromStart;
    max = transform.position.x + maxFromStart;
  }
}
            
Save the code and return to Unity. With the Bird selected, notice in the Inspector Window that the script shows the public property inherited from the EnemyBehavior class (Delay Upon Death) and the new public properties that were defined in the BirdBehavior class.

Back in the code, override the Update method so that it calls the Update method of the base class (which destroys the Enemy after the specified amount of time). Add the code that moves the Bird in the same manner as movingPlatforms.

protected void Update()
{
    base.Update();

    float x = transform.position.x;
    if (x > max || x < min) speed *= -1;

    transform.position += Vector3.right * speed * Time.deltaTime;
}
            
Position the bird where it will be visible while playing without colliding with the player. Then play the game to see the Bird move back and forth in the same manner as a horizontally moving platform.



Notice that the bird is not always facing in the direction it is moving.

One method of changing the direction the Bird is facing is by inverting the scale on the X axis. Try this by changing the value to negative.



This method works well for 2D objects that only have one side. A better option for 3D objects (especially if one side of the object is slightly different than the other) is to spin the object around to face the other direction. Reset the scale value and then change the y value of the rotation to 180 degrees.



This change will actually be handled in code at the appropriate times. Change the y value of the rotation back to zero and modify the code in the Start method to rotate the bird to face right if the initial speed is positive, causing the Bird to move to the right.

protected void Start()
{
    base.Start();

    min = transform.position.x + minFromStart;
    max = transform.position.x + maxFromStart;
    if(speed > 0) transform.Rotate(0, 180, 0, Space.Self);
}
            
The Rotate method of the Transform class does X with X parameters.

Modify the conditional statement of the Update method to rotate the Bird an additional 180 degrees every time the Bird reverses direction.

protected void Update()
{
    base.Update();

    float x = transform.position.x;
    if (x > max || x < min)
    {
        speed *= -1;
        transform.Rotate(0, 180, 0, Space.Self);
    }

    transform.position += Vector3.right * speed * Time.deltaTime;
}
            
Play the game again and you should see that the bird always moves in the same direction it is facing. You may also notice that when the player runs into with the Bird, an unwanted collision occurs.



Why is the Bird is not behaving as an Enemy?

While the BirdBehavior class inherited the properties and methods of the EnemyBehavior class, the colliders are not set up properly on the Bird.

Expand the Bird in the Hierarchy so the Head and Body are visible. Select the Head and remove the SphereCollider component that is attached to it. Likewise, select the Body and remove the CapsuleCollideer that is attached.



Now the Bird will not collide with the player. However, the player and Bird also do not die when they collide. Recall that the EnemyBehavior uses an OnTriggerEnter, and because a BirdBehavior IS an EnemyBehavior, the BirdBehavior inherits that method.

Select the Bird in the Hierarchy (the Empty, not the Head or Body) and add a CapsuleCollider. Check on the Is Trigger property. Change the Direction property by selecting X-Axis from the dropdown. Click on the EditCollider button, which will make handles appear on the Collider in the Scene Window. Click and drag the handles to resize the collider as desired.



If you miss the handle when you click, you will no longer be editing the Collider. You can return to editing the collider by selecting the Bird and clicking on the EditCollider button again.

Play the game again to ensure the player and the Bird die upon collision.



Create a prefab of the Bird so you may add more to the scene later. Leave the Bird in the scene to be used in a moment to create an even more dangerous enemy.

Drop Birds

Extend the Bird class to create a bird that drops falling enemies.



You have made an enemy that flies and an enemy that falls. Now you can leverage those two classes to create a new enemy that flies while dropping falling enemies.

Unpack the Bird and rename it DropBird. If needed, raise the DropBird to be near the top of the camera view, just visible when the player is standing on the initial platform. After all, the player will need some time to dodge the falling enemies that the DropBird drops.



Create a material named dropBirdMat in the Materials folder and apply it to the DropBird to make it visibly distinguishable from other birds.



Create a DropBirdBehavior script in the Scripts folder and attach it to the DropBird. Also, remove the BirdBehavior script from the DropBird.



Edit the DropBirdBehavior class to extend the BirdBehavior class. This will allow the DropBird to move around as a Bird and destroy the player as an Enemy.

Add dropFrequency as a public property of type float with an initial value of 3f. This value represents the number of seconds between each drop.

Add elapsedTime as a private property of type float with an initial value of 0. This value represents the amount of time that has passed since the last drop was made.

Add dropObject as a public property of type GameObject. This property will serve as a reference to the prefab of the GameObject that the DropBird will drop.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DropBirdBehavior : BirdBehavior
{
  public float dropFrequency = 3f;
  private float elapsedTime = 0;
  public GameObject dropObject;
}
            
There is no reason to override the Start method because the properties added already have values or will be initialized in the editor.

You will need to add some additonal code in the Update method, but be sure to call the Update method of the base class first.

public float dropFrequency = 3f;
private float elapsedTime = 0;
public GameObject dropObject;

protected void Update()
{
    base.Update();

    elapsedTime += Time.deltaTime;
    if (elapsedTime > dropFrequency)
    {
        print("drop a " + dropObject.name);
        elapsedTime = 0;
    }
}
            


This is an example of a Timer. Once the elapsedTime has reached 3 seconds, then the timer resets, and currently the code will print a message.

One issue when making games is dealing with time. You can measure time in games in multiple ways.

How many frames have passed?
How many real-time seconds have passed since the start of the game?
How many fractions of a second have passed between frames?

By using Time.deltaTime, you ensure that if the game runs at 30 frames per second or 60 frames per second, the timer will trigger after 3 real-time seconds.

There are a variety of other ways to measure time. To learn more, see the Unity Time Reference Documentation


Save the code, return to Unity, and select DropBird. In the Inspector Window, notice there are properties that were defined in EnemyBehavior (Delay Upon Death), BirdBehavior (Min From Start, Max From Start, and Speed), and DropBirdBehavior (Drop Frequency, Drop Object).

The Drop Object property has not been set. Drag the FallingEnemy prefab from the Prefabs folder to set the value of Drop Object.



With the Console Window open, play the game to make sure that a message with the name of FallingEnemy is being printed to the console every 3 seconds.



Spawning a FallingEnemy will only take a few lines of code which could be placed within the conditional statement. However, it may be useful to create a drop method that will contain this code along with any other code you may want to add later for when a drop occurs, such as playing a sound effect for example.

protected void Update () 
{
  base.Update();

  elapsedTime += Time.deltaTime;
  if(elapsedTime > dropFrequency)
  {
    drop();
    elapsedTime = 0;
  }
}

private void drop()
{
  print("drop a " + dropObject.name);
}
            
Notice the print statement was moved into the newly created drop method. A call to the drop method is made in the conditional statement.

Also notice the access modifier for the drop method is private. This ensures that only methods within this class are allowed to call the method.

Run the game to test out the function and ensure the messages are still being printed to the console.

With the function set up and working, comment out the print statement and add the code to spawn a FallingEnemy.

private void drop()
{
    //print("drop a " + dropObject.name);
    GameObject droppedObj = Instantiate(dropObject, transform.parent);
    droppedObj.transform.position = transform.position;
}
            
Play the game now and you should see that the DropBird drops a FallingEnemy every few seconds. You may notice the FallingEnemy does not rest easily on moving platforms. This could be dealt with in code, but for now just set the range of motion for any DropBirds so that they do not fly over moving platforms.



It would be nice if the object dropped retained its horizontal velocity as it would in the real world. If the DropBird is flying left when an object is dropped, that object should also be moving to the left at the same speed.

Add code to apply a force along the X axis to the Rigidbody component of the FallingEnemy.

private void drop()
{
    //print("drop a " + dropObject.name);
    GameObject droppedObj = Instantiate(dropObject, transform.parent);
    droppedObj.transform.position = transform.position;

    Rigidbody rb = droppedObj.GetComponent<Rigidbody>();
    rb.AddRelativeForce(speed * 40f, 0, 0);
}
            
The three parameters of the AddRelativeForce method represent the amount of force to be added along the x, y, and z axis. No force is needed for the y axis because gravity will force the object to fall. No force is needed for the z axis because you want the position along the z axis to remain at 0 for all objects in the game.

The direction of the force on the x axis (left or right) is determined by the speed being positive or negative.

The amount of force along the x axis is determined by mulitplying the speed of the DropBird by 40.
The value of 40 was determined through trial and error and has not been tested with different speeds. You may want to create a public property for dropThrust which can be modified in the editor for DropBirds moving at different speeds. The property would serve as a feature as well allowing birds to launch objects forward instead of just dropping them.

Play the game again to make sure the FallingEnemies move in the same direction the DropBird is facing at the moment of the drop.



Create a prefab of the DropBird so you may add more to the scene later. Leave the DropBird in the scene to be used in a moment to create another dangerous enemy.

Dive Birds

Extend the Bird class to create a bird that will dive towards the player.



Unpack the DropBird, rename it DiveBird, and remove the DropBirdBehavior script.



Create a material named diveBirdMat in the Materials folder. Apply it to the DiveBird replacing the dropBirdMat by dragging it into the hierarchy and dropping it on the Head and Body.



The movement of the DiveBird will rely on physics provided with the engine instead of the previous method of adding a Vector3 to change the position.

Add a Rigidbody component to the DiveBird to enable physics. Uncheck Use Gravity so the DiveBird will not fall from the sky.

Create a DiveBirdBehavior script in the Scripts folder and attach it to the DiveBird.



The desired behavior for the DiveBird is to move horizontally across the screen from right to left. The DiveBird will never reverse direction. However, when the target gets within range, the DiveBird will dive down at an increased speed towards the player.

Edit the DiveBirdBehavior class to extend the EnemyBehavior class. Do not extend the BirdBehavior class because the movement behavior will be significantly different.

Add rb as a private property of type Rigidbody that will be used to reference the Rigidbody component.

Add speed as a public property of type float with a default value of 4f. This value represents the number of units the DiveBird will move each second.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiveBirdBehavior : EnemyBehavior
{
    private Rigidbody rb;
    public float speed = 4f;
}
            
Override the Start method. Call the Start method of the base class and then add code to move the DiveBird across the screen.

override protected void Start()
{
    base.Start();
    rb = this.gameObject.GetComponent<Rigidbody>();
    rb.velocity = Vector3.left * speed;
}
            
The rb property is initialized first to be a reference to the Rigidbody component of the DiveBird. Then, the velocity property of the Rigidbody is set in the left direction with the speed that is specified in the editor. Note, Vector3.left is equivalent to a Vector3 of {1, 0, 0}.

There is no need to multiply the velocity by Time.deltaTime because this code is in the Start method and therefore only executes once. After the velocity is set, the game engine takes care of moving it the appropriate distance each frame.

Play the game to ensure the DiveBird flies across the screen.



The next step will be to get the enemy to detect when the player is within range. You might consider this the range in which the DiveBird is able to see the player.

In Unity, add a sphere collider to the DiveBird. Set the collider to behave as a trigger and increase the size significantly.



It is ok to have two colliders behaving as triggers on the same GameObject. However, it will be important that the OnTriggerEnter can distiguish between which trigger causing the method to execute. In this case, when the player collides with the outer trigger, the DiveBird should dive. When the player collides with the smaller capsule collider, the player and DiveBird should be destroyed and the game should end.

Return to the script and add two additional properties. The diveSpeed will be used as the speed of the bird after it begins diving. The boolean value for diving will be used to determine if the DiveBird is currently diving. Set the default value of diveSpeed to be twice as fast as the normal speed and set the initial value of diving to false.

private Rigidbody rb;
public float speed = 4f;
public float diveSpeed = 8f;
private bool diving = false;              
            
Override the OnTriggerEnter function using the following code.

protected void OnTriggerEnter(Collider other)
{
    if (target != null && other.gameObject == target)
    {
        if (diving)
        {
            // destroy target
            base.OnTriggerEnter(other);
        }
        else
        {
            // dive
            transform.LookAt(target.transform);
            rb.velocity = transform.forward * diveSpeed;
            diving = true;
        }
    }
}
            
Recall that the Player was set as the target in the Start method of the base class. The outer conditional statement is checking that the target has been set (is not null) and is the GameObject that caused the collision with the trigger.

If either condition fails, nothing happens. If the condition is met, another conditional statement is used to determine which action to take; dive at or destroy the target.

The first time the player causes this method to execute it is reasonable to assume that the Sphere Collider was the trigger. After all, the player could not get to the Capsule Collider without first entering the Sphere Collider.

Because the value of diving is initially false, this causes the else block to be executed. The code in the else block causes the DiveBird to begin diving and sets the value of diving to true.

The next time the player causes the method to execute it is reasonable to assume that the Capsule Collider was the trigger. As long as the player's speed is not greater than the diveSpeed of the DiveBird, the player would not have the ability to enter the DiveBird's Sphere Collider more than once.

Because the value of diving is now true, the condition is met and the OnTriggerEnter of the base class is called which, as you now know well, destroys the Enemy and the player.

Save the code, return to Unity, and play the game to ensure the DiveBird behaves as desired.



Override the Update method to include code that will destroy the DiveBird after the player dodges it. This can be done when it's position on the y value falls below the camera view.

While the methods of a class may be placed in any order, you may want to place the Update method after the Start method (above the OnTriggerEnter method) to be consistent with your other scripts.

override protected void Update()
{
    base.Update();

    if (transform.position.y < -20)
    {
        Destroy(this.gameObject);
    }
}
            
Play the game and dodge the DiveBird when it dives at you. You should then soon see the DiveBird disappear in the Hierarchy.

Remember to create a prefab of the DiveBird so you may add more to the scene later.

Wrap Up

Time to reflect.



This was a long lesson that demonstrated the Object Oriented Programming concept of Inheritance. If Object Oriented programming is new to you, it may be helpful to go over this lesson again to wrap your head around the concept of inheritance. While it is powerful, it takes some time to get the hang of.

Consider how you might modify the enemy behaviors to cause damage to the player's health instead of immediately destroying them. Because you took an Object Oriented approach when creating the enemies, you would not need to modify each enemy separately. Instead, you could simply modify the EnemyBehavior class in which all other enemies are derived from.

You could add a damageAmount property that would be applied to all enemies. In the OnTriggerEnter, you would add code to reduce the health of the player (so you would need to know how to have the script access properties of another script attached to the player).