• entries
    35
  • comments
    14
  • views
    789

About this blog

A blog of a web developer learning Unity and creating a VR application. Help me stay accountable and maybe discover something new!

Entries in this blog

Josh Chang

Welcome back to day 31!

Yesterday, we created a simple time-based system that runs on our screen when the game plays. We only increased the time, but we never pause it when the game is over.

Today, we’re going to:

  1. Pause our time after we win or lose
  2. Stop our player’s shooting animation and sound effects when the game is over

Today’s going to be a relatively short day, but let’s get to it, shall we?

Step 1: Stopping the Timer at Game Over

To stop our game from showing updates to the timer, we need to have 2 things:

  1. Code to stop our ScoreManager from updating the string
  2. Some way for our ScoreManager to know that the game is over. Who’s in charge of that? Our GameManager!

Step 1.1: Stopping ScoreManager from Updating Code

The first thing we need to do is to get our ScoreManager to stop updating the time when the game is over.

Here are the changes we did:

using UnityEngine;
using UnityEngine.UI;

public class ScoreManager : MonoBehaviour
{
    public Text Score;

    private string _time;
    private bool _gameOver;

    void Start ()
    {
        _time = "";
        _gameOver = false;
    }

    void Update()
    {
        if (!_gameOver)
        {
            UpdateTime();
        }
    }

    private void UpdateTime()
    {
        int minutes = Mathf.FloorToInt(Time.time / 60);
        int seconds = Mathf.FloorToInt(Time.time % 60);
        float miliseconds = Time.time * 100;
        miliseconds = miliseconds % 100;
        _time = string.Format("{0:0}:{1:00}:{2:00}", minutes, seconds, miliseconds);
        Score.text = _time;
    }

    public void GameOver()
    {
        _gameOver = true;
    }
}

 New Variable Used

The goal is for us to stop the call to UpdateTime() to do that, we introduced a new bool called _gameOver to indicate that.

Walking Through the Code

The change that we’re making here is straightforward:

  1. In Start() we set instantiate _gameOver
  2. In Update() we update our time if we’re still playing otherwise we don’t do anything
  3. We created a public GameOver() so that something else (our GameManager) can tell us that the game is over and we can stop updating our score

Step 1.2: Updating our GameManager to call GameOver() in ScoreManager

Now that we have a way to stop our timer from going, the next thing we’re going to have to do is call it.

Luckily for us, we have a GameManager script that takes care of everything regarding the state of the game. Let’s add our code there!

Here are our changes:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public Animator GameOverAnimator;
    public Animator VictoryAnimator;

    private GameObject _player;
    private SpawnManager _spawnManager;
    private ScoreManager _scoreManager;

    void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _spawnManager = GetComponentInChildren<SpawnManager>();
        _scoreManager = GetComponent<ScoreManager>();
    }

    public void GameOver()
    {
        GameOverAnimator.SetBool("IsGameOver", true);
        DisableGame();
        _spawnManager.DisableAllEnemies();
    }

    public void Victory()
    {
        VictoryAnimator.SetBool("IsGameOver", true);
        DisableGame();
    }

    private void DisableGame()
    {
        _player.GetComponent<PlayerController>().enabled = false;
        _player.GetComponentInChildren<MouseCameraContoller>().enabled = false;
        _player.GetComponentInChildren<PlayerShootingController>().enabled = false;
        Cursor.lockState = CursorLockMode.None;
        _scoreManager.GameOver();
    }
}

New Variable Used

Like what we said earlier, we introduced our ScoreManager object, _scoreManager so we can call GameOver().

Walking Through the Code

There aren’t any major or surprising changes that were done:

  1. In Start() we got our _scoreManager that was attached to our GameManager game object
  2. In DisableGame(), where we go and disable everything, we call GameOver() from our _scoreManager to pause the timer.

Step 2: Fixing the Player State After the Game Ends

The next thing we’re going to do is fix a problem that I’ve been talking about for the past 2 days or so. When we win, our player, specifically, the gun, is stuck in a perpetual shooting state and the shooting sound effect will keep playing until the game is over.

We’re going to fix that.

Most of the code is already in place, we just need to:

  1. Create a new state transition to stop shooting in our gun Animator
  2. Create a new game over function for our gun to stop everything when the game is over
  3. Call the game over function from our GameManager

Step 2.1: Create Transition to Stop Shooting

If you might recall from Day 16 where we added our weapon, we created a state machine that would transition from our shooting animation to our idle information (and vice-versa) whenever we activate our Shoot Trigger

gun-animator-controller.png

We’re going to make a new GameOver state and create a new transition from Any State to GameOver.

Here’s what we’re going to do:

  1. Select Player > Main Camera > MachineGun_00 and open the Animator tab (Windows > Animator)
  2. Create a new parameter, it’ll be a trigger and call it GameOver
  3. Create a new state called GameOver
  4. Create a new transition from Any State to
  5. Select that transition and set the transition condition to be when GameOver is triggered

When you’re done, we should have something like this:

new-transition-state-1-1024x435.png

At this point, you might be wondering, if we don’t have any clip for our GameOver state, why not just re-use our default state?

The answer to that is, because this is our finished state where we’re done. We don’t want to accidentally trigger a new transition and move us from the default state to another state.

Step 2.2: Add GameOver in PlayerShootingController

Now that we have our new transition, it’s time to use it!

As we might recall, the script that controls our player’s shooting is PlayerShootingController.

We’re going to go in and make some slight changes:

using UnityEngine;
using System.Collections;

public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    public Transform GunEndPoint;
    public float MaxAmmo = 10f;

    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    private LineRenderer _lineRenderer;
    private float _currentAmmo;
    private ScreenManager _screenManager;


    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
        _lineRenderer = GetComponent<LineRenderer>();
        _currentAmmo = MaxAmmo;
        _screenManager = GameObject.FindWithTag("ScreenManager").GetComponent<ScreenManager>();
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;

        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));

        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);

        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0) || _currentAmmo <= 0)
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }

        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }

    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }

    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }

    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }

    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();
        _currentAmmo--;
        _screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);

        _lineRenderer.SetPosition(0, GunEndPoint.position);
        StartCoroutine(FireLine());

        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _lineRenderer.SetPosition(1, hit.point);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            print(health);
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
        else
        {
            _lineRenderer.SetPosition(1, ray.GetPoint(Range));
        }
    }

    private IEnumerator FireLine()
    {
        _lineRenderer.enabled = true;

        yield return ShootingDelay - 0.05f;

        _lineRenderer.enabled = false;
    }

    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
        _currentAmmo = MaxAmmo;
        _screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);
    }

    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }

    public void GameOver()
    {
        _animator.SetTrigger("GameOver");
        StopShooting();
    }
}

New Variable Used

None

Walking Through the Code

For our code change this time, we only added a new public function: GameOver().

We do 2 things:

  1. We call our GameOver trigger to stop our shooting state
  2. We re-use StopShooting() to stop the particle and sound effects.

GameOver() is a public function so that means it can be called from somewhere external, in this case, our GameManager script.

Step 2.3: Calling GameOver() from PlayerShootingController in GameManager

Now that we have the code to stop our player from shooting when the game is over, it’s time to use it in our GameManager.

Here are our changes:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public Animator GameOverAnimator;
    public Animator VictoryAnimator;

    private GameObject _player;
    private SpawnManager _spawnManager;
    private ScoreManager _scoreManager;

    void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _spawnManager = GetComponentInChildren<SpawnManager>();
        _scoreManager = GetComponent<ScoreManager>();
    }

    public void GameOver()
    {
        GameOverAnimator.SetBool("IsGameOver", true);
        DisableGame();
        _spawnManager.DisableAllEnemies();
    }

    public void Victory()
    {
        VictoryAnimator.SetBool("IsGameOver", true);
        DisableGame();
    }

    private void DisableGame()
    {
        _player.GetComponent<PlayerController>().enabled = false;
        _player.GetComponentInChildren<MouseCameraContoller>().enabled = false;

        PlayerShootingController shootingController = _player.GetComponentInChildren<PlayerShootingController>();
        shootingController.GameOver();
        shootingController.enabled = false;

        Cursor.lockState = CursorLockMode.None;
        _scoreManager.GameOver();
    }
}

New Variables Used

None

Walking Through the Code

Everything is already in place, the only thing we want to change is inside DisableGame().

Specifically, instead of just disabling our PlayerShootingController that we already had, we made sure that we called GameOver() and then we disable it to prevent any new user inputs/

Conclusion

With all of this in place, our game is 1 step closer to completion!

Today we made it so that we would stop counting our time when the game is over and we solved an annoying bug that would have the player constantly firing when the game is over.

That’s it for day 31! I think for day 32, we’ll look into creating a high score system and displaying that in our game!

Look forward to it!

Source: Day 31

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to day 30! Another milestone has been made today, we’re 3/10 of the way to the goal!

The question is, will we finish our simple first fps and then ship another simple game by the time we hit day 100?

However, let’s not look too much into the future, quite yet and finish our simple game.

Today, we’re going to implement a new feature into our game. In the current state of the game, after we win, that’s it, no replay value.

To fix this, I’m going to implement a time score system to encourage players to beat the game as fast as they can and then keep playing to try and beat their previous time.

Our goals today are to:

  1. Create the code for our time system
  2. Create the UI that shows the time

Without any delays, let’s get started!

Step 1: Writing the Code for a Time System

We’re going to create a point system that will keep track of our time as the game progressive.

Luckily for us, Unity already has a very handy time function that takes care of this.

It’s Time.time. This nifty little function keeps track of the time elapsed since the game started in seconds. All we need to do is write code to convert our time to seconds and minutes.

Let’s get to it!

Step 1.1: Creating the ScoreManager

To manage our code, we’re going to create a ScoreManager script.

We’re going to attach it to our GameManager. To do that, select our GameManager and then click Add Component in the Inspector and create a new ScoreManager script.

Our ScoreManager class will be used to show our time formatted in minutes, seconds, and miliseconds.

Here’s our code:

 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreManager : MonoBehaviour
{
    private string _time;
    void Start ()
    {
        _time = "";
    }
    
    void Update ()
    {
        UpdateTime();
    }
    private void UpdateTime()
    {
        int minutes = Mathf.FloorToInt(Time.time / 60);
        int seconds = Mathf.FloorToInt(Time.time % 60);
        float miliseconds = Time.time * 100;
        miliseconds = miliseconds % 100;
        _time = string.Format("{0:0}:{1:00}:{2:00}", minutes, seconds, miliseconds);
        print(_time);
    }
}
 

New Variable Used

All we’re currently introducing in our code is a string _time that keeps track of the time that took place once the game started.

Walking through the code

The code is short right now, but here’s what happens.

  1. In Start() we instantiate _time to be empty
  2. In Update() we call a function UpdateTime() to get our correctly formatted time string.
  3. In UpdateTime(), we calculate what our minutes, seconds, and miliseconds are, and then we use String.format to build the string we want.
  4. If you aren’t familiar with Format, we pass in a string for where we want variables to replace, and then we put the variables we want to do the replacing afterwards. In this example: {1:00} refers to seconds and we want to always show the string in the format with 2 numbers. For example: 01 or 02

If we were to play the game now and look at the console you can see that we’re printing out the time.

Step 2: Creating the UI to show our Score

Now that we have the code to create the string for our score, the next thing we’re going to do is to show the time on the player’s score.

Step 2.1: Create a new Score Text

In our hierarchy, under HUD, create a new UI Text game object. Let’s call it Score.

Let’s make some changes to it.

  1. Alignment Anchor: Top and Center
  2. Text: Time
  3. Font Style: Bold
  4. Font Size: 24
  5. Alignment: Center and Middle
  6. Color: White

We should have something like this:

 
0*QOo9Ckaz102CEdk9.png

We should have this on our scene now:

 
0*OwoNiTCdx13SRAyy.png

Step 2.2: Using our new Score UI

Now that we have our Score UI, it’s time to use it in our script.

Back into ScoreManager, we’re going to make some changes….

 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ScoreManager : MonoBehaviour
{
    public Text Score;
    private string _time;
    void Start ()
    {
        _time = "";
    }
    
    void Update ()
    {
        UpdateTime();
    }
    private void UpdateTime()
    {
        int minutes = Mathf.FloorToInt(Time.time / 60);
        int seconds = Mathf.FloorToInt(Time.time % 60);
        float miliseconds = Time.time * 100;
        miliseconds = miliseconds % 100;
        _time = string.Format("{0:0}:{1:00}:{2:00}", minutes, seconds, miliseconds);
        Score.text = _time;
    }
}
 

New Variables Used

We added a new Text variable that we call Score. This is the new UI that we just created to show the score in the top center of our page.

Note that we need to import UnityEngine.UI to be able to use a Text object.

Walking through the code

The changes have been simple:

  1. In UpdateTime() we set the text of our Score Text UI to be the time.

Note: I’m a fond supporter of writing code that sends push notification for changes, however in this, case, because time is always changing, we have no choice but to use Update() every frame to get our new time.

Step 2.3: Attach our Score UI to our ScoreManager script

Finally, we need to add our new Score UI to our ScoreManager so we can update the time.

Just drag the UI into the Score slot in the ScoreManager.

When we’re done, we should have this:

 
0*B11rHFFOSWr5G7Ni.png

And when we play the game we’ll have our time increasing:

 
0*sOsv9xSErfsEfle-.png

Nice!

Conclusion

That’ll be all for today!

We did some great work today laying the foundation to our score system. Right now we created a time system on the top of our screen so that the player will know how long it’s taking them to beat the enemies.

However, if we were to lose or win, you’ll notice that our time will continue to keep running.

We will address these issues tomorrow by making some changes in our victory and game over game states, but until then, good night, and I’ll see you all later on Day 31!

Source: Day 30

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to day 29!

Yesterday we finished creating our final enemy that we’re going to use for the game.

Now that we have everything ready, it’s time to use them!

After finishing the spawn system and played the game for a bit, I noticed a small problem.

Sometimes when we shoot, the enemy doesn’t get hit. This becomes obvious when we try to switch the health of our enemy to be 1.

Our goals today are:

  1. Start spawning all 3 of our enemies
  2. Fix the shooting bug

Without any delays, let’s get started!

Step 1: Spawn our new enemies

Setting up the spawning system with our new enemies is simple, we already have the SpawnManager script to take care of spawning enemies every wave.

Let’s add them in!

In our hierarchy, look at GameManager > SpawnManager and to find our SpawnManager script.

Set our Waves size to be 3.

We want to spawn:

  1. 5 Knights at Wave 1
  2. 5 Bandits at Wave 2
  3. 5 Zombie at Wave 3

When we’re done, we’ll have something like this:

 
0*MP8xL8-w_LNOvXjJ.png

Now we can play the game and we’ll be able to fight all of our new enemies on the map!

Here might be something we’ll see!

 
0*Jfq5zY8N31AExGB3.png

You might notice that after we win, our gun doesn’t stop shooting and our shooting sound doesn’t stop.

Not today, but some other day, we will go in and disable all of those when the player wins.

Step 2: Solving the Mystery Missing Shot

Now that we’re spawning enemies and playing the game, we might encounter a very weird problem.

When we’re shooting an enemy, sometimes they don’t get hit.

I’m not talking about a long distance either, I’m talking very close proximity. What’s happening?

After digging around for a bit, I found the problem: our raycast is still hitting the mesh collider of our dead enemies.

 
0*BmUy6MEQrUO85pxw.png

As we have discussed in Day 20 the Mesh Colliders that we attach to our enemy doesn’t follow the form of our enemies. They’re just still there, as you can see from the image above.

What’s happening here is that even though we defeated our enemies, their mesh collider is blocking our shots from hitting other existing enemies.

But it’s okay, we can fix this. We must disable all colliders attached to the enemy when they die.

Step 2.1: Disabling our Colliders in Code

Now, let’s think, where would we know when the enemy is dead? In the EnemyAttack script!

In EnemyAttack we have a function called Death() that takes care of everything when the enemy dies.

Let’s use that:

 
using UnityEngine;
public class EnemyHealth: MonoBehaviour
{
    public float Health = 100;
    public AudioClip[] HitSfxClips;
    public float HitSoundDelay = 0.5f;
    private SpawnManager _spawnManager;
    private Animator _animator;
    private AudioSource _audioSource;
    private float _hitTime;
    void Start()
    {
        _spawnManager = GameObject.FindGameObjectWithTag("SpawnManager").GetComponent<SpawnManager>();
        _animator = GetComponent<Animator>();
        _hitTime = 0f;
        SetupSound();
    }
    void Update()
    {
        _hitTime += Time.deltaTime;
    }
    
    public void TakeDamage(float damage)
    {
        if (Health <= 0) { return; } Health -= damage; if (_hitTime > HitSoundDelay)
        {
            PlayRandomHit();
            _hitTime = 0;
        }
        if (Health <= 0)
        {
            Death();
        } 
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
    }
    private void PlayRandomHit()
    {
        int index = Random.Range(0, HitSfxClips.Length);
        _audioSource.clip = HitSfxClips[index];
        _audioSource.Play();
    }
    private void Death()
    {
        _animator.SetTrigger("Death");
        _spawnManager.EnemyDefeated();
        foreach (Collider collider in GetComponentsInChildren<Collider>())
        {
            collider.enabled = false;
        }
    }
}
 

New Variables Used

None

Walking Through the Code

There’s only one major change that we did and that’s in Death() where search through all of the enemy’s child game objects looking for any collider component. If a collider is found, we would just disable it.

One interesting thing to note is that we’re looking for anything of type Collider. We’re more used to finding something of exact type, for example, GetComponent<SpawnManager>();

However, taking advantage of the fact that all of the Colliders: Box Collider, Mesh Collider, Capsule Collider, etc all extend from the Collider parent class, they’re all considered of type Collider. What that means is that we can do what we did in Death() and look for anything that is of type Collider or inherits the class.

Now after we added the code, if we play the game, we’ll see that dead enemies no longer have their Colliders on anymore:

 
0*e2OguNZx26VReYB_.png

There are things that are sometimes better off staying dead.

Conclusion

That’s it for today! Today, we incorporated the rest of our enemies to the game and we even went in to fix “ghost” mesh colliders that prevented us from shooting properly.

I think we’re going to work on one more feature before I start moving on to the actual VR portion of this series, and that’s making a point system!

In our current game, we win and that’s it. There’s no replay value at all!

Tomorrow we’re going to start making this game more interesting by adding a scoring system!

With that all being said, it’s getting late, I’m headed out!

Source: Day 29

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to day 28!

Yesterday, we started our next task on creating multiple new types of enemies and made a new bandit enemy.

Today, we’re going to create our last enemy, a slower enemy with more health that won’t get pushed back as far.

Today is going to be very similar to yesterday, however we’re going to try and create a new animator controller for our enemy. We’ve seen this before in the Survival Shooter, which I once again blissfully ignored, however we’re re-visiting this topic today.

Let’s get started!

Step 1: Acquire new asset

Let’s return to our favorite place in the world! The candy store Unity Asset Store!

Just like yesterday, we’re going to look for an asset that’s mecanim ready.

If you’re not aware of the glory that is the mecanim system, you can visit Day 11 where we first used it.

Step 1.1: Get Zombie Asset

Browsing around the asset store for another enemy to use and I found a nice barbarian mage to use for our tank enemy!

It’s called Zombie01:

 
0*GVOFtCDSArTsc00y.png

Download and import the barbarian into our game. It’ll be in Assets > Zombie_0_1.

Step 2: Setup the Zombie

Yesterday, we created our Bandit enemy that we re-use our existing Knight Animator Controller, however for our Zombie I want him (it?) to have different move set and animations.

Today, we’re going to do things differently!

Step 2.0: Set Wave 1 SpawnManager to Spawn -1 Enemies

If you have set our Wave 1 to spawn -1 enemies already then you don’t have to do anything.

However, if not, go to our SpawnManager and make this change:

 
0*WsvXq3Rzdrk_z4gg.png

Step 2.1: Creating an Animator Override Controller

To achieve the goal of creating an enemy with new animations (but the same states), we’re going to use the Animator Override Controller.

An Animator Override Controller allows us to give it an Animator Controller for us to override and put our own animation clips in. This is very handy for us if we want to create an enemy that has the same states from another Animator Controller, but you want to use different animations for each state.

Let’s first create the animator.

  1. Go to Assets > Animator and right click and create a new Animator Override Controller. Let’s call it Zombie Animator Controller.
  2. Set Knight Animator Controller to be our Controller in our Zombie Animator Controller

Now you’ll see something like this:

 
0*m8C85RWGmxmopYKW.png

See how we have the same 4 states from our Knight Animator Controller?

With this we can add our clips in, but which clips?

For this, let’s just re-use the existing ones that came along with the Knight Barbarian.

In Assets > Zombie_0_1 > Animations, we’ll find Zombie_0_1, if we expand the model, we’ll see these animation clips!

 
0*VbuNrY9DFXHv3Gaj.png

As you can see we have 8 animation clips attached:

  • attack0
  • attack1
  • death
  • idle0
  • idle1
  • run
  • skill0
  • wound

If you want to see what each of the animation does, click on the model and you’ll see the clips that are attached and what they each look like:

 
0*7td9kxoCmtIVv_Z6.png

Unfortunately, because these animations share very similar names, I’m going to have to rename them.

The 4 we’ll need are attack01, death, idle0, and run. Click on each of them and hit Ctrl + D to duplicate them.

Rename each of them to be ZombieAttack, ZombieDeath, ZombieIdle, and ZombieRun.

Move these into our Animation folder.

Here’s what we have:

 
0*PKjICLoqTViGeQuZ.png

