Dynamic Enemy Movements in Unity: Side-to-Side, Inheritance, and More…

Brian David
11 min readSep 13, 2024

--

One life remaining. Lasers seek their target but suddenly the Enemy changes position, evading attacks, blocking the Player’s flight path and forcing the Player to make a strategic decision resulting in a collision and GAME OVER. The Enemy’s movement has become more dynamic and unpredictable, creating more difficult circumstances and more dynamic gameplay.

enemy evasive maneuvers — created in collaboration with DALL.E3

As mentioned in the previous article, Unity Spawn Manager Tutorial: Mastering Enemy Spawning with Coroutines, one Enemy creates conflict but when that Enemy multiplies tension becomes even greater, placing demands on the hero, our Player, to use the game mechanics available to strategically overcome all obstacles and prove themself worthy of the title of hero. Why stop there? Let’s make the User prove their worth by taking it one step further by creating an enemy with multiple behaviors?

Side-To-Side Enemy movement

To accomplish this the behavior, i.e., game mechanics will embrace the concept of reusability. Reusability in the sense methods and variables will be reused by a sort of “child” class. If the “child” is reusing methods and variables of the original class then the original class must be some sort of “parent”. This represents the concept of inheritance. With that said, the Enemy or Enemies with behavior from the original Enemy class will inherit yet have their own unique behavior. To make sure that each Enemy’s own unique behavior is easy to maintain a Script dedicated to each one will encapsulate a singular responsibility. Easier said than done… Let’s begin.

Reinforcing Singularity

I will start with my original Enemy (Script). Currently the Update() method has two responsibilities, (1) the movement (i.e., CalculateMovement()) and (2) Instantiation of the Enemy Laser(s).

2 responsibilities but 1 method

To reinforce concept of singularity, I will dedicate the responsibility of getting the Lasers, instantiating and assigning the Enemy Lasers to a custom method, and:

  • declare a private void FireLasers() method,
  • copy the statements within the curly brackets, then paste it into the new method.
creating a custom method, single responsibility for FireLasers()
  • Back up in the Update() method, I will cache a reference of the FireLasers() method so that on each Update() the Enemy fire will continue at the rate set previously.
caching a reference of custom method in Update()

The last thing to do is to deal with the repetition of identical statements in the OnTriggerEnter2D() method.

creating a custom method, single responsibility for TriggerEnemyDeath()

This repetition makes it difficult to maintain if revisions are made in within the Enemy class or between other classes that communicate with the Enemy class. Don’t Repeat Yourself (DRY) is best-practice in the process of developing and maintaining code uses the the strategy that keeps it “DRY” and encapsulates responsibilities that repeat into a single custom method and then cache a reference to that custom method in the same place of the statements they replace.

caching a reference of custom method in the original method

After cleaning up the code, the Enemy class is ready to be edited using access modifiers that facilitate it as a base class.

Preparing the Enemy as a Base Class

Now, to help any developer who opens the Enemy (Script) understand that it is the base class for other Enemy movement behavior within a hierarchy, I copy the CalculateMovement() method then paste it before Start().

Assuming the role of the Enemy, I need to become the parent/base class so that other child/subclasses can inherit my properties/methods. To do this first I need to:

  • know what properties/methods the child will inherit. In this case, the Enemy subclasses will inherit the movement and modify it based on their own properties.
  • To only allow subclasses to inherit properties from a base class, while at the same time excluding other programs access to the base class, the keyword/access modifier protected needs placed in front of the method that will be inherited by the subclass.
Access modifier protected virtual
  • Now that the CalculateMovement() method is protected, notice another keyword, virtual, was added. The keyword virtual allows me, the base Enemy class, to create other methods that will inherit my properties and method but use those properties in a different way.

The Enemy subclasses are going to inherit the CalculateMovement() method and its properties but change how they are used so they can move side-to-side, in a circle, and diagonally. In order for the Enemy child/subclass to take the properties and change them, it will need to have the keywords “protected override” in front of the void CalculateMovement() method (see below).

