How can I call IEnumerator when a boolean is true? - c#

I'm using the IEnumerator function and have had some issues with my if statement working:
IEnumerator Spawning()
{
Debug.Log("Spawning being called...");
if (GameObject.Find("FPSController").GetComponent<BoxCollide>().hitTrigger == true)
{
Debug.Log("CUSTOMER SHOULD SPAWN!");
while (countStop == false) {
yield return new WaitForSeconds(2);
Debug.Log("Fisher Spawned!");
counter++;
spawnNewCharacter.SpawnCharacter();
if (counter >= 3){
countStop = true;
}
}
}
}
After some debugging, it turns out that my if statement actually works. This issue is actually the IEnumerator function being called as soon as I run my game. I need a way to call this IEnumerator function only when my hitTrigger == true, but I can't seem to get anything to work.
I've tried this on top of the IEnumerator function:
void Update()
{
if (GameObject.Find("FPSController").GetComponent<BoxCollide>().hitTrigger == true)
{
Debug.Log("Spawning now...");
StartCoroutine(Spawning());
}
}
But still can't even get any of the Debug.Log's to come through. Would appreciate some help on this!

Side Information
Find() and GetComponent()
You don't want to use GameObject.Find(...) in the Update method, as it's an expensive call. The Update method is called each frame, so you'd call GameObject.Find(...) 60 times in 1 second at 60fps.
So when you use GetComponent() or Find() you want to save a reference to these objects like shown in the snippets below.
Better locations to use methods like GetComponent() or GameObject.Find() are the Awake() and Start() methods.
Awake
Awake is used to initialize any variables or game state before the
game starts. Awake is called only once during the lifetime of the
script instance. Awake is called after all objects are initialized so
you can safely speak to other objects or query them using eg.
GameObject.FindWithTag. [...]
Explanation is taken from the linked documentation.
Start
Start is called on the frame when a script is enabled just before any
of the Update methods is called the first time. Like the Awake
function, Start is called exactly once in the lifetime of the script.
However, Awake is called when the script object is initialised,
regardless of whether or not the script is enabled. Start may not be
called on the same frame as Awake if the script is not enabled at
initialisation time.
Explanation is also taken from the linked documentation
Possible Solution
Add the first Component (FPSControllerCollission) onto the object that holds your FPSController.
It makes use of unities OnTriggerEnter & OnTriggerExit methods.
This script is gonna set the IsTriggered bool to true, when a trigger entered the space of the box collider.
Note: A collider acts as a trigger, when the "Is Trigger" checkbox on
the component is checked.
You can do similar with OnCollisionEnter/Exit to recognize, when a Collider enters the space of the box collider.
Note that the following is only an example and you'll have to tweak /
integrate it into your code
[RequireComponent(typeof(BoxCollider))]
public class FPSControllerCollission : MonoBehaviour {
public bool IsTriggered;
private void OnTriggerEnter(Collider other) {
this.IsTriggered = true;
}
private void OnTriggerExit(Collider other) {
//Maybe check the tag/type of the other object here
this.IsTriggered = false;
}
}
The following SpawnController class could be integrated in the class you allready have.
public class SpawnController : MonoBehaviour {
private FPSControllerCollission _fpsControllerCollission;
private void Awake() {
this._fpsControllerCollission = FindObjectOfType<FPSControllerCollission>();
}
private void Update() {
if (this._fpsControllerCollission.IsTriggered) {
StartCoroutine(nameof(Spawning));
}
}
IEnumerator Spawning() {
Debug.Log("Spawning being called...");
if (this._fpsControllerCollission == true) {
Debug.Log("CUSTOMER SHOULD SPAWN!");
bool countStop = false;
int counter;
while (countStop == false) {
yield return new WaitForSeconds(2);
Debug.Log("Fisher Spawned!");
counter++;
spawnNewCharacter.SpawnCharacter();
if (counter >= 3) {
countStop = true;
}
}
}
}
}

Related

Stop onDestroy on Scene Load.Unity 2d

