Day 43 of 100 Days of VR: Setting up Google Spatial Audio in Unity

Josh Unity VR Development Leave a Comment

Today we’re going to have a relatively short day! Today will perhaps be our final day working on this game before I move on to creating what I hope to be a complete game!

However, before I get ahead of myself, let’s finish today first!

Today, there are two things that I want to do:

  1. Fix our score and health UI on our game which broke
  2. Use Google’s Spatial Audio system! Let’s get to it!

Step 1: Re-Fixing the UI System

One important thing I completely forgot about when we duplicated and moved our gun from the gaze controller to our Daydream controller is that we forgot to change a couple of places to use our new UI element.

Let’s put our components back in the correct places.

There are 2 places that use our UI components:

  1. PlayerHealth
  2. ScoreManager

Let’s add these back in.

  1. In GameManager find the GameManager Drag and drop our new Score UI Text from Player > Main Camera > GvrControllerPointer > MachingGun_00 (1) > MachineGun_01 > Score into our Score slot.
  2. In Player, we have our PlayerHealth script. Drag and drop our new Health Bar Slider from Player > Main Camera > GvrControllerPointer > MachingGun_00 (1) > MachineGun_01 > Health Bar into our HealthBar slot.

With these added in. When we play our game again. We can see the time on our gun and our health.

Plus, now that we can individually move our weapon, it’s a much more pleasant experience with our UI. We no longer have our time and health on our page anymore. Now it’s location is based on our controller. Nice!

Step 1: Adding Spatial Audio to the Game

Looking at the documentation page, there are a couple of elements that are needed to make this work we only need to do 2 things:

  1. Replace the AudioListener component in MainCamera with GvrAudioListener
  2. Replace all AudioSource with GvrAudioSource

There’s also another prefab and script called GvrAudioRoom that allows us to reflect sound as if we were in a room with varying levels of surface materials to bounce off sounds. We won’t be working with this today, but it’s there.

Let’s set everything up!

First, let’s add our GvrAudioListener script:

  1. In the Hierarchy go to Player > Main Camera.
  2. Click Add Component and select
  3. Note: We still need the old AudioListener

Now the more complex part. We’re going to have to go in and replace all the AudioSources with GvrAudioSources.

Looking at the source code, it doesn’t look like GvrAudioSources inherits directly from Audio Source, rather, it’s more like a wrapper that has the same functions to call, and uses an AudioSource component underneath along with its extra changes.

Luckily for us, we only use them in a couple of places and only in code. Specifically, in these scripts:

  • PlayerShootingController
  • EnemyMovement
  • EnemyAttack
  • EnemyHealth

I’ll be replacing all the AudioSource:

PlayerShootingController.cs:

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 GvrAudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;

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

        if (Input.GetButton("Fire1") && _timer >= ShootingDelay)
	    {
            Shoot();
	        if (!_isShooting)
	        {
	            TriggerShootingAnimation();
	        }
	    }
        else if (!Input.GetButton("Fire1"))
	    {
            StopShooting();
	        if (_isShooting)
	        {
	            TriggerShootingAnimation();
            }
	    }
	}

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

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

    public void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();

        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
        }
    }

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

    public void GameOver()
    {
        _animator.SetTrigger("GameOver");
        StopShooting();
        print("game over called");
    }
}

EnemyAttack.cs:

using UnityEngine;

public class EnemyAttack : MonoBehaviour
{
    public FistCollider LeftFist;
    public FistCollider RightFist;
    public AudioClip[] AttackSfxClips;

    private Animator _animator;
    private GameObject _player;
    private GvrAudioSource _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()
    {
        print("attack called");
        if (LeftFist.IsCollidingWithPlayer() || RightFist.IsCollidingWithPlayer())
        {
            print("enemy attacked the player");
            PlayRandomHit();
            _player.GetComponent<PlayerHealth>().TakeDamage(10);
        }
    }

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

    private void PlayRandomHit()
    {
        int index = Random.Range(0, AttackSfxClips.Length);
        _audioSource.clip = AttackSfxClips[index];
        _audioSource.Play();
    }
}

EnemyHealth.cs:

using System;
using UnityEngine;
using Random = UnityEngine.Random;

public class EnemyHealth : MonoBehaviour
{
    public float Health = 100;
    public AudioClip[] HitSfxClips;
    public float HitSoundDelay = 0.1f;

    private SpawnManager _spawnManager;
    private Animator _animator;
    private GvrAudioSource _audioSource;
    private float _hitTime;
    private Boolean _isEnter;


    void Start()
    {
        _spawnManager = GameObject.FindGameObjectWithTag("SpawnManager").GetComponent<SpawnManager>();
        _animator = GetComponent<Animator>();
        _hitTime = 0f;
        _isEnter = false;
        SetupSound();
    }

    void Update()
    {
        _hitTime += Time.deltaTime;
        if (Input.GetButton("Fire1") && _isEnter)
        {
            TakeDamage(1);
        }
    }
    
    private void TakeDamage(float damage)
    {
        if (Health <= 0) { return; } if (_hitTime > HitSoundDelay)
        {
            Health -= damage;
            PlayRandomHit();
            _hitTime = 0;
        }

        if (Health <= 0)
        {
            Death();
        } 
    }

    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<GvrAudioSource>();
        _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;
        }
    }

    public void HealthEnter()
    {
        _isEnter = true;
        print("enter");
    }

    public void HealthExit()
    {
        _isEnter = false;
        print("exit");
    }
}

EnemyMovement.cs:

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 GvrAudioSource _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") || _animator.GetCurrentAnimatorStateInfo(0).IsName("Attack1")))
        { 
            _nav.SetDestination(_player.position);
            if (_time > WalkingDelay)
            {
                PlayRandomFootstep();
                _time = 0f;
            }
        }
        else
        {
            _nav.enabled = false;
        }
    }

    private void SetupSound()
    {
        _walkingAudioSource = gameObject.AddComponent<GvrAudioSource>();
        _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()
    {
        print("victory");
        _animator.SetTrigger("Idle");
        _nav.enabled = false;
    }
}

Now let’s play our game and see what our spatial setting sounds like…

…except when we try to play the game we don’t have any audio at all!

After playing around and looking up why GvrAudioSource is not playing I found the reason. We need to enable a spatial audio plugin.

To do that we have to:

  1. Go to Edit > Project Settings > Audio to open our AudioManager
  2. In the Spatializer Plugin change from None to Gvr Audio Spatializer

With this setup, let’s go back in and try playing again.

It’s amazing how adding spatial sound changes the game. Before, I didn’t know where the enemies were, except that they’re somewhere in the game, however with Spatial Sound enabled, I can figure out where enemies are without having to do a complete 360 look around the game.

Conclusion

Wow! It’s been a long journey! 43 days in and we have gone from a complete beginner in Unity to someone who can create a simple VR First Person Shooter gameplay.

At this point, I’m going to call this project complete.

I’m not saying that this “game” is done, I’m far from having a functional game. Heck, even the gameplay part of the game that we’ve been working on isn’t as complete as I would like it to be!

However, this simple prototype has provided enough value for me (and I hope you the readers too) and spending more time trying to get every detail perfect would not be a good use of time. It’s time to work on something new!

The question now is: what’s next?

I think it’s time to make a new game, one that I hope will be able to ship! Find out more next time!

Day 42 | 100 Days of VR | Day 44

Home

Subscribe To Our Weekly Newsletter!
Like these coding articles? Join my mailing list for the latest updates and influence the code that I write!
We hate spam. Your email address will not be sold or shared with anyone else.

Leave a Reply

Your email address will not be published. Required fields are marked *