Jump to content
  • Advertisement
  • entries
    44
  • comments
    19
  • views
    2164

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

 

Day 40 of 100 Days of VR: Creating a VR First Person Shooter III - UI

Here we are at another milestone in the 100 days of VR challenge! Day 40! Who would have known that I would have made it to this point? We’ve come a long way, we learned a bit about Unity, made a simple game, and now here we are working in VR! Yesterday we finished fixing most of the technical problems involved with porting our game to VR. Well, turns out, I lied, there are a couple more things I’d like to fix today along with working a bit on the UI. For some reason, our camera is lower than we set it Enemies are literally running into us when trying to hit us Get our UI to show up again Step 1: Changing Our Camera Starting Position When we play the game on Unity, we have an interesting problem with our camera position being changed. We set our camera position to be 1.5 at Y: However, what’s interesting is that when we play the game on the Android platform, our camera position gets set to 0: After debugging around, I found the cause. Our GvrEditorEmulator prefab forces our Main Camera to be set to the position: 0, 0, 0. While technically, we don’t need the prefab, it is convenient, so we’ll keep it in. Instead, I’ve found a different solution. While our camera is forced to be a set position, we can child our camera to another game object and then change the position of the parent game object. Coincidentally, we’re already doing that with our Player game object. All we have to do is raise our Player Y position up by the amount of the camera. Select Player, change the Y position from 1 before to 1.5 (Optional) Go to the Main Camera and change the Y position to 0 Now when we play the game, we’ll have a much better height when playing: Step 2: Stopping Enemies from Going Inside the Player Next problem, the enemies are literally running into us. See: There could be many things that are causing the problem, but I decided to look at the Nav Mesh Agent attach to each of the enemy prefabs, because there are options that control how close the enemy would get to their target: After playing around with the settings I got the enemies to stop right in front of us: Stopping Distance: 1.25 Auto Braking: Disable Radius: 1.25 Here are our new settings: With these new settings in, here’s a bit of our gameplay now: Isn’t it nice that they’re beating us from the outside and not the inside? No? Oh well… Step 3: Getting the UI to Show Up Again Now that we have all the technical problems resolved (promise this time!), it’s time for us to go back and look at how we can get the UI to show up for our game. This is going to be an annoying problem to solve because the problem only occurs on our Android device and not anywhere else. Which means there will be A… lot… of… building… Luckily for you, I’ve gone through the torture of re-building multiple of time so the rest of us don’t have to! It turns out that getting the UI to show up isn’t too bad! It turns out that if we have VR mode enabled in Unity, any UI we have on our Android device will NOT be available unless the canvas the UI is set with is set to World Space. Here’s what it looks like on my phone when I disabled the VR options in Player Settings and just have normal Android app: According to Unity’s quick guide for VR UI, and for extra credit, this article about UI expectations for the player, UI in VR must be run in World Space. The biggest key takeaway I got from these 2 articles is that you should NEVER have the UI on your player’s screen, like how we’ve been doing it. This could cause motion sickness. Specifically, we want to use Diegetic UI, where the UI is attached to a game object in the game world as opposed to an overlay on top of our screen. Instead of having it float around or statically placed would be the better way to go. Step 3.1: Getting the UI to show up Anyways, to fix our problem and have our UI show up when VR is enabled, we must set our Canvas to be displayed in World Space. Select HUD in our hierarchy. In the Canvas Component, change Render Mode from Screen Space – Overlay to World Space There are 3 options available to use, here’s the canvas documentation to explain what they are, but for a quick summary of the available render modes: Screen Space – Overlay: The UI is rendered on top of the scene Screen Space – Camera: The UI has put a certain distance from the Camera. It’s very similar to the Overlay, except certain changes to the camera could also cause changes to the UI. An example would be Perspective Camera would render the UI differently from an Orthogonal Camera World Space: The UI Canvas will act like a game object that just hangs somewhere in the game world for us to see. Also, this is the only option we can use for our UI Here’s what our game looks like now with the World Space: Now we need to make some adjustments with our UI. Step 3.2: Figuring out where to put the UI The biggest question now at this point is, where should we put our UI elements? While there isn’t a clear answer, I think our best option might be to attach the UI to be our weapon. On the Google Cardboard, this would be the equivalent of having it right in our face, but if we were to switch to use the Daydream Viewer, we would be able to move it independently of our gaze. With the decision being made, let’s go and see how we can attach our health and time UI to our gun! Step 3.3: Putting Our UI Into World Space We already have an existing HUD canvas game object in our game. We’re going to repurpose that for our game, however, because we have more than just our UI on the canvas (the Victory and Game Over panels), I’m going to duplicate our HUD On HUD in our game hierarchy, hit Ctrl + D to duplicate it. Rename the duplicated HUD (1) to be called GunUICanvas Delete the Victory, Game Over, and Ammo UI child objects Make the GunUICanvas a child of MachineGun_01 When we’re done, here’s what your hierarchy should look like. Next up, we’re going to change the settings of our GunUICanvas so that it would be right in front of our gun on the right side: Select GunUICanvas In the Canvas component, the Render Mode should already be World Space, if not, change it In the Rect Transform component, I’ve played around with the settings and changed our position to be (-0.15, 0.22, -0.14), our Width to be 480, and our Height to be 80. Set Scale to be (0.001, 0.001, 0.001), we want our UI to be small enough to fit in our screen (Optional) Remove the Screen Manager Script Here’s what we should have: Next, I’m going to change the Anchor Presets for our Score UI game object to be in the bottom middle of our screen. Select Score In the Rect Transform component, open our Anchor Presets and hit Alt + Shift and select bottom center preset Now with the width changes of our Canvas and making our Score at the bottom, we should have something like this for our game. If we play the game in our VR device, I don’t think there will be any discomfort. The UI is positioned on the gun and since we’re focused on the cursor in the center, it’s not in our focus. Step 3.4: Connecting the UI Elements to the Rest of our Scripts Now that we have our GunUICanvas looking nice, the last thing that we need to do is re-connect all the UI elements to our scripts that use them, so our UI can get updated as we play. We need to update our: Time Text Health Slider Do you remember which scripts used these UI? No? Don’t worry I do! In GameManager, in the Score Manager script, drag our Score UI into the Score slot In Player, in the Player Health script, drag our Health Bar slider into the Health Bar slot Once we add our new UI components, our code will update the UI correctly! Conclusion And that completes Day 40! Today we looked at fixing our last technical problems with the camera height and getting the enemies to attack us at a more appropriate distance. However, the main topic of today was adding our UI back in. It turns out that with Unity VR enabled, the only Canvas we can use is World Space. Once we have made that change we can see our UI again. With our new UI, we attached it to our gun and I think we made a good solution to provide relevant UI information to the player all without causing nausea. Tomorrow, we’re going to continue working with the UI. Specifically, we’re going to add the game end states back into the game, so we can keep playing it without starting a new app instance. Day 39 | 100 Days of VR | Day 41 Home

Josh Chang

Josh Chang

 

Day 39 of 100 Days of VR: Creating a VR First Person Shooter II