Back to our Zombie Animator Controller, let’s add our clips in and we should have this:

 
0*nNuVk1gczhuXroq-.png

Step 2.2: Attaching the Animator Controller

Now that we have created our Animator controller, let’s create our zombie to use it!

  1. In Assets > Zombie_0_1 > Animations drag the Zombie_0_1 model into our game’s hierarchy.
  2. Add a new Animator component and inside for the Animator Controlleradd our Zombie Animator Controller.

Here’s what we should have now:

Note: The Transform position could be anything. I moved it closer to the player, but in the end, it’s not going to matter, because we’re going to spawn it in our present locations.

Step 2.3: Attaching the Nav Mesh Agent and the 3 scripts: EnemyHealth, EnemyAttack, and EnemyMovement

At this point, everything that we’re going to do is going to be the exact same as the day before.

Select the Barbarian game object and add a Nav Mesh Agent Component, Enemy Health, EnemyAttack, and EnemyMovement script to it.

Step 2.4: Setting up the Scripts and Nav Mesh Agent

Now that we have attached our script, we need to set them up.

Nav Mesh Agent

For our Nav Mesh Agent component, we want to adjust the speed of how fast it moves.

Let’s set the Speed to be 2. Our Knight’s speed is 3 and our Bandit’s speed is 3.25

EnemyHealth

For our Enemy Health we want to set:

  • Health: 15
  • Hit Sfx Clips > Size: 4
  • Set the 4 new Audio Clips to be Male_Hurt_01 — Male_Hurt_04
  • Hit Sound Delta: 5

EnemyMovement

For the EnemyMovement scripts, we want to set:

  • Walking Clips > Size: 4
  • Set the 4 new Audio Clips to be Footstep01 — Footstep04
  • Walking Delay:7
  • Knock Back Force: 9

EnemyAttack

We’re going to do some re-work with our EnemyAttack script, technically speaking, we should use sound effects that are not punching sounds, however, because we’re only practicing, let’s just use the same sound effects we have been using.

  • Attack Sfx Clips > Size: 3
  • Set the 3 new Audio Clips to be Punch_01 — Punch_03
  • Leave the Fist Colliders alone for now

Now if we were to play the game, our Zombie will start chasing after us, but he won’t attack us yet, we haven’t set up our triggers and colliders yet!

Step 2.5: Adding 3 Colliders: Trigger Collider, Mesh Collider, and Fist Colliders

At this point, we’ll be doing nearly the exact same thing as we done yesterday.

We’re going to add:

  • A Capsule Collider to be used as a trigger to tell our zombie to enter attack mode if he’s near the player.
  • 2 Box Collider that will be attached to the zombie’s weapon to tell when we damage the player.
  • A Mesh Collider to be used for detecting when the player shoots the zombie.

Adding the Capsule Collider

Select Zombie in the hierarchy and then add the Capsule Collider component.

We’ll make it a trigger, set the Y to be 1, and then expand Radius to be 1.5.

When you’re done, we should have something like this:

 
0*o3Ehaq3_Hu-GbP_X.png

Note: the radius is the outer sphere that you see.

To do a re-cap, whenever our player enters the circle, in our EnemyAttack script, we’ll hit OnTriggerEnter() which is used to tell the bandit that it’s in range to be able to hit the player.

Adding the 2 Box Colliders

Next up, we need to attach colliders to our weapon.

The process won’t be too different compared to working with fists.

In Zombie > Bip001 > Bip001 Pelvis > Bip001 Spine > Bip001 Spine1 > Bip001 Neck Bip001 L Clavicle > Bip001 L UpperArm > Bip001 L Forearm > Bip001 L Hand > Zombie_0_1_002

We want to attach a box collider. We’re just going to make it as big as our weapon.

We need to make some changes with the collider. Specifically, the Z value for Center and Size.

  1. Set the Z value for Center to be 0.6
  2. Set the Z value for Size to be 0.2

The reason why we’re doing this is because we don’t want our weapon to block our gun raycasting when we try to shoot the enemy.

Next, we’ll attach our FistCollider script to our game object.

At this point, we really should call it a weapon collider, but we’ll keep it the same for simplicity.

When we’re done, we should have something like this:

 
0*WXRbEqWzJ0ytm2sG.png

We’ll do the exact same thing for the right weapon as well.

Adding the Mesh Collider

Next up is our Mesh Collider component.

The same as yesterday, the purpose of the Mesh Collider is for 2 reasons, it allows us to:

  1. shoot the enemy
  2. get pushed by the enemy

For the zombie, this is easy.

All we need to do is add a Mesh Collider component to Zombie > Zombie_0_1.

And for the Mesh, attach Zombie_0_1

When you’re done, you’ll have something like this:

 
0*wpfT_zPk4rZquy3z.png

Note: it has come to my attention, that using Mesh Colliders wouldn't be the best approach. Instead a better approach that was brought to light to me by Scouting Ninja is to use individual Colliders that covers our enemies.

However, seeing how we already have this, I'll leave that as a future optimization we can do.

We’re almost there, we still need to make our game object shootable so we can hit him.

Step 2.6: Set Zombie Layer to be Shootable

Straightforward, just like our Bandit, we did the day before, we want to go to our Zombie and change his layer to be Shootable.

 
0*eBxDbHWMra8bRfLO.png

Step 2.7: Attach the Fist Colliders to EnemyAttack

We attached our First Collider script to the Zombie’s weapons. It’s time to attach both of them to our EnemyAttack Script so we can detect when the weapon makes contact with the player in Attack().

In EnemyAttack set:

  • Left Fist: Zombie_0_1_002
  • Right Fist: Zombie_0_1_1

With this, we can take damage now.

Except for two problems:

  1. If we don’t move and let the enemy attack us, he just stops attacking us at one point!
  2. We don’t take damage when our enemy attacks!

Step 2.7: Allowing Enemies to Continuously Attack Us

Fixing the enemy not attacking problem turned out to be straightforward.

If we were to look at the Animator of our zombie when he attacks us, you’ll notice something interesting.

The zombie is in the right state attack state, but he only attacks us once.

One possible area to look at next is the animation clip: ZombieAttack.

Looking at it, the problem becomes apparent. Our Loop Time is not checked, which means that our animation won’t loop!

Check it and we’ll solve our first problem.

 
0*WpK_vKzsvdIK7EHx.png

Step 2.8: Creating an Event in our Animation to Call Attack()

The next problem is that our player character doesn’t seem to notice when they get attacked.

If we were to do some print debugging, if we were to add a print statement inside our EnemyAttack script at Attack(), we’ll notice that we never call Attack()!

As you might recall, we call Attack() by setting an event in our animation! Our Bandit was using the same knight attack animation so we never had to run into the problem, however, our Zombie uses a new animation!

Let’s fix this:

  1. Select our Zombie game object
  2. In the Animation Tab (Window > Animation), select our ZombieAttack clip.
  3. Experiment around with what’s a good frame to add our event, but I chose frame 21
  4. After creating our event, set the function to run to be Attack()

When we’re done, this is what we’ll have:

 
0*9PObtAn8i4tNsesM.png

With this added in, the Zombie can now damage us!

Step 3: Create a prefab

Now that we finished our Zombie enemy, the last thing we need to do is to add it to our Prefabs folder for our SpawnManager in the future!

We can just call our new prefab Zombie.

Conclusion

There we go! Now we have 2 more enemies that we can spawn in the game!

Here’s what we have now:

 
0*KadA_1xnUn4f4x0k.png

With all our new enemies, the next thing to work on is setting our SpawnManager to create new waves our enemy.

We’ll set that up next time! Until then!

Source: Day 28

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome to Day 27!

Right now, we have a relatively complete game and in my mental roadmap, we only have a couple more things to do before we start exploring how to use VR and then implementing that back into this game.

There will be a lot of fun times ahead!

Our goal is to create a new enemy. Specifically, for practice, I want to create:

  1. A lower health, but faster unit
  2. A higher health, but slower unit

With these new enemies, we’ll have a more diverse and interesting experience in our game!

Today, we’re going to focus on creating our speedy unit.

Step 1: Acquire new assets

The first thing we need to do is go to our favorite place in the world! The Unity Asset Store!

We’re going to look an asset to be used as our enemy. When looking for new assets, the most important thing for us is to find something that’s mecanimready.

If you’re not aware of the glory that is the mecanim system, you can visit Day 11 where we first used it.

The basic summary is that all Unity models can have rigged body structure that can share animations from other models with similar body structures without doing any extra work on our end.

Step 1.1: Get bandit asset

Browsing around the asset store for an enemy to use and I found a nice bandit to use for the speedy enemy!

It’s called Basic Bandit:

 
0*_x2JsJSotyJM5ewN.png

Download and import the bandit into our game. It’ll be in Assets > BasicBandit.

Step 2: Setup BasicBandit

Now that we have our assets the next thing we need to do is to setup up our BasicBandit asset to use our existing animation and scripts!

This work will be the same as what we have done with the knight model, except we don’t have to code anything this time!

Step 2.0: Set Wave 1 SpawnManager to Spawn -1 Enemies

Not necessary, but for the ease of testing our new enemy. If we set Wave 1 of our SpawnManager, to spawn -1 enemies, we won’t create any new enemies and the wave won’t progress, allowing us to experiment around with our enemies.

Another option is to comment out the instantiation code in SpawnManagercode to not create any new enemies.

 
0*IfMtBGHvGhnGrA-4.png

Step 2.1: Attach the Knight Animatior Controller

The great thing about Unity’s mecanim system is that we can re-use existing animator controllers for models that share the same animation.

In this case, because our knight and bandit are both humanoids, we can share our Knight Animator Controller with our bandit.

Now let’s do it!

  1. In Assets > BasicBandit, drag and drop Basic_BanditPrefab into our game.
  2. Select our bandit in the hierarchy and in the Animator component, under Controller, select our Knight Animator Controller to fill in the slot.

You’ll have something like this now:

 
0*MldXUl1WG2e3ab_z.png

Now with this, our bandit will have the same animation rules as our knight (running, attacking, death, and idle)

Step 2.2: Add the Nav Mesh Agent and the 3 scripts: EnemyHealth, EnemyAttack, and EnemyMovement

At this point, we want to re-create everything that’s attached to our Knight and move it to our bandit.

We want to add the following things to our BasicBandit by clicking Add Component in the inspector:

  • Nav Mesh Agent component to be the navigation AI to make the bandit chase after the player
  • EnemyHealth: to receive the damage when the player shoots the enemy
  • EnemyAttack: to detect when the enemy injures the player
  • EnemyMovement: to control the movement logic for the enemy

Step 2.3: Setting up the Scripts and Nav Mesh Agent

Now that we have attached our script, we need to set them up.

Nav Mesh Agent

For our Nav Mesh Agent component, we want to adjust the speed of how fast it moves.

Let’s set the Speed to be 3.25. Our Knight’s speed is 3.

 
0*SLXRhkAMhLxc4Mp7.png

EnemyHealth

For our Enemy Health we want to set:

  • Health: 5
  • Hit Sfx Clips > Size: 4
  • Set the 4 new Audio Clips to be Male_Hurt_01 — Male_Hurt_04
  • Hit Sound Delta: 5
 
0*sdPRdfka9z5RYiW0.png

EnemyMovement

For the EnemyMovement scripts, we want to set:

  • Walking Clips > Size: 4
  • Set the 4 new Audio Clips to be Footstep01 — Footstep04
  • Walking Delay:3
 
0*r10SkeKzZleAq-WS.png

EnemyAttack

We don’t have everything for our EnemyAttack script, but let’s set what we can first.

  • Attack Sfx Clips > Size: 3
  • Set the 3 new Audio Clips to be Punch_01 — Punch_03
  • Leave the Fist Colliders alone for now
 
0*TjkehHUqm7myjPtL.png

Now if we were to play the game, our Basic_Bandit will start chasing after us, but he won’t attack us yet, we haven’t set up our triggers and colliders yet!

Step 2.4: Adding 3 Colliders: Trigger Collider, Mesh Collider, and Fist Colliders

The final thing that we need to do is to add colliders to our bandit that will help us detect when the player is close, when the enemy damages our player, and when they get hit.

To do a re-cap the purpose of what we need to do, we need:

  • A Capsule Collider to be used as a trigger to tell our bandit to enter attack mode if he’s near the player.
  • 2 Box Collider that will be attached to the bandit’s hand to tell when we damage the player.
  • A Mesh Collider to be used for detecting when the player shoots the bandit.

Let’s add them in.

Adding the Capsule Collider

Select Basic_Bandit in the hierarchy and then add the Capsule Collider component.

We’ll make it a trigger, set the Y to be 1, and then expand Radius to be 1.5.

When you’re done, we should have something like this:

 
0*NyoUTz23QCvm-D5m.png

Note: the radius is the outer sphere that you see.

To do a re-cap, whenever our player enters the circle, in our EnemyAttack script, we’ll hit OnTriggerEnter() which is used to tell the bandit that it’s in range to be able to hit the player.

Adding the 2 Box Colliders

Next up, we need to add 2 Box Colliders to our Bandit’s fists.

Select Basic_Bandit > Armature > Hips > Spine > Chest > Shoulder.L > UpperArm > LowerArm > Hand.L

We want to add a Box Collider to Hand.L

In our Transform, we’ll keep position the same, however, let’s change the Size of our Box Collider:

Size (X, Y, Z): 0.1, 0.3, 0.1

Finally, just attach the Fist Collider Script to Hand.L.

When we’re done, we’ll have something like this:

 
0*QuqtRJcW6WNdA0od.png

Now do the exact same thing to the other hand in Hand.R.

Adding the Mesh Collider

Next up is the Mesh Collider component.

As we learned in Day 20, adding our collider into the parent object will not work, we should put the collider in the location of the mesh itself.

This would serve us 2 purposes:

  1. We can shoot the enemy
  2. The enemy can push us around

In the bandit’s case, we have 2 pieces: the body and the clothes.

 
0*7EyN8bZjQFyG7YVZ.png
 
0*y-fjZNLDdNeiuq_B.png

What you see above is the Mesh Collider in each of the pieces of the mesh of the bandit.

Let’s get started!

In BasicBandit_Body, add a Mesh Collider component, under Mesh add the mesh BasicBandit_Clothes. With this, you’ll have the 1st image above.

Here’s what it should look like:

 
0*j8zQxxzxYDojX0rp.png

Next:

In BasicBandit_Clothes, add a Mesh Collider component, under Mesh add the mesh BasicBandit_Clothes. With this, you’ll have the 2nd image above.

And here’s what it should look like:

 
0*lvaRtpzp3zjoZQ6H.png

At this point, we’re almost there.

We have the colliders to detect when our bandits get hit, however, if we were to try and shoot our enemy, we wouldn’t “hit” him yet.

Step 2.4: Set Bandit’s Layer to be Shootable

As you might recall when we were shooting enemies with Raycasting in PlayerShootingController, we did an optimization to only hit objects that have a layer mask set to Shootable.

Select our bandit in the hierarchy and set our Layer to be Shootable.

 
0*IsEWZKLIW6TvOJsZ.png

Now with these colliders set, we can shoot the bandit and they’ll take damage and be knocked back.

Step 2.4: Attach our Fist Colliders to EnemyAttack

Earlier on, we attached our First Collider script to the bandit’s hands. It’s time to attach both of our hands to our EnemyAttack Script so we can detect if either of our the bandit’s fists makes contact with the player in Attack().

In EnemyAttack set:

  • Left Fist: L
  • Right Fist: R

Step 2.5: Create a prefab of our Bandit

Now that we have everything setup, we’re going to make a prefab of our finished bandit model.

Drag our finished copy of Basic_Bandit in our hierarchy into Assets > Prefabs.

Let’s also rename our prefab to be just called Bandit.

 
0*VGDk5JGhhphaamtr.png

Conclusion

Now with all these changes added in, we will have a fully functional Bandit enemy!

I do want to mention that the game won’t function perfectly, for example, if we were to get killed by the Bandit, he’ll continue to attack us.

The reason is that our Bandit isn’t in the Enemy Container game object, which means he won’t be disabled.

In fact, our current Bandit isn’t parented by anything at all! That’s why he doesn’t stop after the player character dies.

We can fix that by removing the bandit and adding him as one of the spawns for our SpawnManager, however before we do that, I want to create one more enemy to use.

Tomorrow we’re going to create our last and final enemy. Until then!

Source: Day 27

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to day 26! We just finished implementing our Enemy Spawning System, but before we move on to the next thing, I would like to go and fix/update some minor things that we glimpsed over.

Specifically, I like to address these 3 points:

  1. When we get hit, there’s no player hit sound effect
  2. We should fix the crosshair to be something nice
  3. When the player dies, I would like the enemy knights to stop moving and enter their idle state

That means today, we’re going back to the Unity Asset Store!

Step 1: Creating punching sound impacts

The first thing we want to do is play a sound effect for when our knight punches our player.

Luckily for us, we already the Action SFX Vocal Kit asset that we installed from Day 14. Inside the asset pack, we have a variety of options for punching sound effects!

Playing the punch sound effect in EnemyAttack

We only want to play the sound effect to play when our player gets hit, so to do that, we’re going to update EnemyAttack so that when the player takes damage, we can play our sound effect.

Here’s the updated EnemyAttack:

 
using UnityEngine;

public class EnemyAttack : MonoBehaviour
{
    public FistCollider LeftFist;
    public FistCollider RightFist;
    public AudioClip[] AttackSfxClips;
    private Animator _animator;
    private GameObject _player;
    private AudioSource _audioSource;
    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
        SetupSound();
    }
    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
        print("enter trigger with _player");
    }
    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
        print("exit trigger with _player");
    }
    private void Attack()
    {
        if (LeftFist.IsCollidingWithPlayer() || RightFist.IsCollidingWithPlayer())
        {
            PlayRandomHit();
            _player.GetComponent<PlayerHealth>().TakeDamage(10);
        }
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
    }
    private void PlayRandomHit()
    {
        int index = Random.Range(0, AttackSfxClips.Length);
        _audioSource.clip = AttackSfxClips[index];
        _audioSource.Play();
    }
}
 

New Variables added

We added 2 new variables and they are:

  • _audioSource — our sound player
  • AttackSfxClip — an array of punching music sound clips that we’ll later add in.

Walking through the code changes.

Here’s the new addition we added into our code:

  1. In Start() we call SetupSound() which is where we create an AudioSource component in code and then set the volume.
  2. In Attack(), which you might recall is called by an event from our Knight’s attacking animation, we call PlayRandomHit() to play a random punch effect.
  3. In PlayRandomHit(), we get a random index from our array of Sound Clips, we set it to our audio source and then we play it.

Step 2: Setting up the Audio Clips for EnemyAttack

We made a slot in our EnemyAttack script for us to put in Audio Clips, so now we’ll put them in.

Where?

We’ll put in our Knight prefab, which is in the Prefabs folder.

Select our Knight prefab, under the Enemy Attack (Script) component, expand Attack Sfx Clips and set Size to be 3.

Then either drag and drop or open the selector to add Punch_01-Punch_03into those slots.

 
0*qK39QK6EYQEXuZDD.png

Now when we play the game, if we were ever hit by the knight, the punching sound will play. Great!

Step 3: Adding A Better Crosshair

The next thing we want to do today is to add an overall better crosshair UI instead of the white square that we have.

Step 3.1: Getting the crosshair from the asset store

To do that, I went to the Unity Asset Store and I found a free crosshair pack: Simple Modern Crosshairs

 
0*YxIFYz1UmobqEA7j.png

Download and import the asset to our game. Everything will be in the SMC Pack 1 in our Assets folder

Step 3.2: Using the crosshairs pack

I’m not going to be picky on my crosshair, so I just chose the first one.

Setting our Crosshair image

Inside our hierarchy, go to HUD > Crosshair and in the Image (Script) component, change Source Image to be one of the crosshairs you like. I chose 1 because it’s the first one.

Changing our crosshair to be red

Right now, it’s White, I decided to change it to Red. Also, it’s hard to see it when it’s so small, so I resized our crosshair. I set the width and height of our image to be 40.

Re-center our crosshair

Finally, after we adjusted the size of our image, we should reposition the crosshair to be in the middle of the screen again.

To do that we click on the Anchor Presets (the box on the top left corner of our Rect Transform component) and hit ctrl + shift in the middle center box.

Now when we’re done, we should have something like this:

 
0*suXQO72XtENl8clg.png

Step 4: Stopping Enemies in Game Over

Now we’re on the final and more complicated part of today.

Currently, in the state of our game, when the player loses, the enemies will continue attacking the player and the spawner would continue spawning.

While leaving that as is could be considered a feature, for practice, we’re going to learn how to disable enemy spawning and movement when the game is over.

The first thing we’re going to do is stop the enemies from moving after we win.

Note: this is only when the player loses. If they win, all the enemies are already dead and there’s already no more enemies to spawn.

Step 4.1: Spawn all enemies inside a parent holder in SpawnManager

If we want to disable our enemy knights after the game is over, a simple change we could do inside our EnemyHealth or EnemyMovement script is to check our GameManager to see if the game is over in Update(), over and over and over again…

As you can imagine doing this for ALL our enemies could become computationally expensive. So instead, I think a better solution is to store all enemies in a parent game object and then when we’re done, cycle through all of them.

The best way for us to have this parent container is to create it in our SpawnManager and then push all enemies that we spawn in there.

For our code to work, we need to access the EnemyHealth and EnemyMovmenet script to:

  1. Check if the enemy is still alive
  2. If alive, set them to an idle state and stop them from moving, all of which is controlled in our EnemyMovement script.

