How to shortcut a coroutine? - c#

I wrote a co-routine to do batch work every frame, I have no problem starting it, but I want to stop the execution when specific conditions are met.
Such as :
public IEnumerator workCoroutine(){
int i = 0;
if(GLOBAL_LOCK){
return; //will not work here.
//we will not execute this coroutine further more, ie,shortcut it;
//but how to ?
}
while( i<=1000){
doingSomeWorkHere(i);
i++;
yield return null;
}
}
I know I could maybe do this by:
public IEnumerator workCoroutine(){
int i = 0;
if(!GLOBAL_LOCK){
while( i<=1000){
doingSomeWorkHere(i);
i++;
yield return null;
}
}
}
But I think this way may cause the code to get ugly when there are multiple conditions.
So is there a way to cut off the co-routine?
EDIT
I really don't understand what people are saying in the comments, here is a fully functioning test script, will somebody point out how to test if the co-routine has exited? or give me some material to read on?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestCoroutine : MonoBehaviour {
private bool GLOBAL_LOCK = true;
public IEnumerator workCoroutine()
{
int i = 0;
if (!GLOBAL_LOCK)
{
while (i <= 10)
{
Debug.Log(i);
i++;
yield return null;
}
Debug.Log(" I am in if condition");
}
Debug.Log(" will exit the coroutine");
}
// Use this for initialization
void Start () {
StartCoroutine(workCoroutine());
}
}

You need to check for the GLOBAL_LOCK inside while loop as follows -
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestCoroutine : MonoBehaviour {
private bool GLOBAL_LOCK = true;
public IEnumerator workCoroutine()
{
int i = 0;
while (i <= 10)
{
if (GLOBAL_LOCK)
{
Debug.Log(" I am in if condition");
Debug.Log(" will exit the coroutine");
yield break;
}else{
Debug.Log(i);
i++;
yield return null;
}
}
}
// Use this for initialization
void Start () {
StartCoroutine(workCoroutine());
}
}
This code will print the Log statements from if condition and exit the coroutine because GLOBAL_LOCK is set to true.

Beside #MukeshSaini, answer you can also stop execution of your coroutine using StopCoroutine Function.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestCoroutine : MonoBehaviour {
private bool GLOBAL_LOCK = true;
// keep a copy of the executing script
private IEnumerator coroutine;
public IEnumerator workCoroutine()
{
int i = 0;
while (i <= 10)
{
Debug.Log(i);
i++;
yield return null;
}
}
// Use this for initialization
void Start () {
StartCoroutine(workCoroutine());
}
void Update() {
if (GLOBAL_LOCK )//your specific condition
{
StopCoroutine(coroutine);
}
}
}
Advantage: This will allow you to stop your coroutine from any place, i provide example with update

Related

How do I make a unity ad play every 5 rounds/losses?

I have been trying to make my game, play an ad every 5 rounds/losses. I have tried copying some scripts, but they don't work for me. I am a really early developer and don't know much about c# and unity. If you find a solution tell me in which script I need to put the code.
My GameManager script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public GameObject gameOverCanvas;
public AdsManager ads;
private void Start()
{
Time.timeScale = 1;
ads.ShowBanner();
}
public void GameOver()
{
gameOverCanvas.SetActive(true);
Time.timeScale = 0;
ads.PlayAd();
}
public void Replay()
{
SceneManager.LoadScene(0);
}
}
My AdsManager script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Advertisements;
public class AdsManager : MonoBehaviour, IUnityAdsListener
{
#if UNITY_IOS
string gameId = "#######";
#else
string gameId = "#######";
#endif
Action onRewardedAdSuccess;
// Start is called before the first frame update
void Start()
{
Advertisement.Initialize(gameId);
Advertisement.AddListener(this);
ShowBanner();
}
public void PlayAd()
{
if(Advertisement.IsReady("Interstitial_Android"))
Advertisement.Show("Interstitial_Android");
}
public void PlayRewardedAd(Action onSuccess)
{
onRewardedAdSuccess = onSuccess;
if(Advertisement.IsReady("Rewarded_Android"))
{
Advertisement.Show("Rewarded_Android");
}
else
{
Debug.Log("Rewarded ad is not ready!");
}
}
public void ShowBanner()
{
if (Advertisement.IsReady("Banner_Android"))
{
Advertisement.Banner.SetPosition(BannerPosition.BOTTOM_CENTER);
Advertisement.Banner.Show("Banner_Android");
}
else
{
StartCoroutine(RepeatShowBanner());
}
}
public void HideBanner()
{
Advertisement.Banner.Hide();
}
IEnumerator RepeatShowBanner()
{
yield return new WaitForSeconds(1);
ShowBanner();
}
public void OnUnityAdsReady(string placementId)
{
Debug.Log("ADS ARE READY!");
}
public void OnUnityAdsDidError(string message)
{
Debug.Log("ERROR: " + message);
}
public void OnUnityAdsDidStart(string placementId)
{
Debug.Log("VIDEO STARTED!");
}
public void OnUnityAdsDidFinish(string placementId, ShowResult showResult)
{
if (placementId == "Rewarded_Android" && showResult == ShowResult.Finished)
{
onRewardedAdSuccess.Invoke();
}
}
}
If you need any more scripts then tell me. I would appreciate any feedback.
Create a counter, increment it after every game and then check if you reached required number of games.
You would need to create counter and required games variables. Then, put the increment and if statement code in your GameOver method:
public void GameOver()
{
gameOverCanvas.SetActive(true);
Time.timeScale = 0;
gameCount++; //increment game count
if(gameCount >= gamesToShowAd) // check if player played enough games to show ad
{
ads.PlayAd();
gameCount = 0; // reset counter
}
}
If you wish, you could consider saving game count using https://docs.unity3d.com/ScriptReference/PlayerPrefs.html or using ready online solution, but it's up to you.