Welcome back to day 39! Yesterday we started to look at fixing problems that involved the limitation of Mobile VR (and a lot of raycasting), today we’re going to make some more changes. Specifically, the goal today is: Change our Event Trigger logic to deal with what happens if we’re holding down on the screen Fix a problem with our player being pushed around Fix why our Knight turns black Change our enemy to be slower to make the game easier Let’s get to it! Step 1: Changing Our Event Trigger Logic for Continuous Fire Right now, we’re trying to solve the problem where we only damage the enemies when we tap on them. If we were to hold on, then we would continue to shoot, but the enemies won’t take damage Yesterday we discovered that there was 2 ways we could have implemented our game. Use our existing code where we shoot a raycast and depending on what we hit, run some function. Use the Event Trigger system along with Google’s changes. I’ve played around quite a bit with the Event Trigger system and made a solution, but it’s not the best, in fact, I might have preferred just keeping what we have, but that’s okay, we’re just learning! There are 2 problems that we must solve: What happens when we’re holding down the screen on an enemy What happens when we’re holding down and then we move to point at another enemy. After playing around for a while, the PointerClick solution we have will no longer work. Instead, I’ve started playing with the PointerEnter and PointerExit events. I’m going to add the changes to EnemyHealth: 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 AudioSource _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<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; } } public void HealthEnter() { _isEnter = true; print("enter"); } public void HealthExit() { _isEnter = false; print("exit"); } } Walking Through the Code In our EnemyHealth, we create 2 new functions: HealthEnter() HealthExit() These functions are going to be called from the PointerEnter and PointerExit from our Event Trigger that we set up. In these functions, we set new variable we introduced called _isEnter so we know when they’re being selected. Inside Update() we check to see if we’re currently hovering over the enemies and if we’re pressing down on the screen. If we are, we would call our already existing Shoot function. I’m not a fan of this method because it requires us to constantly call Update() in all of our enemy health scripts as opposed to just inside our PlayerShootingController script, but for just playing around, this is okay. I also changed the hit sound effect to be able to play every 0.1 seconds just like our shooting delay. Step 1.1: Updating our Event Trigger Now that we have our shooting script in, the next and final thing we need to do is to create the Event Triggers to use them. Get rid of the PointerClick event that we’ve previously set up. Instead, we’re going to create 2 new types of Event Triggers: PointerExit and PointerEnter. Here’s what we’re going to do: Attach HealthEnter() to PointerEnter Attach HealthExit() to PointerExit Now we can play our game like we intended to do from the very beginning. Make sure to make these changes to the Bandit and Zombie too! Step 2: Preventing Our Player from Falling Currently, if we were to play the game, when an enemy gets closed to our player character, we would fall. We should have addressed this in the past when we set constraints inside our RigidBody component, but it appears that we have not. Let’s go back and fix this In the hierarchy, select Player Under the RigidBody component, we’re going to set our constraints. Specifically, we want to freeze our position and rotation so that the enemies won’t push our character around. Having the game move us could cause nausea for our players. Step 3: Fixing our Knight’s Color If we were to play our game on our mobile device, we’ll notice one big problem. Our knights are all black. If we pay attention to our log, we’ll see this: It seems that we have some problems with the shaders that the asset is using. Unfortunately, I don’t know enough about this problem to resolve this. We have 2 choices: Ignore the problem and just have all black knights Change the materials that use these to use the standard shader. In our case, we’re going to explore the 2nd option. In Assets/Knight/models/Materials we have 3 materials that we’re using: clothColor, knight1Color, weaponsColor, and all of them uses one of those 2 shaders above. Let’s select them and changed them to Standard. Now if we were to play the game on Unity, here’s what the knights would look like: It lost the coloring we originally had for it, but at least we keep the details of the models. Step 4: Making the Game Easier Currently, in our game, we would be getting swarmed with enemies. That would have been fine if we can move around, unfortunately, we can’t do that anymore, thus as a result, we need to make some adjustments We’re going to do 2 things: Change the rate of how long it takes for an enemy to spawn Slow down the rate our enemies move Step 4.1: Changing Spawn Rate on the Spawn Manager Currently, we spawn the next enemy every 2 seconds. Let’s change that to 5. Select the SpawnManager game object (child of GameManager) Set Time Between Enemies to be from 2 to 5 Step 4.2: Changing Enemy Walking Speed As we might recall, to control the enemy speed, we must look at the Nav Mesh Agent component in our enemy prefabs. In order of speed, our speed order is Bandit, Knight, Zombie, with the Bandit being the fastest and Zombie being the slowest. I’m going to change the speed a bit. Bandit to 2 Knight to 1.5 Zombie to 1 Here’s an example: Conclusion Now we’re done! We have taken care of a lot of the technical problems that we encountered. Tomorrow, we’re going to continue to finish the rest of the game by figuring out how we would add UI into a virtual reality environment. Until then, I’ll see you all later in day 40! Day 38 | 100 Days of VR | Day 40 Home

Josh Chang

Josh Chang

 

Day 38 of 100 Days of VR: Creating a VR First Person Shooter I

Welcome to Day 38! Today, we’re going to talk about the limitations of mobile VR and make some changes in our game to fix things. We’ve already started to fix some things, specifically adding event triggers to our enemies, but there’s still many more things to solve! Here’s a quick list of things I want to tackle from what we encountered 2 days ago: From a technical limitation: We can’t move We only have one input which is clicking Some actual technical problems: The enemies are all black color We don’t have any of our UI’s anymore We’re going to address these problems over the next couple of days. Today, we’re going to focus on the technical limitations of Mobile VR, today’s priorities are: Discussing how to change our game design to accommodate our new limitations Implementing our new designs Edit, Important Note: After playing around with the Cardboard in Unity today and looking at this article about Google Cardboard’s inputs. It seems that we don’t have to use Google VR SDK. Unity already has most of the internal integration necessary to make a VR app Everything we had already works, the reason why there I initially thought there was a problem is, because of how we did raycasting. Specifically, our raycasting code targeted where our mouse/finger was touching, not the middle of the screen! More on this later. Step 1: Changing the Game to Fit our Mobile Limitations Like mentioned before, in the Google Cardboard, we have 3 limitations: We can’t move our characters position We only have tapping as an input to interact with the game Our cursor will always be in the middle of the screen Even for the Daydream Viewer, we will have the first 2 limitations. However, with the new Daydream Standalone device coming out, we’ll have World Space, finally allowing us to track the player’s movements without requiring external devices like what the Vive does! Anyways, back on topic. Considering these 3 limitations, here are my thoughts of what needs to be changed in our game: Because we can’t move, we should place our character in a more centered location for the enemies to reach us Because we can no longer run away, we should make the enemies weaker so that we don’t get swarmed Because we only have one input, we can shoot, but we can’t reload, we should get rid of the reload system Essentially, we’re going to create a shooter with our player in the center with enemies coming from all around us. Step 2: Implementing Our New Designs Now that we have everything we want to do planned, let’s get started in the actual implementation! Step 2.1: Placing the Character in the Middle Let’s place the character in the middle of where our spawn points are set. After playing around with it, I think the best spot would be at Position: (100, 1, 95) Select Player in our hierarchy. In the Transform component, set our Position to be X: 100, Y: 1, Z: 95 Step 2.2: Making the Enemies Weaker Next up, let’s make the enemies weaker. In the Enemy Health script component attached to our Knight, Bandit, and Zombie prefab, let’s change their health value. In order of our health, the order of size from largest to smallest is: Zombie > Knight > Bandit. Let’s set the health to be: Zombie: 4 HP Knight: 2 HP Bandit: 1 HP Here’s how we change our health: In Assets > Prefabs select our prefabs, in this case, let’s choose Zombie. In the Inspector, select the Enemy Health (Script) component and change Health to be 4 Do the same change with the other 2 prefabs. Step 2.3: Remove our ammo system Now it’s time to back to our Player Shooting Controller (Script) Component that we disabled yesterday. I want to keep the animation and sound effects that we had when shooting our gun, however, I’m going to get rid of the ammo and the need to reload. Here are my 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 + 10; //_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.GetButton("Fire1") && _timer >= ShootingDelay /*&& !_isReloading && _currentAmmo > 0*/) { Shoot(); if (!_isShooting) { TriggerShootingAnimation(); } } else if (!Input.GetButton("Fire1") /*|| _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"); //print("trigger shoot animation"); } private void StopShooting() { _audioSource.Stop(); _particle.Stop(); } public void Shoot() { //print("shoot called"); _timer = 0; Ray ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));//_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)); }*/ } // 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(); print("game over called"); } } I’ve kept what I commented out, here’s the clean version of our script. 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; 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<AudioSource>(); _audioSource.volume = 0.2f; _audioSource.clip = ShotSfxClips; } public void GameOver() { _animator.SetTrigger("GameOver"); StopShooting(); print("game over called"); } } Looking through the Changes We removed a lot of the code that was part of the reloading system. We basically removed any mentions of our ammo and reloading, however, I kept the changes involved with the shooting animation, shooting sound effects, and shooting rate. There were only 2 changes that were made: I changed the input we use to shoot from GetMouseButton to GetButton(“Fire1”), I believe this is the same thing, but I’m making the change anyways. Either option returns true when we’re touching the screen on our mobile device. I also changed our Ray from our raycasting system. Before casted a ray from where our mouse was located at, which before we fixed at the center. However, after we got rid of the code that fixed cursor to the middle, we needed a new way to target the middle. Instead of firing the raycast from our mouse, we now fire the raycast from the middle of our camera, which will fix our problem with our mobile device. Go ahead and play the game now. We should be able to have a playable game now. There are 2 things that will happen when we shoot: We’ll shoot a raycast and if it hits the enemy, they’ll be pushed back The enemies trigger event will detect that we clicked down on the enemy, so they’ll take some damage At this point, we have a problem: if we were to hold down the screen, we’ll push the enemy back, but they’ll only be hit once! That’s because we only have that deals with an OnClick event, but not if the user is currently selecting them. We’re going to fix this problem tomorrow, but I’ve done a lot of investigation work with raycasts now and want to take a break! Step 2.4: Changing the ScreenManager script One more thing we need to do before we leave. The Unity compiler would complain about a missing reference with our ScreenManager, specifically with the MaxAmmo variable that we got rid of. Let’s just get rid of it: 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; } } And we’re good to go! Technically speaking, we won’t be using this script anymore either. Conclusion And another day’s worth of work has ended! There’s a lot of things I learned about VR, such as: we don’t need ANYTHING that the Google VR SDK provides! Unity as a game engine already provides us with everything we need to make a VR experience. Google’s SDK kit is more of a utility kit that help make implementation easier. The TLDR I learned today is that we don’t have to be fixed on using Unity’s Raycasting script, we don’t need it. We can continue to use what we already have. However, for the sake of learning, I’m going to continue down re-implementing our simple FPS with the Google Cardboard assets! We’ll continue tomorrow on Day 39! See you then! Day 37 | 100 Days of VR | Day 39 Home

Josh Chang

Josh Chang

 

Day 37 of 100 Days of VR: Adding Basic VR Changes Into Our FPS I