Here’s the code, note that it won’t compile yet until we change EnemyMovement:

 
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
    public int EnemiesPerWave;
    public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
    public Wave[] Waves; // class to hold information per wave
    public Transform[] SpawnPoints;
    public float TimeBetweenEnemies = 2f;
    private GameManager _gameManager;
    private int _totalEnemiesInCurrentWave;
    private int _enemiesInWaveLeft;
    private int _spawnedEnemies;
    private int _currentWave;
    private int _totalWaves;
    private GameObject _enemyContainer;
    void Start ()
    {
        _gameManager = GetComponentInParent<GameManager>();
        _currentWave = -1; // avoid off by 1
        _totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
        _enemyContainer = new GameObject("Enemy Container");
        StartNextWave();
    }
    void StartNextWave()
    {
        _currentWave++;
        // win
        if (_currentWave > _totalWaves)
        {
            _gameManager.Victory();
            return;
        }
        _totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
        _enemiesInWaveLeft = 0;
        _spawnedEnemies = 0;
        StartCoroutine(SpawnEnemies());
    }
    // Coroutine to spawn all of our enemies
    IEnumerator SpawnEnemies()
    {
        GameObject enemy = Waves[_currentWave].Enemy;
        while (_spawnedEnemies < _totalEnemiesInCurrentWave)
        {
            _spawnedEnemies++;
            _enemiesInWaveLeft++;
            int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
            // Create an instance of the enemy prefab at the randomly selected spawn point's position and rotation.
            GameObject newEnemy = Instantiate(enemy, SpawnPoints[spawnPointIndex].position, SpawnPoints[spawnPointIndex].rotation);
            newEnemy.transform.SetParent(_enemyContainer.transform);
            yield return new WaitForSeconds(TimeBetweenEnemies);
        }
        yield return null;
    }
    
    // called by an enemy when they're defeated
    public void EnemyDefeated()
    {
        _enemiesInWaveLeft--;
        // We start the next wave once we have spawned and defeated them all
        if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
        {
            StartNextWave();
        }
    }
    public void DisableAllEnemies()
    {
        // cycle through all of our enemies
        for (int i = 0; i < _enemyContainer.transform.childCount; i++)
        {
            Transform enemy = _enemyContainer.transform.GetChild(i);
            EnemyHealth health = enemy.GetComponent<EnemyHealth>();
            EnemyMovement movement = enemy.GetComponent<EnemyMovement>();
            // if the enemy is still alive, we want to disable it
            if (health != null && health.Health > 0 && movement != null)
            {
                movement.PlayVictory();
            }
        }
    }
}
 

New variable used

The only new variable that we used is to a GameObject that we call _enemyContainer.

_enemyContainer, is literally an empty game object that we create that’s sole purpose is to function as a container.

Walking through the code

The complexity of this specific feature isn’t the code itself, it’s changing multiple pieces that intermingle with each other.

Here’s what we need to know about the changes done to SpawnManager:

  1. In Start(), we create a new instance of a GameObject, which will put _enemyContainer in our actual game. It’ll be called “Enemy Container”
  2. We create a new public function called DisableAllEnemies(), in here, we check all child game objects in our _enemyContainer. We make sure they all have our EnemyHealth and EnemyMovement If they all do, we’ll call the currently non-existent PlayVictory().

Once again, currently our code does not compile, we need to add PlayVictory() to our EnemyMovement script.

Step 4.2: Creating PlayVictory() in EnemyMovement

In SpawnManager, we’re essentially disabling all enemy movements after the game has ended. To do that we’re putting that logic in a function that we’ll call PlayVictory()

Here are the changes that we made to EnemyMovement:

 
using UnityEngine;
using UnityEngine.AI;
public class EnemyMovement : MonoBehaviour
{
    public float KnockBackForce = 1.1f;
    public AudioClip[] WalkingClips;
    public float WalkingDelay = 0.4f;
    private NavMeshAgent _nav;
    private Transform _player;
    private EnemyHealth _enemyHealth;
    private AudioSource _walkingAudioSource;
    private Animator _animator;
    private float _time;
    void Start ()
    {
        _nav = GetComponent<NavMeshAgent>();
        _player = GameObject.FindGameObjectWithTag("Player").transform;
        _enemyHealth = GetComponent<EnemyHealth>();
        SetupSound();
        _time = 0f;
        _animator = GetComponent<Animator>();
    }
    
    void Update ()
    {
        _time += Time.deltaTime;
        if (_enemyHealth.Health > 0 && _animator.GetCurrentAnimatorStateInfo(0).IsName("Run"))
        { 
            _nav.SetDestination(_player.position);
            if (_time > WalkingDelay)
            {
                PlayRandomFootstep();
                _time = 0f;
            }
        }
        else
        {
            _nav.enabled = false;
        }
    }
    private void SetupSound()
    {
        _walkingAudioSource = gameObject.AddComponent<AudioSource>();
        _walkingAudioSource.volume = 0.2f;
    }
    private void PlayRandomFootstep()
    {
        int index = Random.Range(0, WalkingClips.Length);
        _walkingAudioSource.clip = WalkingClips[index];
        _walkingAudioSource.Play();
    }
    public void KnockBack()
    {
        _nav.velocity = -transform.forward * KnockBackForce;
    }
    // plays our enemy's default victory state 
    public void PlayVictory()
    {
        _animator.SetTrigger("Idle");
    }
}
 

Walking through the code

For possibly the first time in a long time, we aren’t introducing new variables, we just have new code:

  1. We implemented PlayVictory() that our SpawnManager will call. It’s pretty basic, we set our state to be idle.
  2. In Update() I’ve moved the animation state check to the outer if statement. The reason is that the moment we change our state, we’ll disable our Nav Mesh Agent so our enemy won’t move anymore.

Step 4.3 Updating GameManager to use DisableAllEnemies() from the SpawnManager

Now we have everything setup, the last and final thing that we need to do is to set our GameMangager script to use our new SpawnManager.

Here’s the code for that:

 
using UnityEngine;
public class GameManager : MonoBehaviour
{
    public Animator GameOverAnimator;
    public Animator VictoryAnimator;
    private GameObject _player;
    private SpawnManager _spawnManager;
    void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _spawnManager = GetComponentInChildren<SpawnManager>();
    }
    public void GameOver()
    {
        GameOverAnimator.SetBool("IsGameOver", true);
        DisableGame();
        _spawnManager.DisableAllEnemies();
    }
    public void Victory()
    {
        VictoryAnimator.SetBool("IsGameOver", true);
        DisableGame();
    }
    private void DisableGame()
    {
        _player.GetComponent<PlayerController>().enabled = false;
        _player.GetComponentInChildren<MouseCameraContoller>().enabled = false;
        _player.GetComponentInChildren<PlayerShootingController>().enabled = false;
        Cursor.lockState = CursorLockMode.None;
    }
}
 

New variables

We add _spawnManager so that we can stop all enemies when they win.

Walking through the code

The changes here is simple:

  1. In Start(), we grab our SpawnManager script, nothing new or surprising here (remember that our SpawnManager is a child of GameManager)
  2. In GameOver() we use our _spawnManager to disable all the enemies.

Now if we play the game, you should see our enemies enter their idle state (and stop punching our poor body).

 
0*GO4E4nzuBk60Fein.png

Yay!

Step 5: Stop Enemy Spawing On Death

We’ve stopped the enemy from moving, the next thing we need to do now is to disable the spawner from spawning any more enemies when the game is over.

Luckily for us, in our SpawnManager we already have code that runs at exactly when the game is over: DisableAllEnemies()

Here’s what our code looks like in SpawnManager:

 
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
    public int EnemiesPerWave;
    public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
    public Wave[] Waves; // class to hold information per wave
    public Transform[] SpawnPoints;
    public float TimeBetweenEnemies = 2f;
    private GameManager _gameManager;
    private int _totalEnemiesInCurrentWave;
    private int _enemiesInWaveLeft;
    private int _spawnedEnemies;
    private int _currentWave;
    private int _totalWaves;
    private GameObject _enemyContainer;
    private bool _isSpawning;
    void Start ()
    {
        _gameManager = GetComponentInParent<GameManager>();
        _currentWave = -1; // avoid off by 1
        _totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
        _enemyContainer = new GameObject("Enemy Container");
        _isSpawning = true;
        StartNextWave();
    }
    void StartNextWave()
    {
        _currentWave++;
        // win
        if (_currentWave > _totalWaves)
        {
            _gameManager.Victory();
            return;
        }
        _totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
        _enemiesInWaveLeft = 0;
        _spawnedEnemies = 0;
        StartCoroutine(SpawnEnemies());
    }
    // Coroutine to spawn all of our enemies
    IEnumerator SpawnEnemies()
    {
        GameObject enemy = Waves[_currentWave].Enemy;
        while (_spawnedEnemies < _totalEnemiesInCurrentWave)
        {
            _spawnedEnemies++;
            _enemiesInWaveLeft++;
            int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
            if (_isSpawning)
            {
                // Create an instance of the enemy prefab at the randomly selected spawn point's position and rotation.
                GameObject newEnemy = Instantiate(enemy, SpawnPoints[spawnPointIndex].position, SpawnPoints[spawnPointIndex].rotation);
                newEnemy.transform.SetParent(_enemyContainer.transform);
            }
            yield return new WaitForSeconds(TimeBetweenEnemies);
        }
        yield return null;
    }
    
    // called by an enemy when they're defeated
    public void EnemyDefeated()
    {
        _enemiesInWaveLeft--;
        // We start the next wave once we have spawned and defeated them all
        if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
        {
            StartNextWave();
        }
    }
    public void DisableAllEnemies()
    {
        _isSpawning = false;
        // cycle through all of our enemies
        for (int i = 0; i < _enemyContainer.transform.childCount; i++)
        {
            Transform enemy = _enemyContainer.transform.GetChild(i);
            EnemyHealth health = enemy.GetComponent<EnemyHealth>();
            EnemyMovement movement = enemy.GetComponent<EnemyMovement>();
            // if the enemy is still alive, we want to disable it
            if (health != null && health.Health > 0 && movement != null)
            {
                movement.PlayVictory();
            }
        }
    }
}
 

New variable

I introduced 1 new variable, a boolean _isSpawning which we’ll use in our code to stop our enemy from spawning.

Walking through the code

Here’s another update with some minor changes:

  1. In Start(), we instantiate _isSpawning to be true.
  2. In SpawnEnemies() we add an if statement check to see if we’re spawning, if we are, we’ll spawn an enemy, if not, then we don’t do anything.
  3. Inside DisableAllEnemies(), which is called by SpawnManager when the player’s health drops to 0, we set _isSpawning to false so we’ll stop spawning enemies in SpawnEnemies().

Conclusion

Phew, that was a long drawn out day today! We accomplished a lot today!

We added an enemy punch sound, we fixed our crosshair UI, and then we went and did a large change that stops enemies from moving when they win.

Tomorrow, we’re going to start creating new enemies that we can add into our SpawnManager so we can get some variety in the game!

Until then, I’m off to bed!

Source: Day 26

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to day 25! We’re officially ¼ of our way to the goal!

Today is going to be relatively short, we’re going to:

  • finish our Enemy Spawning system by adding a victory state

Let’s get started!

Step 1: Creating the Victory Panel

The first thing we need to do is create another panel that tells the user that they won the game.

Instead of going through the work of creating another UI and animation for our Victory Panel, we’re going to take the easy approach and make a duplicate of our existing GameOver game object and re-purpose it.

Currently, we can use the same script and animator, because they both do the same thing.

Duplicate GameOver

To do that, select GameOver in our hierarchy and then hit Ctrl + D to make a duplicate. Rename the duplicate to Victory. They can both just stay in the canvas.

Changing Victory Text

Now that we have our Victory panel, we need to change our text so that the players will know that they won.

Select Victory > Text (not Button > Text) and change the words from “Game Over” to “You win!”

 
0*KHIE0ckQNnuBWaw-.png

Step 2: Writing the code to use our new Victory Panel

Now that we have a fully functioning Victory Panel, it’s time to use it!

We’re going to have to change 2 scripts to make this work:

  • GameManager
  • SpawnManager

Updating the GameManager Code

The first thing that we’re going to do is setup GameManager to use our Victory panel.

Here’s our code:

 
using UnityEngine;
public class GameManager : MonoBehaviour
{
    public Animator GameOverAnimator;
    public Animator VictoryAnimator;
    private GameObject _player;
    void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
    }
    public void GameOver()
    {
        GameOverAnimator.SetBool("IsGameOver", true);
        DisableGame();
    }
    public void Victory()
    {
        VictoryAnimator.SetBool("IsGameOver", true);
        DisableGame();
    }
    private void DisableGame()
    {
        _player.GetComponent<PlayerController>().enabled = false;
        _player.GetComponentInChildren<MouseCameraContoller>().enabled = false;
        _player.GetComponentInChildren<PlayerShootingController>().enabled = false;
        Cursor.lockState = CursorLockMode.None;
    }
}
 

New Variables

The only new code was that we added the VictoryAnimator, like GameOverAnimator, this is the animator for our victory panel.

Walking through the code

Here’s how our code works:

  1. I created a new public Victory() that our SpawnManager will call to trigger our Victory Panel animation.
  2. To keep our code clean, I moved the code that was previously in GameOver() into DisableGame() that way we don’t have to write the same code twice in both Victory() and GameOver()

Updating the SpawnManager Code

Now that our GameManager can play our victory animation, we need to call it when we win the game from our SpawnManager.

Here’s the code for this:

 
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
    public int EnemiesPerWave;
    public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
    public Wave[] Waves; // class to hold information per wave
    public Transform[] SpawnPoints;
    public float TimeBetweenEnemies = 2f;
    private GameManager _gameManager;
    private int _totalEnemiesInCurrentWave;
    private int _enemiesInWaveLeft;
    private int _spawnedEnemies;
    private int _currentWave;
    private int _totalWaves;
    void Start ()
    {
        _gameManager = GetComponentInParent<GameManager>();
        _currentWave = -1; // avoid off by 1
        _totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
        StartNextWave();
    }
    void StartNextWave()
    {
        _currentWave++;
        // win
        if (_currentWave > _totalWaves)
        {
            _gameManager.Victory();
            return;
        }
        _totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
        _enemiesInWaveLeft = 0;
        _spawnedEnemies = 0;
        StartCoroutine(SpawnEnemies());
    }
    // Coroutine to spawn all of our enemies
    IEnumerator SpawnEnemies()
    {
        GameObject enemy = Waves[_currentWave].Enemy;
        while (_spawnedEnemies < _totalEnemiesInCurrentWave)
        {
            _spawnedEnemies++;
            _enemiesInWaveLeft++;
            int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
            // Create an instance of the enemy prefab at the randomly selected spawn point's position and rotation.
            Instantiate(enemy, SpawnPoints[spawnPointIndex].position, SpawnPoints[spawnPointIndex].rotation);
            yield return new WaitForSeconds(TimeBetweenEnemies);
        }
        yield return null;
    }
    
    // called by an enemy when they're defeated
    public void EnemyDefeated()
    {
        _enemiesInWaveLeft--;
        // We start the next wave once we have spawned and defeated them all
        if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
        {
            StartNextWave();
        }
    }
}
 

New Variables

In our code, we created a new private _gameManager which will be how we access our GameManager.

Walking through the code

The code is straightforward:

  1. In Start() we get an instance of our GameManager script by looking for SpawnManager’s parent, which in this case is the GameManager game object. Then we just take the script from there.
  2. We use _gameManager in StartNextWave(), when we have completed all the waves in the game.

Step 3: Setting up our script components in Unity

We have the script ready, the final thing for us to do is for us to add our new Victory game object to our script.

When selecting our GameManager in our Game Manager script drag our Victory game object into the Victory Animator slot. Unity will automatically pull the animator from the game object for our script.

 
0*RTwSuz03b4CfRQ0k.png

Conclusion

Now that we have set our victory animation, if we were to play the game and win, we’ll have something happen!

Specifically, this!

 
0*vljT-gel6-r4e7Ep.png

This will be the end of our Spawning System (for now).

I’m hoping to introduce at least 2 more enemies into the game, so we’ll have to come back, but for this simple FPS, our spawning system is complete.

Tomorrow, my plan is to go and fix minor parts of the game that we glanced over and then move on to adding more units!

See you then!

Source: Day 25

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Today in Day 24, we’re going to resume working on our EnemySpawner.

Yesterday, we laid the groundwork that allows us to spawn enemies, however, we haven’t implemented finishing a wave yet.

As a result, we only spawn our 5 enemies and then nothing happens after we defeat them.

Today, we’re going to make some changes so that we can move on to the next wave and then win the game.

On a different note, after reading some other articles, I decided that I like their formatting, so I’m going to organize my articles the same way.

Step 1: Create a SpawnManager Tag for our SpawnManager

The first thing that we need to do is to set a Tag for our SpawnManager.

We need to do this, because later in EnemyHealth, when our enemy dies, we want to be able to reference our SpawnManager.

 
0*fN5k947RFjQantHB.png

Select our SpawnManager game object and then under tag, select Add Tag… and create a new Tag called SpawnManager. Then go back to our game object and attach the tag like above.

Step 2: Change EnemyHealth.cs to use our SpawnManager

For our EnemyHealth script, we need access to our SpawnManager script so we can call EnemyDefeated(), which we implemented yesterday.

Here’s our change:

 
using UnityEngine;
public class EnemyHealth : MonoBehaviour
{
    public float Health = 100;
    public AudioClip[] HitSfxClips;
    public float HitSoundDelay = 0.5f;
    private SpawnManager _spawnManager;
    private Animator _animator;
    private AudioSource _audioSource;
    private float _hitTime;
    void Start()
    {
        _spawnManager = GameObject.FindGameObjectWithTag("SpawnManager").GetComponent<SpawnManager>();
        _animator = GetComponent<Animator>();
        _hitTime = 0f;
        SetupSound();
    }
    void Update()
    {
        _hitTime += Time.deltaTime;
    }
    
    public void TakeDamage(float damage)
    {
        if (Health <= 0) { return; } Health -= damage; if (_hitTime > HitSoundDelay)
        {
            PlayRandomHit();
            _hitTime = 0;
        }
        if (Health <= 0)
        {
            Death();
        } 
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
    }
    private void PlayRandomHit()
    {
        int index = Random.Range(0, HitSfxClips.Length);
        _audioSource.clip = HitSfxClips[index];
        _audioSource.Play();
    }
    private void Death()
    {
        _animator.SetTrigger("Death");
        _spawnManager.EnemyDefeated();
    }
}
 

Here are the changes we made:

  1. We get access to our SpawnManager: _spawnManager, which we’ll notify when an enemy die
  2. In Start() we get _spawnManager by finding our SpawnManager game object from its tag and then getting the SpawnManager script attached to it
  3. In Death() when the knight is defeated, we’ll call EnemyDefeated(), which will decrement our enemy count and call the next wave if all enemies are defeated.

Now when we play the game, after we defeat our first 5 enemies, our next 10 will show up.

 
0*fssc10nmGp44r-j6.png

Look at the fate of all those who opposed us!

Conclusion

Today has been a relatively busy day, so today’s progress has been relatively short. However, we’re almost done with our Spawning System.

Tomorrow, we’re going to create a player victory state and then fully complete our player spawning feature to our simple FPS!

I’ll see you all tomorrow!

Source: Day 24

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Here we are back to another day of Unity development! Today on day 23 we’re going to learn how to spawn enemy waves.

Currently, the game isn’t challenging, we only have one enemy!

Today, we’re going to fix this by spawning waves of enemies to defeat, making the game a lot tougher to survive!

It’s going to be a big change requiring multiple days, so let’s get right to it!

Creating an Enemy Wave Spawning Point

If we were to recall from the Unity Survival Shooter tutorial, to spawn enemies, we need to create a SpawnManager class that creates new instances of the enemy.

In our Spawn Manager, the primary thing we need to give it, among many other things that we’ll want to add, are the:

  • location of where we would spawn the enemies
  • enemies that we want to spawn

However, as a challenge, as opposed to the Survival shooter, where the game would continue for it, we’re going to have a limited amount of enemy spawn so we can win.

There was a lot of work involved in the wave system and I borrowed a lot of ideas from Unity’s Enemy Spawner example.

Creating the initial game objects

The first thing we want to do is create a new Empty Game Object that we’ll call EnemyManager. We’re going to make this a child of our GameManager.

Next, we’ll create a new script for our new game object that we’ll call EnemyManager.

We’re going to do a couple of things with our manager:

  • Keep track of what wave we’re in
  • Keep track of how many enemies we defeated in a wave
  • Keep track of how many enemies per wave

By keeping track of the number of enemies and waves, we can tell when to move to the next wave and if we win.

