Adding enemies to our greybox Prototype

GameDev Dustin
10 min readJan 6, 2022

Time to add something to shoot at!

Let’s add another cube to the scene, rename it enemy, set it to position 0,8,0 (not 0,0,0 as shown below) and resize it to 0.75, 0.75, 0.75.

Now let’s create a material and apply it to our enemy.

Drag the enemy game object from the Hierarchy view to the prefabs folder in the Project view to create a prefab.

Go ahead and set the Player game object’s position to 0, -3, 0 so that it matches the starting position in the Player script.

Next, we’ll create a script for our enemy.

In classic top down space shooter fashion, the enemy won’t be very complicated, mostly just moving from the top screen to the bottom screen and shooting a bit.

To add our script, right click in the project view on the Scripts folder, hover over “Create”, then click “C# Script”.

Rename the script to Enemy and drag the Enemy script onto the Enemy prefab game object.

Open the Enemy script in Visual studio and copy / paste the following code after clearing out the existing code:

Enemy Script:

using System.Collections;using System.Collections.Generic;using UnityEngine;public class Enemy : MonoBehaviour{private Vector3 _startingPosition = new Vector3(0, 8, 0);private Vector3 _enemyDirection = new Vector3(0, -1, 0);private float _enemySpeed = 4f;// Start is called before the first frame updatevoid Start() {//assign starting position immediately to ensure enemy spawns off screentransform.position = _startingPosition;//X values for enemy position must be between -8.999 and 8.999float randomX = (float)Random.Range(-8.5f, 8.5f);//Reset spawn position at random x position above top of screentransform.position = _startingPosition + new Vector3(randomX, 0, 0);}// Update is called once per framevoid Update(){//generate random X value every framefloat randomX = (float)Random.Range(-8.5f, 8.5f);//move down at 4 meters per secondtransform.Translate(_enemyDirection * _enemySpeed * Time.deltaTime);//if out of lower boundsif (transform.position.y < -6f){//respawn at top with a new random x position at top of screentransform.position = _startingPosition + new Vector3(randomX, 0, 0);}}}

Now, let’s go over what we have so far.

We have declared 3 variables:

_startingPosition — The default spawn, above the top of the player screen.

_enemyDirection — The direction we want enemy objects to move, which is down, so a default value of -1 in the Y property which will result in a 1 meter per second movement.

_enemySpeed — The default speed we want the enemy to move is 4 meters per second, so a value of 4.0 is used. This will be multiplied against our _enemyDirection variable to achieve a 4 meter per second movement speed.

This value is proceeded by [SerializeField] so we or other game designers can play with the value from the Unity Inspector window of the Enemy game object in real-time.

Start() Method

We first set the initial off-screen position of the enemy object to make sure that no matter what, the player won’t see a momentary flash of the enemy game object appear somewhere in view and then move off screen while math calculations complete in code.

This is probably unnecessary, but better safe than sorry!

We then generate a random float value for the X property of our Enemy game object’s position.

Then we reposition the enemy game object using this randomized value, all of which is happening off-screen so that the player is unable to see the repositioning.

Update() Method

Again, we create a randomX variable. Note that since this variable is declared inside of the Start() and Update() methods, there is no conflict in naming.

These are not the same 2 variables, but actually separate variables of the same name!

This is referred to as scoping. Each variable only exists within the scope of its respective method.

We then use the transform.translate method provided by the UnityEngine namespace library of code to move the enemy game object 4 meters down every second.

Our _enemyDirection Vector 3 variable is (0,-1,0).

When multiplied by our _enemySpeed variable of 4, each value returns as follows for an altered Vector3: 0*4 = 0, -1*4 = -4, 0*4 = 0

Thus the new Vector3 used for the translate method is (0, -4, 0).

We then multiply this by Time.deltaTime so as to force this movement to occur every second, rather than every frame which is the default for the Update() method.

Last, we use an if statement to check when the Enemy game object has moved out of view at the bottom of the player screen.

Once this returns True, we reposition the Enemy game object above the player screen at a new random X value (horizontal).

For now, the Enemy game object is not destroyable so this will always happen.

Destroying Enemy game objects with Collision Triggers

By default, Unity primitive game objects such as cubes, spheres, capsules, etc are created with Colliders as shown above with our Laser game object prefab.

What are collider meshes for?

A collider is essentially an invisible mesh, or 3 dimensional object, around our game object. For complicated game objects these collider meshes will usually be far less complex.

For instance, a human player character in a first-person shooter is a complex game object.

The collider mesh however is often a capsule collider or at least a reduced version of the visual player game object mesh.

This allows for triggered events such as running into a wall to reasonably approximate the player character object without forcing the user device to do more complex calculations than necessary.

Since we are grey boxing, our primitive game objects have very simple meshes for rendering, and an identical collider mesh is used.

What are Rigid Bodies?

Right now, even though we have collider components on all of our game objects, they aren’t set to trigger any events.

So when you run the game currently, the enemy game object just passes through laser and player game objects alike.

“Adding a Rigidbody component to an object will put its motion under the control of Unity’s physics engine.

Even without adding any code, a Rigidbody object will be pulled downward by gravity and will react to collisions with incoming objects if the right Collider component is also present.

The Rigidbody also has a scripting API that lets you apply forces to the object and control it in a physically realistic way.”

So rigidbodies essentially tell the Unity game engine to put into effect all of those wonderful built-in physics Unity has.

Something we should keep in mind while coding related to rigidbodies is using the FixedUpdate() method instead of the Update() method.

“In a script, the FixedUpdate function is recommended as the place to apply forces and change Rigidbody settings (as opposed to Update, which is used for most other frame update tasks).

The reason for this is that physics updates are carried out in measured time steps that don’t coincide with the frame update. FixedUpdate is called immediately before each physics update and so any changes made there will be processed directly.”

We want our physics to look and act appropriately, so we want our code that affects physics or rigidbodies to be on the same time-scale as the Unity physics engine.

Add a rigidbody component to the Laser game object prefab as shown above.

Now add a rigidbody to the enemy and player game object prefabs.

If we run our game now, we notice it is not working at all as desired!

This is because Unity physics is now applying gravity to all of our game objects.

So far, we have manually moved game objects around the screen using the transform.translate() method.

So our manual movements of game objects in code is in direct conflict with the Unity physics system.

We can tell the Unity physics system not to process gravity on these objects by disabling the “Use gravity” field on the rigidbody for each game object.

Our game once again works as intended.

Let’s get triggered

The default Unity class, monobehaviour, which our scripts inherit from has built-in methods for trigger events.

“When a GameObject collides with another GameObject, Unity calls OnTriggerEnter.

OnTriggerEnter happens on the FixedUpdate function when two GameObjects collide.

Note: Both GameObjects must contain a Collider component.

At least one of them must have Collider.isTrigger enabled, and contain a Rigidbody.

If one of the GameObjects has Collider.isTrigger enabled or both GameObjects do not have a Rigidbody component, no physical collision happens. In both cases, Unity still calls OnTriggerEnter.”

So, at least one game object in any collision, for physics to work as expected, must have a rigid body.

The most common collision we want to track is when our laser projectile game object hits our enemy game object.

In Unity, let’s tag our game objects so they are easier to identify and track collisions within our code.

OnTriggerEnter(Collider other) method

Open the Enemy script in Visual Studio.

Add the following method below the Update() method:

private void OnTriggerEnter(Collider other){//if hits playerif (other.tag == "Player"){//knock player backother.GetComponent<Transform>().Translate(new Vector3(0, -2, 0));//Stun player for 1 secondother.GetComponent<Player>().Stunned(1f);//destroy this enemy game objectDestroy(this.gameObject);}//if hits laserif (other.tag == "Laser"){//destroy  laser game objectDestroy(other.gameObject);//destroy this enemy game objectDestroy(this.gameObject);}}

Open the Player script and add the following to the class variables:

private float _noLogerStunnedTimeStamp = -1f;

In the Update() method, replace “CalcMovement();” with:

if (Time.time > _noLogerStunnedTimeStamp){CalcMovement();}

Add the following method beneath the FireLaser() method:

public void Stunned(float duration){//Stun the player game object for duration_noLogerStunnedTimeStamp = Time.time + duration;}

Code Review

We’ve added a stunned state to our Player class.

The Stunned method is public so that other classes from other scripts can call on it.

From now on, the CalcMovement() method will only be called if the player gameobject is not considered to be “stunned”.

We allow external classes to pass in the duration value to our Stunned() method so that depending on situation, this value can vary.

We added an OnTriggerEnter() method to our Enemy script.

Since we have tagged our game objects as either “Player”, “Laser”, or “Enemy”, we can easily check what the Enemy game object has collided with and act accordingly.

For collisions w/ Player, we use GetComponent<T>.Translate to knock the player down 2 meters.

We then call our new Stunned method, with a 1 second input for the duration variable on the Player script.

Then we destroy the enemy game object (this object).

For collisions w/ laser we simply destroy the laser and enemy game objects.

Adding Player Lives and Damage

Open the Player script and add the following variable to the Player class variables:

[SerializeField]private int _playerLives = 3;

Add the LoseLife() method below the Stunned() method:

Open the Enemy script and add a damage player section when the enemy object collides with player game object.

//damage playerother.GetComponent<Player>().LoseLife();

Now when our Player game object hits an enemy game object, the player will lose 1 life.

If the Player game object reaches 0 lives, the Player game object is destroyed.

Simply duplicate the enemy game object in the Unity Hierarchy window as many times as you like, hit play and enjoy the progress!

--

--