Writers Note: Sorry for the lack of updates, I'm not dead yet! I went on a short vacation and got horribly sick. I'm hoping to continue as I have been going before. Welcome to Day 37! Today, things are finally going to get serious in working with VR! Currently, there are a lot of problems with the app from when we launch it. Some are just limitations and others are actual problems. However, before we start to go in and fix everything that we encountered yesterday, today we’re going to add the Google VR SDK into our game. Today we’re going to: Set up wireless debugging so we can both: debug and receive console print statements from our game Remove the old scripts that we’ll replace with VR Add the Google VR SDK into our game Set up a simple Event Trigger Today, we’re going to start working in VR, so let’s not waste time and get to it! Step 1: Setting Up Wireless Debugging/Consoles Before we do anything, one of the most important thing we should do is to set up remote debugging or at the very least, the ability to have our console statements be sent to Unity untethered. Currently, in Development mode, we only get console logs from our Android device if our phone is connected to our computer. This wire would become too limiting if we must do something like spin around in a circle. To fix this, we’re going to set up wireless debugging where our phone can send data remotely to Unity. We’re going to follow Unity’s documentation on attaching a MonoDevelop Debugger to an Android Device. The instructions are straightforward, so I’ll just leave the link to the instruction. In our current state, because we have no way of restarting the game, we must rebuild and run every single time we want to see the console wirelessly. The reason being we lost the ability to restart the game inside the game. However, when we re-add our ability to restart the game, wireless debugging will be more useful. Step 2: Removing Old Scripts that Needs VR Replacements It’s finally time to start working in Unity! Before we do anything, let’s think about the things that the Google VR SDK gave us and what must we get rid of in our current system that conflicts with the VR SDK. The main thing that the Google VR SDK provides is: The ability to move the camera with our head Its own Raycasting system What we need to remove from our game is: The ability to move our character The ability to move our camera The ability to shoot The crosshair UI Luckily for us, this process is going to be fast and easy. First, let’s remove our ability to move: In our game hierarchy, select the Player game object. Select the little cog on the top right-hand corner of the Player Controller (Script) and select Remove Component Next, let’s get rid of the game following our mouse. Select Player > Main Camera Remove our Mouse Camera Controller (Script) Component After that, let’s get rid of the shooting script. We’re going to come back later and re-purpose this script, but that’s going to be for a different day: Select Player > Main Camera > MachineGun_00 Disable the Player Shooting Controller (Script) We’re still going to need this. Finally, let’s get rid of the crosshair. As you recall, when we add the VR SDK, we get a gaze prefab that already adds a cursor in for us. Select HUD > Crosshair and delete it from our hierarchy. When we’re done, we’ll have a completely unplayable game! Yay…. Step 3: Adding the Google VR SDK in Recalling from the Google Cardboard demo, for our game, we’ll need to add: GvrEditorEmulator – to simulate head movement GvrEventSystem – to use Google’s Event System for dealing with raycasting GvrReticlePointer – for our gaze cursor GvrPointerPhysicsRaycaster – The Raycaster that GoogleVR uses to hit other objects The set up for this will also be very straightforward. Drag GvrEditorEmulator in Assets > GoogleVR > Prefabs > GvrEditorEmulator to the hierarchy Drag GvrEventSystem in Assets > GoogleVR > Prefabs > EventSystem to the hierarchy Drag GvrReticlePointer in Assets > GoogleVR > Prefabs > Cardboard to be the child of Main Camera Selectcs from Assets > GooglveVR > Scripts > GvrPointerPhysicsRaycaster.cs and attach it to our Main Camera. When we’re done, we’ll have something like this: Now with these prefabs and scripts in, we can rotate and look around our game by holding Alt. We can also shoot our raycasts with our VR Raycaster, however right now we don’t have an Event Trigger set up in our enemies that will detect them getting hit. Let’s do that! Step 4: Setting Up an Event Trigger Before we end today, I want to make a simple event trigger that allows us to be able to defeat an enemy. Luckily for us, we already have the function available to us! Specifically, inside our Enemy Health script, we have a code that we call to damage an enemy. Let’s set this up. We want to get something like this: For now, we’re only going to change our Knight enemy. Here’s what we’re going to do: Select our Knight prefab in Assets > Prefab > Knight Add an Event Trigger Component to our prefab. Click Add New Event Type to select what type of event we want to listen for Select PointerClick Now click + to add the object we want to access the scripts of. Drag our Knight Prefab into the empty Object slot Then we need to select the function to call: EnemyHealth > TakeDamage(float) Set the float value we pass in as 1 When we play our game now, when our gazer focuses on an enemy and we click, we’ll shoot him! There are a lot of things that we’re missing like the push back, but we can start focusing on the rest of that tomorrow! Now let’s do that to the rest of our prefabs: Bandit and Zombie! Conclusion There we have it! Our first dive into doing some work with VR. It turns out right now, there’s a lot less code that needs to be written, instead, a lot of it is just putting prefabs and scripts to the correct location so our game would work. Either way, now we have a game that is playable. Tomorrow, we’re going to discuss what changes that we should do to make a better VR experience. Or at the very least, as good as it was before we try to VR-ify it! Phew, it’s been a long day, I’ll see you all tomorrow on day 38! Day 36 | 100 Days of VR | Day 38 Home  

Josh Chang

Josh Chang

 

Day 36 of 100 Days of VR: Getting Our FPS Game Running in VR

Welcome back to Day 36! Yesterday we set up our mobile device to be able to play VR device and there’s nothing quite like that experience, especially if you’re the one that “made it”. If you’ve made it this far in the journey, but you haven’t tried using the Cardboard or other VR devices, I highly recommend trying it once! Now… with my pitch out of the way, let’s talk about what we’re going to do today! We finally started working in VR, today, we’re going to try and convert our simple FPS into a VR experience. This will be a multi-day progress. Today, I want to: Do some Asset clean ups for our app so we don’t have to spend forever building Set up our Google VR tools Play our game Getting the Project GitHub Repository Before we get started. I realize that not everyone (or anyone) followed me step by step to get to Day 36. In our Simple FPS game, we have reached a complete prototype so I’ve decided to make a Github Repository of our game before we start adding the VR components in. Specifically, after doing some manual cleanup work in Step 1. Now anyone can start following along to build our VR application. You can find my Simple VR First Person Shooter GitHub repository here. Step 1: (FAILED/SKIP) Clean Up Un-Used Assets If you haven’t tried switching to the Android platform in our simple FPS game, you might notice, that it… takes… FOREVER. I’ve tried building the app (and I waited 10+ minutes). Thank goodness for the Unity previewer, I would not know what to do if I had to build my app on my phone every single time! Luckily, I found that Google/Unity does have a solution for this. Google has Instant Preview. Unfortunately, which loads the game into our phone while also playing on the editor. The bad news is that this only appears to be for the Daydream Viewer so I’m going to hold off for now. However, let’s see what we can do to optimize this! When I say optimize, I really mean get rid of our un-used assets! Looking at what we have, we have 1 GB worth of files! That’s not good! IMPORTANT NOTE Unfortunately, this didn’t exactly work. I tried to export all our dependencies and then import it into a new project and there were some problems. It turns out, things like Layers and Tags do not get preserved so if we wanted everything to work, we had to manually add everything back in. Instead, I used the files I exported into a new project as a reference and then manually removed assets from a copy of our project (that’s what I get for not having source control!) Also from testing with a before and after build time, I believe that un-used assets DO NOT improve our build and runtime, so the only useful thing that came out of Step 1 was that I: Cleared some clutter so we can find files more easily now Made the project smaller so people can download it from Github faster, so not a complete loss! Step 1.1: Exporting our Assets Let’s get rid of our old un-used files! How? Well, a quick search on how to get rid of unused assets in Unity. All we need to do is: Select our scenes, which in this case is just Main Right-click and click: “Select Dependencies” Export our assets by going to Assets > Export Package… and save your package somewhere. Warning: This will not export Editor scripts and plugins, which in our current situation, is not a problem. Now at this point, we have 2 choices: Delete everything and re-import the assets that we exported or… Create a new project and import our exported project I’m going to do the 2nd choice and make a new project. Step 1.2: Importing our Exported Assets We’re going to create a new project and import everything we just exported. To do that: Select File > New Project… Call your file whatever you want, but I’m going to call mine First Person Shooter VR And now we’ll have a fresh new Unity project: Now we need to re-import everything we exported. Go to Assets > Import Package > Custom Package and find the .unitypackage that we just created Afterwards, just choose to re-import everything Step 2: Set Up Our VR Tools and Settings The next thing we need to do after we have our new project is that we need to import the Google VR SDK and configure our Unity. I’m going to be lazy and just refer us to Day 35: Setting Up Google Cardboard In Unity. Just do the exact same thing in downloading the SDK and setting up our Unity configuration. Note: In the repo, I already included Google VR SDK 1.100.1 and the necessary changes for Player Settings. I assume the PlayerSettings are project-based and not computer-based, but if it’s not, follow the instructions in Day 35. Step 3: Playing Our Game At this point, we should be almost ready to play our game! At this point, we have: imported the Google VR SDK Switched to the Android platform configured our PlayerSettings to the appropriate settings to run a Google Cardboard app The last thing we need to do that’s specific to our Game Project is that we try to build in Build Settings… we run into a problem to resolve incompatibilities between Color Space and the current settings. To fix this, we just need to change our Color Space from Linear to Gamma. To do that: Go to File > Build Settings > Player Settings > Other Settings > Color Spaces Change Linear to Gamma With this setting in, we’re ready to build our game. To do that, I recommend running the development build to build our app. Contrary to what the name sounds, development build DOES NOT make our build faster, instead it allows us to have access to useful debugging settings gives us access to the Profiler and Script Debugging. Now once you’re ready, let’s build our game! Make sure your phone is connected to your computer. Go to File > Build Settings… Enable Development Build Click Build And Run You can save the APK anywhere. Now enjoy the long wait~! Conclusion That’s it for today! Today we cleaned up a bit of our project and then set up the game so that we can run our app directly from our phone. The build is long and horrendous, which is unfortunate. There are a couple of solutions available, but I’m going to look at them some other day. We can also play the game directly from Unity. If we were to play the game right now, we’ll encounter problems. From a technical limitation: We can’t move We can’t reload anymore To actual technical problems: The enemies are all black color We don’t have any of our UI’s anymore I’ll have to investigate these things and solve them one at a time, but I can finally say, we’re here! We’re finally working in VR! That’s what we’re going to try and tackle the next couple of days. It’s going to be a fun one! Day 35 | 100 Days of VR | Day 37 Home

