Reputation: 89
I am trying to make a simple Message box that helps guide the readability of my output. I am using C# in Unity.
The reason for this is that the Message is often long, convoluted or occasionally needs a response.
I am trying to call a DisplayMessage(string _message)
Method with a simple bool
flag to allow it to close up and continue. But no matter what i do it either freezes (thank you while
infinite loop) or passes through to the main thread
IEnumerable
s are not one of my strong points and unless I am missing something basic and obvious (a possibility I am more than willing to admit) then the Coroutine is still not going to hold up DoDisplay()
properly until I get the Button input
I have spent some hours looking but my eyes are glazing over at options from the Systems.Threading solutions
public Class MainTurn: MonoBehavior {
public GameControl gCon;
public void OnPhaseOne(GameControl gCon) {
// *** EVENT CARDS ***
DisplayManager dMan = GameObject.FindGameObjectWithTag("Display Manager").GetComponent<DisplayManager>();
dMan.DoDisplay("Take 2 Cards, update Red Count");
// Check for 6+ Event Cards
if (gCon.Cards >= 6) {
dMan.DoDisplay("Discard a Card\n Discard Order:\n1. 2. 3. 4.");
}
// Allow for Exchange
if (gCon.Cards == 2) {
dMan.DoDisplay("Exchange Card as Required");
}
}
}
public class DisplayManager : MonoBehaviour {
public GameObject DisplayPanel;
public Text Message_Text;
public Button Complete_Button;
private bool _isComplete;
public void DoDisplay(string _displayMessage)
{
// Activate Panel
DisplayPanel.SetActive(true);
// Set flag and display message
_isComplete = false;
Message_Text.text = _displayMessage;
// Wait until Complete
while (_isComplete == false)
{
Debug.Log("Waiting");
}
//Clear and close
Message_Text.text = "";
DisplayPanel.SetActive(false);
}
// A simple Button press to clear the flag
public void OnComplete()
{
_isComplete = true;
}
}
Upvotes: 0
Views: 229
Reputation: 90779
First of all: You do not want to use Threads here! Use them for heavy operations like file IO and serialization, yes, etc but not for just waiting until something happens in your App. The only thing achieving by that would be having to additionally take care that the according callback is passed back to the main thread etc.
If it is only about one-time actions that don't require any continuous update or smooth movements etc then how about making it event-based without using a bool
flag and a routine or loop at all:
public void DoDisplay(string _displayMessage)
{
// Activate Panel
DisplayPanel.SetActive(true);
// display message
Message_Text.text = _displayMessage;
// log only once
Debug.Log("Waiting");
}
// A simple Button press to clear the flag
public void OnComplete()
{
//Clear and close
Message_Text.text = "";
DisplayPanel.SetActive(false);
}
In general you should avoid a frame-wise usage of Debug.Log
anyway since it slows down your app even if you don't see the console output anymore in a build.
Since you asked - Alternative could be using a Coroutine and either WaitWhile
or WaitUntil
or if you really want again a while
loop with a simple yield return null
.
Note that coroutines are still executed in the main thread so any heavy operation here might still freeze your app - like for example a closed while
loop if you do not yield return
anywhere within it ;)
See them as temporary Update
methods since actually (using the default yield return
s) it is executed frame-wise after the actual Update
method. There are exceptions like e.g. WaitForFixedUpdate
or WaitForEndOfFrame
but maybe this would go to far for now.
In very simple words for Unity's coroutines yield return
means something like "Pause this routine, continue executing the main thread. Later continue this routine from where you left."
public void DoDisplay(string _displayMessage)
{
// Be careful now with concurrent routines in case this gets called twice
// You probably would want to stop the last routine and start a new one
StopAllCoroutines();
StartCoroutine(DoDisplayRoutine(_displayMessage));
}
public IEnumerator DoDisplayRoutine(string _displayMessage)
{
// Activate Panel
DisplayPanel.SetActive(true);
// Set flag and display message
_isComplete = false;
Message_Text.text = _displayMessage;
Debug.Log("Waiting");
yield return new WaitUntil(() => _isComplete);
// OR if you really want to display the log every frame meanwhile
while(!_isComplete)
{
Debug.Log("Waiting");
// render this frame and continue from here in the next one
// If you missed to yield return somewhere in a while loop
// then yes, this would also freeze the main thread
yield return null;
}
//Clear and close
Message_Text.text = "";
DisplayPanel.SetActive(false);
}
// A simple Button press to clear the flag
public void OnComplete()
{
_isComplete = true;
}
I don't know what your main code does as you didn't add it ... What you could do would be making it a routine as well like e.g.
IEnumerator YourMainCode()
{
... do something
// show message and wait until it gets closed (using the later coroutine solution)
yield return DoDisplayRoutine(_displayMessage);
... continue after message was closed
}
as said you could do it like
public void OnPhaseOne(GameControl gCon)
{
StartCoroutine(PhaseOneRoutine());
}
private IEnumerator PhaseOneRoutine()
// *** EVENT CARDS ***
// if there is only one in the sene you can simply use
DisplayManager dMan = FindObjectOfType<DisplayManager>();
// in general you should rather do it in Awake and only once
// this now "blocks" until the message is completed
yield return dMan.DoDisplayRoutine("Take 2 Cards, update Red Count");
// Check for 6+ Event Cards
if (gCon.Cards >= 6) {
yield return dMan.DoDisplayRoutine("Discard a Card\n Discard Order:\n1. 2. 3. 4.");
}
// Allow for Exchange
if (gCon.Cards == 2) {
yield return dMan.DoDisplayRoutine("Exchange Card as Required");
}
}
Upvotes: 2
Reputation: 2408
From what I see you want the following:
This does not require complex elements such as threading, coroutines or other task managent. This is the work of a Queue
.
You can implement this algorithm:
An implementation, using your code, would be the following.
public class DisplayManager : MonoBehaviour
{
public GameObject DisplayPanel;
public Text Message_Text;
public Button Complete_Button;
private Queue<string> _messages;
public void DoDisplay(string displayMessage)
{
//if no messages are displayed, you display the message
if (string.IsNullOrEmpty(Message_Text.text)) ShowMessage(displayMessage);
//if other messages are displayed you enqueue the message
else _messages.Enqueue(displayMessage);
}
private void ShowMessage(string displayMessage)
{
// Activate Panel, if not already active
if(!DisplayPanel.activeSelf) DisplayPanel.SetActive(true);
// set the message on the text
Message_Text.text = displayMessage;
}
// A simple Button press to clear the flag
public void OnComplete()
{
//if we have other messages we show the next
if (_messages.Count > 0) ShowMessage(_messages.Dequeue());
//otherwise we close the menu
else CloseMessage();
}
private void CloseMessage()
{
Message_Text.text = "";
DisplayPanel.SetActive(false);
}
}
If you want to add further logic on the message displayer you may just do that on ShowMessage
method.
There you may add anything such as
if(displayMessage == "Add Card")
//DO SOMETHING
Upvotes: 1