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.
Related
I don't want the player to spam the continueButtonSE hence, I only want it to appear after the dialogue has finished.
It works in the first index but for the next element, the continueButton does not appear. Here's my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class DialogueSE : MonoBehaviour
{
public TextMeshProUGUI textDisplaySE;
public string[] sentencesSE;
private int index;
public float typingSpeedSE;
public GameObject continueButtonSE;
void Start()
{
StartCoroutine(Type());
}
void Update()
{
if(textDisplaySE.text == sentencesSE[index])
{
continueButtonSE.SetActive(true);
}
}
IEnumerator Type()
{
foreach (char letter in sentencesSE[index].ToCharArray())
{
textDisplaySE.text += letter;
yield return new WaitForSeconds(typingSpeedSE);
}
}
public void NextSentenceSE()
{
continueButtonSE.SetActive(false);
if (index < sentencesSE.Length - 1)
{
index++;
textDisplaySE.text = " ";
StartCoroutine(Type());
}
else
{
textDisplaySE.text = " ";
continueButtonSE.SetActive(false);
}
}
}
I've disabled the continueButtonSE from the start so that it can only appear once sentencesSE[index] is done appearing.
You are prepending a space when you say textDisplaySE.text = " "; and then you add things in the coroutine therefore if(textDisplaySE.text == sentencesSE[index]) is never true because your in the second case textDisplaySE.text is actually [space]test2 instead of just test2 which is what sentencesSE[index].
On line 31 instead you can initialize to textDisplaySE.text = string.Empty; to not have any whitespace.
In addition to the explanation of your issue from this answer
Why do you need the check at all?
You could simply let your Coroutine handle the end of the typing and enabling the continue button like e.g.
IEnumerator Type()
{
foreach (char letter in sentencesSE[index].ToCharArray())
{
textDisplaySE.text += letter;
yield return new WaitForSeconds(typingSpeedSE);
}
if (index < sentencesSE.Length - 1)
{
continueButtonSE.SetActive(true);
}
}
public void NextSentenceSE()
{
continueButtonSE.SetActive(false);
if (index < sentencesSE.Length - 1)
{
index++;
textDisplaySE.text = "";
StartCoroutine(Type());
}
else
{
textDisplaySE.text = "";
Debug.Log("Reached end of dialog");
}
}
This way you don't need the Update method and string compare at all and wouldn't even get into the issue with the prepend space character ;)
In a script :
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class NaviDialogue : MonoBehaviour
{
public ObjectsManipulation op;
public bool scaling = true;
public Scaling scale;
public ConversationTrigger conversationTrigger;
private bool ended = false;
private bool startConversation = false;
private void Update()
{
if (scaling == true && DOFControl.hasFinished == true)
{
DOFControl.hasFinished = false;
scaling = false;
op.Scaling();
PlayerController.disablePlayerController = true;
ConversationTrigger.conversationsToPlay.Add(0);
ConversationTrigger.conversationsToPlay.Add(1);
ConversationTrigger.conversationsToPlay.Add(2);
StartCoroutine(conversationTrigger.PlayConversations());
}
}
And in the top of ConversationTrigger :
public static List<int> conversationsToPlay = new List<int>();
In the method PlayConversations :
public IEnumerator PlayConversations()
{
for (int i = 0; i < conversationsToPlay.Count; i++)
{
yield return StartCoroutine(PlayConversation(conversationsToPlay[i]));
}
}
And the Play Conversation method :
public IEnumerator PlayConversation(int index)
{
isRunning = true;
if (conversations.Count > 0 &&
conversations[index].Dialogues.Count > 0)
{
for (int i = 0; i < conversations[index].Dialogues.Count; i++)
{
if (dialoguemanager != null)
{
dialoguemanager.StartDialogue(conversations[index].Dialogues[i]);
}
while (DialogueManager.dialogueEnded == false)
{
yield return null;
}
}
conversationIndex = index;
conversationEnd = true;
canvas.SetActive(false);
Debug.Log("Conversation Ended");
conversationsToPlay.Remove(index);
}
}
In the last method Play Conversation I'm removing the current played item :
conversationsToPlay.Remove(index);
The problem is that in the PlayConversations method now I value is 1 so it will play next the last item. So if there are 3 items it will play the first and the last but the middle one will not be played.
You should never modify a collection you are currently iterating over (the problem you encountered is one of the reasons for that). In your case, there are a few options, a simple solution could be to copy the list of conversations and at the same time clear the original list:
public IEnumerator PlayConversations()
{
var conversations = conversationsToPlay.ToArray(); // Copy the list
conversationsToPlay.Clear(); // Immediately clear the original list
for (int i = 0; i < conversations.Length; i++) // iterate over the array
{
// Now you also don't need to remove items anymore,
// since you already cleared the list
yield return StartCoroutine(PlayConversation(conversations[i]));
}
}
The array you create stays local to the coroutine, so you can clear the original list and work with the copy.
Alternatively, you could just change the loop to a while-loop and process the list from the start until it's empty:
public IEnumerator PlayConversations()
{
while (conversationsToPlay.Count > 0)
{
// Better remove the item right here, close to the loop condition.
// Makes things easier to understand.
var conversationIndex = conversationsToPlay[0];
conversationsToPlay.RemoveAt(0);
yield return StartCoroutine(PlayConversation(conversationIndex));
}
}
When going with the second example, you might just as well use a Queue<T> instead of a List<T> for the conversations, as a queue is designed specifically with first-in, first-out access in mind.
If you dont have a business requirment to maintain order, then Always iterate the collection in reverse order when you are planning to remove items. This you can remove items without breaking the sequence of the array. So here
public IEnumerator PlayConversations()
{
for (int i = conversationsToPlay.Count-1; i >=0; i++)
{
yield return StartCoroutine(PlayConversation(conversationsToPlay[i]));
}
}
This method works in general for all the situations where we remove something from the collection. However on a side note, Removing the conversation in Playconversation method is just a bad practice. you will end up with hard to maintain code. Remove it in some method which is specifically for this purpose. otherwise you are violating SRP
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.