Страницы

Поиск по вопросам

воскресенье, 8 декабря 2019 г.

Зачем в Unity корутины?

#c_sharp #unity3d


Зачем в Unity корутины?
Ведь, насколько я понял, они позволяют выполнять параллельно какие то действия, во
время основной работы программы. Цикл ожидания подключения там крутить. 
В Unity в каждом скрипте мы можем завести Update,FixedUpdate,LateUpdate.
При этом у нас есть такой отличный метод как InvokeRepeating, который тоже может 

параллельно что то своё запускать, с нужной частотой, и не раз в каждом скрипте.

При том что самих скриптов может быть сколько угодно. 

Есть ли какая то реальная необходимость сопрограмм в Unity? Может, они лучше с точки
зрения производительности? Или могут применяться более точно? 
    


Ответы

Ответ 1



Во-первых, сразу напишу, что не совсем корректно сравнивать корутину с Update,FixedUpdate,LateUpdate и InvokeRepeating. Ибо она может выполняться только один раз. Например void Start() { Debug.Log("Start game"); StartCoroutine(wait()); } IEnumerator wait() { yield return new WaitForSeconds(3f); Debug.Log("Coroutine is work"); } выведет Start game и через 3 секунды Coroutine is work. Всё. Никаких повторных вызовов. Далее. Отличие её от всех вышеперечисленных методов как минимум в наличии ключевого слова yield. Когда в методе-итераторе (коей является корутина) встречается оператор yield return, возвращается выражение expression и сохраняется текущее положение в коде. Выполнение будет продолжено из этого местоположения при очередном вызове функции итератора. Пример void Start () { StartCoroutine(Test(FinalAction)); } IEnumerator Test(Action act) { for (int i = 0; i < 5; i++) { Debug.Log("Test" + i); yield return new WaitForSeconds(1.5f); Debug.Log("Test" + i + i); } } Будет выведено "Test0", а через 1,5 секунды "Test00", "Test1" (т.к. программа продолжило выполнение с момента где остановилась и потом опять продолжила цикл)... потом "Test11", "Test2" и т.д. Таким образом корутина позволяет прерывать вычисления, отдавать ресурсы в основной поток, а потом возобновлять следующую часть вычислений. Другой пример: нам нужно инстанциировать 10.000 объектов. Порциями по 10-100 или просто в цикле, неважно. Если мы воткнем это в методе Update, то пока цикл не отработает обновления экрана не будет, приложение "висит" все это время. У пользователя "бомбит". То есть корутину можно применять для длительных операций, которые можно "размазать" по кадрам. Причем (как написано выше) можно вызывать примерно следующую последовательно действий: // счетчик цикла Debug.Log("Инстанциируем объекты и складируем их в массив"); yield return new WaitForSeconds(1); Debug.Log("делаем доп работу с этим массивом"); yield return new WaitForSeconds(1); Debug.Log("Еще какая-то работа"); yield return new WaitForSeconds(1); другой пример с пулями: void Start () { StartCoroutine("FireThriceAndWait"); } IEnumerator FireThriceAndWait () { while (true) { fire(); yield return new WaitForSeconds(0.5f); fire(); yield return new WaitForSeconds(0.5f); fire(); yield return new WaitForSeconds(5f); } } void fire(){ Instantiate(enemy_bullet,this.transform.position, Quaternion.LookRotation(target.transform.position- this.transform.position)); } Такую работу проделать InvokeRepeating не позволит в принципе. Еще пример. Мы хотим, чтобы до прогона действий и после что-то происходило, например логирование сообщений сделано что-то или нет. Как это делать в InvokeRepeating или Update? Вешать всякий флаги было сделано что-то или нет, зашел в метод или нет? Зачем, если можно сделать её в корутине void Start () { StartCoroutine(Test(StartAction, FinalAction)); } IEnumerator Test(Action actBefore,Action actAfter) { actBefore(); for (int i = 0; i < 5; i++) { Debug.Log("Test" + i); yield return new WaitForSeconds(1.5f); Debug.Log("Test" + i + i); } actAfter(); } void StartAction() { Debug.Log("I'm a start action"); } void FinalAction() { Debug.Log("I'm a final action"); } Ну и еще пример.. допустим хотим мы заставить мигать спрайт (уменьшить прозрачность, увеличить) с интервалом 0.5 сек. Поставим методы в Update - будет виснуть основной поток. Для InvokeRepeating придется ставить разные флаги и доп функции - был ли вызван нужный метод или нет, если да, то повторять другой метод, если нет то первый...Сопрограммой решается это так IEnumerator Test() { while (true) { var color = obj.GetComponent().material.color; for (float i = 1; i >= 0; i-=0.1f) { color.a = i; obj.GetComponent().material.color = color; yield return null; } yield return new WaitForSeconds(0.5f); for (float i = 0; i < 1; i += 0.1f) { color.a = i; obj.GetComponent().material.color = color; yield return null; } yield return new WaitForSeconds(0.5f); } } Циклы можно вынести в отдельные методы - но не суть. Факт в том, что если запустить то сопрограмма будет работать параллельно без зависаний и сложных манипуляций. А еще корутина может дожидаться действий от ForFixedUpdate, т.е. прерывает выполнение до кадра, в котором обновляется физика (вызывается посредством WaitForFixedUpdate) или конца фрейма (WaitForEndOfFrame). Что бывает полезно сделать и для того же InvokreRepeating придется лепить что-то для этого. В итоге. Как я описал выше: корутину можно применять для длительных операций, которые можно "размазать" по кадрам от которых главный поток не повиснет. Для некой отдельной микропрограммки, которая будет работать параллельно (пример с миганием спрайта, или запустить персонажа бродить в одну сторону и в другую "tween"), которую сложно зарепитить из-за разности действий. Да и не стоит забывать, что в том же Update инструкции происходят последовательно, а значит десяток методов с циклами, в которых некие действия, поставленные один за другим будут выполняться последовательно и дольше, нежели корутины и от этого может зависеть сама игра: игрок прыгнул вверх 10 раз, а потом стрельнул 10 раз или прыгнул-стрельнул 10 раз - разница. Надеюсь я не запутал вас)

Комментариев нет:

Отправить комментарий