Josh Chang

Josh Chang

 

Day 35 of 100 Days of VR: How to Run Google Cardboard on an Android Device in Unity

Yesterday we looked at how we can work with VR and went through the basic demo and understood how everything worked. Today, we’re going to look at how we can install our game directly into the phone. To do everything today, we need to have: A phone that supports Android Level 19 (Kit Kat) A USB to Micro-USB (or Type-C for newer devices) cable (Optional) Google Cardboard Today we’re going to: Install the Android SDK so we can build and run our app Install a USB driver for our computer to detect our phone Set up our phone to be in developer mode Build and Run the demo app into our phone With all that being said, let’s get started! Today we’ll be following Unity’s Android SDK setup guide Step 1: Install the Necessary Android Software Since we’re building our VR app for Android applications, we need the required software to compile, build, and run our app on our phone. Download and install the latest Java SDK to run Android Studio Download and Install Android Studio You might have to restart your computer first for your computer to recognize the new Java SDK that you installed. When we’re done downloading and installing Android Studio (which will take a LONG time), we want to open the SDK Manager. In our new project, we can find our SDK Manager under Configure. Now we’ll get this: Under SDK Platform, select the platform we want to support, in this, case it’s Android 4.4 for Cardboard and Android 7.0 for DayDream, however, I believe if you install the newest version that’ll work for both. Under SDK Tools, install: Android SDK Platform-Tools Android SDK Tools Google USB Driver if you have a Nexus device With all of this, we should now have everything we need to be able to build our game into our Android device. Step 2: Install a USB Driver to Detect our Phone The next part (and probably the part I hate the most) is installing a USB driver that allows our computer to detect our phone. Go to Google’s documentation on where to find the appropriate OEM USB driver for your phone and install it. With any luck, your computer should be able to successfully recognize your phone when you plug it into your computer. If not, then I refer you to Google this problem as there are too many possibilities of what could have gone wrong. Step 3: Change Your Phone to Developer Mode Now our computer can connect to our mobile device, the final thing we need to do is have our phone be in developer mode so Unity (or Android) can create the app and install it on our phone. The instructions to enable Developer Mode varies depending on what your phone is. A quick Google search should give you what you need to enable it. However, the most common approach these days is to: Go to Settings > About phone > Build Number Click build number 7 times to enable Developer Mode Now under Settings, you should find Developer options. Go into Settings > Developer options and turn on USB Debugging Hopefully, with this step completed, we can finally move on to our configurations in Unity! Step 4: Configuring Unity to Build and Run our Android App Now that our phone is ready, it’s time to finally build our game into Unity. Make sure that your phone is connected to your computer In Unity go to File > Build & Run to create an APK file (our app) that will install it on our computer That’s it. Now in the perfect world, that’s it, we’re done. Enjoy our VR game! Unfortunately, there are always problems that we would encounter: Your API is at the wrong level. You’re missing a Bundle Identifier Failed to compile resources with the following parameters: major version 52 is newer than 51, the highest major version supported by this compiler. The 1st and 2nd problem can be resolved easily. The first problem is because we need to make sure that we create a minimum version of Android devices that have the software we need to run our VR application. In Player Settings under Other Settings… in Minimum API Level select API Level 19 for Google Cardboard support and API Level 24 for Google Daydream. If you choose API Level 24, just make sure that your phone can run Daydream! For the second problem, every Android app has a unique identifier that Google uses to identify the app. The error that we’re getting is that Unity is telling us that we’re using the default one and we should change it. In Player Settings under Other Settings… in Package Name change the string to be something else. Just make sure you follow the convention of <companyname>.<appname>. In our case, it doesn’t matter what it is, we can put anything we want. Now for the third and final problem. This one more interesting. Most likely your error is something like this: Failed to compile resources with the following parameters: -bootclasspath "C:/Users/JoshDesktop/AppData/Local/Android/android-sdk\platforms\android-24\android.jar" -d "C:\Users\JoshDesktop\git\Cardboard\Temp\StagingArea\bin\classes" -source 1.6 -target 1.6 -encoding UTF-8 "com\google\android\exoplayer\R.java" "com\google\gvr\exoplayersupport\R.java" "com\google\gvr\keyboardsupport\R.java" "com\google\gvr\permissionsupport\R.java" "com\google\vr\cardboard\R.java" "com\google\vr\keyboard\R.java" "com\Josh\Chang\R.java" "com\unity3d\unitygvr\R.java" warning: C:\Users\JoshDesktop\AppData\Local\Android\android-sdk\platforms\android-24\android.jar(java/lang/Object.class): major version 52 is newer than 51, the highest major version supported by this compiler. It is recommended that the compiler be upgraded. warning: C:\Users\JoshDesktop\AppData\Local\Android\android-sdk\platforms\android-24\android.jar(java/lang/AutoCloseable.class): major version 52 is newer than 51, the highest major version supported by this compiler. What all of this is saying is that our Java is out of date and we need to have at least Java SDK 8.52. In my case, I previously had 8.51 installed and when I installed version 8.52, Unity didn’t pick up on the changes. To fix this: Go to Edit > Preferences > External Tools under Android, select JDK and choose the path to your newest JDK file. For me, on my window machine, it was located at C:\Program Files\Java\jdk1.8.0_152 With all of this done, hopefully, you should be able to successfully build and run the GvrDemo on your phone + Google Cardboard if you have one. Conclusion Hopefully, this was a useful guide to getting your Android device set up to play the scene. Leave a comment if you run into problems and I’ll try to help and update this article with any new information. On a different note, it’s truly amazing playing with VR on our own mobile device. Just playing the VR game from Unity was interesting, but words can’t describe how much more realistic and interesting it becomes until you strap your phone onto your face! I think at this point, we have a good understanding of the basics and what is and isn’t possible with the Google Cardboard now. Tomorrow we’re going to look and see how we can incorporate the VR SDK into our simple FPS game to see how our game fairs in VR! Day 34 | 100 Days of VR | Day 36 Home  

Josh Chang

Josh Chang

 

Day 34 of 100 Days of VR: Setting Up the Google Cardboard In Unity

