Skip to content

Enemy Encounters: Part A: Pursuit Blaster

Introduction

Welcome to the “Enemy Encounters” tutorial series! In this series, we’ll be creating a simple, yet engaging game where you, the player, are pursued by a relentless enemy. This enemy won’t just follow you around, but will also try to shoot at you to make your survival even more challenging!

Creating such an enemy might seem like a daunting task if you’re new to programming or to the Unity game engine. Fear not, as this series is designed to guide you through the process step by step, explaining each concept as we go.

By the end of this series, not only will you have a functioning enemy to use in your own projects, but you will also gain a solid understanding of key game development principles. You’ll learn how to create an AI that can follow the player around the map, how to enable it to shoot projectiles, and much more.

Remember, learning to code is like learning a new language - it can seem confusing at first, but with practice, it will start to make sense. Don’t worry if you don’t understand everything immediately. Take your time, experiment, and have fun!

Stay tuned for an exciting journey into the world of game development.

Prior C# Knowledge Required

Before diving into this tutorial series, there are a few fundamental concepts and skills in C# and Unity that you should be comfortable with. Here’s what you need to know:

  • Basic C# Syntax: You should have a basic understanding of the C# language syntax, including how to declare variables, how to write functions, and how to use control flow structures like loops and conditionals.

  • Classes and Objects: The concepts of classes and objects are at the heart of object-oriented programming and Unity. You should understand how to create a class, how to create an instance of a class (an object), and how to use class members (properties and methods).

  • Unity Basics: You should be familiar with the Unity interface, how to create and manipulate game objects in the Unity scene, and how to attach scripts to game objects.

  • Unity Components: You should understand how to use some of the basic Unity components, such as the Transform component for manipulating the position, rotation, and scale of game objects.

  • Vector Basics: Unity uses vectors to represent positions, directions, and even colors in a game. Understanding the basics of vectors and how to work with them in Unity is crucial for game development.

Read up on types, functions and classes here. For more information about Unity basics, Unity components and Vector basics, see the section Further Reading at the bottom of this article.

Objectives

In this first article of the “Enemy Encounters” series, we have a few key goals that we’re aiming to achieve. By the end of this article, you should be able to:

  • Create a simple enemy: You’ll learn how to create a basic enemy character in Unity. This enemy will serve as the primary antagonist for our player to interact with.

  • Make the enemy follow the player: We’ll write a script that makes our enemy follow the player around the game world. This will involve understanding how to calculate direction vectors and move an object towards a target.

  • Make the enemy shoot at the player: We’ll give our enemy the ability to shoot projectiles towards the player. This will involve instantiating new game objects, giving them velocity, and handling collisions.

  • Understand basic AI behaviors: Although our enemy’s behavior is quite simple, it forms the basis of AI (Artificial Intelligence) in games. By making our enemy follow and shoot at the player, we’ll be introducing the concept of state-based AI behavior.

  • Start a simple game loop: We’ll establish a basic game loop where the player can be “hit” by enemy fire, and we’ll set the stage for expanding this loop in future tutorials.

Remember, it’s okay if you don’t understand everything right away. Game development involves learning a lot of new concepts, and it’s normal to have to revisit certain topics or do some additional research. The goal is to keep learning and improving!

Steps

Let’s break down the process of creating our enemy encounter into manageable steps.

  1. Creating the Player and Enabling Movement: We will create a player character and give it the ability to move around the game world. This will be accomplished by attaching a script to our player character that listens for player input and moves the character accordingly.

  2. Creating the Enemy and Enabling Movement Towards a Location: We’ll set up an enemy character that can move towards a specific location in the game world. This will involve creating another script, which will be attached to our enemy character.

  3. Setting the Player as the Enemy’s Target: We’ll modify the enemy’s script so that it targets the player character and moves towards them. This will involve passing the player’s Transform to the enemy script.

  4. Enabling the Enemy to Shoot at the Player: We’ll give the enemy the ability to shoot at the player. This will involve creating a shooting mechanic and applying it to the enemy character.

Implementation

Step 1: Creating the Player and Enabling Movement

This is already working in the start project that you have received. The player should be able to move around using the arrow keys, so you get this step for free!

Step 2: Creating the Enemy and Enabling Movement Towards a Location

There are already three enemies spawned in the start project. However, they don’t really do much. As a challenge for the player, they aren’t very engaging. Let’s change that! The first thing we are going to do is add a new function called MoveTo. We will do this in a new script called FollowTransformBehaviour. It is tempting to put all the enemy behaviour in an EnemyBehaviour script, but chances are that we want to create an enemy later that follows the player, but doesn’t shoot at them or vice versa, so we will

public class FollowTransformBehaviour : MonoBehaviour
{
    Rigidbody2D rigidbody;

