I am developing a game. In the game i have to wait for some scripts to put them in order.
Currently my code is below and it works great.
IEnumerator _Something = null;
IEnumerator Something()
{
yield return null;
//do something
_Something = null;
}
IEnumerator TriggerSomething()
{
while (_Something != null) { yield return null; }
_Something = Something(); StartCoroutine(_Something);
}
The problem is game performance. This code causes low performance in the game.
Is there any way to wait for a script to finish it's task and then the other starts new task in same void method?
Thanks
replace
while (_Something != null) { yield return null; }
with
yield return new waituntill(_Something == null)
PS: it would be better if you paste the whole code instead of a general scenario.
Related
I put this script in, this message pops up and I can't go next
Unity 2021.3.15f1
Windows 10
Project name: 포둥
application.enterplaymode/ wating for Unyty's code to finish excuting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class podungsay : MonoBehaviour
{
public GameObject Textbox;
public bool sayingf;
public UnityEngine.UI.Text sayingtext;
public GameObject openmouth;
public GameObject closemouth;
IEnumerator OnType(float interval, string Say, float wait)
{
sayingf = true;
foreach (char item in Say)
{
sayingtext.text += item;
yield return new WaitForSeconds(interval);
}
yield return new WaitForSeconds(wait);
sayingf = false;
}
void Update()
{
if (sayingf == true)
{
Textbox.SetActive(true);
}
else
{
Textbox.SetActive(false);
}
}
IEnumerator mouth()
{
while (true)
{
while (sayingf == true)
{
openmouth.SetActive(false);
closemouth.SetActive(true);
yield return new WaitForSeconds(0.7f);
openmouth.SetActive(true);
closemouth.SetActive(false);
yield return new WaitForSeconds(0.7f);
}
openmouth.SetActive(true);
closemouth.SetActive(false);
}
}
void Start()
{
StartCoroutine(mouth());
}
}
So I changed the version to 2021.3.17f1 but still got this message
What's wrong with the command?
(Use Google translator)
It is stuck because you are using a while which never ends. Leading to an infinite loop.
Use the Update() Method instead.
Right here:
IEnumerator mouth()
{
while (true)
{
while (sayingf == true)
{
openmouth.SetActive(false);
closemouth.SetActive(true);
yield return new WaitForSeconds(0.7f);
openmouth.SetActive(true);
closemouth.SetActive(false);
yield return new WaitForSeconds(0.7f);
}
openmouth.SetActive(true);
closemouth.SetActive(false);
}
}
When "sayingf" is false, it will set openmouth to be active and closemouse to be inactive, and then it will repeat from the while (true), causing an infinite loop. You need to add a delay. You can use yield return 0 or yield return null to wait for one frame to pass.
Also note that sayingf == true is a sin. This is your warning. The next infraction, I will resort to violence.
when using bool == true, either of these will happen:
true == true which will become true.
false == true which will become false.
You can just write if (sayingf) as == true isn't doing anything to the bool.
I'm in need of means to check if how many times the recursive part of this function was activated to check when the first call was fulfilled, it's part of the designed true game over that needs the first call to be fulfilled without achieving all objectives.
IEnumerator CompositeRun(string name)
{
BotOperation operation;
compositeOps.TryGetValue (name, out operation);
CompositeOperation compOp = operation as CompositeOperation;
List<BotOperation> botOpList = compOp.opList;
if (botOpList != null)
{
for (int i = 0; i < botOpList.Count; i++)
{
BotOperation op = botOpList [i];
CompositeOperation comp = op as CompositeOperation;
if (comp != null)
{
// If this operation is a composite type, then we need to execute it first accordingly.
yield return StartCoroutine (CompositeRun(comp.name));
}
if (op != null && op.ValidateOperation(gameObject, levelDef))
{
op.RunOperation (gameObject, levelDef);
if (CheckGameOver ())
{
// If all objectives are fulfilled, then there's no need to continue running the processes, the stage is concluded.
StopAllCoroutines ();
}
}
yield return new WaitForSeconds (operationDelay);
}
}
yield return null;
}
I suggest starting with passing the recursion depth value
But are you 100% sure you cannot resolve this without recursion? Recursion seems like a heavy cannon to kill a fly here
IEnumerator CompositeRun(string label, uint recursionDepth = 0)
{
compositeOps.TryGetValue (label, out var operation);
var compOp = operation as CompositeOperation;
var botOpList = compOp.opList;
if (botOpList != null)
{
var delay = new WaitForSeconds (operationDelay);
for (int i = 0; i < botOpList.Count; i++)
{
var op = botOpList[i];
var comp = op as CompositeOperation;
if (comp != null)
{
// If this operation is a composite type, then we need to execute it first accordingly.
yield return StartCoroutine (CompositeRun(comp.name,recursionDepth+1));
Debug.Log($"we need to go deeper # depth:{recursionDepth+1}");
}
if ( op?.ValidateOperation(gameObject, levelDef))
{
op.RunOperation (gameObject, levelDef);
if (CheckGameOver ())
{
// If all objectives are fulfilled, then there's no need to continue running the processes, the stage is concluded.
StopAllCoroutines ();
}
}
yield return delay;
}
}
yield return null;
}
This question already has answers here:
Unity - IEnumerator's yield return null
(3 answers)
Closed 4 years ago.
I've been working on this project for some days now and I've encountered a bug that seems impossible to solve because not only no error messages appear but it also 'skips' my debug messages and crashes the editor itself.
The following script is a dialog displayer, it's apparently what's causing the issue (forgive the messed code, i messed it around while trying to solve the problem):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class DialogDisplayer : MonoBehaviour
{
[SerializeField] Dialog[] dialogFiles;
TextMeshPro outputTxt;
bool next, finished;
char comma = (char)44;
char period = (char)46;
// Use this for initialization
void Start()
{
outputTxt = GetComponent<TextMeshPro>();
StartCoroutine(type());
}
IEnumerator type()
{
int dialogIndex = 0;
do
{
foreach (char c in dialogFiles[dialogIndex].dialogText)
{
if (Input.GetKeyDown(KeyCode.Z))
{
outputTxt.text = dialogFiles[dialogIndex].dialogText;
Debug.Log("z pressed in the foreach");
break;
}
outputTxt.text += c;
if (c == ' ')
continue;
if (dialogFiles[dialogIndex].delayforPunctuations)
{
if (c == comma)
yield return new WaitForSeconds(dialogFiles[dialogIndex].delayBetweenLetters + 0.1f);
else if (c == period)
yield return new WaitForSeconds(dialogFiles[dialogIndex].delayBetweenLetters + 0.2f);
else
yield return new WaitForSeconds(dialogFiles[dialogIndex].delayBetweenLetters);
}
else
yield return new WaitForSeconds(dialogFiles[dialogIndex].delayBetweenLetters);
}
Debug.Log("Either finished or broken out of the loop");
while (!finished)
{
Debug.LogWarning("Entering while loop");
if (Input.GetKeyDown(KeyCode.Z))
{
Debug.Log("entered if");
finished = true;
dialogIndex++;
}
Debug.Log("got out");
}
} while (dialogIndex != dialogFiles.Length - 1);
}
}
You do not use yield return anywhere in your Coroutine. This will cause your coroutine to run all iterations in the same frame.
From the Scripting documentation:
// every 2 seconds perform the print()
private IEnumerator WaitAndPrint(float waitTime)
{
while (true)
{
yield return new WaitForSeconds(waitTime);
print("WaitAndPrint " + Time.time);
}
}
Or you can use yield return null to pass the coroutine onto the next frame:
private IEnumerator YourDialogCoroutine()
{
bool finished = false;
while (!finished)
{
If(Input.GetKeyDown("Z"))
{
Debug.Log("Ending loop!");
finished = true;
}
yield return null;
}
}
From what I see....
In English: As soon as dialogIndex is not equal to dialogFiles.length minus one your script crashes.
Once this:
while (dialogIndex != dialogFiles.Length - 1);
is true, it enters into a endless loop. There are no directions of what to do once dialogFiles.Length -1 no longer is equal to dialogIndex.
Not sure what you are trying to accomplish with the final line of code there, but neither is the computer and it just waits for instructions once those conditions are met....forever.
It needs to be
while (dialogIndex != dialogFiles.Length - 1); {
Do Something;
provide Action To Eventually escape this loop (make dialogIndex = dialogFiles.Length - 1)
}
As for as the comment goes, you can do StartCoroutine(type()); or StartCoroutine("type"); That's fine.
All! I'm trying to run a process once my counter hits a certain number.
I currently have this part of code on my Update function:
if (counter == 17)
{
// must call process here
}
This is the process I want to run
private void CallProcess()
{
StartCoroutine(StartProcess());
}
This is the whole code for the IEnumerator
private IEnumerator StartProcess()
{
yield return StartCoroutine (Process1()); //once done, a bool here is set to true
if (proccess1_done)
{
yield return StartCoroutine (Process2());
if (process2_done)
{
process1_done = false;
process2_done = false;
}
}
}
I encounter problems when I just call CallProcess() inside my update bc it gets called over and over again. Any advice on how I should modify my code would be much appreciated.
Don't call the StartCoroutine in update. Instead, wrap the boolean in a property so when it is changed or set, you can also call the StartCoroutine.
Let's say counter is the one that sets it:
private IEnumerator coroutine = null;
private int counter = 0;
public int Counter
{
get{ return this.counter; }
set
{
this.counter = value;
if(this.counter == conditionValue)
{
if(this.coroutine != null){ return; } // already running
this.coroutine = StartProcess();
StartCoroutine(this.coroutine); }
}
}
and for your coroutine:
private IEnumerator StartProcess()
{
yield return StartCoroutine (Process1());
yield return StartCoroutine (Process2());
}
you don't really need to check whether Process 1 is done since your coroutine is already waiting for it to be done before continuing.
if you need to check for something inside process 1 for process 2 to run, here is a solution:
private IEnumerator StartProcess()
{
bool condition = false;
yield return StartCoroutine (Process1(ref condition));
if(condition == false){ yield break; }
yield return StartCoroutine (Process2());
this.coroutine = null;
}
private IEnumerator Process1(ref bool condition)
{
// some code
yield return null;
// more code
condition = true; // or false
}
This question already has answers here:
Unity - need to return value only after coroutine finishes
(3 answers)
Closed 6 years ago.
In my Unity game you can place various objects on the surface of the terrain and I need to save the timestamp in a file when new object is placed. I have the saving system working and with some help I was able to get timestamp from the server. It seems you must use IEnumerator in order to get data from server but this created a new problem for me. Since IEnumerator is not a function it can't simply return anything. And since it can't return anything I need a workaround to pass the timestamp from the IEnumerator back to the place it was asked for. How I planned I would be able to get the timestamp:
int _timestamp = ServerTime.Get();
So I could easily store it like
gameObject.GetComponent<_DrillStats>().timestamp = _timestamp;
gameObject.GetComponent<_OtherStats>().timestamp = _timestamp;
gameObject.GetComponent<_EvenMoreStats>().timestamp = _timestamp;
Here is the full code for receiving the timestamp:
using UnityEngine;
using System;
using System.Collections;
public class ServerTime : MonoBehaviour
{
private static ServerTime localInstance;
public static ServerTime time { get { return localInstance; } }
private void Awake()
{
if (localInstance != null && localInstance != this)
{
Destroy(this.gameObject);
}
else
{
localInstance = this;
}
}
public static void Get()
{
if (time == null)
{
Debug.Log("Script not attached to anything");
GameObject obj = new GameObject("TimeHolder");
localInstance = obj.AddComponent<ServerTime>();
Debug.Log("Automatically Attached Script to a GameObject");
}
time.StartCoroutine(time.ServerRequest());
}
IEnumerator ServerRequest()
{
WWW www = new WWW("http://www.businesssecret.com/something/servertime.php");
yield return www;
if (www.error == null)
{
int _timestamp = int.Parse (www.text);
// somehow return _timestamp
Debug.Log (_timestamp);
}
else
{
Debug.LogError ("Oops, something went wrong while trying to receive data from the server, exiting with the following ERROR: " + www.error);
}
}
}
This is what I meant in the comment section:
public static void Get(Action<int> callback)
{
if (time == null)
{
Debug.Log("Script not attached to anything");
GameObject obj = new GameObject("TimeHolder");
localInstance = obj.AddComponent<ServerTime>();
Debug.Log("Automatically Attached Script to a GameObject");
}
time.StartCoroutine(time.ServerRequest(callback));
}
IEnumerator ServerRequest(Action<int> callback)
{
WWW www = new WWW("http://www.businesssecret.com/something/servertime.php");
yield return www;
if (www.error == null)
{
int _timestamp = int.Parse(www.text);
// somehow return _timestamp
callback(_timestamp);
Debug.Log(_timestamp);
}
else
{
Debug.LogError("Oops, something went wrong while trying to receive data from the server, exiting with the following ERROR: " + www.error);
}
}
I made both function take Action<int> as parameter.
USGAE:
Replace ServerTime.Get(); with:
ServerTime.Get((myTimeStamp) =>
{
Debug.Log("Time Stamp is: " + myTimeStamp);
gameObject.GetComponent<_DrillStats>().timestamp = myTimeStamp;
gameObject.GetComponent<_OtherStats>().timestamp = myTimeStamp;
gameObject.GetComponent<_EvenMoreStats>().timestamp = myTimeStamp;
});
You can save the timestamp to a field, and access it after coroutine is completed.
You can use a while loop with a bool flag to wait for coroutine to finish.