Now that we have made a conscious decision to work in VR, today I finally had the chance to play around with VR in Unity. Today we’re going to explore setting up and using Google’s VR SDK. You might think that setting up VR would be an extremely complex process, but after going through the process, I can say that starting out is simpler than you would think! Here’s our objective for today: Setting up support for Google Cardboard on Unity Going through Unity’s Tutorial Let’s get started! Step 1: Setting up Google Cardboard on Unity For today, I’ll be going through Google’s documentation for setting up VR on Unity. The nice thing about the Google VR SDK is that we can re-use most of the prefabs and scripts that are used with Google Cardboard and use them with Google Daydream. That’s 2 different platforms for the price of one. Today I’ll be following Google’s official documentation on getting started on Unity. Step 1.1: Install Unity At this point, I’m going to assume that we all have an older version of Unity (5.6+). To support being able to run our VR App, we’re going to need to Android Build Support, if you don’t have that installed already, re-download Unity and during the installation process, choose to include Android Build Support. Step 1.2: Adding the Google VR SDK After we have Unity set up correctly with Android Build Support, we need to get Google’s VR assets. Download Google’s VR SDK here. We’re looking to download the .unitypackage. After we have the package downloaded, it’s time to add it to a Unity project. For our case, we’re going to create a new project to play around with. In Unity create a New Project (File > New Project…) Once inside our new project, import everything from the package that we downloaded. In Unity we can import by going to Assets > Import Package > Custom Package. Step 1.3: Configuring Unity to Run VR Now we have imported everything we need, the last thing to do is to change some of our settings in Unity so we can run our game. Change Our Build Setting to Build and Run Android Applications The first thing we need to do is get Unity to run our game project on an Android platform. Open the Build Settings by going to File > Build Settings. Select Android and hit Switch Platform Wait for the game to finish re-packaging our assets for our new platform Change Our Player Settings to Support VR The next thing we need to do is to change our Player Settings so that we can support the specific VR SDK that we want. In our case, it’s going to be Google Cardboard. In Build Settings, next to Switch Platform, we have Player Settings, select it. In Player Settings, enable Virtual Reality Supported and then add Cardboard to our Virtual Reality SDKs Finally, in Minimum API Level, select API level 19 for the minimum Android version the device the players must have. Google Cardboard requires a minimum of level 19 and the Google Daydream Viewer requires a minimum of level 24. Once we have everything installed, we can finally get started on taking a first look at working with VR! Step 2: Looking Through the Unity Tutorial Now that everything is configured, we can officially start looking through Google’s SDK Basics. I went through the SDK basics while also going through the GVRDemo scene. In our new project go to Assets > GoogleVR > Demos > Scenes and open GVRDemo Google provides prefabs and scripts that will take care of the VR features for you. These are all located in Assets > GooglveVR > Prefab and Scripts. Here’s a breakdown of what they and the script attached to them do: GvrEditorEmulator prefab– Allows us to control our camera like how we might control it with our headset. Hold on to the alt button to rotate your view around the camera. GvrControllerMain prefab – Gives us access to the Daydream controller which we can implement actions with Google’s controller API to interact with the game GvrEventSystem prefab – Enables us to use Google’s input pointer system. Specifically, how our gaze/controller interacts and selects objects. GvrPointerGraphicRacyater script – This script is like a normal Graphic Raycaster that we would attach on to a UI canvas so that we can interact with our UI using our input devices (gaze or controller) GvrPointerPhysicsRaycaster script – This script shoots out a raycast directly in the middle of our screen to select something when we decide to click. We should attach this to our main camera. We must also attach Unity’s event system on each object we want to interact with when we select them. GvrControllerPointer prefab – This is the Daydream’s controller. It gives us an arm asset to imitate our controller. This prefab must the sibling of our Main Camera object where we attached our GvrPointerPhysicsRaycaster GvrReticlePointer prefab – This is the Google Cardboard’s gaze controller. It creates a dot in the middle of our screen which we use to select objects that are in the game. For this prefab to work we must make it a child of the Main Camera game object. There are quite a couple of other prefabs and scripts, but on the high level, these are the basics we’ll need to make a VR game. Let’s see this in action with the GvrDemo scene! Step 2.1: Looking at the demo scene When we open up GvrDemo, here’s what we see: I suggest that you explore around the scene and see the objects in our hierarchy, but on the high-level summary, here’s what we have in our hierarchy that’s relevant to just the Google Cardboard (because it has Daydream assets too) GvrEditorEmulator for us to emulate head movement in VR GvrEventSystem for Unity to detect our VR inputs when we select an object Inside Player > Main Camera, we have our GvrPointerPhysicsRaycaster script which allows us to use Google’s raycasting system for 3D objects Inside the Floor Canvas game object, we have the GvrPointerGraphicRacyate for us to interact with the UI. Finally, inside Player > Main Camera > GvrReticlePointer, we have our gaze cursor for Google Cardboard that we use to interact with the game world. The main point of this game is to click on the cube that appears in the game. When we click on the cube, it’ll be randomly moved somewhere else in the game. The interesting part of all of this is how we can trigger the code with our Gaze. Let’s look at the Cube and Unity’s Event Trigger system. The Event Trigger System is a way for Unity to recognize any action taken on the game object that the Event Trigger is registered onto. An action is something like: OnPointerClick OnPointerEnter OnPointerExit In our example, OnPointerClick will be triggered whenever we click on an object that has the Event Trigger attached to it. Here’s the teleport script: // Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using UnityEngine; using System.Collections; [RequireComponent(typeof(Collider))] public class Teleport : MonoBehaviour { private Vector3 startingPosition; public Material inactiveMaterial; public Material gazedAtMaterial; void Start() { startingPosition = transform.localPosition; SetGazedAt(false); } public void SetGazedAt(bool gazedAt) { if (inactiveMaterial != null && gazedAtMaterial != null) { GetComponent<Renderer>().material = gazedAt ? gazedAtMaterial : inactiveMaterial; return; } GetComponent<Renderer>().material.color = gazedAt ? Color.green : Color.red; } public void Reset() { transform.localPosition = startingPosition; } public void Recenter() { #if !UNITY_EDITOR GvrCardboardHelpers.Recenter(); #else GvrEditorEmulator emulator = FindObjectOfType<GvrEditorEmulator>(); if (emulator == null) { return; } emulator.Recenter(); #endif // !UNITY_EDITOR } public void TeleportRandomly() { Vector3 direction = Random.onUnitSphere; direction.y = Mathf.Clamp(direction.y, 0.5f, 1f); float distance = 2 * Random.value + 1.5f; transform.localPosition = direction * distance; } } We can ignore what the code does, but the important thing that I want to bring attention to are the public functions that are available: SetGazedAt() Reset() Recenter() TeleportRandomly() Where are these called? Well, if you look back at our Event Trigger that’s created in Cube, we set 3 event types: Pointer Enter Pointer Exit Pointer Click Then whenever any of these events occur, we call our public function. In this example, when we look at our cube, we’ll trigger the Pointer Enter event and call the SetGazedAt() function with the variable gazedAt to be true. When we look away, we trigger the Pointer Exit event and call the SetGazedAt() function with gazedAt to be false. Finally, if we were to click on the cube, we would trigger the Pointer Click event and call TeleportRandomly() to move our cube to a new location. Conclusion It’s surprising how un-complex this whole process is so far! I’m sure there are a lot more things to consider once we dive deeper into Unity, however for today, I think the progress we have made is sufficient. Tomorrow, we’re going to look at how we can get the demo app to run on a phone that supports a Google Cardboard (which I assume at this point is 99% of you guys here) Day 33 | 100 Days of VR | Day 35 Home

Josh Chang

Josh Chang

 

Day 33 of 100 Days of VR: Implementing the High Score System

