2D Games Core Programming: Enemy Pickup Behavior

Manny Cabacungan
4 min readOct 31, 2023

--

Objective: If a pickup is in front of an enemy, the enemy will fire its weapon at the pickup to destroy it before the player can get it.

I have decided to give this new functionality to the standard enemy type, not to the Laserbeam enemy and not to the Smart enemy (rear-firing).

Currently, the powerups spawn in the main game scene as the parent of the powerup. But the enemies spawn with the game object “Enemy_Container” as their parent game object. The Enemy_Container is a child of the Spawn_Manager.

I created a child game object “Powerup_Container” inside of the Spawn_Manager, to serve as the parent for all the instantiated powerups.

In the SpawnManager.cs script, in the variable declarations in the beginning of the script, I made a new variable for the Powerup_Container.

public GameObject powerupContainer;

In the Start() method I added a null check for the powerupContainer

void Start()
{
_gameManager = GameObject.Find("Game_Manager").GetComponent<GameManager>();
if (_gameManager == null)
{
Debug.LogError("SpawnManager::Start(). Game Manager is NULL");
}

if (_enemyContainer == null)
{
Debug.LogError("SpawnManager::Start(). Enemy Container is NULL");
}
if (powerupContainer == null) // NEW
{
Debug.LogError("SpawnManager::Start(). Powerup Container is NULL"); // NEW
}
}

In the coroutine SpawnPowerupRoutine(), after the powerup is instantiated, I made this new powerup a child of the powerupContainer.

IEnumerator SpawnPowerupRoutine(GameObject[] _spawnList, float _waitTimeMin, float _waitTimeMax)
{
int _randomPowerUpIndex = 0;

while ((_stopSpawning == false) && (_gameManager._isGameOver == false))
{
_waitTimePowerups = Random.Range(_waitTimeMin, _waitTimeMax);

yield return new WaitForSeconds(_waitTimePowerups);

if ((_stopSpawning == false) && (_gameManager._isGameOver == false))
{
// Instantiate prowerup prefab
_randomX = Random.Range(-_xPositionLimit, _xPositionLimit);
Vector3 spawnPosition = new Vector3(_randomX, _yPositionLimit, 0);
_randomPowerUpIndex = Random.Range(0, _spawnList.Length);
GameObject newPowerup = Instantiate(_spawnList[_randomPowerUpIndex], spawnPosition, Quaternion.identity);
newPowerup.transform.parent = powerupContainer.transform; // NEW
}
}
}

Because the Enemy_Container and Powerup_Container share the same parent, Spawn_Manager, making a reference to the powerups from the enemy script becomes easier.

In the Enemy.cs script, I made a method DetermineIfPowerupInfront() in which an array of powerups is made and populated by every powerup in the level. But this enemy script does not have a direct reference to the powerups. I referenced the parent objects and then down to the second child object which should always be the “Powerup_Container”.

For each powerup in the level, the Dot Product function was used to determine if the powerup is in front of this enemy. If it is, a laser is fired.

private void DetermineIfPowerupInfront()
{
// Make array of powerups. Second child of the Spawn_Manager will always be the "Powerup_Container" game object
Powerup[] _allPowerUps = transform.parent.parent.GetChild(1).GetComponentsInChildren<Powerup>();
for (int i = 0; i < _allPowerUps.Length; i++)
{
Vector3 directionPowerup = _allPowerUps[i].transform.position - transform.position;
directionPowerup.Normalize();

// Check if the powerup is in front of this enemy
float checkFront = Vector3.Dot(directionPowerup, -transform.up);
if ((checkFront > -0.75f) & (checkFront < 0.0f))
{
Invoke("FireLaserNormal", 0.1f);
}
}
}

The method DetermineIfPowerupInfront() was called when the enemy is initially spawned and when it wraps around the end of the screen.

void Start()
{

_player = GameObject.Find("Player").GetComponent<Player>();
if (_player == null)
{
Debug.LogError("Enemy::Start() No _player");
}

_gameManager = GameObject.Find("Game_Manager").GetComponent<GameManager>();
if (_gameManager == null)
{
Debug.LogError("Enemy::Start(). Game Manager is NULL");
}

_enemyAnimator = GetComponent<Animator>();
if (_enemyAnimator == null)
{
Debug.LogError("Enemy::Start() No _enemyAnimator");
}

_audioSource = GetComponent<AudioSource>();
if (_audioSource == null)
{
Debug.LogError("Enemy::Start() _audioSource is NULL.");
}

if (_laserPrefab == null)
{
Debug.LogError("Enemy::Start() _laserPrefab is NULL. Add prefab in Inspector.");
}

_laserSpawnPoint = this.gameObject.transform.GetChild(0).gameObject; // First child object is the front spawn point
if ((_laserSpawnPoint == null) | (this.gameObject.transform.GetChild(0).name != "Laser_Spawn_Front"))
{
Debug.LogError("Enemy::Start() _laserSpawnPoint is NULL or not Laser_Spawn_Front.");
}

switch (_enemyID)
{
case _enemyIDs.LaserBeam:
_explosionAnimLength = 0.0f;
break;
case _enemyIDs.SmartRearLaser:

_explosionAnimLength = 0.0f;
_laserSpawnPointBack = this.gameObject.transform.GetChild(1).gameObject; // Second child object is the front back point
if ((_laserSpawnPointBack == null) | (this.gameObject.transform.GetChild(1).name != "Laser_Spawn_Back"))
{
Debug.LogError("Enemy::Start() _laserSpawnPoint is NULL or not Laser_Spawn_Back.");
}
break;
case _enemyIDs.Standard:
default:
if (_enemyShieldsOnEnemy == null)
{
Debug.LogError("Enemy::Start: No Enemy Shields Game Object");
}
_explosionAnimLength = 2.6f;
Invoke("SheildsInitialize", 0.1f);
DetermineIfPowerupInfront(); // NEW
break;
}
}
private void CalcMovementAtScreenLimits()
{
float _randomXPos = Random.Range(-_horizontalLimit, _horizontalLimit);

//if bottom of screen
if (transform.position.y <= -_verticalLimit)
{
//move to top with a new random x position
transform.position = new Vector3(_randomXPos, _verticalLimit, 0);
DetermineIfPowerupInfront(); // NEW
}

// if left or right side of screen
if (Mathf.Abs(transform.position.x) > _horizontalLimit)
{
float _randomYPos = Random.Range(-_verticalLimit, _verticalLimit);
transform.position = new Vector3(-transform.position.x, _randomYPos, 0);
DetermineIfPowerupInfront();// NEW
}

}

When the level is tested, all the powerups can be destroyed and the Standard Enemy fires at a powerup if it is in front of the enemy.

--

--