Here’s our initial code for EnemyManager.cs:

 
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
    public int EnemiesPerWave;
    public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
    public Wave[] Waves; // class to hold information per wave
    public Transform[] SpawnPoints;
    public float TimeBetweenEnemies = 2f;
    private int _totalEnemiesInCurrentWave;
    private int _enemiesInWaveLeft;
    private int _spawnedEnemies;
    private int _currentWave;
    private int _totalWaves;
	void Start ()
	{
	    _currentWave = -1; // avoid off by 1
	    _totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
	    StartNextWave();
	}
    void StartNextWave()
    {
        _currentWave++;
        // win
        if (_currentWave > _totalWaves)
        {
            return;
        }
        _totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
        _enemiesInWaveLeft = 0;
        _spawnedEnemies = 0;
        StartCoroutine(SpawnEnemies());
    }
    // Coroutine to spawn all of our enemies
    IEnumerator SpawnEnemies()
    {
        GameObject enemy = Waves[_currentWave].Enemy;
        while (_spawnedEnemies < _totalEnemiesInCurrentWave)
        {
            _spawnedEnemies++;
            _enemiesInWaveLeft++;
            int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
            // Create an instance of the enemy prefab at the randomly selected spawn point's position and rotation.
            Instantiate(enemy, SpawnPoints[spawnPointIndex].position, SpawnPoints[spawnPointIndex].rotation);
            yield return new WaitForSeconds(TimeBetweenEnemies);
        }
        yield return null;
    }
    
    // called by an enemy when they're defeated
    public void EnemyDefeated()
    {
        _enemiesInWaveLeft--;
        
        // We start the next wave once we have spawned and defeated them all
        if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
        {
            StartNextWave();
        }
    }
}
 

Now this is a lot to take in, which is why I added comments, however, here’s the run through of the code.

About the Wave Class

Before we talk about our variables, I want to introduce the Wave class.

Wave is a container for us to hold the data for each wave that we’re going to face.

If you remember from the Space Shooter tutorial, we did something similar. We created a new class to hold information and we made it Serializable so that Unity knows how to show it in the editor.

Originally, I was going to just pass each of its content to our SpawnManager, but that’s prone to us causing mix-ups of how many enemies to spawn per wave and which enemies.

About the variables

For our public variable we have:

  • Waves — An array of the Wave class that we created for an easy way for us to access data for each wave
  • SpawnPoints — An array of locations that we’re going to instantiate our enemies
  • TimeBetweenEnemies — The delay we wait before we spawn our next enemy

For our private variables to keep track of the enemies, we have:

  • _totalEnemiesInCurrentWave — Self explanatory
  • _enemiesInWaveLeft — The number of enemies that are still alive in the wave
  • _spawnedEnemies — Self explanatory

We also keep track of what wave we’re in:

  • _currentWave — Self explanatory
  • _totalWaves — Self explanatory

The Code Flow

Now that we know the variable we’re using, we can walk through the rest of the code.

  1. In Start() we initialize all our variables to 0. Note that we set _currentWave to be -1 and our _totalWaves is the length of our array — 1. For those who aren’t familiar, all of this is, because we’re working in a 0-based indexing for arrays (meaning everything starts from 0 instead of 1).
  2. From Start() we also call StartNextWave() this code handles what happens when we clear our wave. We increment our _currentWave and assuming we haven’t finished the game, we’ll setup the enemies we need to encounter for the next wave and call SpawnEnemies()
  3. SpawnEnemies() is a coroutine that we use to create our enemies. The function will spawn the enemy for the wave based from one of the random spawn points we set. The code will wait 2 seconds before we spawn the next enemy so we don’t flood the players with all the enemies in a wave at once.
  4. Finally, we have public EnemyDefeated() which means this function is called from something else. In this case, our enemy will call this when they’re killed. We will then decrement our enemy counter and if we have defeated all the enemies in a wave, we’ll call StartNextWave()

That’s the basic summary of the code. That wasn’t so bad, now was it? RIGHT?!

Setting up our SpawnManager script

Now that we have our script up and running, the last thing we need to do is to setup our Wave and create our Spawn Points.

Setting up the Wave

For now, let’s just make 2 waves for our SpawnManager that will create our knight enemy.

There are 2 things we need to provide for our Wave class:

  • The knight game object
  • How many enemies to spawn for the wave

Let’s first set the knight game object.

We could just drag and drop our knight from our hierarchy into the spot and everything would be fine. However, the correct way to do this, is to first create a prefab of our knight.

We can think of a prefab is a template of a game object that we can drag and share to different scenes and games. In our case, we can also instantiate them like what we’re doing here.

The main benefit is that, if you were to change prefab, anytime we need to change something, we can just change the prefab and anything that instantiates it from code will also get the changes.

The first thing we’re going to do is:

  1. Create a folder in our Assets folder called Prefabs
  2. Create our prefab, by dragging our Knight game object into the PrefabsNote, in this example our Knight is already a prefab, but that’s okay, we’ve already changed some things about it, so let’s make another prefab from it.
 
0*JMs351sNCNaLRS7k.png

On a side note: we can delete the Knight game object from our hierarchy. He’s served us well, but we won’t be needing his services anymore.

Now that we have everything we need, we can get started in Creating our waves.

Back in our SpawnManger script, expand Waves and set Size to be 2 so we can create new waves.

Here’s what it’ll look like:

 
0*sPZuwvL7h3ZLylug.png

Setting up the Spawn Point

Now that we have the wave system setup, we need to create our spawn points.

Exactly like the survival shooter, we need to add some game object that we’ll use as the location that we’re going to spawn the enemies.

Let’s get to it!

Create 3 Empty game objects: I call them SpawnPoint1 — SpawnPoint3. I made them the child of the SpawnManager, however, it doesn’t really matter where we put them.

Right now, we can’t see the position of our SpawnPoints in the game, however we can add a Label to them to see where the objects are.

 
0*_PDrhPDHB9ufp66N.png

We just want to set the Spawn points to be in 3 of the houses.

Here are my Position values, yours might be different:

SpawnPoint1: 98, 0, 118

SpawinPoint2: 72, 0, 89

SpawnPoint3: 106, 0, 80

Finally, go back to our SpawnManager and add our SpawnPoints in.

Expand Spawn Points and then for the Size change it to 3, and then drag our SpawnPoints into the empty game spots.

 
0*OX0N5_bASnNRNKpl.png

With all of this in place, if we play the game, we should now have multiple enemies coming at us.

 
0*EWvwR4-oG6zoSy4z.png

Conclusion

We’re not quite done with our Spawning System, however we laid the ground works for spawning enemies.

We still need to implement the logic for generating new enemies and finally creating our victory condition. We’ll start implementing those tomorrow on Day 24!

With that being said, have a good night!

Source: Day 23

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Hi and welcome back to day 22 of the 100 days of VR!

Yesterday, we created our game over panel to show to the player when they lose, however in the current state of the game, the game over panel is being displayed at the start of the game.

Today, we’re going to:

  1. Create a new state controller for our animation so that we don’t instantly play our animation
  2. Play the animation when the player is defeated and all states related to that
  3. Restart the game when the player selects to play again

There’s a lot ahead of us today, so let’s get started!

Creating an Animation State Controller

Right now, our animation just plays whenever the game starts.

The reason for that is, because of our animator controller.

Go to our GameOver.controller in the Animation folder and double click it to open it up in our Animator tab.

 
0*z0i_mO5cF64pjudU.png

Right now, Game Over is the default state we transition into from Entry.

As a result, we always play the Game Over clip that’s attached to it be default when we start our game.

To fix this, we’re going to do a couple of things. We’re going to:

  1. create a new default state,called Start that is our default transition from Entry that transitions into our Game Over state
  2. create a new boolean parameter IsGameOver
  3. set a transition from Start to Game Over
  4. select the transition arrow from Start to Game Over and create a new condition: IsGameOver = true

After you’re done, you should have something like this:

 
0*5FqEr7ymDA25kT3i.png

Now if we’re to play our game again, nothing will show up!

Playing the Game Over animation

Now that we have the correct game states set up, it’s time to use it.

All we need to do is play the animation when our player’s health drops to 0.

Here are the changes we’re going to do:

  1. Create a new GameManager script that will take care of the logic of the ending
  2. Change our PlayerHealth script to use the GameManager to tell it when the game is over.

The first thing we’re going to do is create an empty game object that we’ll call GameManager.

Then create a new script called GameManager and attach it to our new game object.

Here’s what our GameManager script will look like:

 
using UnityEngine;
public class GameManager : MonoBehaviour
{
    public Animator GameOverAnimator;
    public void GameOver()
    {
        GameOverAnimator.SetBool("IsGameOver", true);
    }
}
 

The code right now is straightforward:

  1. We get the GameOverAnimator from our Game Over object and we take the Animator from it.
  2. We create a public GameOver() that someone will call that will set our IsGameOver parameter to be true so that we’ll play the animation to show our game over panel.

When you’re done with the script, make sure to set our GameOver UI GameObject to our GameOverAnimator slot in our GameManagercomponent.

Quick note on pushing vs pulling for updates

For those of you who saw Unity’s tutorial, I’m not actually a big fan of what Unity does with having all of these checks in the Update(), where we’re always checking for something to happen.

Personally, I much prefer to take a more push functionality where we only do something, if something else notifies us of it happening.

A perfect example of a push approach is our public GameOver(). Currently we have some other script call the function to indicated game over.

An example of pulling is that we have a script that checks for changes every frame. For example. We could have given GameManager a reference the the player Game Object and in Update() it could constantly check to see if the player is dead.

Both way works, I just prefer the way where we only do something when we’re told to do it and not check every frame.

Using our GameManager

After creating our GameManger, we need to use it.

In this particular case, we’re going to use it in our PlayerHealth script. We’re going to:

  1. Add our GameManager object
  2. Call GameOver() when the player’s health is 0 or below

Here’s our code:

 
using UnityEngine;
using UnityEngine.UI;
public class PlayerHealth : MonoBehaviour
{
    public Slider HealthBar;
    public float Health = 100;
    private float _currentHealth;
    private GameManager _gameManager;
    void Start ()
    {
        _currentHealth = Health;
        _gameManager = Object.FindObjectOfType<GameManager>();
    }
    public void TakeDamage(float damage)
    {
        _currentHealth -= damage;
        HealthBar.value = _currentHealth;
        if (_currentHealth <= 0)
        {
            _gameManager.GameOver();
        } 
    }
}
 

The code here is also straightforward:

  1. We create a private field for our GameManager class
  2. In Start() we instantiate our GameManager
  3. In TakeDamage(), after our player takes damage and health falls to 0, we would call GameOver() which will show the Game Over panel.

Restarting the Game

Now that we have our game over panel showing up, it’s time for us to disable some functionality so that we can click on the restart button!

There’s a couple of things we need to:

  1. in GameManager, when GameOver() is called disable the player’s movements and re-enable our cursor to select a button
  2. create a GameOverUIManager that will deal with the logic of clicking a button
  3. change our GameOver game object’s Canvas Group to ignore parent elements and check the Blocks Raycasts option so we can click on the button

Updating GameManager to Disable the Player in Game Over

When the game is over, we want to:

  • disable all the scripts that are involved with the controls of the player. It would be weird that at game over we can still move around and shoot.
  • re-enable our Cursor which we locked in the center of the game

Here are the changes we did to GameManager:

 
using UnityEngine;
public class GameManager : MonoBehaviour
{
    public Animator GameOverAnimator;
    private GameObject _player;
    void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
    }
    public void GameOver()
    {
        GameOverAnimator.SetBool("IsGameOver", true);
        _player.GetComponent<PlayerController>().enabled = false;
        _player.GetComponentInChildren<MouseCameraContoller>().enabled = false;
        _player.GetComponentInChildren<PlayerShootingController>().enabled = false;
        Cursor.lockState = CursorLockMode.None;
    }
}
 

As you can see, we:

  1. Created a reference to our player character in Start()
  2. In GameOver() we disabled the player movement script, our mouse script, and our shooting script. We also re-enabled our lock

With this, if you were to play the game right now and let our player die, we regain our mouse and we won’t be able to control our player anymore.

 
0*Py9582hVtm-nCfza.png

Creating the Code for our UI Button Click

Next, we need some code to take care of what happens when we click on our button.

To do that we need to create a onClick event listener for our button.

For those of use that aren’t familiar with the concept of event listeners, you can think of it as code that will only execute when you take a certain action.

In this specific instance, our Button has a onClick event listener so it’ll wait for whenever the user clicks on the button.

When someone does click the button, they’ll execute any function that was passed to it. In this case, our restart code.

Let’s get started.

First, create a new script for our GameOver game object in the hierarchy. We’re going to call it GameOverUIManager. We’re going to use it to manage our play the game when the user clicks play again.

Here’s what it looks like:

 
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameOverUIManager : MonoBehaviour
{
    private Button _button;
    void Start () {
        _button = GetComponentInChildren<Button>();
        _button.onClick.AddListener(ClickPlayAgain);
    }
    public void ClickPlayAgain()
    {
        SceneManager.LoadScene("Main");
    } 
}
 

Now let’s walk through the code:

  1. We have a private _button that is the button from our Game Over Panel.
  2. In Start() we instantiate our button and we set the OnClick Listener to our button to run ClickPlayAgain() whenever the user clicks the button
  3. In ClickPlayAgain(), we use the SceneManager to restart our scene so we can play again. As we might recall, everything in Unity runs in a scene, SceneManager is what we use to help us transition from one scene to the next. Note: using LoadLevel is depreciated for SceneManager

Now with all of this, we’re almost done!

If we were to try and play the game now and let the knight beat us, you’ll realize a problem. We can’t click on the Restart button!

Let’s fix it.

Allow Clicking on the Button UI

The main reason why we can’t touch our button UI is because of our Group Canvas component attached to HUD. We disabled everything so that our UI will just be something the user sees.

 
0*0sBQ8tNLSRLJBNEP.png

We could enable Interactable and Block Raycasts so that our Graphics Raycaster script will be able to detect the button being clicked, like explained here.

However, if we were to do that, the problem is that everything else in the game would become interactable, including our health bar!

We don’t want that, so instead we’re going to make a separate Canvas Groupcomponent just for GameOver.

We need to do 2 things:

  1. Add a new Group Canvas component and check all settings so that we can interact with our game over panel and ignore the rules set by the parents.
  2. Add a Graphic Raycaster, which is used to detect if the user clicks on anything in the canvas.

Once we have these setup, play the game again and you’ll see that we can restart the game!

 
0*4PyQx-NcAjyQglw5.png

Conclusion

We did a lot of work today!

We:

  • Created a new animation state for our GameOver animations
  • Enabled our animation when our player is defeated
  • Allowed the user to restart the game when they lose

There are some more things that we can add to our Game Over scene to make things more complete, however, tomorrow, we’ll jump into a new topic, specifically enemy spawning!

Until then!

Source: Day 22

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to day 21. In the past couple of days, we worked on creating a health system for our player and fixing some problems with our enemy that relied on it.

Now we can kill enemies and enemies can kill us, it’s time for us to make this a more complete game!

Today the first thing to do is create a game over state when the player’s health reaches 0.

To do that we’re going to use Unity’s UI system.

Here’s our game plan today. We’re going to:

  • Create a Panel to hold our content
  • Add in a retry button and a game over text
  • Use an animation for our panel to appear when we lose
  • Use our new Game Over UI into our code

Let’s get to it!

Creating the Game Over UI

The first thing we’re going to do is create the game over UI that we’ll show to the player when their health goes down to 0.

The first thing that we must do is create a new Panel UI element to our existing UI canvas HUD. We’ll call the panel UI GameOver.

Then as a child to our GameOver game object, we’re going to create a new Text and Button UI element.

Let’s make some changes, first we’re going to change the GameOver screen, the first thing we’re going to do is change the Rect Transform.

  • Set the Rect Transform to be the center of the screen with Shift + Ctrl to center our panel.
  • Width: 300
  • Height: 150

Next, let’s change our Text and here are the settings we want to make to our Text:

  • Font Size: 28
  • Font Style: Bold
  • Rich Text: Selected
  • Text: Game Over
  • Color: Alpha set to 255

For the Button game object, inside the Rect Transform:

  • Set the Rect Transform to be the center bottom of the screen with Shift + Ctrl to center our Button
  • Width: 160
  • Height: 30
  • Pos Y: 20

Then inside the Text game object in the Button, we’re going to change the setting for Text.

  • Font Style: Bold
  • Font Size: 18
  • Rich Text: checked

When you’re done, we’ll have something like this:

 
0*iHry7l5CWqPnKBlD.png

So now that we have our Game Over pane, the next thing we need to do is to create an animation to get this to show up.

Creating the Game Over Animation

Right now, we just have a panel standing in front of the screen, if we were to play the game, it’d just be in front of us.

Just like in the Survival Shooter tutorial, let’s create an animation for our panel to show when the game is over.

To do that, we’re going to use Unity’s Animation feature to create a new animation for our Game Over game object.

If the Animation tab isn’t available, go to Windows > Animation to get the window to show up.

Select the GameOver game object from the panel and then in the Animationtab, click Create:

 
0*cJYG2fezmyiGqHWN.png

and you’ll be ask to create a new .anim file.

Move it to our Animation folder (or anywhere you want to store it) and name it Game Over.anim.

This will automatically create both the GameOver animation clip and the GameOver controller that we use to attach to our game object. In fact, Unity will already automatically attach the controller to our game object.

Now that we have the animation selected, the next thing we need to decide what we want our animation to look like.

Here’s what I imagine:

Our panel would be invisible and then it slowly appears (from alpha 0 to 1), as that happens, it’ll start to drop from the top of the screen to the center (let’s say from Y position 100 to 0).

Let’s make it happen!

Setting up our Game Object

The first thing we need to do is add a Group Canvas component to our GameOver game object.

The reason why we need this is that we need to be able to make everything transparent in our animation the parent and the child. Just by changing the parent’s alpha will only change its transparency and not its child’s.

Luckily Unity has thought of that and that’s why among many reasons Group Canvas offers an alpha property.

Creating our Animation Clip

Now that we have all the setup that we need, let’s get started creating our animation.

In the Animation tab with the GameOver game object selected, we’re going to click Add Property and add 2 properties:

  1. Canvas Group > Alpha — to set our alpha
  2. Rect Transform > Anchored Position — to set our Y position

Now that we have all the properties we needed, we’re going to make changes to the value in the animator at different frames and Unity will fill in the values from the start to the end.

By default, for all of our properties, we’ll have a key (the grey diamonds) at the start of our animation at frame 0 and we have another key at frame 60. Each key we can set a value and Unity will automatically lerp the values from one key to the next.

The important part is that we move the white line to the specific key whose value we want to change. Changes only apply to the location of where our white line is.

For Canvas Group, at the key at frame:

  1. 0, set alpha to be 0
  2. 60, we can just leave the value at 1

For Rect Transform, at the key at frame:

  1. 0, set Anchored Position.y to be 100
  2. 60, we can just leave the value at 0

When we’re done, we should have something like this:

 
0*UPayDV5EeXg06umm.png
 
0*5_-wIlyiFguV2trt.png

Finishing Touches with our new animation

Before we finish for today, there are 2 finishing touches that should be done.

The first is that we shouldn’t show our Game Over game object in the scene.

To fix this, we go back to our Canvas Group component inside Game Paneland then we set our alpha to be 0.

Next, if you were to play our scene, our animation would constantly play on repeat. We can fix this by selection our GameOver.anim inside our Animation folder and unselect Loop Time.

This will be our short fix, tomorrow, we’re going to implement a state controller for our animation so we can really control how our animations.

Conclusion

That’s it for today! Tomorrow, we’ll go on and attach our animation to when the player’s health drops below 0 and other things like restarting the game is over.

Until then, have a good night!

Source: Day 21

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to another exciting day of Unity development at day 20! Can you believe it? We’re 1/5 of the way to 100 days! I’m hoping that the momentum will keep me going forward until I ship something!

Now looking back at day 19, we created a health system that we can now use, however we encountered a problem.

For some reason, our collider only hits the enemies once even if we go through the attack animation multiple times.

Today we’ll be solving that problem. So, let’s get to it!

Fixing Our Mesh Collider

The problem lays with the Mesh Collider that we created for our knight on Day 12. Remember this?

 
0*SAgHLekBh_O60Byl.png

That standing mesh collider, is literally what we’re colliding with every time the Knight attacks us.

The problem is that Mesh Colliders can’t follow animations of the model, as a result when the knight first touches us, the mesh might touch us, but afterwards it we, the player, don’t get pushed back by the collider, the mesh will never touch us again.

Here’s a picture to demonstrate the problem:

 
0*FeYx_Kh-ZQoR14WA.png

While the knight model itself might be touching us, our Mesh Collider is static and doesn’t move.

This isn’t good! We want to damage our player when the knight’s fist contacts the player! Not this static mesh collider!

Some changes are going to be needed to fix this problem.

  1. We still need our Mesh collider. This prevents us from walking through our enemy. Right now, we have a problem where we can walk over the knight, but we’ll fix this now.
  2. We’re going to add a Box Collider to the hands of our Knight. This way, we’ll know for sure when our player gets punched.

Fixing the Mesh Collider for the Knight

Right now, we can’t move our mesh, because it’s in the parent body. According to this post about incorrect mesh positioning, we will make a new empty object and add the mesh collider there.

This works for us, because we only need the mesh for collision detection and for our raycasting for shooting.

Let’s do it!

Originally, we put the Mesh Collider in our knightprefab game object. Let’s remove that now.

Select knightprefab, right click, and select Create Empty, to make a new Game Object. Let’s rename this object to Collider.

Select Collider and add a new Mesh Collider component to it. For the Mesh, select body to get our Knight model back.

You might have to move Collider around yourself to get the collider to match our knight, however, at the end you should have something like this:

 
0*TG16lf5DoOFjW1ye.png

Now with this, we can’t just walk over the knight.

Adding new Colliders for our Fists!

Now that we have solved the Mesh Collider problem, we’re going to fix our original problem with dealing damage when the enemy attacks us.

To do this, we’re going to add box colliders, just to the fists of our knights. Specifically, in knightprefab -> hips -> spine -> chest -> L_shoulder/R_shoulder -> all the way down to L_Wrist/R_Wrist.

For each box collider, I made the scale of the box to be 0.25 to fit the size of the hand.

 
0*qNmKtNrrzA4x2psi.png

