Довольно часто в играх существует необходимость выполнять какие-либо действия отложенным образом, в том числе и по времени. Для этого в Unity 3d предусмотрены методы:
• Invoke(string methodName, float time);
• InvokeRepeating(string Method Name, float time, float repeatRate);
• CancelInvoke(); и CancelInvoke(string methodName);
Invoke полагается на Time.timeScale. То есть, если мы вызываем Invoke("SomeMethod", 2f) при Time.timeScale = 0.5, то SomeMethod() начнет выполняться через 4 секунды, а при 0 - не начнет выполняться вовсе.
Не одним Invoke-ом сыты, ведь есть еще способы отложено выполнить действия, например таймеры или корутины. Вот на них, я пожалуй и остановлюсь.
Корутины в данном контексте мне всегда казались очень производительными, практичными и более знакомыми с точки зрения: "А что под капотом?", но не столь удобными в использовании, как тот же Invoke, ведь приходится жертвовать парой строчек кода... А ещё, корутины решают проблему с отложенным запуском при различных значениях Time.timeScale.
Вот как выглядит отложенный запуск с использованием Coroutines:
float _time = 2f;
Coroutine _coroutine;
private void Start() = > _coroutine = Start(SomeCoroutine());
public IEnumerator SomeCoroutine()
{
yield return new WaitForSeconds(_time); //Ожидание в условиях Time.timeScale
//TO DO 1
yield return new WaitForSeconds(_time);
//TO DO 2
yield return new WaitForSecondsRealtime(_time); //Ожидание в реальном времени
//TO DO 3 и таким образом можно создать цепочку действий
}
public void StopMyCoroutine() => StopCoroutine(_coroutine); //Останавливает указанную корутину
public void StopAllMyCoroutines() => StopAllCoroutines(); //Останавливает все корутины
Вот так может выглядеть использование Invoke:
float _time = 2f;
private void Start() => Invoke(nameof(SomeMethod), _time);
private void SomeMethod()
{
//TO DO 1
Invoke(nameof(SomeMethod2), _time); //типо цепочка действий
}
private void SomeMethod2()
{
//TO DO 2
}
public void CancelMyInvoke() => CancelInvoke(nameof(SomeMethod)); //отменяет указанный Invoke
public void CancelAllInvokes() => CancelInvoke(); //отменяет все Invoke
Но, возвращаясь к изначальной теме, кто круче то? В этом вопросе я учитываю такие критерии как:
• Производительность
• Удобство
• Гибкость
Я думаю, некоторые выводы об удобстве и гибкости сделаны (о них чуть позже). А вот с производительностью дела обстоят по-интереснее...
Тестирование производительности
Для тестирования я не придумал ничего лучше, как создать 5940 сущностей, которые одновременно будут вызывать отложенное действие через Invoke и Coroutine, и наблюдать за этим в профайлере.
Из тестов можно сделать вывод, что Invoke оказался производительней... А WaitForSecondsRealtime вносит свою лепту в общую картину.
Заключение
Учитывая всё выше сказанное, я склоняюсь к тому, что Invoke более прост в использовании и, как оказывается, производительней, а корутины более многозадачные и гибкие.
Лично мне отдавать предпочтение чему-то одному не приходится - всегда стоит исходить из ситуации и решаемой задачи. Для простых задач можно использовать Invoke, а для более трудных, требующих гибкого подхода можно использовать Coroutine, жертвуя малой каплей производительности.
Надеюсь, статья была вам интересна и полезна. Если у вас есть иной личный опыт использования Invoke и Coroutine, будет интересно узнать что-то новое. Всем мир)
P.S.: Первая статья в Дзене. Если у вас есть какие-либо пожелания, оценка или совет формата подачи, я открыт к критике.