Add an enemy
Tutorial
·
Beginner
·
+0XP
·
10 mins
·
(131)
Unity Technologies

Now your game is nearly finished! It has all the basic elements of a traditional roguelike except one: an enemy character whose purpose is to increase the game’s difficulty. Adding an enemy to your game will involve everything you’ve learned so far.
By the end of this tutorial, you’ll have done the following:
- Added an Enemy GameObject to the game.
- Coded the Enemy GameObject’s functionality so that it seeks out, moves toward, and attacks the player character on its turn.
1. Overview
Enemies are an integral part of most games, and this is especially true of roguelikes. In this tutorial, you’ll use everything you’ve learned so far to add a challenging enemy to your game. Once you’ve done this, there are some challenges that will allow you to make your enemy even better!
2. Create a basic enemy
The last thing you’ll want to add to your game are enemies. Adding enemies to your game involve everything you’ve learned so far:
- They will act on each turn of the game.
- They are essentially a CellObject that moves, so they act like walls in the sense that they stop the player character from entering the cell where they are, and receiving damage can destroy them when their hit points reach 0.
- They can damage the player character (reducing their food amount by 1) if the player character is adjacent to them when the turn happens.
- If the player character is not next to them when the turn happens, the enemies will move one cell closer to them.
By going back on all the previous tutorials, you should have all the knowledge you need to successfully add enemies to your game by yourself! Here is a list of the requirements your implementation needs to have:
- Your Enemy script inherits from CellObject.
- Enemies will need a notification when a turn happens. Check how you did this when reducing the food on every turn. Don’t forget to add an OnDestroy function to the enemies to remove the callback! Otherwise the callback will try to still notify the destroyed enemy that a turn happened.
Note: As shown in the Add a food resource tutorial, if += allows adding a function to a callback, -= will remove it, so simply calling OnTick -= YourMethod will stop YourMethod from being called every turn.
- When a turn happens, enemies check where the player character is. If they are next to them, they damage the player character (remove some food points), but if they are not next to the player character, they move one cell closer.
- When enemies move, because they are a CellObject, they need to remove themselves from the cell they move from and add themselves to the cell they move to.
As discussed at the very beginning of this project, when presented with a huge task like this, it’s good to break it into individual steps, allowing you to debug bit by bit instead of spending a long time coding before being able to test each functionality and then having a big complex problem that is difficult to debug all at once!
Below is a possible plan for how to create your enemy:
- Start with creating the GameObject with just the idle animation of the enemy. This will be the base of all your work.
- Create your Enemy script as a subclass of CellObject, but leave all its functions empty for now.
- Create a prefab from that GameObject and try to spawn that Enemy prefab into the BoardManager (remember they are CellObjects!)
- Now you can test that your enemy appears in the game when you play! You might already have a bug to fix as your enemy might not appear. Check your Hierarchy window and, if it’s in the Hierarchy window but not visible in the Scene view, try to remember what you had to make the player character and food sprites appear on top of the tilemap.
- Once you confirm your enemy appears in the game, it’s time to start coding it! Modify your Enemy script so that it stops the player character from entering its cell, and instead takes damage (you need to add health tracking to the Enemy script) and gets destroyed once its hit points reach 0. In short, make it exactly like a WallObject.
- Once this is confirmed to work, make the enemy move towards the player character each turn! You need to find which cell the enemy needs to move to get closer to the player. This is probably the hardest part. Try different codes to do this.
Tip: If can’t work out how to make the enemy move towards the player character, here’s a solution: check the distance between those two cells in X and Y, then move along the axis with the biggest distance (for example, if the player character’s distance is a 3 in X and -4 in Y, you need to move the enemy one cell down to get closer to the player). Don’t forget to test if the target cell is empty! You don’t want the enemy to get into a wall! You can handle food separately as a bonus, but a good first test is to also disallow the enemy from entering a cell that contains food.
- Now test to see if the enemy is trying to get closer to the player character. At this step, you might encounter a bug where the enemy gets under the player. Don’t forget to stop the enemy trying to get to the player character if it’s adjacent from it: the enemy should never try to enter the player character’s cell!
- Finally, you can add the functionality that, when it’s the enemy’s turn, it will attack the player character if it’s directly adjacent to it and remove a certain amount of food from the food store.
Here is a possible version of the final enemy script:
Enemy
public class Enemy : CellObject
{
public int Health = 3;
private int m_CurrentHealth;
private void Awake()
{
GameManager.Instance.TurnManager.OnTick += TurnHappened;
}
private void OnDestroy()
{
GameManager.Instance.TurnManager.OnTick -= TurnHappened;
}
public override void Init(Vector2Int coord)
{
base.Init(coord);
m_CurrentHealth = Health;
}
public override bool PlayerWantsToEnter()
{
m_CurrentHealth -= 1;
if (m_CurrentHealth <= 0)
{
Destroy(gameObject);
}
return false;
}
bool MoveTo(Vector2Int coord)
{
var board = GameManager.Instance.BoardManager;
var targetCell = board.GetCellData(coord);
if (targetCell == null
|| !targetCell.Passable
|| targetCell.ContainedObject != null)
{
return false;
}
//remove enemy from current cell
var currentCell = board.GetCellData(m_Cell);
currentCell.ContainedObject = null;
//add it to the next cell
targetCell.ContainedObject = this;
m_Cell = coord;
transform.position = board.CellToWorld(coord);
return true;
}
void TurnHappened()
{
//We added a public property that return the player current cell!
var playerCell = GameManager.Instance.PlayerController.Cell;
int xDist = playerCell.x - m_Cell.x;
int yDist = playerCell.y - m_Cell.y;
int absXDist = Mathf.Abs(xDist);
int absYDist = Mathf.Abs(yDist);
if ((xDist == 0 && absYDist == 1)
|| (yDist == 0 && absXDist == 1))
{
//we are adjacent to the player, attack!
GameManager.Instance.ChangeFood(3);
}
else
{
if (absXDist > absYDist)
{
if (!TryMoveInX(xDist))
{
//if our move was not successful (so no move and not attack)
//we try to move along Y
TryMoveInY(yDist);
}
}
else
{
if (!TryMoveInY(yDist))
{
TryMoveInX(xDist);
}
}
}
}
bool TryMoveInX(int xDist)
{
//try to get closer in x
//player to our right
if (xDist > 0)
{
return MoveTo(m_Cell + Vector2Int.right);
}
//player to our left
return MoveTo(m_Cell + Vector2Int.left);
}
bool TryMoveInY(int yDist)
{
//try to get closer in y
//player on top
if (yDist > 0)
{
return MoveTo(m_Cell + Vector2Int.up);
}
//player below
return MoveTo(m_Cell + Vector2Int.down);
}
}
Note: This script has a slightly more complex movement code! In our case, if we fail moving in one direction, we try to move in the other direction too, to handle when a wall or object would keep the enemy stuck in place.
3. Challenge
Your enemy is still very simple. Below are some ideas to improve it and make it more dynamic. Your challenge is to figure out how to implement this functionality:
- Make the Enemy GameObject play an attack animation when it attacks the player character and even add a damage animation to the player character
Note: You’ll probably have to experiment with transitions to handle playing the attack animation on the enemy and the player character getting damaged. Maybe a transition from the attack to the damage to interrupt one with the other could help; play around to find the best way for you!
- Make the enemy move smoothly across cells just like you did for the player character. The enemy will need to move at the same speed as the player character to be sure both movements last the same amount of time. This might be an avenue for refactoring: create an ObjectMover system that gets a list of objects to move with their target and will stop any input from the user until all of its objects’ movements are finished.
4. Next steps
Your game is now a proper game! In the next tutorial, we’ll give you a number of ideas that will make your game even better if you decide to keep working on it.