Create the Subclass

  • Right-click on Scripts folder > Select Create from pop-up menu > Select C# Script.
create the C# script
  • I will name the script ‘SideToSideEnemy’.
  • I won’t need the Start() or Update() method. Delete them.
  • If the class is using Monobehaviour (i.e., public void class SideToSideEnemy: Monobehaviour) delete Monobehaviour and rename the class to the base class which this subclass inherits properties and method(s) from (i.e., Enemy).
renaming the subclass to inherit from the base class

Next, declare the method that will be inherited. In this case, it is CalculateMovement() and identify it it using the access modifiers protected virtual void.

Next, I need to develop the logic of my movement. Starting with the this.transform.Translate() , I need to move to the side. To accomplish that movement will be passed parameters Vector3.right (or left) * _speed * and Time.deltaTime.

Making an Inaccessible Error, Accessible to a subclass

When I add the statement, I notice that the variable _speed should be inherited but it has a red squiggly line beneath it, throwing an Error CS0122: member is inaccessible due to its protection level .

_speed variable is inaccessible tot he subclass because of private access modifier

In order to make the property of the base class accessible for inheritance, that specific variable must be declared using the keyword, “protected”. Currently in the Enemy base class it is declared as private.

access modifier “private” is not allowing inheritance in this specific case

After it is changed, there is no longer an Error.

access modifier changed to “protected”

Back in the SideToSideEnemy : Enemy subclass the variable is now accessible.

_speed variable from Enemy base class is now accessible

Finetuning Side-To-Side Movement of Enemy

Now that I can move side-to-side in real-time, I can check my position on the X-axis, but checking how far I moved left or right is not enough to reverse the lateral movement. I need to create a boundary for myself to:

  • compare my position on the X-axis to so that I know when to stop and
  • begin to move in the opposite direction.

I will declare my range of lateral movement at the top of the class. This creates a standard of measure for me to decide what to do next. I’ll name it _limitedLateralMovement and set it to 3.0f.

Next, in the CalculateMovement() method, after I have moved sideways I need to check my range. if I (this.transform) move (.Translate) greater than (>) the limits of my lateral movement (3.0f) to my right or (||) left, then I need to reverse my direction. Movement on an axis in the reverse direction is the opposite value. The opposite of the positive value is the negative value. To check the opposite value set in the variable _limitedLateralMovement a negative sign is placed in front of it (i.e., -_limitedLateralMovement ). This equates to -3.0f.

checking the distance of lateral movement

Reversing Direction: Rule of Multiplying Positive and Negative Integers

Once I check my position on the X-axis, only then can reverse my direction. To control my direction, another variable to define an opposing direction will be declared based on the rule in multiplication involving positive and negative integers.

  • If a positive integer (I’m on the right side) is multiplied by a negative integer, it equals a negative (so I move to the left); therefore it will move to the left.
  • If the negative (I’m on the left side) is multiplied by the negative it equals a positive (so I move to the right). With that said, I declare the variable _lateralDirection = 1.0f.
  • Now I need a negative integer to multiply by this positive integer by. Back down in the if statement, I add my _lateralDirection multiplied by -1;.
multiplying positive and negative integers to reverse direction

To complete the movement from side-to-side, the transform.Translate() method will also need to access the data stored in the variable _lateralDirection so I will multiply my _lateralDirection and the existing properties within the transform.Translate() method.

including the lateral direction into the transform.Trranslate method

I am moving laterally but to attack the player I need to inherit the downward movement. Below the if statement that controls lateral direction, I add, this.transform.Translate(_speed * Time.deltaTime * Vector3.down); .

Now, I have an Enemy that can evade laser fire by moving side-to-side but how to spawn it?

Spawning the New Enemy

After creating my base class and a subclass for the Enemy, I need to find a way to Instantiate the new Enemy. The SpawnManager() already handles that responsibility in the Coroutine SpawnEnemyRoutine() (see below).

First, what is my logic? I will need a variable to store the 3 types of Enemy movement that will be randomly instantiated. Then I can use a switch statement to get the different subclasses (i.e., Script(s)) of enemy movement.

