Yeah, Invoke is just a wrapper around calling a method through reflection. So that won’t be able to invoke local functions. Also, you should probably not use Invoke! It’s string-based, aka. hard to debug and bug-prone, as well as slow.
To achieve what you’re doing, we’ve got an InvokeAfter extension that runs a coroutine in the background:
public void HandlePlay()
{
// trigger animation, which lasts 1 sec:
GameObject.Find("Notepad").GetComponent<Animator>().SetBool("fadeOut", true);
// inline-function, which calls a method with parameters:
void LoadGameScene() {SceneManager.LoadScene("Game");};
// delayed method call enabling parameters:
this.InvokeAfter(1f, LoadGameScene);
}
Extension method is:
/// <summary>
/// Invokes an action after a certain time, using a coroutine.
/// </summary>
/// <param name="behaviour">Behaviour to use as runner for the internal coroutine</param>
/// <param name="afterTime">How long before the action should be invoked</param>
/// <param name="action">Action to invoke</param>
/// <returns>The Coroutine containing the waiting and action, so you can stop it if neccessary</returns>
public static Coroutine InvokeAfter(this MonoBehaviour behaviour, float afterTime, Action action) {
if (behaviour != null) {
return behaviour.StartCoroutine(InvokeAfter(action, afterTime, behaviour));
}
return null;
}
private static IEnumerator InvokeAfter(Action action, float time, MonoBehaviour behaviour) {
yield return new WaitForSeconds(time);
if (behaviour != null) {
action();
}
}
Ah ok, I understand, thx dude.
Well, of course using coroutines is a working alternative as I already stated.
If u do Invoke() by reflection it would be a very nice thing if u supply a future Invoke() with parameters also, but I understand, using reflection is always time-consuming. However in my case, I want to wait anyways for some given time, so time doesn’t really matter ;-).
InvokeAfter() is probably a new feature, cause I don’t get it ? I use unity version 2019.3.15f1 ?
I’ve tried that - as in using async over Coroutines in general, ended up with a bunch of issues. Primarily that the async method is not stopped when the object is destroyed, and that cancelling async methods is just an utter pain in the ass compared to just getting a reference to the coroutine and calling Stop on it.
Async is a great replacement for other ways of doing actually asynchronous things, as all the ways in which async is a bit annoying is ways that asynchronous things have to be annoying. Taking on that cost when you’re doing something fundamentally synchronous - like waiting 1 second and then doing something on the main thread - isn’t worth it.
Ok, very interesting to read this and also thx for ur time on this issue.
Even if this solution seems to be very easy and simple to a c#-noob like me, I also wondered if this could interfere with unity’s lifecycle somehow … .
Imagine, you have a gameobject with a script like this
using System.Threading.Tasks;
using UnityEngine;
public class Foo : MonoBehaviour
{
private async void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
Destroy(gameObject);
}
if (Input.GetKeyDown(KeyCode.H))
{
Debug.Log("Hello,");
await Task.Delay(5000);
Debug.Log("World!");
}
}
}
If you press H and then D, the object and all its components will be destroyed but the code after await will still be executed and you’ll see it printing “World!” after the delay. If this is exactly what you want, then it’s fine.
However, you may want to stop all the pending executions if the object gets destroyed. In that case use Invoke, InvokeRepeating or coroutines. They are implicitly bound to the object they are called from, and won’t run if the object is destroyed.
using UnityEngine;
public class Bar : MonoBehaviour
{
private void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
Destroy(gameObject);
}
if (Input.GetKeyDown(KeyCode.H))
{
Debug.Log("Hello,");
Invoke(nameof(PrintWorld), 5);
}
}
private void PrintWorld()
{
Debug.Log("World!");
}
}
This script won’t print “World!” if you destroy the object.
PS
Or you’ll have to check if the object still exists manually like this
using System.Threading.Tasks;
using UnityEngine;
public class Foo : MonoBehaviour
{
private async void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
Destroy(gameObject);
}
if (Input.GetKeyDown(KeyCode.H))
{
Debug.Log("Hello,");
await Task.Delay(5000);
if (this != null)
{
Debug.Log("World!");
}
else
{
Debug.Log("Gameobject or component is destroyed!!!");
}
}
}
}
Most of the .NET programmers will find the condition (this != null) pretty weird though
Well in my simple case I think I can live with this behaviour, since the GameObject gets destroyed only after the Delay-Task has finished by loading the new scene? Thanks for your time and help!