So I have a script that adds money when enemy dies (onDestroy). But when the scene changes that also destroys the enemies and adds money. How I can make it only add money when enemy is destroyed by health=0 and not on scene change?
Code:
void onDestroy()
{
AddCoins(Coins);
}
I can see two main ways to do this :
If it's possible, call the AddCoins Method in the code that calls Destroy() when health =0 instead of in OnDestroy(). Something like :
if(health == 0){
AddCoins(coins);
Destroy(gameObject);
}
and no OnDestroy method.
Otherwise, you could set a variable to true just before loading a new scene, and in OnDestroy() check if this variable is false, and only then adding coins.
If it's compatible with your game, I would recommend for the first solution, much cleaner in my opinion.
You can use scene.isLoaded to only check the times when the scene is loaded and is not changing.
void OnDestroy()
{
if(gameObject.scene.isLoaded)
{
AddCoins(Coins);
}
}
I would make it so that the AddCoin() method gets called not on destroy but instead have a check either in the update loop or ideally in your enemy damage function so it gets called only when health changes.
void Update()
{
if(health == 0)
{
AddCoins(Coins)
Destroy(gameobject)
}
}
if you want to use your existing code structure and still have it be called onDestory you could add another check
void onDestroy()
{
if(health == 0)
{
AddCoins(Coins);
}
}

C# Coroutine doesnt works in Unity

Im trying to make a basic platformer game and my level controller script isnt working, it needs to load loading scene first and then nextlevel but it constantly loads the nextlevel
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelController : MonoBehaviour
{
[SerializeField] string nextLevel;
private Gem[] gems;
void OnEnable()
{
gems = FindObjectsOfType<Gem>();
}
IEnumerator Wait()
{
yield return new WaitForSeconds(3);
}
void Update()
{
if (ReadyToNextLevel())
{
SceneManager.LoadScene("Loading");
StartCoroutine(Wait());
SceneManager.LoadScene(nextLevel);
}
}
bool ReadyToNextLevel()
{
foreach (var gem in gems)
{
if (gem.gameObject.activeSelf)
{
return false;
}
}
return true;
}
}
There are two big issues in your code.
Starting a Coroutine does not delay the method that started the routine!
If you want something happening after the Coroutine is finished you need to either move it into the routine itself or use a callback.
However, the thing is: You are loading a new scene, namely the "Loading" scene -> The current scene is unloaded -> the object this script is on gets destroyed -> the routine would no further executed.
Except your object is DontDestroyOnLoad which seems not the case from your code.
So in order to solve both you will need to make sure this object is not destroyed when another scene is loaded, at least not until it finished the loading process.
You could do this like e.g.
public class LevelController : MonoBehaviour
{
[SerializeField] string nextLevel;
private Gem[] gems;
void OnEnable()
{
gems = FindObjectsOfType<Gem>();
// Makes sure this is not destroyed when another scene is load
DontDestroyOnLoad(gameObject);
}
bool alreadyLoading;
IEnumerator Wait(Action whenDone)
{
yield return new WaitForSeconds(3);
// invoke the callback action
whenDone?.Invoke();
}
void Update()
{
if (ReadyToNextLevel() && ! alreadyLoading)
{
alreadyLoading = true;
SceneManager.LoadScene("Loading");
StartCoroutine(Wait(() =>
{
// This will be done after the routine finished
SceneManager.LoadScene(nextLevel);
// Now we don't need this object anymore
Destroy(gameObject);
}));
}
}
bool ReadyToNextLevel()
{
foreach (var gem in gems)
{
if (gem.gameObject.activeSelf)
{
return false;
}
}
return true;
}
}
EDIT: see derHugo's answer. They are more familiar with Unity than I am and point out that your script will be unloaded on scene change.
Your Wait() coroutine yields immediately, so StartCoroutine(Wait()) will return immediately and load the next scene.
If you want to wait three seconds before loading, then put the load inside the coroutine.
Like this:
private bool isLoading;
void Update()
{
if (!isLoading && ReadyToNextLevel())
StartCoroutine(LoadNextLevel());
}
IEnumerator LoadNextLevel()
{
isLoading = true;
SceneManager.LoadScene("Loading");
yield return new WaitForSeconds(3);
SceneManager.LoadScene(nextLevel);
isLoading = false;
}
See Coroutine help for more details
there are two reasons why this doesn't work, firstly when you load into the loading scene the gameObject is destroyed because you haven't told it to DontDestroyOnLoad, so the script will be sort of 'deleted' from the current scene. Second of all, when you call the coroutine, it calls the coroutine and then immediately continuous on to the next line of code, it doesn't wait for the coroutine to finish before moving on. I think derHugo covered how to fix these.

