Enemy Encounters: Part B: Enemy Evolution¶
Introduction¶
In this part of our tutorial series on creating enemy encounters in Unity, we’re going to elevate the complexity and dynamics of our game by introducing enemy evolution features. As players progress in the game, they will increasingly encounter enemies that not only follow them around, but also rotate to face them and shoot back. These enhancements will require a deeper dive into Unity’s physics engine, as well as a better understanding of concepts such as vector math, collision masks, tagging GameObjects, and handling bullet collisions.
The goal is to create an engaging gaming experience where enemy behavior evolves to present a challenging environment for the player. As the player navigates through the game, they will have to dodge enemy bullets and aim precisely to destroy the enemies. These enhancements not only add more depth to our game but also significantly improve the gameplay experience.
By the end of this article, you’ll have a clear understanding of how to implement these features into your Unity game, and you’ll be one step closer to creating an engaging and dynamic gaming experience for your players.
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.
-
Quaternion Basics: Unity internally uses Quaternions to represent all rotations. More information can be found in the Further Reading section.
Objectives¶
In this part of the tutorial series, we have several key objectives:
-
Implement Enemy Rotation: Our first goal is to make the enemies rotate to face the player as they move. This will enhance the perception of the enemies actively pursuing the player.
-
Set Up Collision Masks: Next, we will set up collision masks using Unity’s Layer system. This will enable our game to distinguish between player’s bullets and enemy’s bullets, and react differently to each.
-
Tag GameObjects: After setting up collision masks, we’ll tag our GameObjects to further differentiate between the types of GameObjects we have in our game.
-
Handle Bullet Collisions: Finally, we’ll implement bullet collision handling for both player and enemies. We’ll use Unity’s physics engine to detect when a bullet hits a player or an enemy, and respond accordingly by registering damage or destroying the enemy.
Steps¶
Step 1: Rotating the Enemies Toward the Player¶
In this step, we’ll modify the enemy’s script so that not only does it move toward the player, but it also rotates to face the player. This will involve some vector math and the use of the Quaternion
class in Unity. For more information about quaternions, see the Further Reading section
Step 2: Setting Up the Collision Mask¶
To make a distinction between the enemy’s bullets and the player’s bullets, we’ll set up a collision mask. This mask will help our game determine what kind of object a bullet has collided with.
Step 3: Add tags to GameObjects¶
Before we can use these collision masks, it good to know what kind of object we are interacting with. We will create tags to distinguish between different types of gameobjects.
Step 4: Handling Bullet Collisions¶
Once we have our collision mask set up, we can handle bullet collisions accordingly. If a player’s bullet hits an enemy, it can register damage and, if the damage is enough, destroy the enemy. Conversely, if an enemy’s bullet hits the player, it can also register damage and possibly end the game if the player’s health drops to zero.
Implementation¶
Step 1: Rotating the enemies towards the player¶
To create a more engaging and challenging game, it’s important that our enemies not only move towards the player, but also face the player as they approach. This will make their movements appear more natural and responsive, as if they are truly aware of the player’s position. In order to achieve this, we’ll need to dive into a bit of vector math and the use of Unity’s Quaternion class. First, we’ll need to find the direction vector from the enemy to the player. We can do this the same way we did in the previous article.
public void Rotate(Vector2 normalizedDirection)
{
/*
* Atan2 computes the angle (in radians) from the x-axis to a point. A radian is another way to represent angles
* from 0-360, radians represent angles from 0-2*PI.
* given by the coordinates (normalizedDirection.y, normalizedDirection.x)
* We convert the angle from radians to degrees using Mathf.Rad2Deg
* and we subtract 90 because in Unity, an angle of 0 degrees points to the right, so we have to rotate 90 degrees counterclockwise.
*/
float angle = Mathf.Atan2(normalizedDirection.y, normalizedDirection.x) * Mathf.Rad2Deg - 90f;
/*
* We then create a quaternion from the Euler angles. In this case, we're only rotating. All rotations in Unity are represented by quaternions.
* We rotate around the Z axis, because we work in 2D, but Unity is actually a 3D engine, so we need to specify which axis to rotate aorund.
*/
Quaternion rotation = Quaternion.Euler(new Vector3(0, 0, angle));
//set the transform rotation.
transform.rotation = rotation;
}
Step 2: Setting up the collision mask¶
Collision masks in Unity are part of the Layer system, which is a way to categorize your game objects. These layers can then be used in various ways, including collision detection. Here is how to create a new layer in Unity:
Step 2.1: Create New Layers¶
Click on the “Layers” dropdown in the top right corner of the Unity interface, then select “Edit Layers”. This will open the Tags and Layers inspector.
Click on an empty slot under “Layers” and type a new layer name. In our case, we’ll want to create two new layers, “EnemyBullet” and “PlayerBullet”.
Step 2.2: Assign Layers to Game Objects¶
To assign a layer by name, you would first need to ensure the layer exists in your project settings. Then you can use the LayerMask.NameToLayer function to get the index of the layer by its name, and assign it to your GameObject. Here’s an example:
void Start()
{
GameObject.layer = LayerMask.NameToLayer("YourLayerName");
//give your gameobjects a tag, so you can differentiate between different types of gameobjects.
GameObject.tag = "Player";
}
Step 2.3: Set Up Layer Collision Matrix¶
To create our collision mask, we need to instruct Unity on which layers should collide with each other. To do this, navigate to Edit > Project Settings > Physics 2D.
Here, you’ll find the Layer Collision Matrix. This is a grid that allows you to specify which layers should collide. For our game, we’ll want to make sure that enemy bullets only collide with the player, and player bullets only collide with enemies.
With these settings, Unity’s physics engine will automatically manage the collisions between these different objects based on their layers. Now, when we proceed to handle bullet collisions in the next step, we can check the layer of the colliding object to determine whether it was an enemy bullet or a player bullet.
Step 3: Add Tags to GameObjects¶
To effectively utilize our collision mask and distinguish between different GameObjects, we need to assign appropriate tags to our Player
, Enemy
, and Bullet
GameObjects. This process involves two key steps:
Step 3.1: Create the Tags¶
Before we can assign tags to GameObjects, we need to create the tags in the Unity Editor.
- Open Unity and load your project.
- In the top menu, go to
Edit
>Project Settings
>Tags and Layers
. - Under
Tags
, click on the+
button to add new tags. - Add tags named
Player
,Enemy
, andBullet
.
Step 3.2: Assign the Tags¶
With the tags created, we can now assign them to our GameObjects in code. This is done using the tag
property of the GameObject, which is a string that corresponds to the name of the tag.
In the Player
, Enemy
, and Bullet
classes, add this line of code after creating the GameObject:
GameObject.tag = "Player"; // for Player
GameObject.tag = "Enemy"; // for Enemy
GameObject.tag = "Bullet"; // for Bullet
### Step 4: Handling Bullet Collisions
In Unity, there are two types of collisions: regular collisions and trigger collisions. Regular collisions are used when you want to simulate real-world physics interactions, such as bouncing off a wall or sliding along a floor.
Trigger collisions, on the other hand, are used when you want to detect a collision but not have it affect the movement or physics of your objects. Instead, when a trigger collision occurs, Unity simply sends a message notifying you of the collision, which you can then handle in your code.
We want to make sure we are using trigger collisions, because we just want to either destroy the enemy or take some health off the player, but we don't want Unity to intervene in how the collisions are resolved.
First thing we need to do is to make sure that the Collider2D is set to triggermode in our Rectangle class:
```csharp
protected override void AddCollider(bool isTrigger = **true**)
{
BoxCollider2D boxCollider2D = GameObject.AddComponent<BoxCollider2D>();
boxCollider2D.isTrigger = isTrigger;
}
The same is true for the Triangle class.
Then, we add a new MonoBehaviour class called CollisionBehaviour
. This will have only one function:
Here’s an overview of the code:
// This is a MonoBehaviour class named CollisionBehaviour. It will be attached to GameObjects to handle their collisions.
public class CollisionBehaviour : MonoBehaviour
{
// This delegate type is used to define the type of the event. It includes a parameter for the other Collider2D that was collided with.
public delegate void CollisionHandler(Collider2D other);
// This event is triggered when a collision occurs.
public event CollisionHandler OnCollision;
//This function will be called in Unity when two objects have a collision and it will take into account the collision matrix we set up in step two.
private void OnTriggerEnter2D(Collider2D other)
{
// This line checks if there are any subscribers to the OnCollision event. If there are, it invokes the event and passes the other Collider2D as an argument.
OnCollision?.Invoke(other);
}
}
We then add this MonoBehaviour to our Enemy and Player class as we did with MonoBehaviours before. Lastly, we need some way for the CollisionBehaviour to communicate to Player or Enemy that a collision has occurred, so that Enemy or Player can do something with this information. We will be using the C# event system for this. For more information about event handling in C#, see the Further Reading section of this article.
public class Player : Rectangle
{
// This is the constructor for the Player class. It sets the GameObject's tag to "Player" and subscribes the HandleCollision method to the OnCollision event of a new CollisionBehaviour component.
public Player(string name, Vector2 startPosition) : base(name, startPosition)
{
//other code...
GameObject.tag = "Player";
GameObject.AddComponent<CollisionBehaviour>().OnCollision += HandleCollision;
}
// This method is called when a collision occurs with this player. It checks if the other GameObject has a tag of "Bullet". If it does, it subtracts health from the player.
private void HandleCollision(Collider2D other)
{
if (other.tag == "Bullet")
{
//retract health from the player.
}
}
}
public class Bullet : Triangle
{
// This is the constructor for the Bullet class. It sets the GameObject's tag to "Bullet" and subscribes the HandleCollision method to the OnCollision event of a new CollisionBehaviour component.
public Bullet(string name, Vector2 startPosition, Vector2 velocity) : base(name, startPosition)
{
//other code
GameObject.tag = "Bullet";
GameObject.AddComponent<CollisionBehaviour>().OnCollision += HandleCollision;
}
// This method is called when a collision occurs with this bullet. It checks if the other GameObject has a tag of "Player". If it does, it destroys itself.
private void HandleCollision(Collider2D other)
{
if (other.tag == "Player")
{
//destroy self...
GameObject.Destroy(GameObject);
}
}
}
Review¶
In this tutorial, we have dived deeper into creating more interactive and realistic enemy behaviors. We’ve learned how to make the enemies rotate to face the player using vector math and Unity’s Quaternion
class, how to set up collision masks using Unity’s Layer system, how to create and assign tags to our GameObjects
, and how to handle bullet collisions with the player and enemies using trigger collisions, MonoBehaviours, and C# events.
The enemy’s rotation gives a more dynamic feel to our game, making the enemies seem more aware of the player’s position and actions. The use of collision masks and tags allows us to efficiently handle collisions and distinguish between player’s bullets and enemy’s bullets, providing us with the ability to create different responses depending on who shot the bullet. Handling bullet collisions further enhances the interactivity of our game by responding appropriately when bullets hit their targets.
All these improvements combined significantly enhance the gameplay, making it more engaging and fun for the players.
Next Steps¶
In the next part of this tutorial series, we will continue to improve our game by adding more advanced enemy behaviors. We will implement a more complex movement pattern for the enemies and introduce different types of enemies, each with its unique behaviors and characteristics.
Stay tuned!