Implementing New Features: New Enemy Type

Gabriel Asay
5 min readApr 21, 2024

--

In this article, I will cover how I’ve added a new enemy type to my game. As opposed to just having rovers I will now have drones in the game attacking the player as well.

To start I’m going to drag the drone sprite into the hierarchy and add the necessary components: Rigidbody2D, Box Collider 2D, Enemy Script, Animator, and Audio Source. I’ve copied all of the components from the existing enemy prefab.

Next, I’m going to edit the enemy script. I’m doing quite a bit here so I’m just going to comment on all of the code I’ve written here.

public class Enemy : MonoBehaviour
{
[SerializeField] private float _minSpeed = 3f;
[SerializeField] private float _maxSpeed = 8f;
//Added another GameObject to reference the drone bullet prefab
[SerializeField] private GameObject _roverBulletPrefab, _droneBulletPrefab;
[SerializeField] private GameObject _explosionPrefab;
[SerializeField] float _minTimeInterval, _maxTimeInterval;
//Determine which enemy this script is attached to
[SerializeField] private int _enemyId;
//Declared variable that represents the rotation speed
[SerializeField] private float rotationSpeed = 0.75f;

private float _switchDirectionTimeInterval;
private float _switchDirectionTime = 0f;
private bool _isDirectionLeft = false;
//Declared boolean to represent which way the drone is rotating
private bool rotateClockwise = true;

private float _speed;

private Player _player;
private Animator _animator;
private AudioSource _audioSource;
private SpawnManager _spawnManager;

private bool _isEnemyAlive = true;


// Start is called before the first frame update
void Start()
{
StartCoroutine(ShootRoutine());

_switchDirectionTimeInterval = Random.Range(_minTimeInterval, _maxTimeInterval);
_speed = Random.Range(_minSpeed, _maxSpeed);
_player = GameObject.Find("Player").GetComponent<Player>();
_audioSource = GetComponent<AudioSource>();
_animator = GetComponent<Animator>();
_spawnManager = GameObject.Find("Spawn Manager").GetComponent<SpawnManager>();

if (_player == null )
{
Debug.LogError("Player is null");
}


if (_animator == null )
{
Debug.LogError("Animator is null");
}

if (_audioSource == null )
{
Debug.LogError("Enemy audio is null");
}
}

// Update is called once per frame
void Update()
{
CalculateMovement();
}

private void CalculateMovement()
{
//Created a switch case to change between enemy movement types depending on the enemy id
switch (_enemyId)
{
case 0:
RoverMovement();
break;
case 1:
DroneMovement();
break;
}

if (_isEnemyAlive)
{
transform.Translate(Vector3.down * _speed * Time.deltaTime);

if (transform.position.y < -6)
{
float randomX = Random.Range(-8.5f, 8.5f);
transform.position = new Vector3(randomX, 7f, 0);
}
}
}
//Moved existing enemy movement into a method named RoverMovement()
private void RoverMovement()
{
if (_isEnemyAlive && Time.time > _switchDirectionTime)
{
if (_isDirectionLeft)
{
_isDirectionLeft = false;
}
else
{
_isDirectionLeft = true;
}
_switchDirectionTimeInterval = Random.Range(_minTimeInterval, _maxTimeInterval);
_switchDirectionTime = Time.time + _switchDirectionTimeInterval;
}

if (_isDirectionLeft)
{
transform.Translate(Vector3.left * _speed / 2.5f * Time.deltaTime);
}
else
{
transform.Translate(Vector3.right * _speed / 2.5f * Time.deltaTime);
}
}

//Created new method for DroneMovement
private void DroneMovement()
{
//Rotate clockwise and switch directions after the rotation on the z axis is greater than or equal to 0.4
if (rotateClockwise)
{
transform.Rotate(0, 0, rotationSpeed);
if (transform.rotation.z >= 0.4)
{
rotateClockwise = false;
}
}
else
{
transform.Rotate(0, 0, -rotationSpeed);
if (transform.rotation.z <= -0.4)
{
rotateClockwise = true;
}
}
}


private void OnTriggerEnter2D(Collider2D other)
{
switch (other.tag)
{
case "Player":
_player.Damage();
DeathSequence();
break;
case "Bullet":
_player.AddScore(10);
Destroy(other.gameObject);
DeathSequence();
break;
case "Explosive Bullet":
_player.AddScore(10);
Instantiate(_explosionPrefab, other.transform.position, Quaternion.identity);
DeathSequence();
break;
case "Explosion":
_player.AddScore(10);
DeathSequence();
break;
}

}

private void DeathSequence()
{
_animator.SetTrigger("OnEnemyDeath");
_isEnemyAlive = false;
_audioSource.Play();
_spawnManager.OnEnemyDeath();
Destroy(GetComponent<Collider2D>());
Destroy(this.gameObject, 1f);

}

IEnumerator ShootRoutine()
{
while (_isEnemyAlive)
{
//Change amount of time in between bullets depending on enemy
float secondsInBetween = 0;
if (_enemyId == 0)
{
secondsInBetween = Random.Range(1f, 3f);
}
else if (_enemyId == 1)
{
secondsInBetween = Random.Range(0.5f, 1f);
}

yield return new WaitForSeconds(secondsInBetween);
// 0 = Rover, 1 = Drone; I'm switching prefab based on enemyId
GameObject newBullet = Instantiate(_enemyId == 0 ? _roverBulletPrefab : _droneBulletPrefab, new Vector3(transform.position.x, transform.position.y-1f, transform.position.z), transform.rotation);
newBullet.GetComponent<Laser>().SetEnemyBullet(_speed * 2f);
}
}
}

Now, I’ll create the new bullet prefab by dragging in the new sprite, resizing it, and copying all the components from the existing enemy bullet.

I’ll also ensure I’ve dragged the bullet prefabs into their appropriate slots within the inspector for the existing enemy and our new “Drone”.

Finally, let’s go to the spawn manager script and ensure our new enemy type is spawning as well.

public class SpawnManager : MonoBehaviour
{
[SerializeField] private GameObject[] _enemies;
[SerializeField] private GameObject _enemyContainer;
[SerializeField] private float _timeBetweenEnemySpawns = 3.0f;

private bool _stopSpawning = false;
private int _enemiesAlive = 3;
private int _enemiesToSpawn = 3;

// Start is called before the first frame update
public void StartSpawning()
{
StartCoroutine(SpawnEnemyRoutine());
StartCoroutine(SpawnPowerupRoutine());
}

IEnumerator SpawnEnemyRoutine()
{
yield return new WaitForSeconds(3f);

while (!_stopSpawning && _enemiesToSpawn > 0)
{
//Declaring a random index (0 or 1) to determine which enemy to spawn
int randomEnemy = Random.Range(0, _enemies.Length);
_enemiesToSpawn--;
//Spawn enemy with the index matching "randomEnemy"
GameObject newEnemy = Instantiate(_enemies[randomEnemy], new Vector3(Random.Range(-8.4f, 8.4f), 8f, 0f), Quaternion.identity);
newEnemy.transform.parent = _enemyContainer.transform;
yield return new WaitForSeconds(_timeBetweenEnemySpawns);
}
}

Now I just need to make sure I’ve dragged the enemy prefabs into the inspector like so:

Finally, I’ll play the game and we can see how this turned out!

Perfect! The drone is shooting out bullets and rotating just as I expected! That is it for today if you enjoyed seeing how I’ve implemented the new enemy then stay tuned for more articles and happy coding!

--

--