Now that we have this, we need to attach a script to each of our hand models that can detect the collision. Scripts from parent objects can’t detect collision for it’s children.

It’s also important that our original script: EnemyAttack, stays where it is, because we need access to the animator component that’s located in the parent.

To solve our problem, we’re going to move the collision part of our code from EnemyAttack to a new script called FistCollider.

FistCollider will deal with the collision of the knight fists and we’ll get the results in our EnemyAttack script.

Here’s our FistCollider script:

 
using UnityEngine;
public class FistCollider : MonoBehaviour
{
    private GameObject _player;
    private bool _collidedWithPlayer;
    void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _collidedWithPlayer = false;
    }
    void OnCollisionEnter(Collision other)
    {
        if (other.gameObject == _player)
        {
            _collidedWithPlayer = true;
        }
        print("enter collided with _player");
    }
    void OnCollisionExit(Collision other)
    {
        if (other.gameObject == _player)
        {
            _collidedWithPlayer = false;
        }
        print("exit collided with _player");
    }
    public bool IsCollidingWithPlayer()
    {
        return _collidedWithPlayer;
    }
}
 

Here’s the flow of the code:

  1. Like our EnemyAttack script, we want access to our player game object and we want to check if we collided with the enemy.
  2. In Start() we make sure to instantiate both of our variables.
  3. In OnCollisionEnter() and OnCollisionExit(), if whatever we collided with is the player than we would set our boolean flag. Once again this is exactly the same as
  4. In IsCollidingWithPlayer() we would give our bool _collidedWithPlayerto whoever calls it. Note that this function is public so other script can have access to this. We’re going to use it later.

Let’s attach the script to both L_Wrist and R_Wrist.

Now that we moved part of our collision code from EnemyAttack to FistCollider, let’s use FistCollider in EnemyAttack. Here’s the code:

 
using UnityEngine;

public class EnemyAttack : MonoBehaviour
{
    public FistCollider LeftFist;
    public FistCollider RightFist;
    private Animator _animator;
    private GameObject _player;
    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
    }
    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
        print("enter trigger with _player");
    }
    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
        print("exit trigger with _player");
    }
    private void Attack()
    {
        if (LeftFist.IsCollidingWithPlayer() || RightFist.IsCollidingWithPlayer())
        {
            _player.GetComponent<PlayerHealth>().TakeDamage(10);
        }
    }
}
 

Here’s the changes in our code:

  1. First, we create new public variables: LeftArm and RightArm that are the FistCollider script that we just created.
  2. We cleaned up a lot of the code, specifically the Collision part and anything that had to do with it.
  3. In Attack() we use our new FistCollider to make collisions with the player and then when the attack event from our animation gets fired, we check if the player gets hit. If they do (meaning the knights fist collided with the player), the player will take damage.

With our script done, the final thing that we need to do is to make sure that we attach the FirstCollider object to our player.

We can directly attach our game object into the spot and the script will be automatically selected.

When you’re done, we should have something like this:

 
0*ojUdyKYdbC_PVlOo.png

Conclusion

Phew that was a lot of work!

In these past 2 days, we created the health system and we re-visited some of the existing problems with the enemy knight’s mesh collider such as walking over them and not accurately colliding with the player.

We also separated out the code with attack collision work while still making our original collision script work as normal.

Now that we finally have health for our player, tomorrow, the next thing we’ll do is start working on an end game state for when the player’s health reaches 0.

Until then, have a nice evening, I’m going to sleep!

Source: Day 20

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

In the current state of our game, we can defeat the enemy, however we can never lose.

Today in Day 19, we’re going to start working on the code to fix this.

The first step is that we’re going to create health and a health bar for our player so that we can receive damage and know when we’re going to lose.

Without any delays, let’s get started!

Creating the Health Bar UI

The first thing we should do when creating a health system is to create the UI for our health.

Just like any UI elements, we’ll be using Unity’s UI system to create a health bar, specifically, we’re going to use Unity’s Slider.

We already have an existing UI canvas called: HUD, right click it and select UI -> Slider to create a new Slider. If we don’t have a UI Canvas already, Unity would have automatically created it for us.

We’re going to rename our Slider to Health Bar.

Now you’ll have something like this if we go to 2D mode in our Scene tab with our hierarchy.

 
0*fei7jVmkL8f8fR75.png

We’re going to need to make some adjustments to our slider to make it look nicer.

First off, we don’t need the slider bar. Delete Handle Slider Area.

Next select Health Bar and in the Slider (Script) component range set theMax Value to be 100. Notice a little problem here?

 
0*zF0IagGICqB7iSjd.png

Our value is 0, but our bar is still there. Also, if we were to move our value to 100, the whole bar wouldn’t be filled either.

I found a great answer regarding how to make a slider empty.

To make the adjustments that we want, we have to:

  1. Set Width of Fill to be 0
  2. Go to Fill Area and drag the bar until it fits into the background. In my case, my ending values are Pos X: 75, Width: 160, and the rest is left to its default value.

Here’s what it looks like now:

 
0*RiLqeCcUgitgLjpN.png

However now we have another problem, if we were to set the value of the slider to 1, here’s what we would get:

 
0*wmuKKmFED0fJwEN1.png

Notice how our fill bar is on the right side of the actual bar itself.

Unfortunately, there doesn’t seem to be an answer that resolved the problem for me. This might be more of a Unity problem than anything else.

However, that doesn’t mean we’ll just give up.

We can simply fix it the same way you might never have noticed it in the Survival Shooter tutorial: we’ll expand the background of the slider so that the bar would naturally look like it’s correct.

To do that, we go to Background and then change Left, Top, Right, and Bottom to -3

After that we should have something like this:

 
0*uxVI6MWDq8uDN8J4.png

Much better, right? Nothing looks out of place!

Now that we have the health bar looking nicer, it’s time to move it to the bottom left corner. Selecting Health Bar, go to the Rect Transformcomponent, click on the square, to open up the UI alignments and hit Shift+Ctrl and left click the bottom left option to move everything to the bottom left corner.

Right now, everything is exactly at the bottom left corner and doesn’t look nice. Let’s add some adding.

Click Health Bar and set Pox X to be 15 and Pos Y to be 10.

When you’re done, your game should look like this now (don’t forget to set your slider value to be 100)!

 
0*ZuQH1nqVYe8aFYIC.png

Creating our Player Health System

Creating the Player’s health

Now that we have an UI health bar, it’s time to write some code to create the player’s health.

First, click on our Player game object and create a new script called PlayerHealth. This script will control the player health whenever they get damaged and then show these changes in our health bar.

Here’s what our PlayerHealth script looks like:

 
using UnityEngine;
using UnityEngine.UI;
public class PlayerHealth : MonoBehaviour
{
    public Slider HealthBar;
    public float Health = 100;
    private float _currentHealth;
    void Start ()
    {
        _currentHealth = Health;
    }
    public void TakeDamage(float damage)
    {
        _currentHealth -= damage;
        HealthBar.value = _currentHealth;
    }
}
 

Here’s how our script works:

  1. We create a public Slider that will be our HealthBar. It’s important to note that we need to import UI otherwise the compiler would complain to us about our Slider object.
  2. Next, we create a public Health to represent our max health and _currentHealth to represent how much health our player has.
  3. In Start() we instantiate our _currentHealth to be our max health.
  4. Finally, we create public TakeDamage(), public meaning that another script can use this component and call the function. Inside this function, we get our damage amount and we update our _currentHealth and we change the value of our slider to reflect health loss.

Before we proceed on, make sure to drag our HealthBar game object to our PlayerHealth script component.

Like so:

 
0*GYcg8xaj10BA3z1V.png

Writing the Damage Dealing Code

Now that we have that setup, the next thing we need to do is create the code that calls TakeDamage().

Luckily for us, we already have a script that deals with most of this: EnemyAttack.

All we need to do is grab an instance of our new PlayerHealth script and then run the take damage code.

 
using UnityEngine;
using System.Collections;

public class EnemyAttack : MonoBehaviour
{
    private Animator _animator;
    private GameObject _player;
    private bool _collidedWithPlayer;
    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
    }
    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
        print("enter trigger with _player");
    }
    void OnCollisionEnter(Collision other)
    {
        if (other.gameObject == _player)
        {
            _collidedWithPlayer = true;
        }
        print("enter collided with _player");
    }
    void OnCollisionExit(Collision other)
    {
        if (other.gameObject == _player)
        {
            _collidedWithPlayer = false;
        }
        print("exit collided with _player");
    }
    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
        print("exit trigger with _player");
    }
    private void Attack()
    {
        if (_collidedWithPlayer)
        {
            _player.GetComponent<PlayerHealth>().TakeDamage(10);
        }
    }
}

 

In our script, we already have access to the player and detection for when we attack, so all we need to do is grab our PlayerHealth script and trigger our TakeDamage function when we call Attack() in EnemyAttack.

As you might recall from previous tutorials, the way that this code works is that:

  1. We have a trigger collider that detects when our knight should start attacking
  2. Then we have a mesh collider on our player that will detect when we’re touching the player
  3. Finally, in our animation, we set a point in time to call Attack() and when that happens, if the knight is still in contact with our player, we’ll take damage.

With this, we have everything we need to have a complete game, or you might think

If you were to play the game right now, you would have encountered a new problem that hasn’t been realized until now:

If we were to play the game while looking at our console. Whenever we bump into the knight, we would hit OnColliderEnter() get hit, and then for some reason run OnColliderExit() even if we don’t move the player.

As a result, if the player never moves, they’ll only get damaged once. How’s that for being boring?

We’ll solve this tomorrow, because it’s getting late today.

Conclusion

Today in day 19, we created a health system that allows our player to receive damage from our already existing enemy attack code.

We’re almost done, but we’ve encountered a problem where the enemy stops damaging us even when it looks like they successfully attacked us.

I have an idea of what the problem is, but we’ll talk more about that tomorrow!

Source: Day 19

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Today in Day 18, we’re going to start adding UI components into our game!

2 days ago, on Day 16, we created a reload system and just yesterday we started shooting lasers at enemies, now would be the perfect time to create a reload system and some UI to display our reload.

While we’re at it, we might as well make some other UI fixes. Here are the things that we’re going to tackle today:

  • Create the reload system and UI

The first thing that needs to be done is to create the reload system.

Without any delays, let’s get started!

Creating the Reload System

There are 2 things that need to be done for us to have a reload system. The first part is the UI that shows how many ammos we have and the second is the code that will manage it.

Let’s create the UI system first.

Player Ammo UI

First thing to do, let’s find a motivation to for our UI.

For my source of inspiration, I’m going to use Overwatch, which I think is pretty common these days:

 
0*Ka2hf9ALShsDqgc5.jpg

The basic thing is that the HP is on the bottom left corner and the ammo is on the bottom right.

Let’s get started creating the ammo UI. In the hierarchy pane, right click and under UI select Text. We’re going to call it Ammo.

As you might recall, when we do this, Unity automatically creates a Canvas screen for us. In this case, we already have one that called HUD.

To work with our UI, make sure to hit 2D button right below the Scene tab.

 
0*ItATTM3OyBH5YZrI.png

Let’s adjust the text to be at the bottom right corner of our screen.

Select Ammo and under the Rect Transform component, select the anchor presets square and while holding Shift + Alt, select bottom right, to move our text to the bottom right corner.

It’s also kind of small, so let’s change the size a bit too:

  • Width: 160
  • Height: 60
  • Text Size: 24
  • Font Style: Bold
  • Text: 30/30
  • Color: White

Here’s what we’ll see now if we pull up our game tab:

 
0*AsWLT8-q6IdfuUDL.png

Now that we have our initial setup let’s write code!

Ammo and Reload Code

We have the UI, now it’s time to write some code for reloading and ammo!

We’ll be changing our PlayerShootingController, here’s the code:

 
using UnityEngine;
using System.Collections;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    public Transform GunEndPoint;
    public float MaxAmmo = 10f;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    private LineRenderer _lineRenderer;
    private float _currentAmmo;

    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
        _lineRenderer = GetComponent<LineRenderer>();
        _currentAmmo = MaxAmmo;
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;
        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0))
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }
    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }
    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }
    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }
    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();
        _currentAmmo--;
        _lineRenderer.SetPosition(0, GunEndPoint.position);
        StartCoroutine(FireLine());
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _lineRenderer.SetPosition(1, hit.point);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
        else
        {
            _lineRenderer.SetPosition(1, ray.GetPoint(Range));
        }
    }
    private IEnumerator FireLine()
    {
        _lineRenderer.enabled = true;
        yield return ShootingDelay - 0.05f;
        _lineRenderer.enabled = false;
    }
    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
        _currentAmmo = MaxAmmo;
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }
}
 

The code flow for this is straightforward:

  1. We made 2 new variables MaxAmmo and _currentAmmo to represent how many bullets we have left to shoot.
  2. In Start() we initialize our current ammo amount.
  3. Whenever we shoot, we make sure that our ammo is above 0 otherwise we can’t shoot and then we decrement our _currentAmmo count.
  4. When we finish reloading, we’ll restore our ammo to max.

With the code, we have one problem, while we’re shooting, if we never let go of our mouse, we’ll continue to play the shooting animation and sound effect. We need to change this.

I fixed the problem by adding another check to make sure that we stop shooting when we either let go of our mouse or when we run out of bullets.

Here’s the change we did for Update():

   
  void Update ()
    {
        _timer += Time.deltaTime;
        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0) || _currentAmmo <= 0)
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }
 

Now that we have the script for reloading and shooting, we need to add our shooting mechanisms.

The first thing we’re going to do is to go to our original HUD component that we created in Day 10 and create a new script called ScreenManager.

The script will be used for ammo and later score count, and in the future health. Here’s our code:

 
using UnityEngine;
using UnityEngine.UI;
public class ScreenManager : MonoBehaviour
{
    public Text AmmoText;
    void Start()
    {
        {
            PlayerShootingController shootingController = Camera.main.GetComponentInChildren<PlayerShootingController>();
            UpdateAmmoText(shootingController.MaxAmmo, shootingController.MaxAmmo);
        }
    }
    public void UpdateAmmoText(float currentAmmo, float maxAmmo)
    {
        AmmoText.text = currentAmmo + "/" + maxAmmo;
    }
}
 

Here’s how our code works:

  1. We take in the Text game object that we use for ammo.
  2. In Start() we initialize our text ammo by getting our PlayerShootingController that’s a child of our camera and using the max ammo value we set
  3. Inside UpdateAmmoText() we give it the ammo amount to print out. I made this public, because we want to call this function from elsewhere.

There was a design decision that I was thinking of as I was going through this.

I was looking back at the score UI that was made for the Survival Shooter tutorial and it used static variables to represent the score. Static meaning you can access is anywhere, anytime without needing access to the script itself.

That worked in the context that only the manager needed to know anything about the score, however in our case, we already have our ammo amount in our PlayerShootingController, so if that’s the case, there’s no need to keep a separate instance to keep track of our ammo amount.

Instead let’s just pass in the values that we want the text to print out.

Another benefit of this is that we don’t have to re-render our text every single time we call Update() and we only change it when we need to.

The only downside is that we must get an instance of ScreenManagerwhenever we want to make any changes as opposed to the static method that was used in the tutorial.

Updating our PlayerShootingController, here’s what we get:

 
using UnityEngine;
using System.Collections;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    public Transform GunEndPoint;
    public float MaxAmmo = 10f;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    private LineRenderer _lineRenderer;
    private float _currentAmmo;
    private ScreenManager _screenManager;

    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
        _lineRenderer = GetComponent<LineRenderer>();
        _currentAmmo = MaxAmmo;
        _screenManager = GameObject.FindWithTag("ScreenManager").GetComponent<ScreenManager>();
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;
        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0) || _currentAmmo <= 0)
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }
    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }
    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }
    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }
    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();
        _currentAmmo--;
        _screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);
        _lineRenderer.SetPosition(0, GunEndPoint.position);
        StartCoroutine(FireLine());
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _lineRenderer.SetPosition(1, hit.point);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
        else
        {
            _lineRenderer.SetPosition(1, ray.GetPoint(Range));
        }
    }
    private IEnumerator FireLine()
    {
        _lineRenderer.enabled = true;
        yield return ShootingDelay - 0.05f;
        _lineRenderer.enabled = false;
    }
    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
        _currentAmmo = MaxAmmo;
        _screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }
}
 

Here’s what we did:

  1. We looked for our ScreenManager by looking for it via a tag we set on it called ScreenManager.
  2. Any time we change our ammo amount we would call UpdateAmmoText() in our _screenManager. In this case, there are 2 places: after we shoot and after we reload.

Now before we try our game, first go to our HUD game object that we attached our ScreenManager script to, and create a new Tag called ScreenManager and make that the Tag for HUD.

If we were to play the game now, you’ll see that when we shoot, our ammo goes down, and when we hit R to reload, it goes back up to our maximum amount!

 
0*51w3MiBsIBfFqdSk.png

Conclusion

That’s it for today! I thought I would get to do more, but it looks like that was not the case for me!

To recap everything we did today, we created an ammo system where after we shoot all of our bullets we have to reload.

Afterwards we create a new Text that represent our ammo count on the bottom right hand corner of the screen.

I think tomorrow on Day 19, we’ll continue to add more pieces of the game like player health and a scoring system.

Source: Day 18

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Today in Day 18, we’re going to start adding UI components into our game!

2 days ago, on Day 16, we created a reload system and just yesterday we started shooting lasers at enemies, now would be the perfect time to create a reload system and some UI to display our reload.

While we’re at it, we might as well make some other UI fixes. Here are the things that we’re going to tackle today:

  • Create the reload system and UI

The first thing that needs to be done is to create the reload system.

Without any delays, let’s get started!

Creating the Reload System

There are 2 things that need to be done for us to have a reload system. The first part is the UI that shows how many ammos we have and the second is the code that will manage it.

Let’s create the UI system first.

Player Ammo UI

First thing to do, let’s find a motivation to for our UI.

For my source of inspiration, I’m going to use Overwatch, which I think is pretty common these days:

 
0*Ka2hf9ALShsDqgc5.jpg

The basic thing is that the HP is on the bottom left corner and the ammo is on the bottom right.

Let’s get started creating the ammo UI. In the hierarchy pane, right click and under UI select Text. We’re going to call it Ammo.

As you might recall, when we do this, Unity automatically creates a Canvas screen for us. In this case, we already have one that called HUD.

To work with our UI, make sure to hit 2D button right below the Scene tab.

 
0*ItATTM3OyBH5YZrI.png

Let’s adjust the text to be at the bottom right corner of our screen.

Select Ammo and under the Rect Transform component, select the anchor presets square and while holding Shift + Alt, select bottom right, to move our text to the bottom right corner.

It’s also kind of small, so let’s change the size a bit too:

  • Width: 160
  • Height: 60
  • Text Size: 24
  • Font Style: Bold
  • Text: 30/30
  • Color: White

Here’s what we’ll see now if we pull up our game tab:

 
0*AsWLT8-q6IdfuUDL.png

Now that we have our initial setup let’s write code!

Ammo and Reload Code

We have the UI, now it’s time to write some code for reloading and ammo!

We’ll be changing our PlayerShootingController, here’s the code:

 
using UnityEngine;
using System.Collections;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    public Transform GunEndPoint;
    public float MaxAmmo = 10f;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    private LineRenderer _lineRenderer;
    private float _currentAmmo;

    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
        _lineRenderer = GetComponent<LineRenderer>();
        _currentAmmo = MaxAmmo;
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;
        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0))
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }
    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }
    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }
    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }
    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();
        _currentAmmo--;
        _lineRenderer.SetPosition(0, GunEndPoint.position);
        StartCoroutine(FireLine());
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _lineRenderer.SetPosition(1, hit.point);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
        else
        {
            _lineRenderer.SetPosition(1, ray.GetPoint(Range));
        }
    }
    private IEnumerator FireLine()
    {
        _lineRenderer.enabled = true;
        yield return ShootingDelay - 0.05f;
        _lineRenderer.enabled = false;
    }
    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
        _currentAmmo = MaxAmmo;
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }
}
 

The code flow for this is straightforward:

  1. We made 2 new variables MaxAmmo and _currentAmmo to represent how many bullets we have left to shoot.
  2. In Start() we initialize our current ammo amount.
  3. Whenever we shoot, we make sure that our ammo is above 0 otherwise we can’t shoot and then we decrement our _currentAmmo count.
  4. When we finish reloading, we’ll restore our ammo to max.

With the code, we have one problem, while we’re shooting, if we never let go of our mouse, we’ll continue to play the shooting animation and sound effect. We need to change this.

I fixed the problem by adding another check to make sure that we stop shooting when we either let go of our mouse or when we run out of bullets.

Here’s the change we did for Update():

   
  void Update ()
    {
        _timer += Time.deltaTime;
        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0) || _currentAmmo <= 0)
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }
 

Now that we have the script for reloading and shooting, we need to add our shooting mechanisms.

The first thing we’re going to do is to go to our original HUD component that we created in Day 10 and create a new script called ScreenManager.

The script will be used for ammo and later score count, and in the future health. Here’s our code:

 
using UnityEngine;
using UnityEngine.UI;
public class ScreenManager : MonoBehaviour
{
    public Text AmmoText;
    void Start()
    {
        {
            PlayerShootingController shootingController = Camera.main.GetComponentInChildren<PlayerShootingController>();
            UpdateAmmoText(shootingController.MaxAmmo, shootingController.MaxAmmo);
        }
    }
    public void UpdateAmmoText(float currentAmmo, float maxAmmo)
    {
        AmmoText.text = currentAmmo + "/" + maxAmmo;
    }
}
 

