MultiCoinChest.cs
Assets > Scripts > MultiCoinChest.cs
Assets > Scripts > MultiCoinChest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using TMPro;
public class MultiCoinChest : MonoBehaviour
{
private PlayerControllerThatLevelsUp playerControllerThatLevelsUp;
private int DiceRollResult = 0;
public int ChestDifficulty = 6;
public int DiceRollResultRequirement = 0;
public Transform Coin;
public int numberOfCoinsToSpawn;
[SerializeField] private GameObject Chest;
[Header("Audio")]
[SerializeField] private AudioSource musicSource;
[SerializeField] private AudioSource WorldSFXSource;
[SerializeField] private AudioClip GameWinMusic;
[SerializeField] private AudioClip LevelRequirementFail;
[SerializeField] private AudioClip LockpickFail;
[Header("UI Elements")]
[SerializeField] private TextMeshProUGUI WinLoseText;
[SerializeField] private TextMeshProUGUI RestartExitText;
[SerializeField] private TextMeshProUGUI LevelRequirementLockpickFailText;
[SerializeField] private TextMeshProUGUI Timer;
private void Start()
{
musicSource = musicSource.GetComponent<AudioSource>();
WorldSFXSource = WorldSFXSource.GetComponent<AudioSource>();
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.GetComponent<PlayerControllerThatLevelsUp>())
{
playerControllerThatLevelsUp = other.gameObject.GetComponent<PlayerControllerThatLevelsUp>();
CheckIfLockPickIsSuccessful();
}
}
private void CheckIfLockPickIsSuccessful()
{
DiceRollResult = Random.Range(0, 6);
Debug.Log(DiceRollResultRequirement + " / " + DiceRollResult);
if (playerControllerThatLevelsUp.currentLockPickSkill >= ChestDifficulty)
{
if (DiceRollResult == DiceRollResultRequirement)
{
SpawnCoins(numberOfCoinsToSpawn);
transform.position = new Vector3(30, 30, 30);
Destroy(Chest);
Debug.Log("Chest destroyed. " + DiceRollResult);
WinLoseText.enabled = true;
WinLoseText.GetComponent<TextMeshProUGUI>().text = "You won!";
LevelRequirementLockpickFailText.enabled = false;
Timer.GetComponent<GameTimer>().enabled = false;
StartCoroutine(WaitForSoundToStop());
}
else if (DiceRollResult != DiceRollResultRequirement)
{
Debug.Log("Unlucky " + DiceRollResult + "!");
StartCoroutine(LockpickFailTextAppear());
return;
}
}
else if (playerControllerThatLevelsUp.currentLockPickSkill < ChestDifficulty)
{
print("You are not a high enough level. " + DiceRollResult);
StartCoroutine(LevelRequirementFailTextAppear());
return;
}
}
private IEnumerator WaitForSoundToStop()
{
musicSource.clip = GameWinMusic;
yield return new WaitUntil(() => musicSource.isPlaying == false);
RestartExitText.enabled = true;
RestartExitText.GetComponent<TextMeshProUGUI>().text = "Exiting...";
yield return new WaitForSeconds(2);
Debug.Log("Exiting runtime.");
#if UNITY_EDITOR
{
EditorApplication.isPlaying = false;
}
#else
{
Application.Quit();
}
#endif
}
private IEnumerator LevelRequirementFailTextAppear()
{
if (LevelRequirementLockpickFailText.enabled == true)
{
yield return null;
}
else if (LevelRequirementLockpickFailText.enabled == false)
{
LevelRequirementLockpickFailText.enabled = true;
LevelRequirementLockpickFailText.GetComponent<TextMeshProUGUI>().text = "Not high enough level!";
WorldSFXSource.PlayOneShot(LevelRequirementFail);
yield return new WaitForSeconds(3);
LevelRequirementLockpickFailText.enabled = false;
}
}
private IEnumerator LockpickFailTextAppear()
{
WorldSFXSource.PlayOneShot(LockpickFail);
if (LevelRequirementLockpickFailText.enabled == true)
{
yield return null;
}
else if (LevelRequirementLockpickFailText.enabled == false)
{
LevelRequirementLockpickFailText.enabled = true;
LevelRequirementLockpickFailText.GetComponent<TextMeshProUGUI>().text = "Lockpick attempt failed!";
yield return new WaitForSeconds(3);
LevelRequirementLockpickFailText.enabled = false;
}
}
private void SpawnCoins(int numberOfCoinsToSpawn)
{
for (int i = 0; i < numberOfCoinsToSpawn; i++)
{
var spawnPosition = new Vector3(transform.position.x + Random.Range(-1.0f, 1.0f), transform.position.y, transform.position.z + Random.Range(-1.0f, 1.0f));
Instantiate(Coin, spawnPosition, transform.rotation);
}
}
}
This code will be explained in sections, which each have a different colour to separate them from each other. Comments in the script have been removed from this excerpt, but will be included in the Google Doc containing the script, linked at the bottom of the page.
SECTION 1: Setting up the script and variables
This first part of the script is dedicated to listing out all the namespaces and variables that will be used in this script.
using System.Collections
using System.Collections.Generic
using UnityEngine
using UnityEditor
using UnityEngine.SceneManagement
using UnityEngine.UI
using TMPro
These are the namespaces that are used in this script. It's longer than the majority of the scripts covered for project 1 and 2, which is to be expected with a longer script as more functions that expand beyond the standard libraries are most likely being used.
There are also some new namespaces being used here, such as UnityEngine.UI, which deals with using the UI in the game, and TMPro, which is Unity's most recent UI Text system (that is a lot better than their old one).
private PlayerControllerThatLevelsUp playerControllerThatLevelsUp
private int DiceRollResult = 0
public int ChestDifficulty = 6
public int DiceRollResultRequirement = 0
public Transform Coin
public int numberOfCoinsToSpawn
[SerializeField] private GameObject Chest
These are the variables that the script will use in making the chest function and determining whether the player will be successful or not at opening the chest, as well as dealing with the coins that will spawn if the player is successful at opening the chest.
This script communicates with the PlayerControllerThatLevelsUp script as the GameObject that that script is attached to will be what this script is looking for.
[Header("Audio")]
[SerializeField] private AudioSource musicSource
[SerializeField] private AudioSource WorldSFXSource
[SerializeField] private AudioClip GameWinMusic
[SerializeField] private AudioClip LevelRequirementFail
[SerializeField] private AudioClip LockpickFail
[Header("UI Elements")]
[SerializeField] private TextMeshProUGUI WinLoseText
[SerializeField] private TextMeshProUGUI RestartExitText
[SerializeField] private TextMeshProUGUI LevelRequirementLockpickFailText
[SerializeField] private TextMeshProUGUI Timer
This section is for the variables that will be used for extra effects to show the player that they've either lost or won, such as a win tune and on-screen text which will announce that they've won. Something to take note of here is the use of the Header attribute at the top of these two lists of variables, which organizes the inspector by adding headers above these groups, allowing the ability to group variables in the inspector.
SECTION 2: Defining what calling the variables alone does
This next part of the script defines what calling the variables alone does, allowing us to use these variables in the script without having to go and access the required component before the function every time.
private void Start()
{
musicSource = musicSource.GetComponent<AudioSource>()
WorldSFXSource = WorldSFXSource.GetComponent<AudioSource>()
}
In this Start method, which calls all the code within it upon the first frame of the game and then never again (unless the script is restarted), two variables have been defined for when they are called alone. The first is the musicSource variable, which is an Audio Source in the game world that has been designated for handling the music. The second is the WorldSFXSource variable, which is also an Audio Source, only this one is designated for handling everything else such as the coin sound effect.
Both have been defined in this method so that whenever they are called on their own, the script automatically gets the AudioSource component from them before doing anything else.
A flow chart that shows the order of operations for each function in this section.
SECTION 3: Making the chest interactive to the player and setting up for Section
The chest is no better than just a scenery prop if you can't do anything with it. This section allows the player to interact with the chest, and allows the rest of the script to work.
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.GetComponent<PlayerControllerThatLevelsUp>())
{
playerControllerThatLevelsUp = other.gameObject.GetComponent<PlayerControllerThatLevelsUp>()
CheckIfLockPickIsSuccessful()
}
}
In this part of the script, the code is called through the OnTriggerEnter method, which is called when the script detects that the specified collider (in this case, other, which is a general term for any other collider) has entered the trigger collider. When the method is called, it will then check if the GameObject with the collider that has just entered the trigger collider has the PlayerControllerThatLevelsUp script attached to it, and if this is the case, it will then define what happens when calling playerControllerThatLevelsUp on its own, which is that it will get the script component from that GameObject. The last thing it does is call the CheckIfLockPickIsSuccessful method, which is where this script really kicks off.
A flow chart that shows the order of operations for each function in this section.
private void CheckIfLockPickIsSuccessful()
{
DiceRollResult = Random.Range(0, 6)
Debug.Log(DiceRollResultRequirement + " / " + DiceRollResult)
This last part of this section is the method that will be called if the player enters the trigger collider of the chest. The first thing this method does is create a six-sided dice
A flow chart that shows the order of operations for each function in this section. There's no "end" as this is not the end of this method, but is the end for this section.
SECTION 4: What happens if the player is successful?
This part of the script is for what happens if the player is successful in opening the chest, therefore winning the game.
if (playerControllerThatLevelsUp.currentLockPickSkill >= ChestDifficulty)
This if statement checks to see if the currentLockPickSkill of the player is higher than or equal to the ChestDifficulty value, that being how difficult it is to unlock the chest; what level your lock pick still needs to be to unlock the chest. If it finds that this is the case, it will call the code within it. If not, see the second part of Section 5.
if (DiceRollResult == DiceRollResultRequirement)
{
SpawnCoins(numberOfCoinsToSpawn)
transform.position = new Vector3(30, 30, 30)
Destroy(Chest)
Debug.Log("Chest destroyed. " + DiceRollResult)
WinLoseText.enabled = true
WinLoseText.GetComponent<TextMeshProUGUI>().text = "You won!"
LevelRequirementLockpickFailText.enabled = false
Timer.GetComponent<GameTimer>().enabled = false
StartCoroutine(WaitForSoundToStop())
}
This is the first if statement that is activated if the condition for the if statement above is met. In this if statement, if the number yielded from the DiceRollResult variable (which, remember, is basically a six-sided dice that yields a random result) matches that of the DiceRollResultRequirement (I believe I set it to either 3 or 5 in the Editor), then all the code within it will be called.
What happens first is the SpawnCoins method is called with the numberOfCoinsToSpawn int variable within it, which will deal with spawning the coins from unlocking the chest. Next, the chest will create a new Vector3 to move itself to a new location where the player cannot re-enter its still active trigger collider. Next, the chest variable is destroyed (which is actually the chest parts, not the entire chest itself). This is then reported to the console as well as the value of DiceRollResult. Once this is all done, the next functions that are called are for showing the player that they've won, such as the WinLoseText functions being for displaying the win text, the LevelRequirementLockPickFailText being disabled to stop the unneeded text from appearing, the timer being disabled to stop the clock and the starting of the WaitForSoundToStop Coroutine to finish off this win event.
A flow chart that shows the order of operations for each function in this section.
private void SpawnCoins(int numberOfCoinsToSpawn)
{
for (int i = 0; i < numberOfCoinsToSpawn; i++)
{
var spawnPosition = new Vector3(transform.position.x + Random.Range(-1.0f, 1.0f), transform.position.y, transform.position.z + Random.Range(-1.0f, 1.0f))
Instantiate(Coin, spawnPosition, transform.rotation)
}
}
This method deals with spawning the coins once the player unlocks the chest. The method takes the int numberOfCoinsToSpawn, creates a new variable called "i", and creates a for loop. In this for loop, as long as the numberOfCoinsToSpawn is bigger than i, it will create a new variable called "SpawnPosition", which it will then create a new Vector3 which will choose a random point on the X and Z scale within a specific range, and then instantiate the coins using that SpawnPosition data. The for loop will increment every loop until i is bigger than numberOfCoinsToSpawn.
A flow chart that shows the order of operations for each function in this section.
private IEnumerator WaitForSoundToStop()
{
musicSource.clip = GameWinMusic
yield return new WaitUntil(() => musicSource.isPlaying == false)
RestartExitText.enabled = true
RestartExitText.GetComponent<TextMeshProUGUI>().text = "Exiting..."
yield return new WaitForSeconds(2)
Debug.Log("Exiting runtime.")
This is the method that is called to finish off the win event. The reason why this is a different method and an IEnumerator instead of a void is because this method utilizes the WaitUntil function, which stops the script and waits until the musicSource Audio Source has stopped playing before continuing.
Firstly, musicSource is assigned the GameWinMusic, which essentially activates it. Once that has stopped playing, the RestartExitText is activated to notify the user that the game is automatically exiting. The script then waits two seconds to give the user time to see this message before then reporting to the console that the game is exiting. The next part is what exits the game.
A flow chart that shows the order of operations for each function in this section.
#if UNITY_EDITOR
{
EditorApplication.isPlaying = false
}
#else
{
Application.Quit()
}
#endif
This is the part that comes right after the WaitForSoundToStop method. This checks to see if the game is being run in the Unity Editor, and if this is true, it will exit playmode and return to the editor, otherwise it will try to quit the application.
A flow chart that shows the order of operations for each function in this section.
SECTION 5: What happens if the player is unsuccessful?
This part of the script is for what happens if the player is unsuccessful in opening the chest, whether they're not a high enough level or wasn't able to unlock it in time.
else if (DiceRollResult != DiceRollResultRequirement)
{
Debug.Log("Unlucky " + DiceRollResult + "!")
StartCoroutine(LockpickFailTextAppear())
return
}
This else if statement is called if the player has a high enough level to unlock the chest but doesn't land the dice roll with the right number. In this statement, the fact of the player losing is reported to the console along with the value of DiceRollResult, and then the LockpickFailTextAppear Coroutine, before returning, indicating that the method is done.
A flow chart that shows the order of operations for each function in this section.
else if (playerControllerThatLevelsUp.currentLockPickSkill < ChestDifficulty)
{
print("You are not a high enough level. " + DiceRollResult)
StartCoroutine(LevelRequirementFailTextAppear())
return
}
If the lock pick skill of the player isn't high enough for the chest difficulty, this else if statement will be called. In this statement, the fact of the player not being a high enough level to unlock the chest, as well as the value of DiceRollResult. Then the LevelRequirementFailTextAppear Coroutine is started, and then the code returns, indicating that it has finished in this section.
A flow chart that shows the order of operations for each function in this section.
private IEnumerator LevelRequirementFailTextAppear()
{
if (LevelRequirementLockpickFailText.enabled == true)
{
yield return null
}
else if (LevelRequirementLockpickFailText.enabled == false)
{
LevelRequirementLockpickFailText.enabled = true
LevelRequirementLockpickFailText.GetComponent<TextMeshProUGUI>().text = "Not high enough level!"
WorldSFXSource.PlayOneShot(LevelRequirementFail)
yield return new WaitForSeconds(3)
LevelRequirementLockpickFailText.enabled = false
}
}
This IEnumerator is called when the player does not have a high enough lock picking level. This method first checks to see if the LevelRequirementLockpickFailText is enabled, and if it is, the method will return null, ending the method. If it finds that the LevelRequirementLockpickFailText is not enabled, it will enable it, change the text to say "Not high enough level!", access the WorldSFXSource Audio Source to play one shot of the LevelRequirementFail Audio Clip, then wait three seconds and then finally disable the LevelRequirementLockpickFailText.
A flow chart that shows the order of operations for each function in this section.
private IEnumerator LockpickFailTextAppear()
{
WorldSFXSource.PlayOneShot(LockpickFail)
if (LevelRequirementLockpickFailText.enabled == true)
{
yield return null
}
else if (LevelRequirementLockpickFailText.enabled == false)
{
LevelRequirementLockpickFailText.enabled = true
LevelRequirementLockpickFailText.GetComponent<TextMeshProUGUI>().text = "Lockpick attempt failed!"
yield return new WaitForSeconds(3)
LevelRequirementLockpickFailText.enabled = false
}
}
This method is called when the player does have the sufficient lockpicking skill level but doesn't quite land the lucky dice roll with lockpicking the chest. In this method, the first thing that happens is the WorldSFXSource is used to play a one shot of the LockpickFail Audio Clip to show that the lockpicking attempt as failed. The method then checks if the LevelRequirementLockpickFailText is enabled, and if it is enabled, it will return null. If it is not enabled, the method will enable the text and set it to say "Lockpick attempt failed!". It will then wait three seconds and then disable the text.
A flow chart that shows the order of operations for each function in this section.
The full script
This the full MultiCoinChest.cs script, straight from the project. This version of the script includes the original colouring and also has the original comments that were removed from the code above.