Many Enemies, Few Scripts

Goombas from Mario series

Oftentimes in game development, you’ll reuse an enemy from before but repurpose it to fit the current need. For instance, this new enemy could essentially be a “palette swap” of another enemy or just a duplicate from another part of the game. However, this poses an issue: should you reuse the same script, although the logic is different, or create a whole new script, even though the appearance and logic is similar?

This is when creating a modular script comes in: not only does it solve both issues, you can modify it to fit most, if not all, logic needs required for the enemy to work properly.

Before Starting

Possibly the biggest things you should think of when creating a modular script are:

  • Figuring out the scope of the script (does it only control how the enemy walk or does it control essentially everything the enemy do)
  • Figuring out which enemies will be using this script (will all the enemies use this script or only a few)

These two points are important because it ensures you don’t cram as much logic as possible into one script, thus making it harder to debug or modify it in the future, while allowing you to create a few logic scripts as possible by combining similar enemies together to form one script.

Getting Started

A simple stage with the enemies are in the red, orange, and yellow.

In this example, I will be creating a script that allows enemies to walk to the waypoints created on the map. Even though the enemies are different (red is a capsule, orange is a cylinder, and yellow is a sphere), in the end, they all do the same thing: traveling to the next waypoint defined in the Inspector.

3 enemies with 8 waypoints in the scene

Under the Hood

Now, it’s time to create the script. Because the script calls for the enemies to walk to waypoints, there must be a way to accept all these waypoints, whether it’s 1 or 100. Fortunately for us, C# has a class called Lists that can hold an a large amount of data and, unlike arrays, can be defined at runtime.

Other variables include

  • Booleans to determine if the enemy has reached the waypoint or not
  • Transforms to know which waypoint the enemy is heading to
  • Integers to determine if the count should reset
  • NavMeshAgent to allow the enemy to walk

Although the minimum in the Start method should be null checking to make sure components are being accessed (in this case, the NavMeshAgent on the enemy), I’ve implemented the start of the movement by assigning the enemy the first waypoint in the List.

Now, it’s time to create the actual logic. Even though I could’ve implemented everything in the Update method, I’ve separated it into its own method in case I, or other developers, want to implement more logic to the enemy.

Here, the EnemyWalk method checks to see how far the enemy is from the waypoint and if it’s close enough, it’ll assign the next waypoint. However, depending on which way the enemy is going, if the enemy reaches the final waypoint, it will do the following

  • if reversing, the count will reset back to the total amount of data in the List
  • if going forward, the count will reset back to zero

Assigning Waypoints

Finally, it’s time to assign the waypoints to each enemies in the scene. Two of them will go forward while will go in reverse. Furthermore, two of them will only have 4 waypoints while the other one will have 8

The Final Product