How can I cancel/stop the coroutine and also to pause the coroutine?

For starting and stopping/cancelling the coroutine I'm using the startTimer bool flag but it's not working. When I'm running the game and check the flag of startTimer the timer start but when I uncheck the flag startTimer the timer never stop and when I check again the startTimer nothingchange the timer is keep counting back regular.
The pauseTimer I'm not sure how to do it either. I want that when pauseTimer is true the coroutine will pause at where it is and when pauseTimer is false unchecked the coroutine should continue from last pause place.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CountdownController : MonoBehaviour
{
public int countdownTime;
public Text countdownDisplay;
public bool startTimer = false;
public bool pauseTimer = false;
private bool startOnce = true;
private void Start()
{
if (startTimer)
{
StartCoroutine(CountdownToStart());
}
}
private void Update()
{
if(startTimer)
{
StartTimer();
}
else
{
StopCoroutine(CountdownToStart());
}
}
private IEnumerator CountdownToStart()
{
while(countdownTime > 0)
{
countdownDisplay.text = countdownTime.ToString();
yield return new WaitForSeconds(1f);
countdownTime--;
}
countdownDisplay.text = "GO!";
yield return new WaitForSeconds(1f);
countdownDisplay.gameObject.SetActive(false);
}
public void StartTimer()
{
if (startOnce)
{
StartCoroutine(CountdownToStart());
startOnce = false;
}
}
}
Base on your code, here is how to stop/pause your timer
private IEnumerator CountdownToStart()
{
while (countdownTime > 0)
{
// Use break to stop coroutine
if (!startTimer)
yield break;
// Use continue to pause coroutine
if (pauseTimer)
{
// wait a while before continue to avoid infinite loop
yield return new WaitForEndOfFrame();
continue;
}
countdownDisplay.text = countdownTime.ToString();
yield return new WaitForSeconds(1f);
countdownTime--;
}
countdownDisplay.text = "GO!";
yield return new WaitForSeconds(1f);
countdownDisplay.gameObject.SetActive(false);
}
Fyi,
People used to implement timer with Time.deltaTime in Update()
public float timer = 100f;
private void Update()
{
if (!pauseTimer)
{
timer -= Time.deltaTime;
countdownDisplay.text = timer.ToString("0.0");
}
}

Unity InvokeRepeating() not working

I have a script in my new clicker game that uses InvokeRepeating:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AutomineController : MonoBehaviour {
static int WoodMiners = 0;
static int StoneMiners = 0;
public Text already_mining;
public Text invalid_transaction;
public Text too_many_miners;
// Error message code
private IEnumerator TooManyMinersCore ()
{
already_mining.gameObject.SetActive (false);
invalid_transaction.gameObject.SetActive (false);
too_many_miners.gameObject.SetActive (true);
yield return new WaitForSeconds (1.5f);
too_many_miners.gameObject.SetActive (false);
}
// The code that does the actual mining
private static void WoodMiner ()
{
Debug.Log("Here");
ChopWood.Add ();
}
// The code that "hires" the miners
public void HireWoodMiner ()
{
if (WoodMiners < 5) {
WoodMiners += 1;
InvokeRepeating("WoodMiner", 0.1f, 3.0f);
} else {
StartCoroutine(TooManyMinersCore());
}
}
}
For some reason, the InvokeRepeating() doesn't work- it should run the WoodMiner() method when the HireWoodMiner() method is run. Why is this?