Here’s how our code works:

  1. We take in the Text game object that we use for ammo.
  2. In Start() we initialize our text ammo by getting our PlayerShootingController that’s a child of our camera and using the max ammo value we set
  3. Inside UpdateAmmoText() we give it the ammo amount to print out. I made this public, because we want to call this function from elsewhere.

There was a design decision that I was thinking of as I was going through this.

I was looking back at the score UI that was made for the Survival Shooter tutorial and it used static variables to represent the score. Static meaning you can access is anywhere, anytime without needing access to the script itself.

That worked in the context that only the manager needed to know anything about the score, however in our case, we already have our ammo amount in our PlayerShootingController, so if that’s the case, there’s no need to keep a separate instance to keep track of our ammo amount.

Instead let’s just pass in the values that we want the text to print out.

Another benefit of this is that we don’t have to re-render our text every single time we call Update() and we only change it when we need to.

The only downside is that we must get an instance of ScreenManagerwhenever we want to make any changes as opposed to the static method that was used in the tutorial.

Updating our PlayerShootingController, here’s what we get:

 
using UnityEngine;
using System.Collections;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    public Transform GunEndPoint;
    public float MaxAmmo = 10f;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    private LineRenderer _lineRenderer;
    private float _currentAmmo;
    private ScreenManager _screenManager;

    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
        _lineRenderer = GetComponent<LineRenderer>();
        _currentAmmo = MaxAmmo;
        _screenManager = GameObject.FindWithTag("ScreenManager").GetComponent<ScreenManager>();
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;
        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0) || _currentAmmo <= 0)
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }
    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }
    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }
    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }
    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();
        _currentAmmo--;
        _screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);
        _lineRenderer.SetPosition(0, GunEndPoint.position);
        StartCoroutine(FireLine());
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _lineRenderer.SetPosition(1, hit.point);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
        else
        {
            _lineRenderer.SetPosition(1, ray.GetPoint(Range));
        }
    }
    private IEnumerator FireLine()
    {
        _lineRenderer.enabled = true;
        yield return ShootingDelay - 0.05f;
        _lineRenderer.enabled = false;
    }
    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
        _currentAmmo = MaxAmmo;
        _screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }
}
 

Here’s what we did:

  1. We looked for our ScreenManager by looking for it via a tag we set on it called ScreenManager.
  2. Any time we change our ammo amount we would call UpdateAmmoText() in our _screenManager. In this case, there are 2 places: after we shoot and after we reload.

Now before we try our game, first go to our HUD game object that we attached our ScreenManager script to, and create a new Tag called ScreenManager and make that the Tag for HUD.

If we were to play the game now, you’ll see that when we shoot, our ammo goes down, and when we hit R to reload, it goes back up to our maximum amount!

 
0*51w3MiBsIBfFqdSk.png

Conclusion

That’s it for today! I thought I would get to do more, but it looks like that was not the case for me!

To recap everything we did today, we created an ammo system where after we shoot all of our bullets we have to reload.

Afterwards we create a new Text that represent our ammo count on the bottom right hand corner of the screen.

I think tomorrow on Day 19, we’ll continue to add more pieces of the game like player health and a scoring system.

Source: Day 18

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to day 17 of our Unity development. Previously, we switched out our cube gun with a real gun model.

Today, we’re going to look at how to make a gun shoot in Unity.

Originally our code sends a raycast directly from our camera, however I’ve learned from Scouting Ninja that when we shoot a projectile it comes from the gun muzzle. We did something similar in the Survival Shooter back in the Unity tutorial.

Technically since we still don’t have any projectiles coming out, shooting straight from the middle is fine, but let’s say we want to shoot something out, what would we do?

Today, I’m going to make adjustments to fire a raycast from the muzzle of our gun and then go towards where we’re facing.

To do that, I found this great Unity video tutorial to follow: Let’s Try: Shooting with Raycasts.

A lot of the things that were talked about were mentioned in the Survival Shooter tutorial and I’ve also worked on a lot of similar parts my own… wow why didn’t I just follow this tutorial….

Anyways, I’m going to cover the things I learned and added. Let’s get to it!

Debug Raycast

Have you ever wondered what your raycast would look like if you visualized it? Great! Neither have I! Glad I’m not alone on this.

The first addition I want to make is a raycast debugger that visualizes your raycast in the scene tab when you play the game.

Like so:

 
0*BkIdVLsqSO_mDXDT.png

See the green line on the right?

To attach this, all we need to do is add a line of code:

Debug.DrawRay()

Adding that into PlayerShootingController inside Update(), we’ll have something like this:

    void Update ()
    {
        _timer += Time.deltaTime;
        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
        // rest of code…
    }
 

The important part is that we’re using our camera to create a raycast with Debug.DrawRay()

We pass in very similar parameters as you might expect with a raycast except we don’t detect if we hit anything, instead we just create a green line.

Shooting from our Gun

If we want to shoot from the end of our gun, we need a location to start shooting from. We can’t use the gun itself, because we can’t exactly pinpoint the muzzle of the gun for something to be shot out.

We need to create something that indicates we shot something.

We’ll be using the example provided by the tutorial to make a Line Rendererto create a line from our gun muzzle to the target.

The first thing we need to do is some setup work.

Adding our Game Objects

The first thing we need to do is create the end of our gun:

 
0*dsnaMIUHorc-ODXL.png

I created a cube called gunEnd as a child MachingGun_00 to represent the end of the gun.

I used a cube so that it’s easier showing you the end of the gun, but I’ll be removing the Mesh Filter and Mesh Renderer so that it’ll just be an empty object.

The position/scale might differ for you than the ones I set above, but just mess around with it in Unity until you get something satisfactory.

In MachingGun_00 we’re going to create a Line Renderer component to represent a laser that we will shoot from our gun to our mouse click.

Make sure that the Line Renderer has a width of 0.1

With all of this in place, we’re ready to use these components for our script.

Writing the code to Fire Bullets from our Gun

We’re going to make some new additions to our PlayerShootingController to use our new pieces and shoot a laser from our gun endpoint.

Here’s the code:

 
using UnityEngine;
using System.Collections;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    public Transform GunEndPoint;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    private LineRenderer _lineRenderer;

    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
        _lineRenderer = GetComponent<LineRenderer>();
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;
        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0))
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }
    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }
    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }
    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }
    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();
        _lineRenderer.SetPosition(0, GunEndPoint.position);
        StartCoroutine(FireLine());
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _lineRenderer.SetPosition(1, hit.point);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
        else
        {
            _lineRenderer.SetPosition(1, ray.GetPoint(Range));
        }
    }
    private IEnumerator FireLine()
    {
        _lineRenderer.enabled = true;
        yield return ShootingDelay - 0.05f;
        _lineRenderer.enabled = false;
    }
    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }
}
 

Here’s the flow for our code:

  1. We have two new variables GunEndPoint and _lineRenderer that we instantiate in Start()
  2. The way LineRenderer works is that you set 2 points to form the line you want to make. The 1st point is at the tip of our gun. The second point depends on our raycast.
  3. If our raycast hits something, we want to set whatever we hit as our 2nd point in our line.
  4. If our raycast doesn’t hit anything, we just make the player’s shot go towards the center of our screen by the length of our bullet
  5. Finally, we create a Coroutine that calls FireLine() that enables our LineRenderer component so we can it, wait about 0.05 seconds and then disable it. Doing this gives us the illusion that we’re shooting something.

After we add in our code, make sure that we drag our gunEnd game object into the appropriate slot in our PlayerShootingController script and now we can shoot purple lasers!

Conclusion

So that’s it for Day 17.

Today we learned how to use raycast debug so that we can see where our raycast will end up and we used a line renderer to shoot lasers from the end of our gun to where the player is standing at.

It’s very exciting to say that I’ve come a long way since I first started doing the Survival Shooter tutorial, especially since I was just copying code.

Now I’m fluent enough in Unity that I can write some code myself!

With all that being said, it’s getting late so I’m going to sleep. I’ll see you all in day 18 where I’ll be setting up the UI for the game!

Original Day 17

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

Welcome back to Day 16! Today we’re going to go fix some of the assets for the game.

Specifically, today I want to:

  1. Use a real gun asset
  2. Add the shooting effect back in
  3. Create code to trigger reloading

Do you know what that means?

We’re going to go back to the Unity asset store to find new assets! Let’s get started!

Setting Up a Real Gun Asset

I looked around for a gun asset and I found a great machine gun model to use!

I found this great looking Machine Guns asset.

 
0*0tNTvu8WuApvHAfN.png

After downloading and loading the asset into our Unity game, you’ll find the files in the FreeMachineGun folder in the main project tab.

The first thing we should do is replace our cube with our gun asset.

In the folder FreeMachineGun: FreeMachineGun > Prefabs we find MachineGun_00. Drag the prefab into our hierarchy and make it a child of our Main Camera game object, making it the child of our previous Gun game object.

I made some configuration to my transform to make it fit here’s what I have, however you might have to eyeball it yourself.

  • Position: (0.25, -0.5, 0.75)
  • Rotation: (0. 90, 0)

You should have something like this:

 
0*4OEUBwAtHo0rTe5c.png

I like how straightforward this is!

Setting up the Gun Animation to Run When We Shoot

Right now, when we left click, our gun will start shooting and spilling out ammos.

You might be thinking: “Wow! This model is amazing it even has the shooting code done for me!”

Unfortunately, it isn’t that easy. What we’re seeing is the particle effect of the gun that we activate in our PlayerShootingController.

You might also notice that after we shoot, our particle effect never stops either, that’s because in our code, we never had to stop our particle effect before.

Before we go in and fix this, let’s first understand the states to our animation.

For us to interact with the animation of a model, we must go to the Animatortab to set animation states and transitions.

Select MachineGun_00 in the hierarchy pane and then go to the Animatortab.

If the Animator tab isn’t available for you can find it in Window > Animator

Here’s what we see.

 
0*ptThed578wRnCBsU.png

We have 3 trigger parameters to work with:

  • DoOpen
  • DoReload
  • Shoot

As for our state, we have 4 states:

  • Default
  • MachingGun_open
  • MachineGun_shoot
  • MachineGun_reload

Looking at our transitions…

In default:

  • Shoot transition to the MachineGun_shoot
  • DoOpen transitions to MachineGun_open
  • DoReload transitions to MachineGun_reload

In MachineGun_shoot

  • Shoot transition back to default
  • DoOpen transitions to MachineGun_open
  • DoReload transitions to MachineGun_reload

In MachineGun_open:

  • Shoot transition to the MachineGun_shoot
  • DoOpen transitions back to default
  • DoReload transitions to MachineGun_reload

In MachineGun_reload:

  • After the animation time is finish we transition to default

Now that we understand how the trigger logic works, we can work on the shooting script.

Now I don’t know what the DoOpen animation is for, however we don’t need it for the game in my mind. We just need to shoot and reload.

Creating the Code to Shoot

Now that we have the assets in place, we’re going to write the code to use it.

The first thing is that for some reason, I attached the PlayerShootingController to the camera. I’m going to move that to our new MachineGun_00 game object.

If you created a new PlayerShootingController, make sure the settings are:

  • Range: 100
  • Shooting Delay: 0.2
  • Shot Sfx Clips: Machine_Gunfire_01

Before going to the code, I had to think about the general cases that I would encounter for our animation to work.

It’s interesting to think about it, because we have 3 states that our gun can be in: default, shooting, and reloading. Now imagine how much more complicated if we had to transition between even more!

When I first started coding, I just had the general case, press R to reload and then shoot.

However, there were problems where my code would put me in permanent shooting animation mode among many other things.

There were some that I had to encounter, here were the specific animation cases I had to consider:

  1. Hit reload while we’re not doing anything
  2. Hit reload while we’re shooting
  3. Shoot while we’re reloading
  4. Never let go of our mouse before we shoot, reload, and then hold on until after we’re done reloading

Without any further waiting, here’s our code:

 
using UnityEngine;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    void Start () {
     _camera = Camera.main;
     _particle = GetComponentInChildren<ParticleSystem>();
     Cursor.lockState = CursorLockMode.Locked;
     _shootableMask = LayerMask.GetMask("Shootable");
     _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading)
        {
               Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
           else if (!Input.GetMouseButton(0))
        {
               StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
               }
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }
    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }
    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }
    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }
    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
    }
    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }
}
 

There’s been a lot of changes and addition to the code, but in general here’s the flow:

  1. We created more variables to keep track of what state we’re in. Currently we’re using Booleans, however I think using Enums to represent our state might be a better idea, especially if we have more states to keep track of.
  2. In Start() we initialize our new boolean states
  3. In Update(), while we’re not reloading and we hit Shoot, if we haven’t started shooting, we’ll start our shooting animation in TriggerShootingAnimation() and we’ll run our shooting logic in Shoot()
  4. Whenever we let go of our mouse, we’ll go to the else statement in Update() and stop our shooting animation and run our stop shooting logic in StopShooting() which stops our music and particle effect
  5. A side note: in Shoot(), I started the particle effect when we hit something with the raycast, we want it to always run whenever we shoot.
  6. In Update() when we want to reload, we press R and we’ll go into StartReloading(), here we would trigger our reload animation to start and disable our shooting so the players can’t continue shooting.
  7. We have a public function ReloadFinish() that tells us we’re down reloading. We’re going to have to go to the animation for our machine gun and add an event to run this function whenever we’re done reloading.

So now that we have our code in place, let’s add the event into our reload animation to run our ReloadFinish() code when the animation finishes.

To do that, select MachineGun_00 and select the Animation tab.

If you don’t have the Animation tab, you can open it by going to Window -> Animation.

Here’s what we will have at the end:

 
0*sro6dcB3cMcljozD.png

In the animation tab:

  1. Select MachineGun_reload as our animation
  2. Go to frame 68
  3. Click the add event button below our frame number
  4. Select ReloadFinish() as the function to run once we get to that frame

With all of this in place, we’ll be able to reload now when we hit R and then resume normal shooting controls after the animation ends.

Conclusion

There we go! Today we added a real gun asset to replace our fake cube gun. As you can see, it wasn’t that bad and a lot of the things we did, we already learned from the Zombie Shooter tutorial we did at the end.

With our gun in place, we’re done now, right? WRONG!

Back when I was doing my Survival shooter post, Scouting Ninja commented to me on how shooting really works and it turns out that the raycast we create isn’t actually coming directly from the screen.

It’s coming from the muzzle of our gun to the middle of the screen.

Tomorrow, I’m going to investigate how to shoot a raycast from our gun muzzle instead of directly from our screen!

Until then, I’ll see you all in Day 17!

Source: Day 16

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

 

Josh Chang

In Day 15, we’re going to continue adding more sound effects to our existing game, specifically the:

  • shooting sound effect
  • sound of the knight getting hit
  • player walking

Today’s going to be a relatively short day, but get started!

Adding Enemy Hit Sounds

To start off, we’re going to do something like in Day 14. We’re going to create Audio Source Components in code and play the sound effects from there.

For shooting we need to add our code to EnemyHealth:

 
using UnityEngine;
public class EnemyHealth : MonoBehaviour
{
    public float Health = 100;
    public AudioClip[] HitSfxClips;
    public float HitSoundDelay = 0.5f;
    private Animator _animator;
    private AudioSource _audioSource;
    private float _hitTime;
    void Start()
    {
        _animator = GetComponent&lt;Animator&gt;();
        _hitTime = 0f;
        SetupSound();
    }
    void Update()
    {
        _hitTime += Time.deltaTime;
    }
    
    public void TakeDamage(float damage)
    {
        if (Health &lt;= 0) { return; } Health -= damage; if (_hitTime &gt; HitSoundDelay)
        {
            PlayRandomHit();
            _hitTime = 0;
        }
        if (Health &lt;= 0)
        {
            Death();
        } 
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent&lt;AudioSource&gt;();
        _audioSource.volume = 0.2f;
    }
    private void PlayRandomHit()
    {
        int index = Random.Range(0, HitSfxClips.Length);
        _audioSource.clip = HitSfxClips[index];
        _audioSource.Play();
    }
    private void Death()
    {
        _animator.SetTrigger("Death");
    }
}
 

The flow of new our code is:

  1. We create our Audio Source component in SetupSound() called from Start()
  2. We don’t want to play the sound of the knight being hit every time we hit it, that’s why I set a _hitTime in Update() as a delay for the sound
  3. Whenever an enemy take damage, we see if we’re still in a delay for our hit, if we’re not, we’ll play a random sound clip we added.

The code above should seem relatively familiar as we seen it before in Day 14.

Once we have the code setup, the only thing left to do is to add the Audio clips that we want to use, which in this case is Male_Hurt_01 — Male_Hurt_04

That’s about it. If we were shoot the enemy now, they would make damage hits.

Player Shooting Sounds

The next sound effect that we want to add is the sound of our shooting. To do that, we’re going to make similar adjustments to the PlayerShootingController.

 
using UnityEngine;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren&lt;ParticleSystem&gt;();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
           SetupSound();
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;
        if (Input.GetMouseButton(0) &amp;&amp; _timer &gt;= ShootingDelay)
        {
            Shoot();
        }
           else if (!Input.GetMouseButton(0))
        {
            _audioSource.Stop();
        }
    }
    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _particle.Play();
            EnemyHealth health = hit.collider.GetComponent&lt;EnemyHealth&gt;();
            EnemyMovement enemyMovement = hit.collider.GetComponent&lt;EnemyMovement&gt;();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
    }
    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent&lt;AudioSource&gt;();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }
}
 

The flow of the code is like the previous ones, however for our gun, I decided that I want to use the machine gun sound instead of individual pistol shooting.

  1. We still setup our Audio Component code in Start()
  2. The interesting part is that in Update(), we play our Audio in Shoot() and as long as we’re holding down the mouse button, we’ll continue playing the shooting sound and when we let go, we would stop the audio

After we added our script, we attach Machine_Gunfire_01 to the script component.

Player Walking Sound

Last but not least, we’re going to add the player walking sound in our PlayerController component.

 
using UnityEngine;
public class PlayerController : MonoBehaviour {
    public float Speed = 3f;
    public AudioClip[] WalkingClips;
    public float WalkingDelay = 0.3f;
    private Vector3 _movement;
    private Rigidbody _playerRigidBody;
    private AudioSource _walkingAudioSource;
    private float _timer;
    private void Awake()
    {
        _playerRigidBody = GetComponent<Rigidbody>();
        _timer = 0f;
        SetupSound();
    }
    private void SetupSound()
    {
        _walkingAudioSource = gameObject.AddComponent<AudioSource>();
        _walkingAudioSource.volume = 0.8f;
    }
    private void FixedUpdate()
    {
        _timer += Time.deltaTime;
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");
        if (horizontal != 0f || vertical != 0f)
        {
            Move(horizontal, vertical);
        }
    }
    private void Move(float horizontal, float vertical)
    {
        if (_timer >= WalkingDelay)
        {
             PlayRandomFootstep();
            _timer = 0f;
        }
        _movement = (vertical * transform.forward) + (horizontal* transform.right);
        _movement = _movement.normalized * Speed * Time.deltaTime;
        _playerRigidBody.MovePosition(transform.position + _movement);
    }
    private void PlayRandomFootstep()
    {
        int index = Random.Range(0, WalkingClips.Length);
        _walkingAudioSource.clip = WalkingClips[index];
        _walkingAudioSource.Play();
    }
}
 

Explanation

This code is like what we’ve seen before, but there were some changes made.

  1. Like usual, we create the sound component in Start() and a walking sound delay
  2. In Update(), we made some changes. We don’t want play our walking sound whenever we can. We only want to play it when we’re walking. To do this, I added a check to see if we’re moving before playing our sound in Move()

Also notice that the audio sound is 0.8 as oppose to our other sounds. We want our sound to be louder than the other players so we can tell the difference between the player walking and the enemy.

After writing the script, we don’t forget to add the sound clips. In this case, I just re-used our footsteps by using Footstep01 — Footstep04

Conclusion

I’m going to call it quits for today for Day 15!

Today we added more gameplay sound into the game so when we play, we have a more complete experience.

I’m concerned about what happens when we have more enemies and how that would affect the game, but that’ll be for a different day!

Original Day 15.

Visit the 100 Days of Unity VR Development main page.

Visit our Homepage

Josh Chang

We’re back in Day 14. I finally solved the pesky problem from Day 13 where the Knight refuses to get pushed back when we shoot at him.

Afterwards, I decided to get some sound effects to make the game a little livelier.

Without delay, let’s get started!

Adding Player Hit Effects Part 2

As you might recall, we last ended up trying to push back the Knight when we shoot them by changing the Knight’s velocity, however the Knight continues to run forward.

The problem

After a long investigation, it turns out that Brute running animation that I used naturally moves your character’s position forward.

The solution

After finally searching for unity animation prevents movement I found the answer on StackOverflow.

In the animator, disable Apply Root Motion and then we must apply the movement logic ourselves (which we already are).

uncheck-root-animation.png

Writing the Knock Back code

Once we have our Root Motion disabled. We’re relying on our code to move our knight.

The first thing we need to do is update our PlayerShootingController script to call the knock back code:

using UnityEngine;

public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;

    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;

    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;

        if (Input.GetMouseButton(0) && _timer >= ShootingDelay)
        {
            Shoot();
        }
    }

    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();

        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _particle.Play();

            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
    }
}

The biggest change is that we get our EnemyMovement script and then call KnockBack() which we haven’t implemented yet.

Once we have this code in, we need to implement KnockBack() inside our EnemyMovement script. Here’s what it looks like:

