New Enemy Type
Objective: Create and implement a new and different type of enemy.
So we are going to create a new enemy. First things first, we need a new sprite for our enemy. I found online a for a ship that I liked.
https://free-game-assets.itch.io/free-enemy-spaceship-2d-sprites-pixel-art
Its a simple model, but I want a new simple enemy. Its movement will be zigzag, and it will be rotated, so it looks like its going down. This enemy won’t shoot, but it will be faster and harder to deal with.
In our folder, you can see that we also have 4 sprites for exhaust, which shows the turbo behind our enemy when he moves. Lets also add that, and animate it.
As you can see, our turbo is an object of its own, which is a child of our enemy. This means that when our enemy moves, the turbo is properly moving in the right offset with it.
Now that we added all other components, such as rigidbody, collider, audio source and our EnemyBehaviour script, we can start to work on our enemy.
private enum EnemyType
{
Default,
Zigzag
}
[SerializeField] GameObject _explosionPrefab;
[SerializeField] private EnemyType _enemyType;
Our new enemy doesn’t have the exploding animation similar to our previous one, so we added the Explosion prefab, in order to use it when our enemy is destroyed.
We also created an enum for our enemy types, allowing us to select it in the inspector. This allows us to have a different movement pattern for our enemies, such as other features.
private void Update()
{
CalculateMovement();
if(Time.time >= _canFire && _enemyType != EnemyType.Zigzag)
{
FireLaser();
}
}
We added our enemy type on our if statement, making sure that our Zigzag enemies cannot fire, only the ones that aren’t it.
private void CalculateMovement()
{
switch (_type)
{
case EnemyType.Default:
if (isMovingSideways)
transform.Translate(_enemySpeed / 2f * _direction * Time.deltaTime * Vector3.right);
transform.Translate(_enemySpeed * Time.deltaTime * Vector3.down);
break;
case EnemyType.Zigzag:
transform.Translate(_enemySpeed * _direction * Time.deltaTime * Vector3.right);
transform.Translate(_enemySpeed * Time.deltaTime * Vector3.down);
break;
default:
break;
}
CheckForBounce();
TeleportNewPosition();
}
Inside our CalculateMovement method, we’ve updated to use a switch statement, meaning that we use the same method for multiple enemies, and inside each case, we can determine the movement. Our zigzag enemy will move sideways and down, always. Different than other enemies, he will move sideways at the speed that he has.
Now we have to create the coroutine which is going to handle how our zigzag enemy will change directions every couple of seconds. This also means that we have to rotate our enemy.
private IEnumerator ZigZagMovementRoutine()
{
while (true)
{
_direction = -_direction;
if (transform.rotation.y == 0)
{
Vector3 rotation = new(0f, 180f, -90f);
transform.Rotate(rotation);
}
else
{
Vector3 rotation = new(0f, 0f, -45f);
transform.Rotate(rotation);
}
yield return new WaitForSeconds(Random.Range(0.5f, 2f));
}
}
While our enemy stays alive, we will run this Coroutine, which will change our direction. Meaning if we were going to the right, we now go to the left and vice-versa. Then we alter the current rotation of our object.
So since our enemy will always move downwards to where he is looking, we have to make sure to always fix his rotation accordingly.
If he is going right, we go inside our if statement, and make sure to have the rotation for it. Otherwise, we update the rotation to the left.
And at the end, we just randomize an amount of time, in this case from 0.75s — 1.5s. And restart the process, so the enemy zigzags every few seconds.
private void CheckForBounce()
{
if (transform.position.x >= _maxXPosition || transform.position.x <= _minXPosition)
{
if(_type == EnemyType.Default)
_direction *= -1;
else
{
_direction *= -1;
if (transform.rotation.y == 0)
{
Vector3 rotation = new(0f, 180f, -90f);
_spriteTransform.Rotate(rotation);
}
else
{
Vector3 rotation = new(0f, 0f, -45f);
_spriteTransform.Rotate(rotation);
}
}
}
}
We have to make sure to adjust our bounce of the walls method, making sure to adjust our direction as well as our rotation.
private void Start()
{
InitializeEnemyComponents();
if (_type == EnemyType.Zigzag)
_ = StartCoroutine(ZigZagMovementRoutine());
else
_ = StartCoroutine(MoveSidewaysRoutine());
}
Lets not forget to initialize our Coroutine for movement, checking the enemy type before starting it.
Now we have to update our SpawnManager, to make sure we can spawn a random enemy, instead a specific as it was doing it.
[SerializeField] private GameObject[] _enemiesPrefab;
private void SpawnEnemy()
{
float randomPositionX = Random.Range(_minXPosition, _maxXPosition);
_enemySpawnPosition.Set(randomPositionX, _spawnPositionY, 0f);
int randomEnemy = Random.Range(0, _enemiesPrefab.Length);
var enemy = Instantiate(_enemiesPrefab[randomEnemy], _enemySpawnPosition, Quaternion.identity, _enemyContainer.transform);
_activeEnemies.Add(enemy);
var enemyBehaviour = enemy.GetComponent<EnemyBehaviour>();
if (enemyBehaviour != null)
enemyBehaviour.IncreaseSpeed(_enemySpeedMultiplier);
}
Now we just add both enemies into our new enemiesPrefab array and inside our SpawnEnemy method, we just randomize one of enemies to instantiate.
Now lets check our new enemy in action!
Why is our enemy behaving all weird ? Why did he go out of bounds randomly ?
Because we are rotating the enemy itself and ordering to move, when we have it on a different angle, it will move in a different pattern then the one we intended.
Instead of always going down and sideways (as intended), since its rotated, it follows a different trajectory as we can see.
But how do we fix this ? How can we make things easier on ourselves, to not account for the rotation ? The solution is simpler than it looks.
By adding a blank GameObject as a parent, now if we rotate the sprite of our enemy, which is now a child object, this won’t affect our pathing.
We can just create a blank game object that will hold all information and act as our enemy, and inside we just place the visuals, such as our sprite, and turbo. Our main object will not have its rotation altered, therefore this would resolve our issue regarding the rotation of our object.
private Transform _spriteTransform;
private void InitializeEnemyComponents()
{
if (_type == EnemyType.Default)
{
_animator = GetComponent<Animator>();
if (_animator == null)
Debug.LogError("Animator is NULL on " + gameObject.name);
_onEnemyDeathHash = Animator.StringToHash("OnEnemyDeath");
}
else
{
_spriteTransform = transform.GetChild(0);
}
}
Now when we call our method to initialize our enemy components, we just check what kind of enemy it is, if its our Zigzag enemy, we set our sprite object to our transform variable.
private IEnumerator ZigZagMovementRoutine()
{
while (true)
{
_direction = -_direction;
if (transform.rotation.y == 0)
{
Vector3 rotation = new(0f, 180f, -90f);
_spriteTransform.Rotate(rotation);
}
else
{
Vector3 rotation = new(0f, 0f, -45f);
_spriteTransform.Rotate(rotation);
}
yield return new WaitForSeconds(Random.Range(0.75f, 1.5f));
}
}
Now inside our coroutine, when perform the rotation, instead of rotating our enemy, we just rotate the child, which is the sprite itself.
Now we have our new enemy working properly, ready to destroy our player!