Stop coroutine from other script - c#

I am making a game with a maze with audio hints on what way to go. I have the sounds playing on a coroutine and it work in terms of only starting once. However, what I need to do is be able to stop it from another script with a trigger on so that the audio does not keep playing when the player passes a certain point. This is my code so far.
public AudioSource direction;
private bool running = false;
IEnumerator AudioPlay()
{
while (true)
{
direction.Play();
yield return new WaitForSeconds(2);
}
}
void OnTriggerEnter(Collider col)
{
if (col.gameObject.CompareTag("Player"))
{
if (running == false)
{
StartCoroutine(AudioPlay());
Debug.Log("Started");
running = true;
}
else if (running == true)
{
Debug.Log("Void");
}
}
}

Use StopCoroutine(previouslyRunCoroutine)
If you use the IEnumerator form (StartCoroutine(AudioPlay());) to start the coroutine, the Unity documentation recommends that you save a reference to the IEnumerator and use that in future calls to StopCoroutine:
public AudioSource direction;
private bool running = false;
public IEnumerator audioPlayCoroutine;
IEnumerator AudioPlay()
{
while (true)
{
direction.Play();
yield return new WaitForSeconds(2);
}
}
void OnTriggerEnter(Collider col)
{
if (col.gameObject.CompareTag("Player"))
{
if (running == false)
{
audioPlayCoroutine = AudioPlay();
StartCoroutine(audioPlayCoroutine);
Debug.Log("Started");
running = true;
}
else if (running == true)
{
Debug.Log("Void");
}
}
}
Then, in your other script you can use StopCoroutine:
OtherScript.StopCoroutine(OtherScript.audioPlayCoroutine);
If you were to use the method name form such as StartCoroutine("AudioPlay");, the documentation would recommend using the method name form to stop it:
OtherScript.StopCoroutine("AudioPlay");

Related

Coroutine function isn't working as expected in unity. (I am a beginner somewhat)

Ok so I have been trying to write a Coroutine that will wait 2 seconds before the health gets deducted more. No errors show up, but the script doesn't wait 2 seconds. I am a beginner, so I don't know what's going on here that could cause the error. I'm guessing I spelled something incorrectly.
Here is the code:
using System.Collections.Generic;
using UnityEngine;
public class Die : MonoBehaviour
{
public int Health;
public int GolemDamage;
public Animator anim;
public Transform enemy;
public bool canAttack = true;
public bool justAttacked = false;
// Start is called before the first frame update
void Start()
{
}
void Update()
{
float distance = Vector3.Distance(enemy.position, transform.position);
canAttack = true;
if(canAttack == false)
{
GolemDamage = 0;
}
if(canAttack == true)
{
GolemDamage = 10;
}
if(distance <= 2.5f)
{
Health = Health - GolemDamage;
justAttacked = true;
}
if(justAttacked == true)
{
canAttack = false;
StartCoroutine("Attack");
}
if(justAttacked == false)
{
canAttack = true;
}
}
IEnumerator Attack()
{
yield return new WaitForSeconds(2);
justAttacked = false;
StartCoroutine("Attack");
}
}
You are again starting the coroutine in the coroutine itself:
IEnumerator Attack() {
yield return new WaitForSeconds(2);
justAttacked = false;
StartCoroutine("Attack");//here
}
Try starting it just where you need it to execute and add some helper logs Like so:
if(justAttacked == true) {
canAttack = false;
Debug.Log("coroutine started");
StartCoroutine("Attack");
}
IEnumerator Attack() {
Debug.Log("log before wait");
yield return new WaitForSeconds(2);
Debug.Log("log after wait");
justAttacked = false;
StartCoroutine("Attack");//here
}
I the console you can check if the logs make sense so that you can move on. Also you can check if between Debug.Log("log before wait"); and Debug.Log("log after wait"); there are 2 seconds, as you can see the time of each log in the console.
I discovered that when you call something that doesn't run inside the unity editor like (Admob) this happen to StartCoroutine, the solution is to set
for example:
public static void show()
{
#if !UNITY_EDITOR
if (interstitial.IsLoaded())
{
interstitial.Show();
requist_ad = true;
}
else
{
requist_ad = true;
}
#endif
//Debug.Log("not mobile");
}
I know that it's not the case here but it's for others that came here about the Startcoroutine problem

Everything breaks after using PlayOneShot