    // Start is called before the first frame update.
    //Make sure that the rigidbody can be used. Enemy should have a Rigidbody2D attached to its GameObject.
    void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
    }

    //this function accepts a Vector2 as a parameter, the position in the world where we want this enemy to move towards.
    public void MoveTo(Vector2 targetPosition)
    { 
        /**
          * First, we will want to know where this vector is in regards to the enemy. We can simply subtract the target position from the enemy position. 
          * This will cause an error, since we are trying to subrtact a Vector2 from a vector3, which isn't allowed.
          * By using the (Vector2) cast in front of the transform.position statement, we tell Unity that it needs to interprit this Vector3 as a Vector2. In this case, all it does is get rid of the Z axis.
          */
        Vector2 direction = targetPosition - (Vector2)GameObject.transform.position;

        /**
          * As you might imagine, this will lead to somewhat of a problem: If the targetPosition is far away, this new direction vector will be long, and the enemy will move fast, 
          * whereas, if targetPosition is close by, this vector will be quite short and thus the enemy will move slower.
          * To combat this problem, we can normalize the direction, meaning that it will keep the same rotation, but its length will be set to 1.
          */
          direction.Normalize();

        /**
          * Now, all we have to do is set the velocity of the rigidbody equal to this new direction vector, and we are done!
          */
          rigidbody.velocity = direction;
    }
}

//don't forget to add this new FollowTransformBehaviour to our Enemy in it's constructor. To see how you can do this, look at Player and how it adds PlayerBehaviour.

Step 3: Setting the Player as the Enemy’s Target

Now that we have the ability to let the enemy move to a specific location, let’s change it up a bit. We don’t want the enemy to move to any random location, we want them to follow the player. If the player moves, we want the enemy to follow. Luckily, Unity has a build-in type that we can use for this: the transform. If you don’t know what a transform is, I suggest you watch the video under Further reading first. To get the transform of the player, we first need to make sure it can be reached. We can do this by making the Player static. We will later change this, but for now, it does the job.

public class GenerateWorldBehaviour : MonoBehaviour
{
    //create a static reference for the player. This reference can be accessed from anywhere in the code.
    public static Player player;
    public void GenerateWorld()
    {
        player = new Player("Player", Vector2.zero);
        new Enemy("Enemy", new Vector2(0, 5));
    }
} 

public class Enemy : Triangle
{
    public Player player;
    public Enemy(string name, Vector2 startPosition) : base(name, startPosition)
    {
        //notice that we use the class GenerateWorldBehaviour instead of an instance of this class.
        //this is because player is static.
        player = GenerateWorldBehaviour.player;

        //add the EnemyBehaviour to the Enemy and make sure that it knows who to follow. See transform in Further Reading.
        FollowTransformBehaviour = GameObject.AddComponent<FollowTransformBehaviour>();

        followTransformBehaviour.followTransform = player.GameObject.transform;

        //do other initialization
    }
}

Now we just need to check where the player is at all times. Unity provides a lifecycle for all its MonoBehaviour objects. See Unity Order of Execution for event functions in Further Reading for a list of functions that come prebuild in Unity.

public class FollowTransformBehaviour : MonoBehaviour
{
    public Transform followTransform;

    //this is a Unity function. It will be called once per frame (so around 60 times a seconds).
    public void Update()
    {
        //just get the target position.
        Vector2 targetPosition = (Vector2)followTransform.position;
        MoveTo(targetPosition);
    }

    public void MoveTo(Vector2 targetPosition) 
    {
        //our code from earlier.
    }
}

That should do it. We now have an enemy that follows the player around!

Step 4: Enabling the Enemy to Shoot at the Player

The last step in this first article is to let the enemy shoot at the player. This works similar to the FollowTransformBehaviour, in the sense that we want to create a new behviour that needs a transform. This time however, we want the enemy to shoot every so many seconds. For this we will use a Coroutine. If you don’t know what a coroutine is, check out the section Coroutines under Further Reading.

public class ShootBulletAtTransform : MonoBehaviour
{
    public Transform followTransform;
    Rigidbody2D rigidbody;

    // Start is called before the first frame update
    void Start()
    {
        //get access to the rigidbody.
        rigidbody = GetComponent<Rigidbody2D>();


        StartCoroutine(ShootAtTransform());
    }

    /**
      * A coroutine is a function that allows you to spread a task over multiple frames or seconds.
      */
    IEnumerator ShootAtTransform()
    {
        //we want this function to keep repeating every 3 to 5 seconds, so we put it in an endless while loop.
        while (true)
        {
            //yield return waits for the specified amount of seconds, before continueing to the next section of the function.
            yield return new WaitForSeconds(Random.Range(3f, 5f));

            //get the target position, like we did in the FollowTransformBehaviour.
            Vector2 targetPosition = (Vector2)followTransform.position;
            Vector2 direction = targetPosition - (Vector2)transform.position;
            direction.Normalize();

            //This time, we create a new bullet and pass it the direction as a parameter.
            //Remember that Normalize() makes sure that the direction vector has length 1, while the angle stays the same.
            //to increase the speed, all we have to do is multiply the resulting normalized vector by our intended speed.

            Bullet bullet = new Bullet("Bullet", transform.position, direction * 5);
        }        
    }
}
How to make the bullet (I just created a triangle) and to make it move in the direction we specified I will leave up to you. Check out player and enemy for some basic assistance.

Troubleshooting

Review

We created a simple enemy that will follow the player whereever it goes and shoots at them.

Next Steps

Next time we will add rotation to the enemies so that they look a bit cooler and also start implementing collisions, so that you know when the player was hit.

Further Reading


Last update: May 16, 2023