Current spawn enemy routine with pseudocode

The different Enemy subclasses will be identified as integers and stored the a variable named randomEnemyType. The method Random.Range() method will then be passed a minimum value of 0 and a maximum value of 3.

Next a switch statement will be passed the parameter that uses the method Random.Range(), i.e., randomEnemyType. Now I can randomly choose between 3 different cases.

Random.Range selects and integer that is passed to the switch statement via randomEnemyType

Currently the variable named newEnemy is being used to Instantiate the _enemyPrefab.

newEnemy currently Instantiates the original _enemyPrefab

I need to add my SideToSideEnemy behavior to the newEnemy. Google search: “How do I add a component to an existing component? Unity”. Search results reveal the Scriptin API: GameObject.AddComponent().

Google returns Unity Scripting API

The AddComponent() method me to “Adds a component class of type componentType to the GameObject. C# Users can use a generic version”. The Declaration reveals the syntax that needs followed but how to actually implement it? In the generic script example, and within the Start() method the variable named gameObject uses the AddComponent method to retrieve a <SphereCollider>(); component.

Unity Documentation

In theory can follow the same format within the case definition.

  1. First, I will identify newEnemy then AddComponent, identify the <SideToSideEnemy>(); component to the new enemy GameObject.
  2. Next, I need to store the component in a variable. To do this I will type the name of my component, then give the component a name, sideToSideEnemy , and then set it to the newEnemy then have the newEnemy Get the Component<SideToSideEnemy(); // get the side to side enemy behavior
  3. After that, I will call a Setter Method for the _enemyLaserPrefab to be accessed. First, I call the variable identifying the component (i.e., Script), sideToSideEnemy, then get the setter method from the Enemy base class, SetLaserPrefab , then pass in the (_enemyLaserPrefab). Finally add break; to end the case.
adding the new script to the GameObject being instantiated in Spawn Manager

The Setter Method

Setter Method? What was that? Bear with me for a little longer.

After testing the first time I was running into a problem. Even though my enemies movement behavior was correct, the Enemy Laser was being instantiated but I kept receiving an error message from Console: “_enemyLaserPrefab is inaccessable due to its protection level”. After looking into it further, I understood that the _enemyLaserPrefab access modifier, private, within the Enemy base class was interfering with the inheritance of the _enemyLaserPrefab by the SpawnManager().

  • Private variables are only accessible within the that defines them. Subclasses and external classes can’t access them.
ERROR

To solve the bug, and make the _enemyLaserPrefab accessible I would add:

  • A private reference with the [SerializeField] attribute to the private GameObject _enemyLaserPrefab within the SpawnManager (Script) then drag and drop the prefab into the SpawnManager’s Inspector.
add a reference to drag and drop (assign) the Enemy Laser Prefab
assign the prefab
  • In the base class, Enemy (Script) create the public void method, SetLaserPrefab(), pass in the (GameObject laserPrefab) which represents the _enemyLaserPrefab. This is referred to as a Setter Method. A Setter Method is a public or protected method that allows other classes to set the value of a private or protected variable, making the variable accessible.
setter method in the Enemy base class

Thereafter, I returned to the switch statement in the SpawnManager (Script) and added the SetlaserPrefab() method (See above: AddComponent).

Finally, back into Unity editor. Run the game. When the new Enemy is Instantiated, the side to side movement completely interrupts my rhythm causing me to have to conserve ammo and reposition the Player before deciding whether or not I can take the shot. This threw off my timing leading to collisions with the Enemy and then an Enemy Laser resulting in GAME OVER.

Side-to-Side Enemy movement behavior

Upcoming Story

In the upcoming story, I will be adding two additional Enemy movement behaviors, a “Circling” movement behavior and an “Angled” movement behavior. Utilizing inheritance, custom methods, and the SpawnManager, will be used again, along with new methods to create a variety of enemy movement behaviors that make gameplay more dynamic and engaging.

Additional resources surrounding Access modifiers and Inheritance can be found below.

--

--

Brian David

an exploration of creativity and software development