using UnityEngine;
using UnityEngine.AI;

public class EnemyMovement : MonoBehaviour
{
    public float KnockBackForce = 1.1f;

    private NavMeshAgent _nav;
    private Transform _player;
    private EnemyHealth _enemyHealth;

    void Start ()
    {
        _nav = GetComponent<NavMeshAgent>();
        _player = GameObject.FindGameObjectWithTag("Player").transform;
        _enemyHealth = GetComponent<EnemyHealth>();
    }
    
    void Update ()
    {
        if (_enemyHealth.Health > 0)
        { 
            _nav.SetDestination(_player.position);
        }
        else
        {
            _nav.enabled = false;
        }
    }

    public void KnockBack()
    {
        _nav.velocity = -transform.forward * KnockBackForce;
    }
}

I know this was a one liner for KnockBack(), but there was a lot of work involved to get to this point.

Here’s how the code works:

  1. When our shooting code hits the enemy, we call KnockBack() which sets the velocity to be the direction behind the knight, making the illusion of being pushed back.
  2. This is only temporary as our Nav Mesh Agent will come back and move our Knight towards the player in the next Update()
  3. Here’s how KnockBackForce effects the velocity
    1. At 1, the knight stays in place when you shoot
    2. <1, the knight gets slowed down
    3. >1, the knight gets pushed back

Adding Sound Effects

Now that we finally solved the knockback problem, we moved on to the next thing.

At this point, playing the game seems dull. Do you know what could make things a little bit more interesting? Sound effects!

I went back to the Unity Asset Store to find sound effect assets specifically:

  1. Player shooting sound
  2. Player walking sound
  3. Player hit sound
  4. Enemy hit sound
  5. Enemy running sound
  6. Enemy attack sound

Randomly searching on Unity, I found the Actions SFX Vocal Kit which contains everything we need. Fantastic!

action-sfx.png

Once we have finished downloading and importing the SFX into our Unity project, we’ll start using them.

Adding Enemy Hit Sound Effects

Adding the script

The first thing we’re going to do is that we need to add our Male_Hurt audio clips to our Knight.

Normally, we need to add an Audio Source component for our Knight. However, before that, let’s step back and think: what sounds do our knight need to play?

  1. Hit sound
  2. Walking sound
  3. Attack sound

If we were to add an Audio Source component to the Knight Object and use that to play the sound, what will happen is that one sound will immediately be replaced by the other one. We don’t want that.

What we could do is create multiple AudioSources components and then manually attach them to our script, however that’s not very scalable if we ever decided that we needed more types of sounds.

Instead I found this great way to add multiple audio sources on a single GameObject.

The idea is that instead of manually creating multiple components and then attaching them to a script component, why not create the component in code?

Here’s what I did:

using UnityEngine;
using UnityEngine.AI;

public class EnemyMovement : MonoBehaviour
{
    public float KnockBackForce = 1.1f;
    public AudioClip[] WalkingClips;
    public float WalkingDelay = 0.4f;

    private NavMeshAgent _nav;
    private Transform _player;
    private EnemyHealth _enemyHealth;
    private AudioSource _walkingAudioSource;
    private float _time;

    void Start ()
    {
        _nav = GetComponent<NavMeshAgent>();
        _player = GameObject.FindGameObjectWithTag("Player").transform;
        _enemyHealth = GetComponent<EnemyHealth>();
        SetupSound();
        _time = 0f;
    }
    
    void Update ()
    {
        _time += Time.deltaTime;
        if (_enemyHealth.Health > 0)
        { 
            _nav.SetDestination(_player.position);
            if (_time > WalkingDelay && _animator.GetCurrentAnimatorStateInfo(0).IsName("Run")))
            {
                PlayRandomFootstep();
                _time = 0f;
            }
        }
        else
        {
            _nav.enabled = false;
        }
    }

    public void KnockBack()
    {
        _nav.velocity = -transform.forward * KnockBackForce;
    }

    
}

There’s a lot of code that was added in, but I tried to separate them as much as I can to easy to understand pieces.

Here’s the flow:

  1. In Start(), we instantiate our new private fields, specifically our new variables:
    1. _walkingAudioSource: our AudioSource for our steps
    2. _time: to track how long the enemy steps take
  2. We call SetupSound() from Start() and create a new instance of an AudioSource that will only appear when the game starts and we set the volume to 0.2f
  3. In Update(), we add logic to play the stepping sound whenever the it has been 0.2 seconds and that if we’re still in the running animation.
    1. Note: GetCurrentAnimatorStateInfo(0) the 0 refers to index 0 layer, which I’m not really sure why, but that’s what people use. From there we can check which state the knight is in.
  4. In PlayRandomFootstep(), we randomly choose the walking sound clips that we downloaded and play them

Once we have all of this we need to add the audio clips in.

Go to EnemyMovement script attached to the Knight and then under Walking Clips change the size to 4. We can do this, because Walking Clips is an array of clips.

Then add in Footstep01-04 into each spot. Make sure that Walking Delay is set to 0.4 if it’s not already.

movement-script.png

Run the game and you’ll see that the enemy makes running sounds now!

If you’re using a different animation, you might have to change the Walking Delay to match the animation, but on the high level, that’s what you must do!

Whenever the knight attacks us, the sound will stop and whenever the knight resumes running after us (with the help of some shooting knockback) the running sound will resume!

Conclusion

Today in Day 14, we found the problem with the knight knockback had something to do with the root animation we used.

After disabling it we can start adding our knockback code without any problems.

With the knock back implemented, the next thing that we added was sound effects. We found some assets in the Unity store and then we added them to our enemy, where for the first time, we created a component via code.

My concern at this point is what happens when we start spawning a lot of knights? Will that create an unpleasant experience?

Either way, come back tomorrow for Day 15, where I decided I’m going to add the enemy hit sound and the player shooting sound.

Original Day 14

Visit the 100 Days of Unity VR Development main page

Visit the Homepage

 

Josh Chang

Welcome back to day 13 of the 100 days of VR! Last time we created enemy motions that used the Nav Mesh Agent to help us move our enemy Knight.

We added a trigger collider to help start the attack animations when the enemy got close to the player.

Finally, we added a mesh collider to the body of the knight so when it touches the player during its attack animation we’ll be able to use the damage logic.

Today we’re going to go on and implement the shooting logic for our player and to fix the annoying bug where the player would be perpetually moving after they come in contact with any other colliders.

Fixing the Drifting Problem

My first guess at what the problem is that something must be wrong with our Rigid Body component of the player.

If we recall, the Rigid Body is in charge Unity’s physics engine on our player.

According to the documentation for RigidBody, the moment that anything collides with our player, the physics engine will exert velocity on us.

At this point we have 2 options:

  • Set our velocity to be 0 after any collision
  • Make our drag value higher

What is drag? I didn’t really understand it the first time we encountered it either, but after doing more research, specifically reading it here in Rigidbody2D.drag drag is how long it takes for an object to slow down over friction. Specifically, the higher the faster it is for us the faster for us to slow down.

I switched the drag value in the RigidBody from 0 to 5.

 
0*R1pNI1nw_710eEnj.png

I’m not sure what the value represents, but before our velocity never decreased from friction because of our drag value, but after we added one in, we’ll start slowing down over time.

Adding the Enemy Shooting Back into the Game

After solving the drag problem, we’re finally going back to the main portion of the game: shooting our enemy.

There will be 2 places that we’re going to have to add our code in: EnemyHealth and EnemyMovement.

EnemyHealth:

using UnityEngine;
public class EnemyHealth : MonoBehaviour
{
    public float Health = 10;
    private Animator _animator;
    void Start()
    {
        _animator = GetComponent<Animator>();
    }
    
    public void TakeDamage(float damage)
    {
        if (Health <= 0)
        {
            return;
        }
        Health -= damage;
        if (Health <= 0)
        {
            Death();
        } 
    }
    private void Death()
    {
        _animator.SetTrigger("Death");
    }
}

Here’s the new flow of the code we added:

  1. In Start() we instantiate our Animator that we’ll use later to play the death animation
  2. In TakeDamage() (which is called from the PlayerShootingController) when the enemy dies, we call Death().
  3. In Death(), we set death trigger to make the Knight play the death animation

Next, we need to make a quick change to EnemyMovement to stop our Knight from moving when it dies.

using UnityEngine;
using UnityEngine.AI;
public class EnemyMovement : MonoBehaviour
{
    private NavMeshAgent _nav;
    private Transform _player;
    private EnemyHealth _enemyHealth;
    void Start ()
    {
        _nav = GetComponent<NavMeshAgent>();
        _player = GameObject.FindGameObjectWithTag("Player").transform;
        _enemyHealth = GetComponent<EnemyHealth>();
    }
    
    void Update ()
    {
        if (_enemyHealth.Health > 0)
        { 
            _nav.SetDestination(_player.position);
        }
        else
        {
            _nav.enabled = false;
        }
    }
}

Here’s the code flow:

  1. In Start() we grab the EnemyHealth script so we can access the knights health.
  2. In Update() if the knight is dead, we disable the Nav Mesh Agent, otherwise it continues walking like normal.

Now when we play the game, the knight enters the death state when defeated, like so:

 
0*C2DwF7gT_TvhEP4A.png

Improving Shooting Mechanics

At this point you might notice a problem….

…Okay, I know there are many problems, but there are two specifics problems I’m referring to.

  1. The knight dies almost instantly whenever we shoot
  2. When we shoot, we don’t really have anything happen to the enemy to make us feel we even shot them

So we’re going to fix these problems

Adding a shooting delay

Right now, we always shoot a raycast at the enemy knight whenever Update() detects that our mouse is held down.

So, let’s add a delay to our Player Shooting Controller script.

using UnityEngine;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;
        if (Input.GetMouseButton(0) && _timer >= ShootingDelay)
        {
            Shoot();
        }
    }
    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _particle.Play();
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
    }
}

Here’s the logic for what we added:

  1. We created our time variables to figure out how long we must wait before we shoot again
  2. In Update() if we waited long enough, we can fire again
  3. Side note: I decided to move all of the shooting code into Shoot()
  4. Inside Shoot() because the player fired, we’ll reset our timer and begin waiting until we can shoot again.

Adding Player Hit Effects

Setting up the Game Objects

When we shoot our enemy knight, nothing really happens. He’ll just ignore you and continue walking towards you.

There are a lot of things we can do to make this better:

  1. Add sound effects
  2. Add damage blood effects
  3. Push him back
  4. All of the above

1) will be added in eventually, 2) might be done, but 3) is what I’m going to implement.

Every time we shoot the knight, we want to push it back. This way if a mob of them swarm at us, we’ll have to manage which one to shoot first.

This little feature took a LONG time to resolve.

The problem

Whenever we shoot an enemy, we want to push them back, however, the Nav Mesh Agent would override any changes we tried. Specifically, the knight will always continue moving forward.

The solution

We write some code that changes the velocity of the Nav Mesh Agent to go backwards for a couple of units.

However, when I did that, the knight continued running forward!

Why?

That’s a good question, one that I’m still investigating and hopefully find a solution by tomorrow.

End of Day 13

For the first time ever today, I started on a problem, that I couldn’t solve in a day.

I’m expecting this to become more common as we start jumping deeper and deeper.

With that being said, today we fixed the player’s drifting problem by using drag and adding an enemy death animation when they run out of health.

Tomorrow I’ll continue investigating how I can push the enemy back.

See you all on Day 14! Or whenever I can figure out this knockback problem!

Original Day 13

Visit the main 100 Days of Unity VR Development page

Visit my homepage

Josh Chang

Here we are at Day 12 of the 100 days of VR. Yesterday, we looked at the power of rig models and Unity’s mecanim system (which I should have learned but ignored in the Survival Shooter tutorial…)

Today, we’re going to continue off after creating our animator controller.

We’re going to create the navigation component to our Knight Enemy to chase and attack the player. As you might recall, Unity provides us an AI pathfinder that allows our game objects to move towards a direction while avoiding obstacles.

Moving the Enemy toward the Player

Setting up the Model

To be able to create an AI movement for our enemy, we need to add the Nav Mesh Agent component to our Knight game object. The only setting that I’m going to change is the Speed, which I set to 2.

At this point, we can delete our old enemy game object. We don’t need it anymore.

Next up, we need to create a NavMesh for our enemy to traverse.

Click on the Navigation panel next to the Inspector.

If it’s not there, then click on Window > Navigation to open up the pane.

Under the bake tab, just hit bake to create the NavMesh. I’m not looking to create anything special right now for our character.

Once we finish, we should have something like this if we show the nav that we created.

Make sure that the environment parent game object is set to static!

 
0*llXrx6FTpGAwl0DC.png

Creating the Script

At this point, the next thing we need to do is create the script that allows the enemy to chase us.

To do that, I created the EnemyMovement script and attach it to our knight.

Here’s what it looks like right now:

 
using UnityEngine;
using UnityEngine.AI;
public class EnemyMovement : MonoBehaviour
{
    private NavMeshAgent _nav;
    private Transform _player;
	void Start ()
	{
	    _nav = GetComponent<NavMeshAgent>();
	    _player = GameObject.FindGameObjectWithTag("Player").transform;
	}
	
	void Update ()
	{
	    _nav.SetDestination(_player.position);
	}
}
 

It’s pretty straightforward right now:

  • We get our player GameObject and the Nav Mesh Agent Component.
  • We set the Nav Agent to chase our player

An important thing that we have to do to make sure that the code works is that we have to add the Player tag to our character to make sure that we grab the GameObject.

After that, we can play the game and we can see that our Knight enemy will chase us.

 
0*JtOjaGIFNeWqRZD6.png

Using the Attack Animation

Right now the Knight would run in circle around us. But how do we get it to do an attack animation?

The first thing we need to do is attach a capsule collider component onto our knight game object and made these settings:

  • Is Trigger is checked
  • Y Center is 1
  • Y Radius is 1.5
  • Y Height is 1

Similar to what we did in the Survival Shooter, when our Knight gets close to us, we’ll switch to an attack animation that will damage the player.

With our new Capsule Collider get into contact with the player, we’re going to add the logic to our animator to begin the attack animation.

First, we’re going to create a new script called EnemyAttack and attach it to our Knight.

Here’s what it looks like:

 
using UnityEngine;
using System.Collections;

public class EnemyAttack : MonoBehaviour
{
    Animator _animator;
    GameObject _player;
    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
    }
    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
    }
    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
    }
}
 

The logic for this is similar to what we seen in the Survival Shooter. When our collider is triggered, we’ll set our “IsNearPlayer” to be true so that we’ll start the attacking animation and when our player leaves the trigger range, the Knight will stop attacking.

Note: if you’re having a problem where the Knight stops attacking the player after the first time, check the animation clip and make sure Loop Time is checked. I’m not sure how, but I disabled it.

Detecting Attack Animation

Adding a Mesh Collider

So now the Knight will start the attack animation. You might notice that nothing happens to the player.

We’re not going to get to that today, but we’re going to write some of the starter code that will allow us to do damage later.

Currently, we have a Capsule Collider that will allow us to detect when the enemy is within striking range. The next thing we need to do is figure out if the enemy touches the player.

To do that, we’re going to attach a Mesh Collider on our enemy.

Unlike the previous collider which is a trigger, this one will actually be to detect when the enemy collides with the player.

Make sure that we attach the body mesh that our Knight uses to our Mesh Collider

 
0*DMeE5Yxev4tR0oWf.png

I will take note that for some reason the Knight’s mesh is below the floor, however I’ve not encountered any specific problems with this so I decided to ignore this.

Adding an Event to our Attack Animation

Before we move on to writing the code for when the Knight attacks the player, we have to add an event in the player animation.

Specifically, I want to make it so that when the Knight attacks, if they collide with the player, we’ll take damage.

To do that, we’re going to do something similar to what the Survival Shooter tutorial did. We’re going to add an event inside our animation to call a function in our script.

We have 2 ways of doing this.

  1. We create an Animation event on imported clips from the model
  2. We add the Animation Event in the Animation tab from the animation clip

Since our knight model doesn’t have the animation we added in, we’re going to add our event the 2nd way.

We want to edit our Attack1 animation clip from the Brute Warrior Mecanim pack. inside the Animator tab

While selecting our Knight Animator Controller, click on Attack1 in the Animator and the select the Animation tab to open it.

If either of these tabs aren’t already opened in your project, you can open them by going to Windows and select them to put them in your project.

Now at this point, we’ll encounter a problem. Our Attack1 animation is read only and we can’t edit it.

What do we do?

According to this helpful post, we should just duplicate the animation clip.

So that’s what we’re going to do. Find Attack1 and hit Ctrl + D to duplicate our clip. I’m going to rename this to Knight Attack and I’m going to move this into my animations folder that I created in the project root directory.

Back in our Animator tab for the Knight Animator Controller, I’m going to switch the Attack1 state to use the new Knight Attack animation clip instead of the previous one.

 
0*dhRrzXbbxXG6CxVd.png

Next, we’re going to have to figure out what’s a good point to set our trigger to call our code.

To do this, I dragged out the Animation tab and docked it pretty much anywhere else in the window, like so:

 
0*aBof0RY-d73q4BcR.png

Select our Knight object in the game hierarchy and then you can notice that back in the animation tab, the play button is clickable now.

If we click it, we’ll see that our knight will play the animation clip that we’re on.

Switch to Knight Attack and press play to see our attack animation.

From here, we need to figure out where would be a good point to run our script.

Playing the animation, I believe that triggering our event at frame 16 would be the best point to see if we should damage the player.

 
0*c7KATYMpgSLC4fzF.png

Next we need to click the little + button right below 16 to create a new event. Drag that event to frame 16.

From under the Inspector we can select a function from the scripts attached to play. Right now, we don’t have anything, except for OnTrigger().

For now, let’s create an empty function called Attack() in our EnemyAttackscript so we can use:

 
using UnityEngine;
using System.Collections;

public class EnemyAttack : MonoBehaviour
{
    Animator _animator;
    GameObject _player;
    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
    }
    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
    }
    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
    }

    void Attack()
    {
        
    }
}
 

All I did was that I added Attack() in.

Now that we have this code, we might have to re-select the animation for the new function to be shown, but when you’re done, you should be able to see Attack() and we should have something like this now:

 
0*B9eZfqkJqgC4z-Io.png

Updating our EnemyAttack Script

So now that we finally have everything in our character setup, it’s finally time to get started in writing code.

So back in our EnemyAttack script, here’s what we have:

 
using UnityEngine;
using System.Collections;

public class EnemyAttack : MonoBehaviour
{
    private Animator _animator;
    private GameObject _player;
    private bool _collidedWithPlayer;
    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
    }
    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
        print("enter trigger with _player");
    }
    void OnCollisionEnter(Collision other)
    {
        if (other.gameObject == _player)
        {
            _collidedWithPlayer = true;
        }
        print("enter collided with _player");
    }
    void OnCollisionExit(Collision other)
    {
        if (other.gameObject == _player)
        {
            _collidedWithPlayer = false;
        }
        print("exit collided with _player");
    }
    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
        print("exit trigger with _player");
    }
    void Attack()
    {
        if (_collidedWithPlayer)
        {
            print("player has been hit");
        }
    }
}
 

Here’s what I did:

  1. Added OnCollisionExit() and OnCollisionEnter() to detect when our Mesh Collider comes into contact with our player.
  2. Once it does, we set a boolean to indicate that we’ve collided with the enemy.
  3. Then when the attack animation plays, at exactly frame 16, we’ll call Attack(). If we’re still in contact with the Mesh Collider, our player will be hit. Otherwise we’ll successfully have dodged the enemy.

And that’s it!

Play the game and look at the console for the logs to see when the knight gets within attacking zone, when he bumps into the player, and when he successfully hits the player.

There’s actually quite a bit of ways we could have implemented this and I’m not sure which way is correct, but this is the thing I have come up with:

Other things that we could have done, but didn’t was:

  1. Made it so that if we ever come in contact with the enemy, whether attacking or not, we would take damage.
  2. Created an animation event at the beginning of Knight Attack and set some sort of _isAttacking boolean to be true and then in our Update(), if the enemy is attacking and we’re in contact with them, the player takes damage, then set _isAttacking to be false so we don’t get hit again in the same animation loop.

Conclusion

And that’s that for day 12! That actually took a lot longer than I thought!

Initially, I thought it would be simply applying the Nav Mesh Agent like we did in the Survivor Shooter game, however, when I started thinking about attack animations, things became more complicated and I spent a lot of time trying to figure out how to damage the player ONLY during the attack animation.

Tomorrow, I’m going to update the PlayerShootingController to be able to shoot our Knight enemy.

There’s a problem in our script. Currently, whenever we run into an enemy, for some strange reason, we’ll start sliding in a direction forever. I don’t know what’s causing that, but we’ll fix that in another day!

Original Day 12.

Visit the main 100 Days of Unity VR Development page.

Visit our home page!

Josh Chang

Sorry for the late post. I posted it on my site, but I forgot to share it!

Welcome back to Day 11 of my 100 days of VR development!

Continuing from where we left off, we have a cube as an enemy, we can do better than that. In fact, today, after playing around with creating my own character animations with rigging and Unity’s Mecanim system and I’ll tell you that I can finally begin to appreciate what Unity provides for amateur developers.

What am I talking about? You’ll see!

Getting the Assets

The first thing I did for the enemies was that I went straight to the Asset store to find a free model that I can use.

As expected, they exist.

Here was a couple that I found:

Toon RTS Units

0*3mIEgFirNfH3VO0H.png

Fantasy Monster Skeleton

0*C-Nxmp74s9nwe18D.png