In a game I am working on, the player uses a cube to open doors. When the player interacts with the cube while near the door, the door slides open. I am now trying to make it so that while it slides open, it makes a sound as doors should but I kinda ran into some issues. When I tried to add in the sound for the door, it ignored the part where the door should open altogether.
Here is what I did:
I added an AudioSource to the Cube's child object, CORE because the Cube object already contains an AudioSource which will play at the same time as this sound and assigned it in my Cube's script...
public AudioSource Woosh;
void Start()
{
//Debug.Log("Script initialized!");
Hm = gameObject.GetComponent<AudioSource>();
anim = GameObject.Find("TheFirstDoor").GetComponent<Animator>();
//Door = GameObject.Find("TheFirstDoor").GetComponent<AudioSource>();
Woosh = GameObject.Find("CORE").GetComponent<AudioSource>();
}
Here's the interactive part of the script, it runs a check but for some reason, it is pretending CanBeKey is false...
public void OnActivate()
{
if(HasPlayed == false) //Have I played? Has the Time out passed?
{
HasPlayedFirstTime = true;
Hm.Play();
HasPlayed = true;
PlayTimeOut = 30.0f;
if(CanBeKey == true)
{
anim.Play("Door_Open");
//Door.Play();
StartCoroutine(PlayDaWoosh());
Debug.Log("OPENING!");
}
}
}
Now here is the IEnumerator part of the script, PlayDaWoosh()
IEnumerator PlayDaWoosh()
{
Woosh.PlayOneShot(Woosh.clip);
yield return new WaitForSeconds(Woosh.clip.length);
}
I am aware that the last code snippet is a bit messy but it was the best thing I can think of.
Here is the full script in case you are that curious.....
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CUBE_CORE : MonoBehaviour
{
public AudioSource Hm; // HM!!!
private bool HasPlayed = false;
public float PlayTimeOut = 0.0f;
public static bool CanBeKey = false;
public Animator anim;
public static bool HasPlayedFirstTime = false;
public static AudioSource Door;
public AudioSource Woosh;
// Start is called before the first frame update
void Start()
{
//Debug.Log("Script initialized!");
Hm = gameObject.GetComponent<AudioSource>();
anim = GameObject.Find("TheFirstDoor").GetComponent<Animator>();
//Door = GameObject.Find("TheFirstDoor").GetComponent<AudioSource>();
Woosh = GameObject.Find("CORE").GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if(HasPlayed == true)
{
if (PlayTimeOut < 0.0f)
{
HasPlayed = false;
}
PlayTimeOut -= Time.smoothDeltaTime;
}
}
public void OnActivate()
{
if(HasPlayed == false) //Have I played? Has the Time out passed?
{
HasPlayedFirstTime = true;
Hm.Play();
HasPlayed = true;
PlayTimeOut = 30.0f;
if(CanBeKey == true)
{
anim.Play("Door_Open");
//Door.Play();
StartCoroutine(PlayDaWoosh());
Debug.Log("OPENING!");
}
}
}
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.name == "SecondDoor")
{
anim = GameObject.Find("DoorSecond").GetComponent<Animator>();
}
if(other.gameObject.name == "ThirdDoor")
{
anim = GameObject.Find("TheThirdDoor").GetComponent<Animator>();
}
if(other.gameObject.name == "FourthDoor")
{
anim = GameObject.Find("TheFourthDoor").GetComponent<Animator>();
}
}
IEnumerator PlayDaWoosh()
{
Woosh.PlayOneShot(Woosh.clip);
yield return new WaitForSeconds(Woosh.clip.length);
}
}
Expected result: Door opening and sound playing
Actual result: Neither happening unless I remove anything that has to do with the Woosh
I apologize ahead of time if I wasn't specific enough or if the question was answered somewhere else. I am relatively new here.

How can I make a C# script execute in a certain order?