Side Note: I've been feeling sick recently and progress have been slow, but I'm feeling better and ready to get back to it! Welcome back to day 33! Yesterday, we looked at 3 ways we can save and load data in Unity: with PlayerPrefs, Data Serialization, and saving our data to a server. Today we’re going to use what we learned the previous day to save our score in our simple FPS. Here’s the goal for today: Implement our SaveManager to help us save score Update our UI to show our high score when the game is over So, let’s get started! Step 1: Saving our Data Of the methods we’ve talked about, I’m going to use the PlayerPrefs to help us save our data. While we can technically use our PlayerPrefs anywhere we want between our scripts, it’s better for us to create a manager where we will centralize everything all our Saving/Loading work so that when we need to make changes, we don’t have to comb through all our Script to fix things. Step 1.1: Creating our SaveManager The first step to saving our score is to create our ScoreManager script and attach it to our GameManager game object. Select our GameManager game object in our hierarchy. In the Inspector, click Add Component and create a new ScoreManager In our SaveManager, we want to be able to save and load our high score. Here’s what we’ll have: using UnityEngine; public class SaveManager : MonoBehaviour { private string _highScoreKey = "highscore"; public void SaveHighScore(float score) { PlayerPrefs.SetFloat(_highScoreKey, score); } public float LoadHighScore() { if (PlayerPrefs.HasKey(_highScoreKey)) { return PlayerPrefs.GetFloat(_highScoreKey); } return 99999999999; } } Variables Used For our SaveManager, we only create a string _highScoreKey that we use to store the text that we want to use for our score. We never want to manually type our key in as that might lead to us mistyping and many hours spent debugging over a single spelling mistake. Walking Through the Code Our SaveManager script is only used to help us access our PlayerPrefs in one centralized location. The beauty of this system is that if one day we decide that we don’t want to use PlayerPrefs and use DataSerialization instead, we can just change SaveManager, but everything else that’s using it can stay the same. Here’s the code flow: In SaveHighScore() we save the score that we’re given to our high score. In LoadHighScore() we return the high score that we saved. It’s important to note that if we don’t have a value in our Prefab, we would return 0, however, in our case, a lower score is better, instead we return a very high score. Step 1.2: Modifying our ScoreManager to Expose Our Score Previously, we had our ScoreManager change the text of the score in our page, however, if we want to be able to show our high score at the end of the game. To do that, we need to use the Victory and GameOver panels that we made in the past. Luckily for us, in our GameManager, we already have some code that uses them. Now, for our GameManager to access our time (and save it with our SaveManager), we need to expose the score for other scripts to access them. Here are our changes to ScoreManager: using System; using UnityEngine; using UnityEngine.UI; public class ScoreManager : MonoBehaviour { public Text Score; private string _time; private bool _gameOver; private float _score; void Start () { _time = ""; _gameOver = false; _score = 9999999999; } void Update() { if (!_gameOver) { UpdateTime(); } } private void UpdateTime() { _score = Time.time; _time = ScoreManager.GetScoreFormatting(Time.time); Score.text = _time; } public void GameOver() { _gameOver = true; } public float GetScore() { return _score; } // we can call this function anywhere we want, we don't need to have an instance of this class public static string GetScoreFormatting(float time) { int minutes = Mathf.FloorToInt(time / 60); int seconds = Mathf.FloorToInt(time % 60); float miliseconds = time * 100; miliseconds = miliseconds % 100; return string.Format("{0:0}:{1:00}:{2:00}", minutes, seconds, miliseconds); } }  New Variables Used We create a new float _score that we’ll use to keep track of the time that passed for the player. Walking Through the Code Most of the code is the same. We just wrote some new functions, here’s what we did: In Start() we set _score to have a starting value In UpdateTime() we update _score to be the current time in the game. I also moved the code that gave us the time in a nice: minutes:seconds:milliseconds format to a static function called GetScoreFormatting() where we used to set our _time. Notice how I use GetScoreFormatting()? GetScoreFormatting() can be used just like this anywhere else in our game now, even if we don’t have an instance to ScoreManager. GetScoreFormatting() is just a copy and paste of what we originally had in UpdateTime(). Finally, we create a public function GetScore() to get the score the player earned in GameManager when they win That’s it! Now let’s combine everything we’ve worked on to create our high score system. Step 1.3: Use Everything in Our GameManager Now that we have everything we want, let’s use it in our GameManager script! Now we’re going to write some code that allows us to save our when the player wins. Here’s what we’ll have: using UnityEngine; public class GameManager : MonoBehaviour { public Animator GameOverAnimator; public Animator VictoryAnimator; private GameObject _player; private SpawnManager _spawnManager; private ScoreManager _scoreManager; private SaveManager _saveManager; void Start() { _player = GameObject.FindGameObjectWithTag("Player"); _spawnManager = GetComponentInChildren<SpawnManager>(); _scoreManager = GetComponent<ScoreManager>(); _saveManager = GetComponent<SaveManager>(); } public void GameOver() { GameOverAnimator.SetBool("IsGameOver", true); DisableGame(); _spawnManager.DisableAllEnemies(); } public void Victory() { VictoryAnimator.SetBool("IsGameOver", true); DisableGame(); if (_scoreManager.GetScore() < _saveManager.LoadHighScore()) { _saveManager.SaveHighScore(_scoreManager.GetScore()); } } 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 The first thing we did was we got ourselves an instance of SaveManager: _saveManager. Now with our SaveManager in GameManager, we can save and load scores. Walking Through the Changes The code that we’re adding uses our SaveManager to save our score. In Start() we instantiate our SaveManager which is also attached to the GameManager game object. When Victory() is called, we check their current score and then compare it with our high score from our SaveManager if our time is lower than the high score, than we set our score as the new high score With our changes to the GameManager, we now have a working high score system. Now the problem? We have no way of seeing if any of this works! Worry not, that’s going to be the next step of our work! Step 2: Update the Game Over Panels to Show Our Score Now that we have the code to change our high score, we’re going to work on displaying our high score in our UI. To do this, we’re going to make a couple of changes to our script, we’re going to: Change our GameOverUIManager to change the Text that we show. Change our GameManager to get our GameOverUIManager from our game over panels and then set the high score to show when the game is over. Step 2.1: Making Changes to GameOverUIManager If you recall, our GameOverUIManager was created to help us detect when the player clicks on the start over a button in our panel. We’re going to make some changes to change the text in our Panel to also say what our high score is. Let’s get to it! Here are the changes that were made: using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; public class GameOverUIManager : MonoBehaviour { private Button _button; private Text _text; void Start () { _button = GetComponentInChildren<Button>(); _button.onClick.AddListener(ClickPlayAgain); _text = GetComponentInChildren<Text>(); } public void ClickPlayAgain() { SceneManager.LoadScene("Main"); } public void SetHighScoreText(string score, bool didWin) { print(_text.text); if (didWin) { _text.text = "You Win! \n" + "High Score: " + score; } else { _text.text = "Game Over! \n" + "High Score: " + score; } print(_text.text); } } New Variables Used The only new variable that we used is a Text UI that we call _text. Specifically, this is the UI element that we use to tell the player that they won (or lost) the game. Walking Through the Changes The only changes we did was: Instantiate our Text UI in Start(). In the case of our panel, the Text was a child the panel that GameOverUIManager was attached to, so we have to look for the component in our child. Once we have an instance of our text, I created a new SetHighScoreText() that, depending on if we won or loss, change our text to show the high score. One important thing I want to mention. Do you notice the “\n”? \n is an escape character for new line. Which means that in our UI, we’ll see something like: Game Over High Score: 1:21:12 Step 2.2: Calling our GameOverUIManager from GameManager Next up, we want to be able to set the score from our GameOverUIManager from our GameManager. We must make some pretty big changes to our Game Panels that we use. Before we just grabbed the animator component, now, we need that and the GameOverUIManager. Besides that, we just need to call our GameOverUIManager script and set the text with our high score. Here’s what we’ve done: using UnityEngine; using UnityEngine.UI; public class GameManager : MonoBehaviour { public GameObject GameOverPanel; public GameObject VictoryPanel; private GameObject _player; private SpawnManager _spawnManager; private ScoreManager _scoreManager; private SaveManager _saveManager; void Start() { _player = GameObject.FindGameObjectWithTag("Player"); _spawnManager = GetComponentInChildren<SpawnManager>(); _scoreManager = GetComponent<ScoreManager>(); _saveManager = GetComponent<SaveManager>(); } public void GameOver() { DisableGame(); _spawnManager.DisableAllEnemies(); ShowPanel(GameOverPanel, false); } public void Victory() { DisableGame(); if (_scoreManager.GetScore() < _saveManager.LoadHighScore()) { _saveManager.SaveHighScore(_scoreManager.GetScore()); } ShowPanel(VictoryPanel, true); } private void ShowPanel(GameObject panel, bool didWin) { panel.GetComponent<Animator>().SetBool("IsGameOver", true); panel.GetComponent<GameOverUIManager>().SetHighScoreText(ScoreManager.GetScoreFormatting(_saveManager.LoadHighScore()), didWin); } 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 The biggest change is with the GameOverPanel and VictoryPanel. Before these were Animators that we used to show our panel, now we have the game objects themselves because we need to access more than just the animator. New Functions Created We created a new function: ShowPanel(), which takes in the GamePanel that we’re changing the text to and whether or not we won the game. From this information, we play our animator and get the GameOverUIManager and call SetHighScoreText() to change the text. Walking Through the Code Here’s how our new code gets used: Whenever the game is over, either the player lost or won, we would call GameOver() and Victory(). From there, we would disable our game and then call our new function ShowPanel() Depending on whether we won or not, we would pass in the correct Panel and state we’re into ShowPanel() Finally, in ShowPanel(), we would play the animation to show our Panel and call setHighScoreText() from our GameOverUIManager to change the text to display our high score. Step 2.3: Attaching our Panels back into GameManager Now with everything in place, we need to add our GameOverPanel and VictoryPanel, because when we changed them, we got rid of any reference to our previous models. Here’s what to do: Select our GameManager game object from the hierarchy. Look for the GameManager script component, drag our Panels (Victory and GameOver) from HUD in our game hierarchy, and put them in the appropriate slots. With that done, we should have something like this: Step 2.4: Fixing our Text UI Display Now with all of this implemented, we can finally play our game! After winning (or losing) in our game, we’ll get our BRAND-NEW panel: New… right? Wrong! It looks the same as what we had! What happened? It turns out, the text is still there, however, we didn’t have enough space in our Text UI to show the remaining text! This problem can be solved easily. We just need to increase the size of our Text UI in our GameOver and Victory Panel in our hierarchy. The first thing we need to do is reveal our Panels that we hid. In our Panels, find the CanvasGroup component, we previously set our alpha to be 0 to hide our panel. Let’s change that back to 1 so we can see our panel again. Just don’t forget to change it back to 0. In the same Panel, select its child game object, Text. We want to try playing around with the Width and Height field in our Rect Transform component. I ended up with: Width: 300 Height: 80 I also added some new Text for a demo of what to expect. Here’s what we have now: Make sure that you make this change to both our GameOver Panel and our Victory Panel in the hierarchy. Now if we were to play the game, here’s what we’ll have when we win: And when we lose: Don’t ask how I won in 2 seconds. I modified our scripts a bit, okay? Conclusion With all of this, we’re done with day 33! Which means we’re officially 1/3 of the way through the 100 days of VR challenge! Not only that, now that we have this high score system, I’m going to officially call our simple FPS finished! Tomorrow, I’m finally going to start looking more into how to do VR development! Until then, I’ll see you all on day 34! Day 32 | 100 Days of VR | Day 34 Side topic: Choosing a phone and platform to develop VR in Home

Josh Chang

Josh Chang

 

Day 32 of 100 Days of VR: Learning about Saving and Loading Data in Unity

Hello and welcome back to day 32! Today is going to be an exciting day. We’re going to work on something that we’ve never seen before in any of the tutorials: Saving and Loading data in Unity! Specifically, we’re going to save and load our high score from our game in Unity. From this great Unity video on how to Save and Load data and my own research, I found 3 ways for us to save data: Using PlayerPrefs Using Data Serialization Using a Server Now with our goals set, let’s get started! Step 1: Saving with PlayerPrefs In the first step, we’re going to look at some of the possible ways we can save and load data. From this great Unity video on how to Save and Load data and my own research, I found 3 ways for us to save data: Note: for step 1, none of the code will be used in our actual game. In fact, we’re going to make a separate script just for this. Step 1.1: Using PlayerPrefs The first method is PlayerPrefs. PlayerPrefs is a class that we call that allows us to save and load simple data (think numbers and string) across multiple game sessions. We can think of PlayerPrefs as a dictionary/table object that takes in a key/value pairing. For those who haven’t worked with something like this before, what this means is that PlayerPrefs is an object that we can store specific values to a string and when we want to get that value back, we can give it the string. We’ll see how this works: In our GameManager game object, click Add Component and create a new TestSaveManager In the code, we’re just going to store a value in and then print it out to our console. Here it is: using UnityEngine; public class FakeSaveManager : MonoBehaviour { void Start () { if (PlayerPrefs.HasKey("FakeScore")) { int savedScore = PlayerPrefs.GetInt("FakeScore"); print("We have a score from a different session " + savedScore); } else { PlayerPrefs.SetInt("FakeScore", 1337); print("We don't have a score yet, so we're adding one in"); } } } New Variables Used None Walking Through the Code PlayerPrefs is a static object that we can just access. Here’s what we did: In Start() the first thing we did was check if we stored a value to our key string: FakeScore. From here 2 things will happen. First Run Through The first time we run the code, we haven’t stored anything yet, so we’ll go to our else statement and set an Int with the value 1337 to our key FakeScore. We also print in our console notifying us that it’s the first time we did that. Now if we run the game for the first time, here’s what we’ll see: Notice that it says we don’t have a score yet? Second Run Through Now after we ran the code for the first time and we added our key/value pair to our PlayerPrefs. If we were to run the code again, we’ll go to the other code path. In the if statement, we’ll get the value we stored inside our key “FakeScore”, which is 1337. We’ll print out 1337. Play our game again and here’s what we get: See how we have a score now? That means our score persisted from our previous game. Neat! Step 1.2: Why use PlayerPrefs Looking at what we’ve done, when do we want to use PlayerPrefs? The benefit of PlayerPrefs is: Fast and easy to use with no setup required to store primitive variables The con of PlayerPrefs: If the data we’re storing is important, the user can easily access the data and change it Why is it insecure you might ask? Well, it turns out that any data that we’re saving is saved in open space on your computer. In a Windows machine, you can find these values un-encrypted in our registry. Here it is: Do you see the 2nd item from the top of the list FakeScore and the value is 1337. Any computer savvy hacker who has knowledge of how Unity works can easily go in and make changes to these values to increase our score. To summarize: Only use PlayerPrefs to store information that doesn’t really affect the game, such as player settings: brightness, audio sound, special effects, etc. Do not use it to store things like high scores or sensitive information like your credit card information. Step 2: Saving with Data Serialization After looking at PlayerPrefs we have another method of storing data and that is using data serialization with the help of BinaryFormatter and FileStream class. How this method works, is that we’re: Taking data (either an object we made serializable, a primitive value, or a JSON) Convert it to numbers Storing it somewhere in our device We’ll discuss the details further down. Step 2.1: Using Data Serialization I’ve made some changes to our FakeSaveManager: using System.IO; using System.Runtime.Serialization.Formatters.Binary; using UnityEngine; public class FakeSaveManager : MonoBehaviour { void Start () { /*if (PlayerPrefs.HasKey("FakeScore")) { int savedScore = PlayerPrefs.GetInt("FakeScore"); print("We have a score from a different session " + savedScore); } else { PlayerPrefs.SetInt("FakeScore", 1337); print("We don't have a score yet, so we're adding one in"); }*/ SaveWithSerializer(); LoadWithSerializer(); } private void SaveWithSerializer() { BinaryFormatter formatter = new BinaryFormatter(); print("Save location: " + Application.persistentDataPath); FileStream fileStream = File.Create(Application.persistentDataPath + "/save.dat"); formatter.Serialize(fileStream, 1337); fileStream.Close(); } private void LoadWithSerializer() { BinaryFormatter formatter = new BinaryFormatter(); FileStream fileStream= File.Open(Application.persistentDataPath + "/save.dat", FileMode.Open); print("serializer got back: " + formatter.Deserialize(fileStream)); fileStream.Close(); } } New Variables Used None Walking Through the Code The first thing you might notice is that I commented out the PlayerPrefab code. We don’t need that anymore, we’re just going to focus on the serialization part. Before we walk through the code, let’s talk about these individual classes that we’re using. FileStream We can think of FileStream is a class that allows us to interact with files. In this case, use File.Create() and File.Open() to give us access to these files that we want to access. Notice how we use something called Application.persistentDataPath when we create and open a file? persistentDataPath is a file location in our computer/phone that we can use to store our game files. Each path is different depending on the OS that the game is running on. On a windows machine, here’s where the file would be saved to: Notice how we called our file “save.dat”, that’s just what we call it, the file can be any format we want and it’ll still function the same way. After we set up our FileStream, we will give it to our BinarySerializer so that it can use it to store our data. It’s also important to remember that when we’re done using a FileStream, we should always call Close() to prevent a memory leak. BinaryFormatter We can think of the BinaryFormatter class as a useful utility that takes in a FileStream and reads/write data into the file. In our specific example, I put the float 1337 inside, but we can put in classes that we create ourselves. However, if we were to create our own object, we must make sure we tell Unity to serialize it (by putting [Serialize] on top of the class name). The two important function we have to know is Serialize() and Deserialize(). Serialize is how we change our data into binary and then store them in our File Deserialize is how we read the data back from binary. If we were to open the file that we saved data into, here’s what we have: Now that we understand everything used, let’s walk through the code: In Start(), we call SaveWithSerializer() which uses a BinaryFormatter and a FileStream to help us store our data, which in this case is just a float value. We create a new FileStream with the path to where we want to save our data to. We use Application.persistentDataPath to give us a location to store our file that changes depending on what OS is running our game. This is Unity making sure we put our save files in the right location so we can access it back later no matter what platform we’re using. After we have our FileStream we would use the BinaryFormatter to Serialize our data to the path we specified. After that, make sure to close our FileStream to prevent memory leaks! After we save, we call LoadWithSerializer() we do a similar operation where we create a BinaryFormatter and a FileStream to the location of the file we previously saved. With these two objects, we Deserialize, which means we unencrypt our data and put it back into a usable form for us. At this point, we have our data back. Step 2.2: Why Use Data Serialization? At this point, you might guess why we might want to use Data Serialization: Our data is encrypted so it’s harder for the players to find it and modify the file. In our example, we only store one variable, but we can actually save a lot more than just that However, just like the PlayerPrefab, the con is: We know the location of the file, and just because it’s in binary, it doesn’t mean it can’t be reverse engineered! When should we use Data Serialization? The answer: almost everything! Data serialization can be used as a database for our equipment, magic spells, and more! We can also use it to store information about our player character. My only caveat is that if the game you’re creating is multiplayer where you don’t want players to have an unfair advantage, then you DO NOT want to save data about the player locally. We can store information that the game references where we don’t modify, such as stats of an equipment locally, however information like player inventory is best stored somewhere else. Where is that somewhere else? Good question, that’s the topic of our next method. Step 3: Saving Data to A Server The final and last way for us to store data for our game are to save the data to an external web server which we control and access. From the external server, we would keep track of the state of the player, for example: What level they are How much money they are What items they have in their inventory All information that is changeable by the player should be saved externally in a server, where we can do verifications to make sure the player is what they say they are. Step 3.1: Using a Server I’m not going to create a server, but rather discuss how we would do it if we wanted to. The first thing we must do is host a web server online somewhere. Once the server is up, from our game, ie the client, we would make HTTP network request to our server passing around whatever data our server requires. A general flow of what we would expect is that: Whenever we’re ready to save our data, we would send the data to our server Whenever we want to load our data, we would make a request to our server for our data. When sending data over the network, we can’t just send our whole class over, our web server have no idea what anything is. To solve this problem, we’re going to have to convert our objects into a JSON object and then send it across the network. I won’t go too deep into the details, but a JSON object is basically a string representation of our object that we can easily convert back and forth. We would send this string to our server where we would parse it, validate the data, and then save it in the database. When we’re loading information, the server would send a JSON object back to the game where we would also parse and change the JSON back into something we can use. Step 3.2 Why use a server? The benefit of using this approach to store our data is: Cheating is impossible. Assuming there are no vulnerabilities and we have good data checks, it’ll be very hard for the player to be able to modify their own data and have it saved on our server. Similar to Data Serialization, we can save more complex data than just primitive variables. The cons are: We need a server, which costs money. There’s more overhead work needed. We need to create our own database, know how to set up a server, and deal with storing multiple players instead of one. The big question here is: when should we use a server? The answer: In any game that has multiplayer that we don’t want players to get an unfair advantage against each other. If not, there should be no problem to storing everything locally. Conclusion Today, we explored the options that are available to us to save data in a game. The 3 ways we explored are: PlayerPrefs Data Serialization Sever Most likely, in a real game, we would use all 3, but the best way for me to summarize when to use each is: Use PlayerPrefs to store player setting information that doesn’t affect the game itself. Use Data Serialization to store database information that we will only reference, but not change Use a Server when we have a multiplayer game where we want to ensure no one can cheat by sending all player data to a server where we can do validation on the authenticity of the data. For a single player game, however, we can save the data in a serialized file or if it’s simple enough, even in our PlayerPrefs. Now that we spent Day 32 looking at saving data, it’s finally time for us to start using it on Day 33! I’ll see you all next time to start putting everything we learned into action! Source: Day 32 Visit the 100 Days of Unity VR Development main page. Visit our Homepage

Josh Chang

Josh Chang

 

Unity Day 31 of 100 Days of VR: Stopping The Time Score System When The Game Is Over

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: Pause our time after we win or lose 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: Code to stop our ScoreManager from updating the string 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: In Start() we set instantiate _gameOver In Update() we update our time if we’re still playing otherwise we don’t do anything 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: In Start() we got our _scoreManager that was attached to our GameManager game object 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: Create a new state transition to stop shooting in our gun Animator Create a new game over function for our gun to stop everything when the game is over 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 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: Select Player > Main Camera > MachineGun_00 and open the Animator tab (Windows > Animator) Create a new parameter, it’ll be a trigger and call it GameOver Create a new state called GameOver Create a new transition from Any State to Select that transition and set the transition condition to be when GameOver is triggered When you’re done, we should have something like this: 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: We call our GameOver trigger to stop our shooting state 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

Josh Chang

 

Day 30 of 100 Days of VR: Creating a Time Score System in Unity

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: Create the code for our time system 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. In Start() we instantiate _time to be empty In Update() we call a function UpdateTime() to get our correctly formatted time string. In UpdateTime(), we calculate what our minutes, seconds, and miliseconds are, and then we use String.format to build the string we want. 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. Alignment Anchor: Top and Center Text: Time Font Style: Bold Font Size: 24 Alignment: Center and Middle Color: White We should have something like this:   We should have this on our scene now:   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: 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:   And when we play the game we’ll have our time increasing:   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

Josh Chang

 

Day 29 of 100 Days of VR: Spawning New Enemies in Unity and Dealing With “Ghost” Colliders

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: Start spawning all 3 of our enemies 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: 5 Knights at Wave 1 5 Bandits at Wave 2 5 Zombie at Wave 3 When we’re done, we’ll have something like this:   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!   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.   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:   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

Josh Chang

 

Day 28 of 100 Days of VR: Adding A New Tank Enemy

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:   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:   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. Go to Assets > Animator and right click and create a new Animator Override Controller. Let’s call it Zombie Animator Controller. Set Knight Animator Controller to be our Controller in our Zombie Animator Controller Now you’ll see something like this:   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!   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:   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:   Back to our Zombie Animator Controller, let’s add our clips in and we should have this:   Step 2.2: Attaching the Animator Controller Now that we have created our Animator controller, let’s create our zombie to use it! In Assets > Zombie_0_1 > Animations drag the Zombie_0_1 model into our game’s hierarchy. 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:   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. Set the Z value for Center to be 0.6 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:   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: shoot the enemy 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:   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.   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: If we don’t move and let the enemy attack us, he just stops attacking us at one point! 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.   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: Select our Zombie game object In the Animation Tab (Window > Animation), select our ZombieAttack clip. Experiment around with what’s a good frame to add our event, but I chose frame 21 After creating our event, set the function to run to be Attack() When we’re done, this is what we’ll have:   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:   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

Josh Chang

 

Day 27 of 100 Days of VR: Adding New Bandit Enemy

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: A lower health, but faster unit 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:   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.   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! In Assets > BasicBandit, drag and drop Basic_BanditPrefab into our game. 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:   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.   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   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   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   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:   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:   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: We can shoot the enemy The enemy can push us around In the bandit’s case, we have 2 pieces: the body and the clothes.     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:   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:   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.   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.   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

Josh Chang

 

Day 26 of 100 Days of VR: Adding Missing Audio, UI, and Creating Enemy Victory State

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: When we get hit, there’s no player hit sound effect We should fix the crosshair to be something nice 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: In Start() we call SetupSound() which is where we create an AudioSource component in code and then set the volume. 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. 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.   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   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:   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: Check if the enemy is still alive 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: In Start(), we create a new instance of a GameObject, which will put _enemyContainer in our actual game. It’ll be called “Enemy Container” 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: We implemented PlayVictory() that our SpawnManager will call. It’s pretty basic, we set our state to be idle. 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: In Start(), we grab our SpawnManager script, nothing new or surprising here (remember that our SpawnManager is a child of GameManager) 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).   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: In Start(), we instantiate _isSpawning to be true. 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. 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