Strong Knight

0*wzJ3Cp_xbDXaA8Pd.png

Don’t ask me why 2 of the 3 models were skeletons. They just were okay!

I imported the first toon skeleton into my game and went to create my Animator Controller for it, but at this point I realized something important.

There aren’t any death animation clips for the model!

Without knowing anything about 3D modeling I’m stuck.

Having no choice I decided to move on and look at the next skeleton. It has 9 animations, including death, which is exactly what I needed.

Feeling happy having one working model, I went to the next one the Strong Knight. Reading the description, it says: “mecanim humanoid rig(no animations!)”.

What does that mean?

I investigated further and here’s what I learned.

What are rigs?

Rigs are basically a skeleton structure for our character models. Unity provides classifications for these, the most common one being humanoid creatures.

From my understanding, we can create animations by capturing the movements of these bone structures to create an animation.

What this means for us is that if we want to animate the Strong Knight, we need to get Blender and Rigify to create the animations…

…yeah, no thanks. I’m a programmer. I want to write code, not mess with 3D modeling software like Blender.

Luckily (for rig models anyways), Unity has a solution. We can use Unity’s Mecanim animation tool to use existing animations!

What is Mecanim

We’ve actually already seen Unity’s Mecanim system before. It’s our animator system. We used it to create our Animator Controller. Remember this?

0*3v6qQ7PtABH8wM9R.png

Remember in the Survival Shooter tutorial series, I skipped the part where we made the Zombear, because I couldn’t find any animation models for it? We’re coming back full circle to it.

In Unity, if you have rigged character models that have similar bone structures to other rigged characters, we can actually share the animations from one character model to the next.

Here’s what I mean by humanoid body structure. As long as our model has these similar points, we can share the animation

0*aqTc0eHap8LvPtqD.png

For example: our Zombunny had animation clips that we used, but because our Zombear had a similar humanoid structure to our Zombunny, we can re-apply those animation effects on our Zombear without needing to have those same animations!

Ahhhhhh! It makes sense now.

After looking around for animatiosn in the Unity Asset Store, I found a lot of animation that can be used for free!

I chose to use the Mecanim Warrior for their death animation

0*4jrhdlSTMUtsM2qq.png

And I chose to use the Warrior Pack Bundle 1, for the Brutes animation. Downloaded everything? Good!

0*RRTGyIM1_CWwq4Sx.png

Next to keep everything organized, I made a new folder called Animation in my project.

In that folder, I created a new Animator Controller which I called, Knight Animator Controller.

Next I dragged in the animation clips from the models we downloaded and add them into the animator. Specifically:

  • death from Mecanim Warrior
  • Attack1 from Brute Warrior Mecanim
  • Idle from Brute Warrior Mecanim
  • Run from Brute Warrior Mecanim

Next, create 3 parameters:

  • IsNearPlayer — Bool
  • Idle — Trigger
  • Death — Trigger

Finally we have to connect everything together.

  • Run is our default state
  • Transition from Run to Attack1 when IsNearPlayer is true
  • Transition from Attack1 back to Run when IsNearPlayer is false
  • Transition from Any State to death when death is triggered
  • Transition from Any State to Idle when idle is triggered.

The goal is that if the enemy is near the player, they’ll start attacking, otherwise, they’ll run after the player.

When you’re done, we should have something like this for our Knight Animator Controller

0*4NKGqt9p8s-LGyrL.png

Next drag the String Knight prefab into the scene and in the Animator component, attach our animator to our knight and he’ll be off running!

Also don’t forget to disable/remove the Rotation script that’s attached to the Strong Knight game object.

0*n8dG-LZuRKO_iv9i.png

Of course, if you want to try and see what happens in the other states, you can set the other states as the default and see what our Knight would do.

And there we go! Now we have the animation for our knight character!

Stepping back and reflecting

Now before we move on. I want to step back a bit and explore what we did.

What we essentially did was that we took multiple random rigged character model from the Unity app store that has their own animations and created an animator controller for our Strong Knight.

The amazing thing is that we only had one animation clip from the creator: idle.

The other animations we’re using are all from other animation packs/characters that we can just take and apply into our animator for a knight. That’s CRAZY!

What this means is that as a developer. I don’t need to know how to do 3D modeling. As long as I have a rig character model, I can apply other people’s animation to my character as long as they share a similar rigged body. That’s a crazy powerful feature right there!

Conclusion

Well, that’s enough excitement for day 11. I spent quite a bit of time looking into rig character models, searching for animations, and then experimenting with how I can use any humanoid animations for the Strong Knight model that we created.

Tomorrow, my goal is to start writing a script for the enemy and creating a navigator agent for our enemy so that they can start chasing after the player.

See you guys tomorrow for Day 12!

Original Day 12.

Visit the main 100 Days of Unity VR Development challenge page.

 

Josh Chang

Welcome to a very special day of my 100 days of VR. Day 10! That’s right. We’re finally in the double digits!

It’s been an enjoyable experience so far working with Unity, especially now that I know a bit more about putting together a 3D game now.

We haven’t made it into the actual VR aspects of the game, but we were able to get *some* foundational skills for Unity, which I’m sure will help translate into the skills needed to create a real VR experience.

We’re starting to get the hang of what we can use in Unity to make a game. Yesterday, we created the beginning of the shooting mechanism.

Currently, whenever we hit something, we just print out what we hit. Today we’re going to go in and create an enemy player that we can shoot and make some fixes.

Updating the Shooting Code

The first thing I would like to fix is that when we shoot, we shoot at whatever our cursor is pointing at, which is kind of weird.

Looking the cursor to the middle

This can be easily fixed by adding:

Cursor.lockState = CursorLockMode.Locked;

To Start() in our PlayerShootingController script

We’ll have something like this:

void Start () {
    _camera = Camera.main;
    _particle = GetComponentInChildren<ParticleSystem>();
    Cursor.lockState = CursorLockMode.Locked;
}

Now when we try to play the game, our cursor will be gone. It’ll be in the middle of the screen, we just can’t see it.

Adding a Crosshair

At this point, we want some indicator to show where our “center” is.

To do this, we’re going to create an UI crosshair that we’ll put right in the middle.

In the hierarchy, add an Image which we will call Crosshair. By doing this Unity will also create a Canvas for us. We’ll call that HUD.

By default, our crosshair is already set in the middle, but it’s too big. Let’s make it smaller. In the Rect Transform, I set our image to have Width and Height 10, 10.

You should have something like this now:

Before we do anything else, we need to make sure that our mouse collider doesn’t send a raycast onto our UI elements.

In HUD, attach a Canvas Group component and from there, uncheck Interactable and Blocks Raycasts. As you might recall, the Canvas Groupcomponent will allow us to apply these 2 settings to its children without us having to manually do it ourselves.

Go ahead and play around with it. If we observe our console, whenever we fire we hit where our little “crosshair” is located at.

Creating our Enemy

So now we fixed our cursor to be the center, the next thing we need to do is to create an enemy.

We’ll improve upon this, but for now, let’s create our first enemy! A cube!

Add a Cube to your hierarchy, name it Enemy, and then drag it near our player.

Boom! First enemy!

Now currently, nothing really happens when you shoot at it, so let’s fix it by adding an enemy health script. We’ll call it EnemyHealth

Here’s what the code looks like:

 
using UnityEngine;
public class EnemyHealth : MonoBehaviour
{
    public float Health = 10;
	
    public void TakeDamage(float damage)
    {
        Health -= damage;
        if (Health <= 0)
        {
            Destroy(gameObject);
        } 
    }
}
 

It’s relatively simple:

  1. We have our health
  2. We have a public function that we’ll call our player hits the enemy that’ll decrease the enemies HP
  3. When it reaches 0, we make our enemy disappear

Now before we update our script, let’s make some optimizations to our raycast.

Go to our Enemy game object and then set its layer to Shootable if it doesn’t exist (which it most likely doesn’t), create a new layer, call it Shootable, and then assign it to the Enemy layout.

Now let’s go back to our PlayerShootingController and grab the EnemyHealth script that we just created and make them take damage:

 
using UnityEngine;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
	void Start () {
		_camera = Camera.main;
	    _particle = GetComponentInChildren<ParticleSystem>();
	    Cursor.lockState = CursorLockMode.Locked;
	    _shootableMask = LayerMask.GetMask("Shootable");
	}
	
	void Update () {
	    if (Input.GetMouseButton(0))
	    {
            Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit = new RaycastHit();
	        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
	        {
	            print("hit " + hit.collider.gameObject);
	            _particle.Play();
                EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
	            if (health != null)
	            {
	                health.TakeDamage(1);
	            }
                }
	    }
	}
}
 

The changes we’ve done is very similar to what we have seen before with Survival Shooter, but here’s the addition that we added:

  1. We created our LayerMask for our Shootable layer and passed it into our Raycast function
    1. Note, I tried to use an int at first to represent our LayerMask, but for some reason, the Raycast ignored the int. From searching around online, I found that instead of using the int representation, we should just try the actual LayerMask object. When I gave that a try, it worked…. So yay?
  2. Next, when we hit an object, which at this point, can only be Enemy, we grab the EnemyHealth script that we added and then we make the enemy take 1 damage. Do this 10 times and the enemy will die.

Now with this script attached to our enemy, shoot our cube 10 times (which should happen really fast), and then BOOM, gone.

Conclusion

And that’s about as far as I got for Day 10! Today was a bit brief, because I didn’t get much time to work, but I think we made some good progress!

We created the basis for an enemy and added a basic crosshair UI that we can use. Tomorrow, I’m going to start looking into seeing how to add an enemy from the Unity Asset Store into the game.

Until then, I’ll see you all in day 11!

Original Day 10

Visit the 100 Days of VR main page

Josh Chang

Welcome back to day 9!

Were you wondering why there was a large gap between today and the last post? Well that’s because I went to participate in the Seattle AR/VR hackaton #6!

The hackathon was 3 days long and it took me 2 days to recover from it! Check out my hackathon postmortem!

With the hackathon over, I’m back to my regularly scheduled daily posting! Yay!

Continuing from where we last left off,

The next thing to do is to attach a weapon to our character and then add the game logic for shooting.

As usual let’s get to it!

Adding our Weapon

In our current state, we just have our character:

We need to add a weapon and from my research, in most games, when a player shoots, it’s not actually from the gun, it’s from the camera’s center view.

Not only that, we don’t actually shoot any projectiles as that can become computationally expensive. Instead, we just fire a Raycast and if we hit, we do some gun animation effect to make it appear that the bullets are coming from the gun.

For larger weapons like rocket launchers we have to create the projectiles, but for a fast shooting weapon like an assault rifle or pistol we can just use a Raycast.

Creating our weapon

The first thing we should do is create our “gun”. I’m sure we can grab a gun asset from the game store, but instead, I’m going to first create a simple cube that’ll represent our weapon. I’m sure later on we can just attach the asset and *most likely* everything will be fine. Probably…

First let’s create a Cube and make it the child of our Main Camera. I’m going to name it Gun. I set the transform to these values:

  • Position: (0.25, -0.5, 1)
  • Scale: (0.25, 0.25, 1)

Make sure to disable/remove the Box Collider that gets created with the Cube. If we have it, our player object will collide with the gun causing unintended consequences.

With our addition, we should have something like this:

 
0*SGrgLsUcGYoRqmzd.png

if we look at our Game tab, we should see something like this:

 
0*VTKvMcH4lzzHnEVf.png

Next, we can add some particle effect or something along the lines of that to give the illusion that we’re firing.

I won’t lie and say I know exactly what I’m doing (I don’t), but for now, I’m going to create a simple Particle system and attach it to the tip of the gun.

My intention is that I’ll play the particle effect when we fire and stop it when we stop.

So the first thing that we need to do is to create a new Particle System and make it the child of Gun.

I did some configuration settings such as changing the Z position to 0.2

And here’s the rest of the configurations:

  • Duration: 1
  • Looping: unchecked
  • Start Lifetime: 0.05
  • Start Speed: 5
  • Start Size: 1
  • Start Color: Yellow
  • Play Awake: unchecked

Here’s what it’ll look like and the settings. It doesn’t look great, but I’m sure if we had a material asset we could make this better.

 
0*pTRPpAqFVbvXxZTh.png

Adding the Shooting Script

Next up, I created the shooting script and attached it to our Main Camera. The script will be called PlayerShootingController.

Here’s what a simple shooting script would look like:

 
using UnityEngine;
public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    private Camera _camera;
    private ParticleSystem _particle;
	void Start () {
	    _camera = Camera.main;
	    _particle = GetComponentInChildren<ParticleSystem>();
	}
	
	void Update () {
        
	    if (Input.GetMouseButton(0))
	    {
            Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit = new RaycastHit();
	        if (Physics.Raycast(ray, out hit, Range))
	        {
	            print("hit " + hit.collider.gameObject);
	            _particle.Play();
            }
	    }
	}
}
 

Nothing in this code is particularly complex. We just created a Ray from wherever our mouse is located at and then if we were to hit something, we would print what we hit and play our particle system.

 
0*N0grjDiX3yjR-iLL.png

We want to do more like create a layer mask for our raycast to collide with (such as the Shootable layer), but that’s for another day.

End of Day 8

That’s it for today! Short? Yeah I know, great right?

To summarize what we accomplished today: we created the beginning of our shooting system.

We added a fake gun that is a child of the camera so that whenever we move our mouse, the gun will stay in front of our camera.

Then we added a particle system that will generate particles whenever we shoot.

In the future, I’d like to go back and figure out how to make the particles look more realistic: probably by using better materials for the particle system, but we’ll figure that out when we get there.

Finally, we created a script that will shoot raycasts to where our mouse is pointing at in the screen. We should really make that the center, but that’s something we can fix tomorrow.

Well, it’s been a long day, I’ll see you all tomorrow!

Original Day 9

Visit the 100 Days of VR main page

Josh Chang

Here we are on day 8! Yesterday we created a very simple map for a simple FPS feature.

Today, I went to look to see how we can move a character around in the scenery that was created.

Here’s what I got!

Creating our character

The first thing I figured out who was the “character”. Turns out for our game, our main character is someone we know well.

Here it is:

Camera-man-1024x401.png

That’s right its Main Camera! Who knew?

If we think about it, it does make sense.

The camera object is what we see when we run the game. In an FPS game, we see what our character sees. Which means our camera must be part of the character, specifically their eyes.

Setting up our character

The first thing we do is to create an Empty Game object that we will call Player then we’ll make our main camera a child of player.

I’d imagine when we want a character model, we would have the camera be right in front of the face of the model, but for now, we don’t need one.

Make sure the camera is set at position (0, 1, 0) and the Player position to be (5, 1, 5).

The X and Z position is so that we don’t start falling through the edge of our map and the Y value would give us some height, otherwise we would be facing the floor.

Next, we need to add a collider to our Game Object so that we won’t fall through the world when we start the game.

I created a Capsule Collider and I set the height to be 2 and left the rest as the same.

I’ve also attached a RigidBody component, because later on, we’re going to have to write some code to make our camera move when we click on our keyboard.

And here we are, our player character:

player-character-1024x298.png

And here’s what we’ll see when we play:

player-sight.png

If you want, you can give our character a Mesh Renderer and a Mesh Filter so that you can see where our character is in the Scene tab. We also get a nice little shadow!

Creating Movement for our Character

Now that we have a character, the next thing we have to do is to move it.

I found that Unity actually offers some very good built in FPS controls from this thread: How to make a first-person player for my game.

If you don’t have the fps scripts like in the thread, you can go to Assets > Import Package > Player to get these scripts.

After downloading them, all you have to do is attach the scripts to the Player game object.

However, since I’m doing this for learning, I’m going to attempt to write my own script to do something similar.

The first thing I’m going to do is create a new script called PlayerController, this will be used to control our player’s position based off of the user’s input.

I actually had to spend a lot of time working on this and searching on Google, but here are the results.

using UnityEngine;

public class PlayerController : MonoBehaviour {

    public float Speed = 3f;

    private Vector3 _movement;
    private Rigidbody _playerRigidBody;

    private void Awake()
    {
        _playerRigidBody = GetComponent<Rigidbody>();
    }
    

    private void FixedUpdate()
    {
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");

        Move(horizontal, vertical);
    }

    private void Move(float horizontal, float vertical)
    {
        _movement = (vertical * transform.forward) + (horizontal * transform.right);
        _movement = _movement.normalized * Speed * Time.deltaTime;
        _playerRigidBody.MovePosition(transform.position + _movement);
    }
}

The code is pretty straight forward, here’s the flow:

  • In Awake() we get our RigidBody so we can move our player
  • In FixedUpdate(), we use GetAxisRaw() to get our horizontal and vertical movement so that we can detect if the user is moving left/right and up/down.
  • In Move() we’re given the movement directions. We want to create a movement vector that captures the direction from where the user is facing, which we achieve with transform.forward and transform.right. By multiplying everything together, we get the direction that the player should move based off of their inputs.
  • Finally we normalize our movement vector and then we move our position with our new Vector.

It’s important to note that we must use the RigidBody to move.

Before I was trying to move the player with transform.Translate, what I found out is that this function is the equivalent of teleportation. What happens is that if there are obstacles in front of the player, our character will just teleport past them.

I also want to emphasis that it’s important that we use transform.forward and transform.right especially once we start trying to get the camera to follow the mouse.

When we start rotating our character to follow the mouse, our vertical and horizontal values don’t give us the direction adjusted for which direction we’re facing.

For example, if we were to face forward and press W to move forward, we would move forward. However, if we were to turn 90 degrees to the right, and press W again, instead of moving forward like you’d expect, you would move to the left where “forward” use to be.

transform.forward gives us vector that the player is facing and then we multiply that with our vertical value which tells us if we’re moving forward or back. We do the same with transform.right and horizontal.

Rotating our Camera

The next problem after making our character move is trying to figure out how to get the camera to follow the mouse.

There were a lot of answers, however I think the best explanation is from this Youtube video: How to construct a simple First Person Controller.

I only took the snippet for the camera controller.

I made another class called MouseCameraController and attached it to our Main
Camera
.

The script allows us to we want to be able to view up and down with the camera without moving the character, but we want to change our player rotation value as we look horizontally.

Here’s the code:

using UnityEngine;

public class MouseCameraContoller : MonoBehaviour
{
    public float Sensitivity = 5.0f;
    public float Smoothing = 2.0f;

    private Vector2 _mouseLook;
    private Vector2 _smoothV;

    private GameObject _player;

    void Awake()
    {
        _player = transform.parent.gameObject;
    }

    // Update is called once per frame
    void Update () {
        Vector2 mouseDirection = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));

        mouseDirection.x *= Sensitivity * Smoothing;
        mouseDirection.y *= Sensitivity * Smoothing;

        _smoothV.x = Mathf.Lerp(_smoothV.x, mouseDirection.x, 1f / Smoothing);
        _smoothV.y = Mathf.Lerp(_smoothV.y, mouseDirection.y, 1f / Smoothing);

        _mouseLook += _smoothV;
        _mouseLook.y = Mathf.Clamp(_mouseLook.y, -90, 90);

        transform.localRotation = Quaternion.AngleAxis(-_mouseLook.y, Vector3.right);
        _player.transform.rotation = Quaternion.AngleAxis(_mouseLook.x, _player.transform.up);
    }
}

There’s a lot of math to digest for this code, but let’s go through them.

Like mentioned, it’s important that the script is attached to the camera and not the main character body.

We want to rotate the characters body when we look left to right, but when we look up to down, we don’t want to rotate the body.

  • In our Start() function, we want to get the parent element (the Player Game Object) so we can rotate it as we turn.
  • In Update() we create a vector that would represent where our mouse is facing which we will use to determine where our camera will be facing by using our Mouse X and Mouse Y positions
  • Next we want to multiply the direction we created with 2 variables: Sensitivity and Smoothing
    1. Sensitivity is used to determine how big the vector of our mouseDirection would be. The higher the number, the more sensitive our mouse would be, allowing us to rotate more quickly.
    2. Smoothness is used to help us evenly move our camera from its starting location to the new location from the mouse location. We use lerp to help us make gradual movement instead of one massive movement
  • Once we calculate the new direction we’re facing into smoothV, we add that into our mouseLook variable which is where our character will be facing.
  • We make sure to clamp the y rotation, because we don’t want to be able to go from looking in front of us to behind us. It’s unnatural human anatomy.
  • Finally, for rotation. We want to rotate the localRotation of our camera, meaning we rotate the game object, relative to its parent.
    1. For example, if our player is facing 90 degree, if we were to set rotation of our camera to 90, we would be at 90, the same location. If we use localRotation, it would take in the fact our character is facing 90 degree and rotate 90 more.
    2. We create our angle by using our y value (the up and down) and rotating by the angle Vector3.right, which is the X axis.
  • Our last rotation is when we want to turn our player around instead of the camera. We don’t have to use localRotation, because the player doesn’t have a parent (which means it defaults to the game world as the parent), so we can just use rotation, though both would work.
    1. As for our rotation, we rotate our player by our left and right mouse movements using Vector3.up, which is the Y axis.

And that’s it! Play the game and you should be able to move your character around in the game world and bump into our environment that we setup.

Conclusion

Now wasn’t this short compared to the video tutorials?

Today we learned how to move and rotate the character around in the game screen.

It’s important to know that Unity provides a standard asset that makes movement easy for us, but I do think there’s value in being able to understand some of the inner workings of the code.

Either way, stay tune for Day 9, where we’ll hopefully start exploring how we can equip a gun in our player and maybe even start shooting something!

Original Day 8

Visit the main 100 days of VR Page