I have a script conflict that checks if there is network connection and the version of the game (if it's up to date); but it's skipping those conditions and loading the game directly.
How do I make the script follow this pattern?:
Check if there is internet connection
Check the version of the game
Only load the game if there is an internet connection and the version of the game is current
You'll notice there are some loading function who repeat themselves; I'm not sure which ones to apply.
CheckNetwork.cs
public Text AlertText;
public GameObject alert;
public string URL = "";
public string CurrentVersion;
string latestVersion;
public GameObject newVersionAvailable;
private void Start()
{
StartCoroutine(LoadTxtData(URL));
}
void Awake()
{
if (Application.internetReachability == NetworkReachability.NotReachable)
{
alert.SetActive(true);
AlertText.text = "Sorry, you have to enable your conection to play this game";
if (Input.GetKeyDown(KeyCode.Escape))
Application.Quit();
}
else
{
alert.SetActive(false);
CheckVersion();
}
}
public void CheckVersion()
{
if(CurrentVersion != latestVersion)
{
newVersionAvailable.SetActive(true);
}
else
{
newVersionAvailable.SetActive(false);
}
}
private IEnumerator LoadTxtData(string url)
{
UnityWebRequest www = UnityWebRequest.Get(url);
yield return www.SendWebRequest();
latestVersion = www.ToString();
CheckVersion();
}
public void OpenURL(string url)
{
Application.OpenURL(url);
}
Loading.cs
private Image progressBar;
private void Start()
{
Invoke("LoadAsyncOperation", 3f);
StartCoroutine(LoadAsyncOperation());
}
public IEnumerator LoadAsyncOperation()
{
AsyncOperation gameLevel = SceneManager.LoadSceneAsync(1);
while (gameLevel.progress < 1)
{
progressBar.fillAmount = gameLevel.progress;
yield return new WaitForEndOfFrame();
}
}
Loading2.cs
public GameObject loadingScreenObj;
public Slider slider;
AsyncOperation async;
public void StartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
public void LoadScreen(int LVL)
{
//StartCoroutine(loadingScreen());
}
IEnumerator LoadingScreen(int lvl)
{
loadingScreenObj.SetActive(true);
async = SceneManager.LoadSceneAsync(lvl);
async.allowSceneActivation = false;
while (async.isDone == false)
{
slider.value = async.progress;
if (async.progress == 0.9f)
{
slider.value = 1f;
async.allowSceneActivation = true;
}
yield return null;
}
}
SplashScrean.cs
void Start()
{
Invoke("loadMenuLevel", 3f);
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
void loadMenuLevel()
{
SceneManager.LoadScene("gameplay");
}
void Update()
{
if (Input.GetKey(KeyCode.Mouse0))
{
loadMenuLevel();
}
if (Input.GetKey(KeyCode.Escape))
{
Application.Quit();
}
}
This is happening because you call the Invoke("loadMenuLevel", 3f); method in the Start() method in the SplashScrean.cs script.
Start() is invoked very soon after a MonoBehaviour is attached to an object. Ostensibly, you have it attached to a GameObject in the scene. All of these are being hit at once.
You also have it as invokable via left mouse click in the same script's Update() method:
if (Input.GetKey(KeyCode.Mouse0))
{
loadMenuLevel();
}
I would add bools to each of the loading scripts that are set to true when each task completes. Then, in SplashScrean.cs, change the above condition to load the level automatically if all three are complete. Or, keep the key press condition and add the bools.
if (Application.internetReachability != NetworkReachability.NotReachable
&& loadScriptComplete && isCurrentVersion && Input.GetKey(KeyCode.Mouse0))
{
loadMenuLevel();
}
Now, if you also need the Loading MonoBehaviours to wait to execute their loading logic too, then consider adding Update() logic to them that checks the flag you set in CheckNetwork.cs.
//For example. Instead of this
private void Start()
{
Invoke("LoadAsyncOperation", 3f);
StartCoroutine(LoadAsyncOperation());
}
//Do this
private void Update()
{
//Note that we only need to check if it is a CurrentVersion, as if that is true, then we definitely have internet access.
if(CheckNetwork.IsCurrentVersion) {
Invoke("LoadAsyncOperation", 3f);
StartCoroutine(LoadAsyncOperation());
}
}
Edits/Additional Notes As I notice:
CheckNetwork.cs references CheckVersion(); in the else clause. That seems pointless. It is impossible for LoadTxtData() to have set the logic that CheckVersion() relies on in that time frame. A sort of race condition, if you will. Plus, you handle that check in LoadTxtData() anyways, so I would just do that there.
I can't give you a complete copy-pastable code here (mainy because I don't fully understand yet how all your scripts are connected between each other) but I see you are already using Coroutines.
I would as mentioned before use one central manager to handle all those things.
I will only give you a general pseudo-code here and hope you find your way frome here
public class LoadManager
{
public CheckNetwork check;
public Loading loading;
public Loading2 loading2;
public SplashScrean splashScreen;
// suprise: a not well documented fact
// you can simply make the Start method an IEnumerator (Coroutine)
// so you can directly wait for anything you want
private void IEnumerator Start()
{
// Next suprise: You can simply yield other IEnumerators
// that makes them execute and at the same time waits until they are finished
// so all methods that you want to wait for from here
// should be IEnumerators
// as said this is only an example
// you could directly within that single
// Coroutine do your complete download and check routine together
yield return check.CheckPermissions();
if(!check.isReachable)
{
yield break;
}
// same for chekcing the version
yield return check.CheckVersion();
if(!check.isUpToDate)
{
// do something for update
yield break;
}
// is up to date so start and wait for loading
yield return loading.LoadAsyncOperation();
// and so on
}
}
And there are also some builtin ways for waiting like e.g. WaitUntil, WaitForSeconds etc.

Unity ads not working on real device

In my current Unity program I wanted to implement ads. The ads do work in the Unity editor when I run the game, but when I try to run the ads on my iPhone 7 or iPad Air, there are no ads showing up. Does someone know what I am doing wrong?
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Advertisements;
public class GameManager : MonoBehaviour
{
void Start()
{
Advertisement.Initialize("Appstore-id");
}
bool gameHasEnded = false;
public float restartDelay = 1f;
public float addDelay = 1f;
public GameObject completeLevelUI;
public void CompleteLevel ()
{
completeLevelUI.SetActive(true);
Invoke("ShowAdd", addDelay);
}
public void EndGame()
{
if (gameHasEnded == false)
{
gameHasEnded = true;
Debug.Log("Game over");
Invoke("Restart", restartDelay);
}
}
public void ShowAdd()
{
if (Advertisement.IsReady())
{
Advertisement.Show ();
}
}
void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
I had the same problem and the way I fixed it was a recursive function until the ad was ready. Something like:
IEnumerator ShowAd()
{
yield return new waitForSeconds(1);
if (Advertisement.IsReady())
{
Advertisement.Show ();
}
else { StartCoroutine(ShowAd()); }
}
This way it will call the method until the ad is ready to show.
Also, make sure you have test (or debug) mode enabled on Unity Ads Settings.
Just to add another option besides recursively starting an IEnumerator:
IEnumerator ShowAd()
{
while(!Advertisement.isReady())
{
yield return new waitForSeconds(1);
}
Advertisement.Show();
}

How to send commands from non - player objects UNET

I am unable to send commands from my gun prefabs in my game. How can I send commands from non-player game objects? Every time I try to send a command, I get an error. I do not want to move my shooting scripts from my guns to my player, as this method will require me to change my game drastically.
My Shooting Script:
[Command]
void CmdOpenFire(){
if (Physics.Raycast(camTransform.TransformPoint(startPosition), camTransform.forward, out hit, range)){
gunsMasterScript.CallEventShotDefault(hit.point, hit.transform);
if (hit.transform.parent != null) {
if (hit.transform.parent.CompareTag ("Enemy")) {
gunsMasterScript.CallEventShotEnemy (hit.point, hit.transform.parent);
}
}
if (hit.transform.tag == "Player") {
gunsMasterScript.CallEventShotEnemy (hit.point, hit.transform);
}
}
}
My shooting Detection script:
public PlayerManager_AmmoBox localPlayerTester;
public GunsManager_GunsAmmo gunsAmmo;
public GunsManager_GunsMaster gunMaster;
float nextAttack;
public float attackRate = 0.5f;
Transform myTransform;
public bool isAutomatic = true;
public bool hasBurstFire;
bool isBurstFireActive;
public string attackButtonName;
public string reloadButtonName;
public string burstFireButtonName;
NetworkIdentity netID;
// Use this for initialization
void OnEnable () {
if (transform.root != null) {
if (transform.root.GetComponent<PlayerManager_AmmoBox> () != null) {
localPlayerTester = transform.root.GetComponent<PlayerManager_AmmoBox> ();
}
}
SetInitialReferences ();
}
// Update is called once per frame
void Update () {
if (localPlayerTester != null) {
CheckIfWeaponShouldAttack ();
CheckForReloadRequest ();
CheckForBurstFireToggle ();
}
}
void SetInitialReferences(){
netID = GetComponent<NetworkIdentity> ();
myTransform = transform;
gunMaster = transform.GetComponent<GunsManager_GunsMaster> ();
gunsAmmo = transform.GetComponent<GunsManager_GunsAmmo> ();
gunMaster.isGunLoaded = true;
}
void CheckIfWeaponShouldAttack(){
if (gunMaster != null) {
if (Time.time > nextAttack && Time.timeScale > 0 && myTransform.root.CompareTag ("Player") && gunMaster.isGunLoaded) {
if (isAutomatic && !isBurstFireActive) {
if (Input.GetButton (attackButtonName)) {
AttemptAttack ();
}
} else if (isAutomatic && isBurstFireActive) {
if (Input.GetButtonDown (attackButtonName)) {
StartCoroutine (RunBurstFire ());
}
} else if (!isAutomatic) {
if (Input.GetButton (attackButtonName)) {
AttemptAttack ();
}
}
}
}
}
void AttemptAttack(){
nextAttack = Time.time + attackRate;
if (gunMaster.isGunLoaded) {
gunMaster.CallEventPlayerInput ();
} else {
gunMaster.CallEventGunNotUsable ();
}
}
Commands have to be called on the gameobject (which has a NetworkBehavior) that is associated with a certain player. Thus, I do not believe they can be called from non-player objects.
However, you could make the gun script communicate to the script that is on the player (the one which is a NetworkBehavior that is associated with a player's connection). This would cause the error to stop. (This communication could be done through SendMessage or just regular function calls to the player object.)
In conclusion, currently, UNet does not allow Commands from non-player objects, so some change will need to be done to the layout of your player/gun scripts in order to use commands. However, this could be done with only a few lines of code by just making the gun script "talk to" the player script -- the script inheriting from NetworkBehavior that is associated with a player's connection.
More information on commands can be seen here:
https://docs.unity3d.com/ScriptReference/Networking.CommandAttribute.html

Categories