Josh Chang

 

Day 25 of 100 Days of VR: Creating Victory State After We Defeat All Enemies

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!”   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: I created a new public Victory() that our SpawnManager will call to trigger our Victory Panel animation. 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: 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. 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.   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!   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

Josh Chang

 

Day 24 of 100 Days of VR: Creating Waves for Spawning System in Unity

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.   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: We get access to our SpawnManager: _spawnManager, which we’ll notify when an enemy die In Start() we get _spawnManager by finding our SpawnManager game object from its tag and then getting the SpawnManager script attached to it 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.   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

Josh Chang

 

Day 23 of 100 Days of VR: Creating a Spawning System for Enemies in Unity

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. 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). 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() 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. 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: Create a folder in our Assets folder called Prefabs 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.   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:   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.   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.   With all of this in place, if we play the game, we should now have multiple enemies coming at us.   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

Josh Chang

 

Day 22 of 100 Days of VR: Creating the Game End State and Game Over Animation

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: Create a new state controller for our animation so that we don’t instantly play our animation Play the animation when the player is defeated and all states related to that 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.   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: create a new default state,called Start that is our default transition from Entry that transitions into our Game Over state create a new boolean parameter IsGameOver set a transition from Start to Game Over 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:   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: Create a new GameManager script that will take care of the logic of the ending 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: We get the GameOverAnimator from our Game Over object and we take the Animator from it. 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: Add our GameManager object 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: We create a private field for our GameManager class In Start() we instantiate our GameManager 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: in GameManager, when GameOver() is called disable the player’s movements and re-enable our cursor to select a button create a GameOverUIManager that will deal with the logic of clicking a button 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: Created a reference to our player character in Start() 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.   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: We have a private _button that is the button from our Game Over Panel. In Start() we instantiate our button and we set the OnClick Listener to our button to run ClickPlayAgain() whenever the user clicks the button 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.   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: 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. 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!   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