Make a loop run only once when script is added to gameObject

I have a script that runs in editor as well as in Play Mode. However I want a loop to be executed ONLY ONCE when I add the script to a gameObject. That is I do not want it to be called 2nd time when I play the game. How is this possible?
[ExecuteAlways]
..
..
void Start()
{
for (int i = 0; i < Go.Count; i++) {
//Only Once
}
}
You can use the OnValidate method who is called everytime your MonoBehaviour changed on editor time. You'll have to set a variable to validate that the script did the call.
public class Test : MonoBehaviour
{
[HideInInspector]
[SerializeField]
private bool isInitializedOnEditor = false;
#if UNITY_EDITOR
private void MySpecialMethod()
{
//...
}
private void OnValidate()
{
if(isInitializedOnEditor == false)
{
MySpecialMethod();
isInitializedOnEditor = true;
//Don't forget to save your change on this gameobject if require
UnityEditor.EditorUtility.SetDirty(this);
}
}
#endif
}
I used attributes [HideInInspector] and [SerializeField] to set the bool serializable (because otherwise the next time you load the scene it'll call the method again) but not show on inspector (you don't want the unity editor user can change it manually).
#if UNITY_EDITOR is required otherwise you cant compile because UnityEditor is only available on editor mode.
SetDirty() is used to tell Unity this MonoBehaviour have change and it should serialize it again. Otherwise the next time you will load the scene you'll loose your change here. You don't have to use it if your code job don't change the MonoBehaviour
Edit
If you don't want to call you method when on play mode, you can make it like:
#if UNITY_EDITOR
private void OnValidate()
{
if (isInitializedOnEditor == false && Application.isPlaying == false)
{
MySpecialMethod();
isInitializedOnEditor = true;
//Don't forget to save your change on this gameobject if require
UnityEditor.EditorUtility.SetDirty(this);
}
}
#endif
Note on == false
When I need to use a if and check if a bool is false I prefer to write the whole comparison if(myBool == false) than the shortcut if(!myBool) for readability purpose. We could debate it but it's on purpose.
Use break;
[ExecuteAlways]
..
..
void Start()
{
for (int i = 0; i < Go.Count; i++) {
//Only Once
break;
}
}
The Loop will run Once till it hits the break, and the the loop will end.

