Scripting our greybox prototype

GameDev Dustin
9 min readJan 6, 2022

Now that we have all of our pieces in place for our greyboxed player and laser projectile, let’s go over the code we’ve written so far to add behavior to these objects.

We’ll start with the player script.

Here is the script in its entirety for reference purposes:

using System.Collections;using System.Collections.Generic;using UnityEngine;public class Player : MonoBehaviour{[SerializeField]private float _playerSpeed =  7.36f;[SerializeField]private GameObject _laserPrefab;[SerializeField]private float _laserFireRate = 0.2f;private float _nextLaserFireTimeStamp = -1f;// Start is called before the first frame updatevoid Start(){//Set start position of playertransform.position = new Vector3(0, -3, 0);}// Update is called once per framevoid Update(){CalcMovement();//Fire laser at appropriate fire rateif (Input.GetButton("Fire1") && Time.time > _nextLaserFireTimeStamp){FireLaser();}}void CalcMovement(){float horizontalInput = Input.GetAxis("Horizontal");float verticalInput = Input.GetAxis("Vertical");Vector3 direction = new Vector3(horizontalInput, verticalInput, 0);//Move player based on user inputif (transform.position.x < 9f && transform.position.x > -9f){ // In horizontal boundsif (transform.position.y < 5.5f && transform.position.y > -3.8f)  // In vertical bounds{//Move playertransform.Translate(direction * _playerSpeed * Time.deltaTime);}else  // Out of vertical bounds{if (transform.position.y > 0f) {transform.position = new Vector3(transform.position.x, 5.499f, transform.position.z);}else {transform.position = new Vector3(transform.position.x, -3.799f, transform.position.z);}}}else  // Out of horizontal bounds{//Reset to boundaryif (transform.position.x > 0f) {transform.position = new Vector3(8.999f, transform.position.y, transform.position.z);}else {transform.position = new Vector3(-8.999f, transform.position.y, transform.position.z);}}}void FireLaser(){_nextLaserFireTimeStamp = Time.time + _laserFireRate;Instantiate(_laserPrefab, transform.position + new Vector3(0, 0.8f, 0), Quaternion.identity);}}

Namespace

As Microsoft’s documentation tells us, Namespaces are used to organize large code projects.

More than that, they allow us to differentiate some code from other code.

For instance, if two namespaces both have the class named “Player”, it will not be an issue.
If one namespace has two classes named “Player” it will not compile without error. One of the classes would have to be renamed.

It is generally a good idea if you have a lot of code from different people that or large code bases for different aspects of a game that they exist in separate namespaces.

Most code downloaded from the Unity Asset Store for instance should exist in it’s own namespace so as not to conflict with other code written by the developer.

Think of a namespace as a collection of C# class objects, or a library.

You can type the “namespace.class.property” to access a particular property of a class in a namespace, or you can use the “using” keyword to shorten your code.

using SampleNamespace;

With a using statement, typing just “class.property” is the same as the example in the prior paragraph.

Unity provides it’s own namespace, “UnityEngine”.

All scripts created in unity will automatically have the above using UnityEngine statement in the namespace.

Instead of typing “UnityEngine.transform.position”, this allows to simply type “transform.position”.

If you have an issue where a unity defined class, such as “transform”, doesn’t seem to be recognized by Visual Studio, check your namespace has the using statement for UnityEngine.

Player Class

After declaring the namespaces we will be using, we declare our Class. With a Unity created script, the Class name has to be identical to the Script name you created.

So in our case, “Player” must be used.

Our class is derived from a Unity specific parent class, called “Monobehavior”.

This is known in coding as inheritance.

For now, just know that Unity’s parent class makes it easy for us to interact with the game engine by providing us with built in methods such as Update() and Start().

For more information, see Unity’s documentation:

Class Variables

Inside of our class, we have declared some variables that can be seen by all of the methods of our class.

Whether we are in the Update(), Start(), CalcMovement(), or FireLaser() methods writing code, all of these variables are usable.

Notice the 3 variables in the CalcMovement() method, horizontalInput, verticalInput, and direction.

These variables are only useable within the CalcMovement method itself.

If you attempted to access them from the FireLaser() method, Visual Studio would throw an exception because they are not in the correct scope.

Variables allow us to store data in memory.

Since we want to be able to adjust the player speed, we have the _playerSpeed variable with an initial assignment of 7.36f.

We’ve declared this variable as a float (similar to a decimal value) and as private.

A private variable will not be accessible outside of the class, only within.

Unity will not normally show private variables in the Inspector window which allows for easy on-the-fly changes for testing purposes.

To force Unity to show the private variable, we use the [SerializeField] statement before the variable we want shown.

We’ve also created a variable for the Laser game object prefab so that we can instantiate a laser game object in code, whenever the player fires.

Since we don’t want the player to put an unending stream of lasers, we have to use a cooldown approach.

To accomplish this, we have 2 additional variables, _laserFireRate and _nextLaserFireTimeStamp.

We’ll explain these in detail once we get to the FireLaser() method.

Start() Method

This default method, provided by Unity from the parent Monobehavior class will run as soon as this script becomes active.

Since our script is attached to a game object, that means it will run as soon as the game object is instantiated.

In this prototype, we have the Player game object in the Hierarchy window, which means it is automatically instantiated as soon as the game runs.

Thus, the Start() method will also run as soon as the game runs.

The only code we have used here is to set the start position of the greybox player character game object.

You’ll notice in Unity that every game object has a Transform component.

Using that component, we can reference our game object, the blue cube (in my case) that represents the player.

Think of the transform as the basic elements of a game object.
Where is it in space? How big is it? Which way is it facing?

Update() Method

Our Update method runs every time the game renders a frame!
So if your game is running at 60 frames per second, this method will run 60 times per second.

We do not want to put any code in the Update method that is unnecessary or it will bog down system resources, especially on devices such as smart phones or tablets.

Here we are accomplishing two things at this time.

We are moving the player by calling CalcMovement() every frame and we are checking to see if the player fired the laser with our if statement.

If the player fired the laser according our checks, Input.GetButton(“Fire1”) and enough time has passed since it was last fired, Time.time > _nextLaserFireTimeStamp, we call the FireLaser() method to create the Laser game object and move it in our game.

CalcMovement() Method

In our CalcMovement() method we declare 3 variables that are assigned immediately when the method is called based on user input.

We get the horizontal direction (“a” and “d” keys by default) and the vertical direction (“w” and “s” keys by default).

The Input.GetAxis calls are associated with Unity’s Input manager system.

We then use these user inputs to create a direction for the player game object.

In Unity, if you click “Edit”, “Project Settings”, and then “Input Manager” you will see the above.

I’ve highlight the horizontal input that is set by default.

We use another if statement to determine if the player game object is within our screen bounds.

You can determine this simply by moving the player game object around in the scene view, or running the game and moving the player game object around the screen while keeping an eye on the values of it’s Transform component.

In my case, I decided that the horizontal, or “x” values should be less than 9.0 and greater than -9.0 while the vertical, or “y” values should be between 5.5 and -3.8.

We use nested if statements to accomplish this.

First we check our horizontal position based on the bounds defined, then the vertical bounds.

If both return “true”, we move the player accordingly.

I noticed that without an else statement, often the player game object would get stuck at the boundaries probably due to values slightly overshooting.

To keep the player from being stuck, I simply move the player game object slightly in bounds for either the horizontal or vertical location (depending on which failed) whenever they hit the boundary thus assuring the player can continue moving.

FireLaser() Method

Every time our player fires their laser, we want a cool down so that the laser isn’t constant with a held down button press.

To accomplish this, when the player fires the laser, take the current timestamp of the game, Time.time, and add our _laserFireRate to it.

Since our _laserFireRate is set to 0.2, it will cause a 1/5th of a second delay before firing again.

This is checked in our Update() method:

The laser will not fire until the new timestamp is reached.

Once we have established the laser is not on cooldown, we Instantiate (or create) the laser game object in the game based on the Laser prefab game object, the parent object’s transform plus an offset, and the world position.

Laser Script

The laser script is attached to the Laser game object, which is not in our Unity Hierarchy window.

That means the Laser game object will not exist when the game runs.

However, using the Laser prefab, we instantiate (or create) this object whenever our player presses the fire button.

Once that prefab is instantiated from the Player script, a Laser game object with the Laser script is created and the laser script runs its Start() method immediately.

Since there is nothing we want it to do on start, the method is left blank and could even be removed all together.

The Update() method also begins running each frame after instantitation.

This we are interested in because we want the laser to move!

Since our game prototype is a top down shooter, we simply want the laser to move up.

So we use the variable _direction, a Vector3 which is what Unity uses for all game space variable positions, and set it to 0 on the x axis, 1 on the y axis, and again 0 on the z axis.

We also have a _speed variable set to 8, which represents moving at 8m per frame.

Since that is way too fast and devices have varying frame rates, we times our _speed variable by Time.deltaTime which makes it 8 meters per second.

To do this, in our Update() method we use Transform.translate.

Per Unity:

“Moves the transform in the direction and distance of translation.”

We end the Update() method with an if statement to destroy the Laser game object once it goes beyond the player’s screen.

This way, we won’t have thousands of laser game objects hanging around, still flying away infinitely, from previous player fire button events.

Alright, that’s it for this article.

Next article we start adding enemies for our player to shoot at!

--

--