How to access a value from OnTriggerEnter2D in another function?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RedHP : MonoBehaviour
{
public float HP = 5;
public GameObject BlueWon;
public GameObject Restart;
void OnTriggerEnter2D(Collider2D trig)
{
if (trig.gameObject.tag == "ThrowableBlue")
{
StartCoroutine(BowlDestroyTime());
HP--;
if (HP <= 0)
{
BlueWon.SetActive(true);
Restart.SetActive(true);
PlayerBlueController.canMove = false;
PlayerBlueController.canFire = false;
}
}
}
IEnumerator BowlDestroyTime()
{
yield return new WaitForSeconds(1);
Destroy(trig.gameObject);
}
}
I simply want to destroy my object after too little period of time to make it look better. In IEnumerator I can't access trig.gameObject because it is defined in OnTriggerEnter2D. Is there a way to access this value?
I also tried to put IEnumerator in OnTriggerEnter2D it also didn't work. Kinda newbie
You don't have to do that. The Destroy function can take a second parameter as a delay time before the Object is destroyed.
Destroy(trig.gameObject, 1f);
If you still want to use coroutine to do this, simply make the BowlDestroyTime function to take GameObject as parameter then pass the GameObject from the OnTriggerEnter2D function to the BowlDestroyTime function to be destroyed.
void OnTriggerEnter2D(Collider2D trig)
{
if (trig.gameObject.tag == "ThrowableBlue")
{
StartCoroutine(BowlDestroyTime(trig.gameObject));
HP--;
if (HP <= 0)
{
BlueWon.SetActive(true);
Restart.SetActive(true);
PlayerBlueController.canMove = false;
PlayerBlueController.canFire = false;
}
}
}
IEnumerator BowlDestroyTime(GameObject tartgetObj)
{
yield return new WaitForSeconds(1);
Destroy(tartgetObj);
}

How can i get animator specific state length ? And how to make a loop to be infinity?

It's not getting animation clip length but animator state length.
I'm waiting 5 seconds but i want to wait until each state to finish playing.
yield return new WaitForSeconds(5);
Instead 5 i want to get anim[index] length. But anim[index] doesn't have any length property.
The second goal is to make the while loop infinity so it will keep playing all the states all over again non stop.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Animations;
using UnityEngine;
public class SwitchAnimations : MonoBehaviour
{
private Animator animator;
private static UnityEditor.Animations.AnimatorController controller;
private AnimatorState[] states;
// Use this for initialization
void Start()
{
animator = GetComponent<Animator>();
states = GetStateNames(animator);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
StartCoroutine(QueueAnim(states));
}
}
private static UnityEditor.Animations.AnimatorState[] GetStateNames(Animator animator)
{
controller = animator ? animator.runtimeAnimatorController as UnityEditor.Animations.AnimatorController : null;
return controller == null ? null : controller.layers.SelectMany(l => l.stateMachine.states).Select(s => s.state).ToArray();
}
IEnumerator QueueAnim(params AnimatorState[] anim)
{
int index = 0;
while (index < anim.Length)
{
if (index == anim.Length)
index = 0;
animator.Play(anim[index].name);
yield return new WaitForSeconds(5);
index++;
}
}
private void RollSound()
{
}
private void CantRotate()
{
}
private void EndRoll()
{
}
private void EndPickup()
{
}
}
Update:
This is what i tried now:
But when i press on A once it's playing one state only and if i click many times on A then it will play another state but that's it.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Animations;
using UnityEngine;
public class SwitchAnimations : MonoBehaviour
{
private Animator animator;
private static UnityEditor.Animations.AnimatorController controller;
private AnimatorState[] states;
// Use this for initialization
void Start()
{
animator = GetComponent<Animator>();
states = GetStateNames(animator);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
StartCoroutine(QueueAnim(states));
}
}
private static UnityEditor.Animations.AnimatorState[] GetStateNames(Animator animator)
{
controller = animator ? animator.runtimeAnimatorController as UnityEditor.Animations.AnimatorController : null;
return controller == null ? null : controller.layers.SelectMany(l => l.stateMachine.states).Select(s => s.state).ToArray();
}
IEnumerator QueueAnim(AnimatorState[] anim)
{
int index = 0;
while (index < anim.Length)
{
if (index == anim.Length)
index = 0;
animator.Play(anim[index].name);
string name = anim[index].name;
while (animator.GetCurrentAnimatorStateInfo(0).IsName(name) && animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1.0f)
{
//Wait every frame until animation has finished
yield return null;
}
index++;
}
}
private void RollSound()
{
}
private void CantRotate()
{
}
private void EndRoll()
{
}
private void EndPickup()
{
}
}
I'm waiting 5 seconds but i want to wait until each state to finish
playing.
You don't need to get the state length in order to do this. You can use GetCurrentAnimatorStateInfo and the normalizedTime to check when the animator has finished playing. Simply replace the yield return new WaitForSeconds(5); with the code below:
string name = anim[index].name;
while (animator.GetCurrentAnimatorStateInfo(0).IsName(name) && animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1.0f)
{
//Wait every frame until animation has finished
yield return null;
}
If you want it loop forever just put it in another while loop.
Unrelated but I advice you to avoid using params in functions when using Unity. It's simply not worth it. Just use array instead. The reason has a lot to do with performance and this depends on how frequently the function is called.
try this one, there you can save the length as a variable and keep using it
http://answers.unity3d.com/questions/692593/get-animation-clip-length-using-animator.html

Categories

Resources