How I can load and return file from another method(use WWW)?
I want to do next:
Method GetSettings(). Download file text, parse json and
return result.
Call method from Start() and wait while GetSettings() return result.
How I can do this?
Looks like you want to downloaded data then wait then wait for the download to finish then download another data. If this is true the you can the code below will download data 2 times. You can increase the number of times by increasing the REQ_AMOUNT value.
It uses yield return StartCoroutine to wait for the current coroutine function to return before running again.
IEnumerator Start()
{
int REQ_AMOUNT = 2;
for (int i = 0; i < REQ_AMOUNT; i++)
{
yield return StartCoroutine(GetSettings());
}
}
IEnumerator GetSettings()
{
string url = RoomSettings.AbsoluteFilenamePath;
if (Application.isEditor)
{
url = "file:///" + url;
}
var www = new WWW(url);
yield return www;
// Do some code, when file loaded
}
Related
I am working on a game, that has a keypad puzzle. A specific key combination lights up one by one, which the player must repeat to solve that puzzle. I am going to let the player know what the combination is by activating and deactivating some GameObjects systematically, one by one. As it suggests, there is some time delay between the deactivation of one GameObject and the activation of another. The problem is, in my code, all the GameObjects activate simultaneously instead of one by one, after a delay.
Here is the code:
public string Generate(int length, float delay)
{
// Variables for logic
string combination = "";
int prev = -1; int current = 0;
int rnd = 0;
for (int i = 0; i < length; i++)
{
rnd = Random.Range(0, BUTTONS);
while (rnd == prev)
{
rnd = Random.Range(0, BUTTONS);
}
prev = current;
current = rnd;
combination += current.ToString();
// Activation and Deactivation
StartCoroutine(GenerateDelay(delay, current));
}
return combination;
}
IEnumerator GenerateDelay(float delay, int index)
{
ButtonClicks[index].SetActive(true);
yield return new WaitForSeconds(delay);
ButtonClicks[index].SetActive(false);
}
The loop counter specifies the length of the combination. I believe there is something wrong with the Coroutine I made? Since all the objects activate simultaneously.
Here is the result in the game as well:
We can see, only one button should turn green at a time, but all of them do in this case. Any solutions?
You start all your Coroutines parallel so things happen at the same time.
StartCoroutine does not delay the method which calls it (unless it is yielded as well.
You would need to run the entire loop within a Coroutine in order to delay it as well.
You could simply split up the creation of the combination and while you already return it you start the visualization in the background in parallel
public string Generate(int length, float delay, Action<string> onCombination)
{
// Variables for logic
var combination = List<int>();
var prev = -1;
for (var i = 0; i < length; i++)
{
int rnd;
do
{
rnd = Random.Range(0, BUTTONS);
}
while (rnd == prev);
prev = rnd;
combination.Add(rnd);
}
StartCorouine (ShowCombination(combination, delay));
return string.Join("", combination);
}
private IEnumerator ShowCombination (IEnumerable<int> combination, float delay)
{
foreach(var i in combination)
{
ButtonClicks[i].SetActive(true);
yield return new WaitForSeconds(delay);
ButtonClicks[i].SetActive(false);
}
}
something alot easier then a coroutine is invoking a function, basically create a function to activate/deactivate the wanted object and whenever you want to call it do:
Invoke("FUNCTIONNAME", TIME);
and it will run the function specified after the TIME.
I have a coroutine like so:
private IEnumerator ShortFlashes()
{
this.startedShortFlashes = true;
this.finishedShortFlashes = false;
const int maxFlashes = 3;
int numFlashesSoFar = 1;
if (numFlashesSoFar > maxFlashes)
{
Debug.Log("All 3 short flashes finished!");
this.finishedShortFlashes = true;
yield break;
}
while (numFlashesSoFar <= maxFlashes)
{
yield return new WaitForSecondsRealtime(0.05f);
this.Renderer.enabled = true;
yield return new WaitForSecondsRealtime(0.05f);
this.Renderer.enabled = false;
Debug.Log("Number of short flashes so far: " + numFlashesSoFar);
numFlashesSoFar++;
}
}
When this coroutine is running, I can see messages in the Unity console enumerating the number of short flashes, just as intended. (Debug.Log("Number of short flashes so far: " + numFlashesSoFar);).
However, Debug.Log("All 3 short flashes finished!"); is never executed, even when numFlashesSoFar exceeds maxFlashes.
This is very inconvenient, because in my Update() method, there are some additional actions that I would like to perform if this.finishedShortFlashes is true.
How can I fix this issue?
You've hardcoded the values.
const int maxFlashes = 3;
int numFlashesSoFar = 1;
if (numFlashesSoFar > maxFlashes)
{
//...
}
if (numFlashesSoFar > maxFlashes) is effectively equal to if (1 > 3), which is never true.
I genuinely don't understand why you structured the code the way you did, which makes it hard for me to understand the core issue here.
This makes much more sense:
const int maxFlashes = 3;
int numFlashesSoFar = 1;
while (numFlashesSoFar <= maxFlashes)
{
yield return new WaitForSecondsRealtime(0.05f);
this.Renderer.enabled = true;
yield return new WaitForSecondsRealtime(0.05f);
this.Renderer.enabled = false;
Debug.Log("Number of short flashes so far: " + numFlashesSoFar);
numFlashesSoFar++;
}
Debug.Log("All 3 short flashes finished!");
this.finishedShortFlashes = true;
Note that you don't need the if. When the while loop finishes, you already know that the condition is met (otherwise the while loop would not have finished yet.
I don't understand the purpose of the yield break; in your code. It seems unnecessary, so I removed it.
I think you're missing the point of yield keyword.
Once the execution encounter yield return the execution is transferred back to the calling method and the execution state for the routine is retained. The next time you call that same routine, the execution resumes from where it has been yielded.
More information is available in the official documentation
In that particular case, the line Debug.Log("All 3 short flashes finished!"); is never hit, because when the control steps into the method initially, the numFlashesSoFar variable is set to 1 so the condition is never met. Then it goes into the loop, where the yield keyword is encountered. So the next time, the execution continues from within the loop.
I don't understand why you put that if-statement above the loop. The while loop will continue until the while-statement is false. There is no need for the if-statement, you can simply put the code you have inside your if-statement below the loop:
private IEnumerator ShortFlashes()
{
this.startedShortFlashes = true;
this.finishedShortFlashes = false;
const int maxFlashes = 3;
int numFlashesSoFar = 1;
while (numFlashesSoFar <= maxFlashes)
{
yield return new WaitForSecondsRealtime(0.05f);
this.Renderer.enabled = true;
yield return new WaitForSecondsRealtime(0.05f);
this.Renderer.enabled = false;
Debug.Log("Number of short flashes so far: " + numFlashesSoFar);
numFlashesSoFar++;
}
Debug.Log("All 3 short flashes finished!");
this.finishedShortFlashes = true;
}
Also, you can see yield return in a Coroutine as a "temporary break in code", because it waits for the next frame to continue the code. Unless you return a WaitForSeconds of course, then it will wait until the given amount of time has passed.
This is using Unity3D. I have three coroutines: GetJSONFromSelectedSubreddit(), LoadMoreMemes(), and a function in a separate script that needs to be able to access the array of memes through the GetNewMemes() function (must return type Meme[]). LoadNewMemes() produces. The thing is, LoadMoreMemes() requires the json to work, so they have to run in the mentioned order. If you need the functions, here they are:
public void GetNewMemes(string subReddit, int count)
{
SetSelectedSubreddit(subReddit);
memesAtATime = count;
subJSON = null;
StartCoroutine(GetJSONFromSelectedSubreddit());
StartCoroutine(LoadMoreMemes());
}
IEnumerator GetJSONFromSelectedSubreddit()
{
gettingJSON = true;
WWW requester = new WWW("https://www.reddit.com/r/" + selectedSub + "/new.json?sort=new&count=25&after=" + postIndex);
yield return requester;
subJSON = requester.text;
json = new JSONObject(subJSON);
gettingJSON = false;
}
IEnumerator LoadMoreMemes()
{
while (gettingJSON)
yield return new WaitForSeconds(0.1f);
for (int i = 0; i < memesAtATime; i++)
{
yield return StartCoroutine(GetUserPostKarma(json["data"]["children"][i]["data"]["author"].str));
string sourceURL = json["data"]["children"][i]["data"]["preview"]["images"][0]["source"]["url"].str;
sourceURL = sourceURL.Replace("&", "&");
yield return StartCoroutine(GrabImage(sourceURL));
Meme currentMeme = new Meme(
json["data"]["children"][i]["data"]["preview"]["images"][0]["source"]["url"].str,
authorPostKarma,
(int) json["data"]["children"][i]["data"]["score"].i,
json["data"]["children"][i]["data"]["permalink"].str,
json["data"]["children"][i]["data"]["title"].str,
currentBitmap
);
Debug.Log(currentMeme.cost);
memes[i] = currentMeme;
}
}
Here's the other script:
void Start ()
{
RedditCommunicator redditCommunicator = GetComponent<RedditCommunicator>();
redditCommunicator.GetNewMemes("me_irl", 1);
Meme[] memes = redditCommunicator.GetCurrentMemes();
Debug.Log(memes[0].currentScore);
redditCommunicator.SpawnOneMeme(memes[0]);
}
Each function works fine on its own, but they need to wait for each other to finish, as well as run in the correct order to work. I'd like the functions to stay separate so I can call them individually in the future. memes is a private variable, and the one I'd like to pass to the other script calling these functions. If you don't think I've tried my options Googling and solving this on my own, just believe me, I've done my best. Thanks for your help in advance. If you need more information, just ask me for it. The current state of this code is it returns memes to early, before the coroutines can finish, resulting in empty memes.
You can yield a Coroutine in an IEnumerator which will halt the progression of that Coroutine until that Coroutine is done. Like this:
void Start()
{
StartCoroutine(DoThings((text) => {
Debug.Log("Dothings told me: " + text);
}));
}
IEnumerator DoThings(Action<string>() callback)
{
yield return StartCoroutine(DoThisFirst());
callback("Returning a value mid-method!");
yield return StartCoroutine(ThenThis());
Debug.Log(3);
}
IEnumerator DoThisFirst()
{
yield return new WaitForSeconds(1);
Debug.Log(1);
}
IEnumerator ThenThis()
{
yield return new WaitForSeconds(1);
Debug.Log(2);
}
Problem is that GetJSONFromSelectedSubreddit and LoadNewMemes methods are called as two "parallel" coroutines in GetNewMemes method.
If you do not need to run a coroutine "asynchronously", you can just enumerate through it:
public void GetNewMemes(string subReddit, int count)
{
SetSelectedSubreddit(subReddit);
memesAtATime = count;
subJSON = null;
var enumerator = GetJSONFromSelectedSubreddit();
while (enumerator.MoveNext());
enumerator = LoadNewMemes();
while (enumerator.MoveNext());
}
I've got a class. It has a method that does a lot of work. I would like to not have the program hang while it does the work. I understand yield will do this for me.
void Start() {
DoWork(10,10);
}
void DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
}
}
If I add a yield like this
void Start() {
DoWork(10, 10);
}
IEnumerable DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
}
}
None of the work gets done and on top of that I see no log statements at all.
How do I yield my code so the program doesn't freeze?
This is Unity3D engine so your coroutine needs to return IEnumerator to be valid:
void Start() {
StartCoroutine(DoWork(10, 10));
}
IEnumerator DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
}
}
This is in no way multithreading. It is run just like an update once per frame between the Update and the LateUpdate except if you use
yield return new WaitForEndOfFrame();
then it is postponed until after the rendering process. What it does is create a new object of type Coroutine and place it on the calling MonoBehaviour stack of coroutines.
This works as a method that performs some repetitive action but always return to the main program when hitting a yield. It will then catch back from there on the next frame.
You need to use the StartCoroutine method:
void Start() {
StartCoroutine(DoWork(10, 10));
}
IEnumerator DoWork (int x, int y) {
// (A)
yield return null;
// (B)
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
// (C)
}
}
Yur code is executed piece by piece where delimiter of steps is the yield operator, i.e. when Framework calls MoveNext() the first time - the code (A) will be executed, when it calls MoveNext() second time - the code (B) will be executed, then code (C), and so on and so forth.
When you add a yield statement, the compiler actually generates a private class that acts as a state machine that implements IEnumerable. As such none of the code wrapped up from the original method will be called unless you enumerate the result of the method - in your example, you're throwing away the return value, so nothing would happen.
Yield keyword is used for lazy loading/computation support in C#.
Try doing:
var result = DoWork().ToList();
This forces an evaluation of the DoWork() method and you will see the logging taking place.
C# yield in Unity works just like C# yield always does. Unity does not influence this in any way.
yield is a keyword that is used to allow enumeration over a set of return values.
IEnumerator<int> MyEnumerationMethod()
{
yield return 5;
yield return 1;
yield return 9;
yield return 4;
}
void UserMethod1()
{
foreach (int retVal in MyEnumerationMethod())
Console.Write(retVal + ", ");
// this does print out 5, 1, 9, 4,
}
void UserMethod2()
{
IEnumerator<int> myEnumerator = MyEnumerationMethod();
while (myEnumerator.MoveNext())
Console.Write(myEnumerator.Current + ", ");
// this does print out 5, 1, 9, 4,
}
UserMethod1() and UserMethod2() are pretty much the same. UserMethod1() is just the C# syntactic sugar version of UserMethod2().
Unity uses this language feature to implement Coroutines:
When you call StartCoroutine() and pass it an IEnumerator, Unity stores this enumerator and calls MoveNext() for the first time. This will cause MyEnumerationMethod() to be called and executed up until the first yield return. At this point, MoveNext() returns and the first result (5) can be retrieved by looking at the Current property of the enumerator.
Now, Unity regularly checks the Current property and - depending on its value - decides whether the time has come to call MoveNext() again. The value of Current might be an instance of WaitForEndOfFrame, an instance of WWW or whatever, and depending on that the time, MoveNext() is called is decided.
Once MoveNext() is called again, execution of MyEnumerationMethod() will be continued at the point where it was interrupted last time, and executes until the next yield return is executed. And so on.
That's all there is to yield, and to Coroutines in Unity.
I have been trying to convert the code at (the 2nd sample on the page): http://unity3d.com/support/documentation/ScriptReference/WWWForm.html
..to C# in Unity3D:
void Start ()
{
string dataUrl = "http://www.my-site.com/game/test.php";
string playName = "Player 1";
int score = -1;
// Create a form object for sending high score data to the server
var form = new WWWForm();
// Assuming the perl script manages high scores for different games
form.AddField( "game", "MyGameName" );
// The name of the player submitting the scores
form.AddField( "playerName", playName );
// The score
form.AddField( "score", score );
// Create a download object
WWW downloadW = new WWW( dataUrl, form );
// Wait until the download is done
yield return downloadW;
if(downloadW.error == null) {
print( "Error downloading: " + downloadW.error );
return false;
} else {
// show the highscores
Debug.Log(downloadW.text);
}
}
I get the following error:
error CS1624: The body of rr2game.Start()' cannot be an iterator block becausevoid' is not an iterator interface type
After doing some reading I have tried changing void Start() to IEnumerator Start()
..but it says IEnumerator is is not declared..?
If I comment out the yield command the errors go away, but of course the data doesn't load.
Please can someone help?
Thank you.
You need to change the return type of Start(), the Start callback supports both void and IEnumerator as it's return types.
IEnumerator Start ()
{
string dataUrl = "http://www.my-site.com/game/test.php";
string playName = "Player 1";
int score = -1;
// Create a form object for sending high score data to the server
var form = new WWWForm();
// Assuming the perl script manages high scores for different games
form.AddField( "game", "MyGameName" );
// The name of the player submitting the scores
form.AddField( "playerName", playName );
// The score
form.AddField( "score", score );
// Create a download object
WWW downloadW = new WWW( dataUrl, form );
// Wait until the download is done
yield return downloadW;
if(downloadW.error == null) {
print( "Error downloading: " + downloadW.error );
return false;
} else {
// show the highscores
Debug.Log(downloadW.text);
}
}
Once the return type is IEnumerator you are allowed to use the yield keyword.
Most callbacks let you return IEnumerator, some that can't are: Awake, Update, LateUpdate, FixedUpdate, OnGUI, OnEnable, OnDisable, OnDestroy. You will need to check the documentation of the event callback to see if it does not support being a co-routine.
yield can not be used in the Start() function, it needs to be called within its own thread, instead try this:
void Start()
{
StartCoroutine(SaveScore());
}
IEnumerator SaveScore()
{
string dataUrl = "http://www.my-site.com/game/test.php";
string playName = "Player 1";
int score = -1;
// Create a form object for sending high score data to the server
var form = new WWWForm();
// Assuming the perl script manages high scores for different games
form.AddField( "game", "MyGameName" );
// The name of the player submitting the scores
form.AddField( "playerName", playName );
// The score
form.AddField( "score", score );
// Create a download object
WWW downloadW = new WWW( dataUrl, form );
// Wait until the download is done
yield return downloadW;
if(!string.IsNullOrEmpty(downloadW.error)) {
print( "Error downloading: " + downloadW.error );
} else {
// show the highscores
Debug.Log(downloadW.text);
}
}