In the example below, how can I get FinishFirst() to complete first before running DoLast(), while still retaining the 'public void StartPage()' signature?
I'm trying to avoid making "StartPage()" return an IEnumerator as that would force me to change it in the interface. It would be great if my Interface for StartPage() supported both IEnumerator and Void without needing to implement both.
public void StartPage()
{
print("in StartPage()");
StartCoroutine(FinishFirst(5.0f));
DoLast();
print("done");
}
IEnumerator FinishFirst(float waitTime)
{
print("in FinishFirst");
yield return WaitForSeconds(waitTime);
print("leave FinishFirst");
}
void DoLast()
{
print("do after everything is finished");
}
Use of a lock-object should work:
object lockObject = new object();
public void StartPage()
{
print("in StartPage()");
StartCoroutine(FinishFirst(5.0f));
DoLast();
print("done");
}
IEnumerator FinishFirst(float waitTime)
{
lock(lockObject)
{
print("in FinishFirst");
yield return WaitForSeconds(waitTime);
print("leave FinishFirst");
}
}
void DoLast()
{
lock(lockObject)
{
print("do after everything is finished");
}
}
I know this is an old question, but if I understand the question correctly, something like this works. DoLast() will run at the end of the co-routine.
IEnumerator FinishFirst(float waitTime)
{
print("in FinishFirst");
yield return WaitForSeconds(waitTime);
print("leave FinishFirst");
DoLast();
}
I use a lot of similar code inside co-routines to clean and kill the routine if another one was created when this one is running (mainly for typed-text dialog boxes where the text might change at any time).
Related
I have an EventSystem for managing my turn-based game in Unity.
public class EventSystem : MonoBehaviour
{
private static List<Action> _commandsQueue = new List<Action>();
private bool _canExecuteCommand = true;
public void AddToQueue(Action command)
{
_commandsQueue.Add(command);
}
private void StartCommandExecution()
{
_commandsQueue[0]();
_canExecuteCommand = false;
}
public void CommandExecutionComplete()
{
_canExecuteCommand = true;
}
public void PlayFirstCommandFromQueue()
{
if (_commandsQueue.Any() && _canExecuteCommand)
{
StartCommandExecution();
}
else
{
Debug.LogError("No Command In Queue");
}
}
}
How do I put a method in Update() until _canExecuteCommand is true again but only for some methods?
It is quite broad what you are trying to do but in general you would use an endless loop within a Coroutine.
You can create a generic routine which invokes any Action you pass in as parameter once a frame like e.g.
private IEnumerator InvokeEveryFrame(Action action)
{
// This looks strange but is okey in a Coroutine as long as you yield somewhere within
while(true)
{
action?.Invoke();
// This tells Unity to "pause" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
So all that's left is starting the routine using MonoBehaviour.StartCoroutine like e.g.
Coroutine routine = StartCoroutine(SomeParameterlessMethod);
or if you need parameters
Coroutine routine = StartCoroutine(() => SomeMethod(x, y, z));
and then at some point later stop it using MonoBehaviour.StopCoroutine and the stored Coroutine reference like e.g.
StopCoroutine(routine);
how exactly you store that reference is up to you of course up to you.
In my game I have LevelManager and LevelManager.cs and it's not a singleton.
I wanna make fade transition between scenes. I made animations for Fade_In and Fade_Out. And for the first it works fine but when I'd like to leave a scene, it (Fade_Out) doesn't work.
Some of LevelManager.cs:
private void Start() {
if (autoLoadNextLevelAfter != 0) {
Invoke("FadeOut", autoLoadNextLevelAfter);
}
}
public void LoadLevel(string name) {
SceneManager.LoadScene(name);
}
public void LoadNextLevel() {
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
private void FadeOut() {
animator.SetTrigger("FadeOut");
}
In the Start method I call Invoke of animation and at the end of Fade_Out I call LoadNextLevel method. In this case, Fade_Out works fine.
But If I wanna make transition with LoadLevel (it calls when I push the button) it breaks. I tried to make IEnumerator LoadLevel:
public void NewLoadLevel(string name) {
FadeOut();
StartCoroutine(CoroutLoadLevel(name));
}
private IEnumerator CoroutLoadLevel(string name) {
yield return new WaitForSeconds(1f);
SceneManager.LoadScene(name);
}
The console says: Coroutine couldn't be started because the the game object 'LevelManager' is inactive!. I guess it's because LevelManager is not a singleton.
Make sure the LevelManager is active and enabled. A coroutine needs an active object to run (because if it is not active update logic will not be called).
I have various methods that work fine, but I want to call them only after a delay. To avoid writing a different method for all of them I figured it's more beneficial to Invoke them somehow. I made the methods so that they exclude Unity's timeScale, so they always wait for Real seconds, using a custom built short function.
The WaitForRealSeconds:
public class WaitForRealSecondsClass
{
#region Wait for real seconds
public Coroutine WaitForRealSeconds(float aTime, MonoBehaviour mono)
{
return mono.StartCoroutine(_WaitForRealSeconds(aTime));
}
private IEnumerator _WaitForRealSeconds(float aTime)
{
while (aTime > 0.0f)
{
aTime -= Mathf.Clamp(Time.unscaledDeltaTime, 0, 0.2f);
yield return null;
}
}
#endregion
}
The way I wish to Invoke a Move function of mine:
public void InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction, MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _InvokeAnim(timeBeforeStart, MoveFunction, mono);
}
IEnumerator _InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction, MonoBehaviour mono)
{
yield return new WaitForRealSecondsClass().WaitForRealSeconds(timeBeforeStart, mono);
MoveFunction(mono);
}
And the Move(MonoBehaviour mono) itself:
public void Move(MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _Move(from, to, overTime, mono);
mono.StartCoroutine(moveRoutine);
}
What I tested and worked is the Move itself, the WaitForRealSeconds I used in another project for UI waiting when the game was stopped, it was fine then.
As I said I have many methods to invoke, all of them return void and have a parameter MonoBehaviour. Currently it doesn't do anything and I have no idea why.
Sod it, I was dump enough to forget actually Starting that coroutine.
In my Invoke:
public void InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction,
MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _InvokeAnim(timeBeforeStart, MoveFunction, mono);
mono.StartCoroutine(moveRoutine); //one line was missing
}
IEnumerator _InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction,
MonoBehaviour mono)
{
yield return new WaitForRealSecondsClass().WaitForRealSeconds(timeBeforeStart, mono);
MoveFunction(mono);
}
I have an IEnumerator for a coroutine that spawn object like this
public IEnumerator spawnCoroutine;
private float spawnObstacleTimer;
void Start () {
spawnObstacleTimer = GameObject.Find("EventSystem").GetComponent<gameManager>().spawnObstacleTimer;
spawnCoroutine = spawnObsCoroutine(spawnObstacleTimer);
startSpawnCoroutine();
}
public IEnumerator spawnObsCoroutine(float timer)
{
while (true)
{
yield return new WaitForSeconds(timer);
spawnObs();
}
}
public void startSpawnCoroutine()
{
StartCoroutine(spawnCoroutine);
}
public void stopSpawnCoroutine()
{
StopCoroutine(spawnCoroutine);
}
And I start it on the Start() which it run fine. But I've put a collision detection to stop this Coroutine when triggered. Again this is running fine but it's when I call the OnCollisionExit() that restart the Coroutine with the function startSpawnCoroutine(); a new object is spawned immediatly and is ignoring the yield return new WaitForSeconds()
So how this happen? StopCouroutine should stop it all and when I restart it, it should wait for seconds before executing the spawn.
Thanks for your help
The function StopCoroutine(spawnCoroutine) could be better named PauseCoroutine(spawnCoroutine), when you call start again it restarts from the last yield inside the routine.
Change your startSpawnCoroutine() function to start a new instance of the routine to get it to "start from scratch"
public IEnumerator spawnCoroutine;
private float spawnObstacleTimer;
void Start () {
spawnObstacleTimer = GameObject.Find("EventSystem").GetComponent<gameManager>().spawnObstacleTimer;
startSpawnCoroutine();
}
public IEnumerator spawnObsCoroutine(float timer)
{
while (true)
{
yield return new WaitForSeconds(timer);
spawnObs();
}
}
public void startSpawnCoroutine()
{
//Moved the creation of the IEnumerable in to this function.
spawnCoroutine = spawnObsCoroutine(spawnObstacleTimer);
StartCoroutine(spawnCoroutine);
}
public void stopSpawnCoroutine()
{
StopCoroutine(spawnCoroutine);
}
I'm stuck at Unity scripting. I got 3 files : Scene.cs , Player.cs and NetworkUtil.cs . I can't compile my code, as I don't know how to pass Coroutine response back to Scene.
In Scene.cs ( MonoBehavior class ) :
void OnGUI() {
if(GUI.Button(new Rect(0, 0, 100, 100), "Register")) {
StartCoroutine(registerPlayer());
}
}
IEnumerator registerPlayer() {
return Player.NewPlayer("Raptor"); // since Player is not IEnumerator, this line causes error.
}
In Player.cs ( Object class ) :
public static Player NewPlayer(string name) {
Player p = new Player();
result = p.tryRegister();
if(result) {
return p;
} else {
return null;
}
}
private bool tryRegister() {
Dictionary<string, string> data = new Dictionary<string, string>();
data.Add("some_key", "some_value");
NetworkUtil.ExecuteAPI(data);
}
In NetworkUtil.cs ( Object class ) :
public static IEnumerator ExecuteAPI(Dictionary<string, string> data) {
WWWForm form = new WWWForm();
form.AddField("mode", request);
foreach(KeyValuePair<string, string> entry in data) {
form.AddField(entry.Key, entry.Value);
}
WWW handler = new WWW(API_URL, form);
yield return handler;
if(handler.error != null) {
Debug.Log("Error occurred: " + handler.error); // Server Error
} else {
Debug.Log("Response: " + handler.text);
}
}
How can I change the codes to make the flow complete?
Note: In Object class, StartCoroutine() does not exist.
EDIT
I have changed my codes to use Delegates & Events:
// DelegatesAndEvents.cs
public class DelegatesAndEvents : MonoBehaviour {
public delegate void RegisterEventHandler(Player player);
public static event RegisterEventHandler onPlayerRegister;
public static void NewPlayerRegistered(Player player) {
if(onPlayerRegister != null) {
onPlayerRegister(player);
}
}
}
Then in Scene.cs:
void Start() {
DelegatesAndEvents.onPlayerRegister += this.userRegistered;
}
public void userRegistered(Player player) {
Debug.Log("User registered.");
}
How should I put the trigger in NetworkUtil.cs & Player.cs ?
There are several ways of doing that, because you not only need to return an IEnumerator, but it's Unity itself that will iterate on it, so you don't have the possibility to catch the return value.
Technically is possible to do the following:
IEnumerator registerPlayer() {
yield return Player.NewPlayer("Raptor"); // this is legal, player will be the Current value of the IEnumerator
}
If you want to do do something like the code above, you need to wrap the coroutine inside another, and iterate on it by yourself (I did something similar implementing behavior trees):
IEnumerator Wrap(IEnumerator playerRegister)
{
while(playerRegister.MoveNext())
{
Player p = playerRegister.Current as Player; //this can be done if you know what you are doing
yield return null;
}
}
Another way is not return anything and pass a delegate to the coroutine, that will be called passing back to the caller the required parameter.
Something like:
IEnumerator DoSomething(Action<Response> whenDone)
{
while (doingSomething)
yield return null;
whenDone(response);
}
EDIT
The other problem with your code is that you are calling another coroutine (ExecuteAPI)from inside Player's constructor.
So, making registerPlayer a coroutine is pointless since you are not yielding anything.
In your case the simplest way to go is to pass a delegate to ExecuteAPI that will be called when you receive the response from the server.