Rare Enemies Complementing their Allies (2D Space Shooter)
Rare enemies spawn less frequent than their allies, common enemies, and have a much slower attack rhythm, but a deadlier damage.
- New Enemy Type (Sweeper)
- Aggressive Enemy Type (Rams the Player)
The new Enemy Type is the Sweeper; as it’s name implies, it sweeps the field with a very long laser. This was a very fun enemy to implement and I’m very happy with the way it forces the player to use the side-lanes to teleport to the other end of the screen. This enemy must be killed ASAP to avoid increased difficulty during a wave. It is a simple cube flattened, rotated, with a metallic texture. Added a very long laser as a child.
It’s rhythm is what I like the most about this enemy.
//Inside the sweeper script
public class Sweeper : MonoBehaviour
{
[SerializeField]
private GameObject _sweepingLaser;
[SerializeField]
private int _sweeperID;
[SerializeField]
private int _sweeperHealth;
[SerializeField]
private bool _isSweeping;
private Quaternion _originalRotation;
[SerializeField]
private Transform _posRight;
[SerializeField]
private Transform _posLeft;
[SerializeField]
private bool _canMove;
[SerializeField]
private bool _canGoDown;
private SpawnManager _spawnManagerScript;
[SerializeField]
private GameObject _explosionFire;
[SerializeField]
private AudioClip _explosionClip;
#region Start, Update
void Start()
{
_spawnManagerScript = GameObject.Find("SpawnManager").GetComponent<SpawnManager>();
if (_spawnManagerScript == null)
{
Debug.Log("SpawnManager is null");
}
this.transform.rotation = Quaternion.Euler(0, 0, 45);
_sweeperID = Random.Range(0, 2);
_sweeperHealth = 2;
_isSweeping = false;
_canMove = false;
_canGoDown = false;
_sweepingLaser.gameObject.SetActive(false);
_originalRotation = this.transform.rotation;
StartCoroutine(PositioningDelay());
}
void Update()
{
if (_canMove)
{
GetInPoisition();
}
if (_isSweeping && _sweeperID == 0)
{
SwipeLeft();
}
else if (_isSweeping && _sweeperID == 1)
{
SwipeRight();
}
if (_canGoDown)
{
MoveDown();
}
}
#region Movement Attack Cycle
void GetInPoisition()
{
if (!_isSweeping)
{
switch (_sweeperID)
{
case 0:
transform.position = Vector3.Lerp(this.transform.position, _posRight.transform.position, 1.5f * Time.deltaTime);
StartCoroutine(ChargeAttack());
break;
case 1:
transform.position = Vector3.Lerp(this.transform.position, _posLeft.transform.position, 1.5f * Time.deltaTime);
StartCoroutine(ChargeAttack());
break;
}
}
}
IEnumerator PositioningDelay()
{
yield return new WaitForSeconds(2f);
_canMove = true;
}
IEnumerator ChargeAttack()
{
yield return new WaitForSeconds(1.8f);
_sweepingLaser.gameObject.SetActive(true);
yield return new WaitForSeconds(2f);
_isSweeping = true;
_canMove = false;
StartCoroutine(WaitForSweep());
}
void SwipeLeft()
{
transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.Euler(0, 0, -45), (0.3f * Time.deltaTime));
}
void SwipeRight()
{
transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.Euler(0, 0, 135), (0.3f * Time.deltaTime));
}
#endregion
void MoveDown()
{
// transform.Translate(Vector3.down * 2 * Time.deltaTime);
transform.Translate(Vector3.down * (2 * Time.deltaTime), Space.World);
if (transform.position.y < -6)
{
_canGoDown = false;
_isSweeping = false;
_sweepingLaser.gameObject.SetActive(false);
_canMove = true;
this.transform.rotation = _originalRotation;
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Laser")
{
other.gameObject.SetActive(false);
SweeperDamaged();
}
if (other.tag == "Homing Missile")
{
_sweeperHealth = 1;
HomingMissile hmScript = other.GetComponent<HomingMissile>();
hmScript.OriginalPosition();
SweeperDamaged();
}
}
void SweeperDamaged()
{
_sweeperHealth -= 1;
if (_sweeperHealth < 1)
{
_spawnManagerScript.EnemyDestroyed(1);
Instantiate(_explosionFire, transform.position, Quaternion.identity);
AudioSource.PlayClipAtPoint(_explosionClip, transform.position);
Destroy(this.gameObject);
}
}
IEnumerator WaitForSweep()
{
yield return new WaitForSeconds(10);
_canGoDown = true;
}
#endregion
With a couple of boolean variables, a switch statement to define it’s attack position (left or right), this whole sweeping routine stays constant until the enemy is killed. Coroutines were very helpful in giving each moment it’s appropriate timing, leaving a code that is easier to adjust it’s speed for faster or slower sweeping, although I’m pretty content with it’s actual rhythm and damage. A separate script is attached to the long laser, checking for collision with the player, having a tag “enemy laser” to also destroy power ups. This enemy must be killed pretty quickly to avoid losing a possible shield or triple shot when most needed.
The second rare enemy is the Ram, or Enemy C. It is an aggressive enemy type that rams the player within certain proximity. Since the sweeper is an enemy that attacks from the outsides of the map to the center. I wanted the Ram to have a bulky and center-focused playing field. Rams do not shoot any type of lasers, but can hold up to 4 shots. They are a meat shield for their common enemies and can be deadly when they find the player.
void DamageRam()
{
if (_ramHealth >= 0)
{
_ramHealth--;
AudioSource.PlayClipAtPoint(_metalHit, transform.position);
if (_ramHealth == 3)
{
_ramRenderer.color = Color.blue;
}
if (_ramHealth == 2)
{
_ramRenderer.color = Color.yellow;
}
else if (_ramHealth == 1)
{
_ramRenderer.color = Color.white;
}
else if (_ramHealth == 0)
{
DestroyRam();
}
}
}
void DestroyRam()
{
_ramRenderer.color = Color.black;
Destroy(_thisCollider);
_animator.SetTrigger("OnEnemyDeath");
_speed = 0;
_enemyaudioSource.Play();
_spawnManagerScript.EnemyDestroyed(1);
Destroy(this.gameObject, 0.3f);
}
void LookingForPlayer()
{
if (_player != null)
{
_distanceToPlayer = Vector3.Distance(_player.transform.position, transform.position);
if (_distanceToPlayer < _sensorLength)
{
_playerFound = true;
}
} else
{
return;
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player" && _playerFound)
{
if (_player != null)
{
_player.Damage();
_player.Damage();
DestroyRam();
}
}
else if (other.tag == "Laser")
{
other.gameObject.SetActive(false);
DamageRam();
}
else if (other.tag == "Blaston")
{
DamageRam();
}
else if (other.tag == "Homing Missile")
{
HomingMissile hmScript = other.GetComponent<HomingMissile>();
hmScript.OriginalPosition();
DestroyRam();
}
}
Rams have a very similar movement to the common enemies. These methods make it be the Ram. It’s health is displayed through colors, as it can hold up to 4 laser shots. It is a great shield for its allies, and having allies shooting behind him can make a frightening scenario for the player to be under the Ram’s way.
The good thing is both of these enemies are great targets for the Homing Missile; it is a missile that looks for the sweeper and will follow it, but may explode if another enemy is on it’s way. Rams die to one homing missile and can alleviate a tense moment at the shooting range.