I'm trying to create a message box class in unity and I want it to work the same way as the message box in windows forms which waits for a button to be pressed and than executes the code after that.
var mbox = MessageBox.Show("Test", "test", MessageBoxButtons.YesNo);
var test = mbox == DialogResult.Cancel; <- it will wait here
I've tried to recreate that in 2 ways
Joining 2 threads
public void TestClick()
{
Thread thread1 = new Thread(TestMethod);
thread1.Start();
thread1.Join();
Debug.Log("Done");
}
private void TestMethod()
{
float time = 0;
while (time <= 20)
{
Thread.Sleep(100);
time++;
Debug.Log("Im doing heavy work");
}
}
This one blocks the main thread and will resume only after TestMethod
is completed but I don't want that because the user wont be able to interact with the message box during that time.
Asynchronous approach
public delegate int AsyncTask();
public void TestClick()
{
RunAsyncAndWait();
Debug.Log("Done");
}
public int Method1()
{
float time = 0;
while (time <= 20)
{
Thread.Sleep(100);
time++;
Debug.Log("Im doing heavy work");
}
return 0;
}
public void RunAsyncAndWait()
{
AsyncTask ac1 = Method1;
WaitHandle[] waits = new WaitHandle[1];
IAsyncResult r1 = ac1.BeginInvoke(null, null);
waits[0] = r1.AsyncWaitHandle;
WaitHandle.WaitAll(waits);
}
This works exactly like the first one but it behaves weirdly if we change some stuff like the size of the WaitHandle[]
to WaitHandle[] waits = new WaitHandle[2];
.
Now this works more like what I need as it continuously writes stuff in the Console rather than just posting 21 messages at once like the previous methods but the moment it runs it pauses the unity scene (I can manually resume it and the program will run just fine) and it keeps on printing stuff in the console and I get this error
ArgumentNullException: null handle Parameter name: waitHandles System.Threading.WaitHandle.CheckArray (System.Threading.WaitHandle[] handles, Boolean waitAll) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Threading/WaitHandle.cs:77) System.Threading.WaitHandle.WaitAll (System.Threading.WaitHandle[] waitHandles) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Threading/WaitHandle.cs:109) Assets.Scripts.Test.RunAsyncAndWait () (at Assets/Scripts/Test.cs:40) Assets.Scripts.Test.TestClick () (at Assets/Scripts/Test.cs:16) UnityEngine.Events.InvokableCall.Invoke (System.Object[] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:153) UnityEngine.Events.InvokableCallList.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:630) UnityEngine.Events.UnityEventBase.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:765) UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:53) UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:35) UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:44) UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:52) UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:269) UnityEngine.EventSystems.EventSystem:Update()
The first line sounded to me like I might need a callback function here so I quickly added something just to test it out
IAsyncResult r1 = ac1.BeginInvoke(ar => Debug.Log("Done"), null);
But with no luck nothing changed.
Any tips how can I approach this problem as a whole (making a message box, blocking the thread until a button is pressed), or maybe some more info on how Microsoft has implemented that in windows forms ?
There is a big difference between WinForms and Unity. In WinForms you have one thread for UI which could be blocked by a modal form. In Unity you have multiple objects with multiple methods where script execution order and some engine mechanisms decide how they should be executed in each frame.
However if you want to have a modal message box in Unity, you can simply block the execution of the Update or FixedUpdate of a specific script by adding a boolean check to it or by disabling the script. First way provides more options but second one is easier. However be aware that disabling a script stops everything in it except Invoke and Coroutine.
You can block user's interactions with underlying objects by putting a simple SpriteRenderer or Image over them. This mask can have zero transparency, should be full screen size and must have
Raycast Target
toggled on.I would prefer a message box with a fullscreen mask behind it which has a simple black sprite with alpha = .1
Note that all other scripts will run nevertheless. If you have to block the execution of other scripts as well, then you need to do it one by one.
Note:
Since disabling a script does not stop the coroutines it started, you might as well disable the script itself
where Script1,2,3 are singleton classes and script1,2,3 are references of scripts you want to block.