Josh Chang

 

100 Days of VR: Day 21 Creating Game Over UI in Unity

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:   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:   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: Canvas Group > Alpha — to set our alpha 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: 0, set alpha to be 0 60, we can just leave the value at 1 For Rect Transform, at the key at frame: 0, set Anchored Position.y to be 100 60, we can just leave the value at 0 When we’re done, we should have something like this:     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

Josh Chang

 

100 Days of VR: Day 20 Fixing Game Object Collider in Unity

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?   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:   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. 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. 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:   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.   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: Like our EnemyAttack script, we want access to our player game object and we want to check if we collided with the enemy. In Start() we make sure to instantiate both of our variables. 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 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: First, we create new public variables: LeftArm and RightArm that are the FistCollider script that we just created. We cleaned up a lot of the code, specifically the Collision part and anything that had to do with it. 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:   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

Josh Chang

 

Day 19 Creating Player Health System and Health UI in Unity

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.   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?   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: Set Width of Fill to be 0 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:   However now we have another problem, if we were to set the value of the slider to 1, here’s what we would get:   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:   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)!   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: 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. Next, we create a public Health to represent our max health and _currentHealth to represent how much health our player has. In Start() we instantiate our _currentHealth to be our max health. 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:   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: We have a trigger collider that detects when our knight should start attacking Then we have a mesh collider on our player that will detect when we’re touching the player 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

Josh Chang

 

100 Days of VR: Day 18 Creating Weapon Ammo in Unity

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:   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.   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:   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: We made 2 new variables MaxAmmo and _currentAmmo to represent how many bullets we have left to shoot. In Start() we initialize our current ammo amount. Whenever we shoot, we make sure that our ammo is above 0 otherwise we can’t shoot and then we decrement our _currentAmmo count. 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: We take in the Text game object that we use for ammo. 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 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: We looked for our ScreenManager by looking for it via a tag we set on it called ScreenManager. 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!   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

Josh Chang

 

100 Days of VR: Day 18 Creating Weapon Ammo in Unity

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:   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.   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:   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: We made 2 new variables MaxAmmo and _currentAmmo to represent how many bullets we have left to shoot. In Start() we initialize our current ammo amount. Whenever we shoot, we make sure that our ammo is above 0 otherwise we can’t shoot and then we decrement our _currentAmmo count. 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: We take in the Text game object that we use for ammo. 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 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: We looked for our ScreenManager by looking for it via a tag we set on it called ScreenManager. 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!   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

Josh Chang

 

100 Days of VR: Day 17 Shooting With A Gun In Unity

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:   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:   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: We have two new variables GunEndPoint and _lineRenderer that we instantiate in Start() 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. If our raycast hits something, we want to set whatever we hit as our 2nd point in our line. 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 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

Josh Chang

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!