StartCoroutine is being called so many times (C# Unity)

I'm creating a Pop up menu Option in Unity. Now my Problem here is that the coroutine i made in void update is being called so many times. What i mean by that is on my Unity Console the Debug.Logs are incrementing . It should not right because its already coroutine. Could some help me understand more coroutine and help me solve my little problem .
Here is my code:
[SerializeField]
GameObject Option;
[SerializeField]
Button btn,btn2;
[SerializeField]
GameObject open, close;
[SerializeField]
GameObject[] opt;
bool startFinding = false;
void Start()
{
Option.SetActive(false);
Button popUp = btn.GetComponent<Button>();
Button popUp2 = btn2.GetComponent<Button>();
popUp.onClick.AddListener(PopUpOption);
popUp2.onClick.AddListener(ClosePopUp);
}
void Update()
{
if (startFinding)
{
StartCoroutine(GameOptions());
}
}
IEnumerator GameOptions()
{
//Get All the tags
opt = GameObject.FindGameObjectsWithTag("MobileOptions");
if (opt[0].GetComponent<Toggle>().isOn == true && opt[1].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Disable first the check box then choose only 1 option between" + "'rendering'"+ "and" + "'livestreaming'");
}
//Livestreaming
if (opt[0].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Livestreaming Activate");
} else
{
Debug.Log("Livestreaming Deactivate");
}
//Rendering
if (opt[1].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Rendering Activate");
} else
{
Debug.Log("Rendering Deactivate");
}
//Fog
if (opt[2].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Fog Activated");
} else
{
Debug.Log("Fog Deactivated");
}
//Camera Effect
if (opt[3].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Camera Effect Activated");
} else {
Debug.Log("Camera Effect Deactivated");
}
yield return null;
}
void PopUpOption()
{
startFinding = true;
//Disable The Mobile Option Button
open.SetActive(false);
//Enable the Close Option Button
close.SetActive(true);
//activate the Mobile Options
Option.SetActive(true);
}
void ClosePopUp()
{
startFinding = false;
//eanble the mobile option button
open.SetActive(true);
//disable the close option button
close.SetActive(false);
//deactivate the Mobile Option
Option.SetActive(false);
}
Here is how coroutines work:
Let's say I have a couroutine function called MyRoutine (in your case, you called it GameOptions)
private IEnumerator MyRoutine()
Then, anywhere in my code, calling
StartCoroutine(MyRoutine));
Is going to simply call MyRoutine like any usual method. So if you call it in update, it will be called all the time, as any method would. This is not what you want. What make coroutines special is that you can use the yield keyword in them. There are many ways to use it but the most used (and simple) one is to do yield return null
yield return null means "Stop this coroutine, but resume the execution on next frame". You don't need to call any other function (certainly not StartCoroutine). The execution will resume next frame.
To go back to what you posted in your question, you wrote yield return null at the end. So your method is executing, and just at the end, stops and resumes next frame, but since there is nothing left to do, it exits on the next frame.
A typical way to use coroutines is to have the yield return null in a while loop, so when it resumes, it continues the loop. Here is an example that do it
private IEnumerator MyRoutine()
{
while(running) //running is a member bool that you could set to false to exit
{
// Do all the stuff you want to do in ONE frame
// ...
yield return null;
}
}
Typically, the StartCoroutine would be called in the Start() function, or later when an event is triggered.
If you want to know more about coroutine, or check that you understood them properly, check out this page: https://docs.unity3d.com/Manual/Coroutines.html
or this video https://unity3d.com/learn/tutorials/topics/scripting/coroutines
// Edit: quickly present one useful option
In the snippet above, the while loop is very similar to the Update function (the inside of the loop is executed each frame). One nice option is to replace
yield return null
by
yield return new WaitForSeconds(waitTime)
where waitTime is a the time you want to wait before resuming, in seconds
// End of edit
Do not use StartCoroutine() in the Update method. Call it in another method and use a while loop inside your coroutine function if needed. Just control your StartCoroutine() outside of Update method
Update is called every frame, if your condition is ever true, you launch your coroutine every frame.
Just set down your flag to only join 1 time.
void Update()
{
if (startFinding)
{
startFinding = false;
StartCoroutine(GameOptions());
}
}

Call OnTriggerEnter Twice

I'm coding a 2D platformer in Unity and need to call the OnTriggerEnter function twice, I've created one Public Collider2D, named headPos,
the first time I use the OnTriggerEnter2D is here
void OnTriggerEnter2D(Collider2D headPos)
{
//Run My Code
{
And the second time I use it is here
void OnTriggerEnter2D(Collider2D other)
{
//Run More Code
{
I get the following error
Type 'Player' already defines a member called 'OnTriggerEnter2D' with the same parameter types
How do I check for two seperate OnTriggerEnter2D's?
When you say different triggers, it looks like you are trying to detect different gameobjects with trigger. If this this is true, then you can use if statement to check which gameobject was triggered.
You can tag each body part with collider/trigger and detect with code like below:
void OnTriggerEnter(Collider col)
{
if(col.CompareTag("head")){
//Run My Code
Debug.Log("Head Triggered!");
}
else if (col.CompareTag("hand"))
{
//Run My Code
Debug.Log("Hand Triggered!");
}
else if (col.CompareTag("leg"))
{
//Run My Code
Debug.Log("Leg Triggered!");
}
}
You can also compare them by name
void OnTriggerEnter(Collider col)
{
if (col.name == "head")
{
//Run My Code
Debug.Log("Head Triggered!");
}
else if (col.name == "hand")
{
//Run My Code
Debug.Log("Hand Triggered!");
}
else if (col.name == "leg")
{
//Run My Code
Debug.Log("Leg Triggered!");
}
}
What is the use case? You should never use the same function twice, especially when it comes to optimization.
I guess what you want to achieve is detecting two separate collisions happening in the same time. In this case your function OnTriggerEnter2D(Collider2D headPos) will be called twice with different colliding object passed in every time.

Categories