Перейти к содержимому

Как выйти из корутины unity

  • автор:

Coroutines

Когда вы вызываете функцию, она должна полностью выполниться, прежде чем вернуть какое-то значение. Фактически, это означает, что любые действия, происходящие в функции, должны выполниться в течение одного кадра; вызовы функций не пригодны для, скажем, процедурной анимации или любой другой временной последовательности. В качестве примера мы рассмотрим задачу по уменьшению прозрачности объекта до его полного исчезновения.

void Fade() < for (float ft = 1f; ft >= 0; ft -= 0.1f) < Color c = renderer.material.color; c.a = ft; renderer.material.color = c; >> 

Как можно заметить, функция Fade не имеет визуального эффекта, который мы хотели получить. Для скрытия объекта нам нужно было постепенно уменьшить прозрачность, а значит иметь некоторую задержку для того, чтобы были отображены промежуточные значения параметра. Однако функция выполняется в полном объеме, за одно обновление кадра. Промежуточные значения, увы, не будут видны и объект исчезнет мгновенно.

С подобными ситуациями можно справиться, добавив в функцию Update код, изменяющий прозрачность кадр за кадром. Однако для подобных задач зачастую удобнее использовать корутины.

A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. In C#, a coroutine is declared like this:

IEnumerator Fade() < for (float ft = 1f; ft >= 0; ft -= 0.1f) < Color c = renderer.material.color; c.a = ft; renderer.material.color = c; yield return null; >> 

It is essentially a function declared with a return type of IEnumerator and with the yield return statement included somewhere in the body. The yield return null line is the point at which execution will pause and be resumed the following frame. To set a coroutine running, you need to use the StartCoroutine function:

void Update() < if (Input.GetKeyDown("f")) < StartCoroutine("Fade"); >> 

Можно заметить, что счетчик цикла в функции Fade сохраняет правильное значение во время работы корутины. Фактически, любая переменная или параметр будут корректно сохранены между вызовами оператора yield.

By default, a coroutine is resumed on the frame after it yields but it is also possible to introduce a time delay using WaitForSeconds:

IEnumerator Fade() < for (float ft = 1f; ft >= 0; ft -= 0.1f) < Color c = renderer.material.color; c.a = ft; renderer.material.color = c; yield return new WaitForSeconds(.1f); >> 

This can be used as a way to spread an effect over a period of time, but it is also a useful optimization. Many tasks in a game need to be carried out periodically and the most obvious way to do this is to include them in the Update function. However, this function will typically be called many times per second. When a task doesn’t need to be repeated quite so frequently, you can put it in a coroutine to get an update regularly but not every single frame. An example of this might be an alarm that warns the player if an enemy is nearby. The code might look something like this:

function ProximityCheck() < for (int i = 0; i < enemies.Length; i++) < if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) < return true; >> return false; > 

If there are a lot of enemies then calling this function every frame might introduce a significant overhead. However, you could use a coroutine to call it every tenth of a second:

IEnumerator DoCheck() < for(;;) < ProximityCheck(); yield return new WaitForSeconds(.1f); >> 

Это значительно уменьшит число проверок, но не окажет заметного влияния на игровой процесс.

Note: You can stop a Coroutine with StopCoroutine and StopAllCoroutines. A coroutines also stops when the GameObject it is attached to is disabled with SetActive(false). Calling Destroy(example) (where example is a MonoBehaviour instance) immediately triggers OnDisable and the coroutine is processed, effectively stopping it. Finally, OnDestroy is invoked at the end of the frame.

Coroutines are not stopped when disabling a MonoBehaviour by setting enabled to false on a MonoBehaviour instance.

Корутины

Сопрограмма позволяет распределять задачи по нескольким фреймам. В Unity сопрограмма — это метод, который может приостановить выполнение и вернуть управление Unity, но затем продолжить с того места, на котором остановился, в следующем кадре.

В большинстве случаев при вызове метода он выполняется до завершения, а затем возвращает управление вызывающему методу, а также любые необязательные возвращаемые значения. Это означает, что любое действие, выполняемое внутри метода, должно происходить в рамках одного обновления кадра.

В ситуациях, когда вы хотите использовать вызов метода, чтобы содержать процедурную анимацию или последовательность событий во времени, вы можете использовать сопрограмму.

Однако важно помнить, что сопрограммы — это не потоки. Синхронные операции, выполняемые внутри сопрограммы, по-прежнему выполняются в основном потоке. Если вы хотите уменьшить количество процессорного времени, затрачиваемого на основной поток, так же важно избегать блокирующих операций в сопрограммах, как и в любом другом коде скрипта. Если вы хотите использовать многопоточный код в Unity, обратите внимание на систему заданий C#.

Сопрограммы лучше всего использовать, если вам нужно иметь дело с длительными асинхронными операциями, такими как ожидание передачи HTTP, загрузки ресурсов или завершения файлового ввода-вывода.

Пример сопрограммы

В качестве примера рассмотрим задачу постепенного уменьшения значения альфы (непрозрачности) объекта, пока он не станет невидимым:

В этом примере метод Fade не дает ожидаемого эффекта. Чтобы сделать затухание видимым, вы должны уменьшить альфа-канал затухания в последовательности кадров, чтобы отобразить промежуточные значения, которые отображает Unity. Однако этот пример метода полностью выполняется в рамках одного обновления кадра. Промежуточные значения никогда не отображаются, и объект мгновенно исчезает.

Чтобы обойти эту ситуацию, вы можете добавить код в функцию Update , которая выполняет покадровое затухание. Однако для такого рода задач может быть удобнее использовать сопрограмму.

В C# вы объявляете сопрограмму следующим образом:

Сопрограмма — это метод, который вы объявляете с помощью IEnumerator и с выходом оператор возврата включен где-то в теле. Строка yield return null — это точка, в которой выполнение приостанавливается и возобновляется в следующем кадре. Чтобы запустить сопрограмму, вам нужно использовать функцию StartCoroutine:

Счетчик циклов в функции Fade сохраняет свое правильное значение в течение всего времени существования сопрограммы, и любая переменная или параметр сохраняется между операторами yield .

Время задержки сопрограммы

По умолчанию Unity возобновляет сопрограмму во фрейме после оператора yield . Если вы хотите ввести временную задержку, используйте WaitForSeconds:

Вы можете использовать WaitForSeconds для распространения эффекта на определенный период времени, а также в качестве альтернативы включению задач в Обновить метод. Unity вызывает метод Update несколько раз в секунду, поэтому, если вам не нужно, чтобы задача повторялась так часто, вы можете поместить ее в сопрограмму, чтобы получать регулярное обновление. но не каждый кадр.

Например, в вашем приложении может быть будильник, который предупреждает игрока о приближении врага с помощью следующего кода:

Если врагов много, вызов этой функции в каждом кадре может привести к значительным накладным расходам. Однако вы можете использовать сопрограмму, чтобы вызывать ее каждую десятую долю секунды:

Это уменьшает количество проверок, которые выполняет Unity, без заметного влияния на игровой процесс.

Остановка сопрограмм

Чтобы остановить сопрограмму, используйте StopCoroutine и Остановить все сопрограммы. Сопрограмма также останавливается, если вы установили для SetActive значение false , чтобы отключить GameObject Фундаментальный объект в сценах Unity, который может представлять персонажей, реквизит, декорации, камеры, путевые точки и многое другое. Функциональность GameObject определяется прикрепленными к нему компонентами. Подробнее
См. в Словарь , к которому прикреплена сопрограмма. Немедленный вызов Destroy(example) (где example – это экземпляр MonoBehaviour ) запускает OnDisable, и Unity обрабатывает сопрограмму, фактически останавливая ее. Наконец, в конце кадра вызывается OnDestroy .

Примечание. Если вы отключили MonoBehaviour , установив enabled значение false , Unity не останавливает сопрограммы.

Анализ сопрограмм

Корутины выполняются иначе, чем код других скриптов. Большая часть кода сценария в Unity отображается в трассировке производительности в одном месте, под определенным вызовом обратного вызова. Однако код ЦП сопрограмм всегда появляется в двух местах трассировки.

Весь исходный код сопрограммы, от начала метода сопрограммы до первого оператора yield , появляется в трассировке каждый раз, когда Unity запускает сопрограмму. Исходный код чаще всего появляется всякий раз, когда вызывается метод StartCoroutine. Сопрограммы, генерируемые обратными вызовами Unity (например, обратные вызовы Start , которые возвращают IEnumerator ), сначала появляются в соответствующем обратном вызове Unity.

Остальная часть кода сопрограммы (с момента ее первого возобновления до завершения выполнения) отображается в строке DelayedCallManager , которая находится внутри основного цикла Unity.

Это происходит из-за того, как Unity выполняет сопрограммы. компилятор C# автоматически создает экземпляр класса, который поддерживает сопрограммы. Затем Unity использует этот объект для отслеживания состояния сопрограммы при нескольких вызовах одного метода. Поскольку переменные локальной области внутри сопрограммы должны сохраняться при вызовах yield , Unity поднимает переменные локальной области видимости в сгенерированный класс, который остается выделенным в куче во время сопрограммы. Этот объект также отслеживает внутреннее состояние сопрограммы: он запоминает, в какой точке кода сопрограмма должна возобновить работу после завершения.

Из-за этого нехватка памяти, возникающая при запуске сопрограммы, равна сумме фиксированных накладных расходов и размера ее переменных локальной области.

Код, который запускает сопрограмму, создает и вызывает объект, а затем Unity DelayedCallManager снова вызывает его всякий раз, когда сопрограмма yield состояние устраивает. Поскольку сопрограммы обычно запускаются вне других сопрограмм, это распределяет их выполнение между вызовом yield и вызовом DelayedCallManager .

Вы можете использовать окно Unity Profiler , которое поможет вам оптимизировать игру. Он показывает, сколько времени вы тратите на различные области вашей игры. Например, он может сообщать о проценте времени, затраченном на рендеринг, анимацию или игровую логику. Дополнительная информация
См. Словарь , чтобы изучить и понять, где Unity выполняет сопрограммы в вашем приложении. Для этого профилируйте свое приложение с включенным глубоким профилированием, которое профилирует каждую часть кода вашего скрипта и записывает все вызовы функций. Затем вы можете использовать модуль CPU Usage Profiler для исследования сопрограмм в вашем приложении.

Сеанс профилировщика с сопрограммой в DelayedCall

Рекомендуется сократить серию операций до наименьшего возможного количества отдельных сопрограмм. Вложенные сопрограммы полезны для ясности кода и обслуживания, но они требуют больших затрат памяти, поскольку сопрограмма отслеживает объекты.

Если сопрограмма запускается в каждом кадре и не дает выгоды при длительных операциях, более эффективно заменить ее Update или обратный вызов LateUpdate . Это полезно, если у вас есть длинные или бесконечно зацикленные сопрограммы.

Studhelper IT

Что такое корутины и как с ними работать в Unity. По книге Lucas Faustino “Unity5 Coroutines”.

Эта серия постов для тех, кто интересуется корутинами (сопрограммами), но не очень уверенно использует их в проектах Unity. Требуется базовое знание языка C# и знание интерфейса Unity.

Содержание
1 часть. Что такое корутины. В этой части будет описано, что делает корутина.
2 часть. Coroutine vs Update. Сравним сопрограммы и метод Update и выясним, когда лучше использовать то и другое.
3 часть. Соберем все знания и приведем примеры использования сопрограмм.
4 часть. Финал. Несколько слов, которые не вошли в предыдущие части.

Часть 1. Что такое coroutines?

Coroutine (корутина, сопрограмма) – это выполняемая процедура. Она создается, как и обычный метод, но имеет некоторые полезные функции, которые отличают ее от стандартных методов.

  • ее выполнение можно приостановить. Например, при истечении периода времени или выполнении логического условия.
  • можно использовать бесконечные циклы для создания повторяющихся процедур. Это похоже на метод Update, но имеет свои преимущества в некоторых сценариях.
  • ее полезно использовать при создании машин состояний (например, для искусственного интеллекта).
  • она может быть использована для действий, которые совершаются один раз в определенное время или вообще один раз, таких как открывание двери, движения по локации.

Если вы не привыкли к сопрограммам, то, возможно, думаете, что эти вещи можно сделать с помощью Update. Вы правы, можно. Но корутины позволят это сделать менее грязным и подверженным ошибкам способом.

Создание корутины

Для создания основной корутины нужно сделать два шага. Первый – нужно создать метод, который будет действовать, как корутина. Второй – нам нужно его запустить. Давайте запустим корутину в методе Start скрипта Unity. Наша первая реализация сопрограммы:

using UnityEngine;
using System.Collections;
public class BasicCoroutine : MonoBehaviour
Private void Start()
StartCoroutine(Example());
>
Private IEnumerator Example()
Yield return new WaitForSeconds(1f);
Debug.Log(“We Waited for one second, cool.”):
>
>

Этот пример довольно легко понять. Давайте его разберем. Мы используем встроенный в Unity метод Start(), чтобы сопрограмма запускалась при старте сцены. StartCoroutine – это обязательный метод для запуска. Если вы его не напишете, то сопрограмма работать не будет. Метод StartCoroutine принимает IEnumerator в качестве параметра. Этому требованию отвечает наш метод с названием Example. Если он не будет возвращать IEnumerator, то не подойдет для запуска сопрограммы. В данном примере программа ждет 1 секунду, после чего выполнение корутины прекращается, а в лог выводится запись об ожидании.

Yield

Yield – ключевое слово. В Unity применяется как указание ожидать указанное количество времени. В примере это одна секунда, но есть еще несколько классов, которые могут использованы:

  • WaitForSeconds(float) — пауза на заданное время.
  • WaitForEndOfFrame () — пауза до конца кадра.
  • WaitForFixedUpdate — пауза до следующего фиксированного кадра (стоит подумать о методе FixedUpdate).
  • null — не ждать. Дальше я покажу, где это может быть полезно.
  • break — преждевременный выход. Это похоже на использование break в циклах.

Остановка корутины

Остановка корутины, в основном, означает ранний выход. Она не требуется постоянно, но в некоторых случаях ее нужно знать. Существует два метода для остановки. Метод StopAllCoroutines () остановит все корутины в скрипте. Он нужен, если необходимо убедиться, что все сопрограммы остановлены. Метод StopCoroutine (IEnumerator) остановит определенную сопрограмму.

Первый метод будет выглядеть так:

private void Start()
StartCoroutine(Example());
StopAllCoroutines();
>

Если вы так сделаете, то заметите, что сообщение «We Waited for one second, cool» никогда не выводится на консоль. Это потому, что мы сразу после запуска останавливаем корутину.

private void Stat()
StartCoroutine(Example());
StopCoroutine(Example());
>

Тут происходит то же самое, что и в предыдущем примере. Разница только та, что мы останавливаем одну сопрограмму, а не все.

На базовом уровне это все, что требуется знать для создания сопрограмм в Unity. Дальше поговорим, когда лучше использовать корутины, а не метод Update.

async/await, одновременная чтение/запись глобальной переменной, автоматически синхронизируется как-то?

Здравствуйте, несколько дней бился, что бы приостановить работу скрипта, до ожидания результата функции, в итоге не знаю правильно я понял или нет, но штатных способов без завешивания основного потока нет, очень не хотелось лезть в асинхронность на пустом месте, но похоже дольше буду искать, чем сделаю так.

Подскажите пожалуйста, для понимания работы функций в асинхронном режиме.

Вот к примеру запустить 100500 функций из разных скриптов, которые выполняются в фоне (не знаю как правильно сказать) и в них вызвать что-то типа такого:

int GlobalVariable1; < - глобальная переменная в другом скрипте. int GlobalVariable2; < - глобальная переменная в другом скрипте. private async void TestFunc. . . bool res = await test(); . private async Tasktest() int i =0; while (i ==0) < GlobalVariable = 1; GlobalVariable2 = GlobalVariable1; int tmp = GlobalVariable1; >.

То есть получается, запустили пачку одновременно работающих функций, и внутри них в бесконечном цикле без задержек присваиваем какой-то одной и той же глобальной переменной значение в первом варианте, во втором из одной читаем, другой присваиваем и в третьем варианте просто присваиваем локальной переменной значение из глобальной.

Надо ли это дело как-то синхронизировать? Или Unity сама решает вопрос синхронизации доступа к памяти? Будет ли тут как на винде в один прекрасный момент «память не может быть read» ну или write, без разницы.

При работе из функций вызванных в асинхронном режиме:
Надо ли синхронизировать чтение переменных?
Надо ли синхронизировать запись в переменные?
Про некорректные/устаревшие/отсутствие данных в переменных речь не идет, это легко проверить, будет ли глюк рано или поздно, что кто-то одновременно будет пытаться что-то прочитать/записать?

Так же не понимаю, если функцию не остановить, но завершить работу программы в редакторе unity, эта функция продолжает выполняться. никак не пойму где она может выполняться, если программа закрыта? Такое же эффект будет в apk, если вызванную асинхронную функцию не остановить, но выйти из приложения? Оно, что то же где-то будет выполняться отдельно от проги %))) ?

Просто отловить все это естественно при выходе можно, но странно, вроде оно должно само все завершать при выходе.

P.S.
Вероятно я вообще не правильно решил поставленную задачу но суть проблему была такая:
Дорабатываю инвентарь до конца. Делаю «стакающиеся» предметы.

При сдвиге предмета у которого есть соответствующий параметр должно вылезать окно для деления предметов и желательно:

— завесить скрипт на месте
— запустить окно для деления предмета, получить необходимые данные
— продолжить выполнение скрипта с этими данными

ключевой момент в том, что все остальные части программы должны работать в штатном режиме без остановки.

Цель, что бы все действия были в одном скрипте, без пложения чего-то дополнительного.

Но если приостановить работу скрипта повисает все, так как судя по всему unity работает в одном потоке.

я рассчитывал использовать грабли типа такого:
While Next = false
sleep(1000);
Не прокатило, но мало ли может есть какой-то аналог?

В противном случае придется работать уже с другим скриптом, который навешан на окно для деления предметов, а хотелось бы в одном все.

#1
11:52, 17 мар 2021

асинхронность и многопоточность разные вещи

в асинхронности нет конкурентного доступа, и вообще оно заточено под ввод-вывод, который и без CPU работает
https://tooslowexception.com/net-asyncawait-in-a-single-picture/

#2
12:02, 17 мар 2021

>#!
Спасибо, мало ли. что бы знать, потом ошибку фиг найдешь, когда оно падать будет на пустом месте при всех корректных данных.

#3
12:41, 17 мар 2021

А что делает бесконечный цикл?) — я думаю задача сама как-то по тупому у вас решается, и возможно вам даже отдельные потоки не нужны.

Вообще любой программер работающий с многопоточностью первым делом должен прочитать про модель памяти: https://docs.microsoft.com/ru-ru/archive/msdn-magazine/2012/decem… -and-practice.

И книжку Конкурентность в C# Стивет Клири.

Самый простой способ работы с тредами + синхронизациями это использовать библиотеку UniRx. Где можно создать реактивное свойство, которое простым каскадным методом синхронить с юнити тредом.

#4
13:13, 17 мар 2021

и возможно вам даже отдельные потоки не нужны

Да вообще они и не нужны, я крайне не хотел никакой асинхронности или многопоточности, но как уже выяснилось это пока только асинхронность. Я в конце первого поста описал необходимую цель.

С многопоточностью я работал, и тут ключевой момент (не знаю правильно ли я все понял или нет из всего этого) все должно быть хорошо синхронизировано, никаких одновременных доступов не должно быть чисто физически и если не нужны параллельные вычисления, то потоки не нужны. Так же все очищать, что там насоздавал.

У меня как раз такой вариант, мне не нужно никаких потоков, но любая попытка приостановить выполнения скрипта посередине, вешает Unity насмерть, если не организовать выход по таймеру, но и в момент пока скрипт приостановлен, нельзя ничего делать соответственно.

Этот цикл, что в примере кривая реализация временной приостановки, да криво, но если не перебарщивать, то работает стабильно.
> запустили необходимую функцию асинхронно/в отдельном потоке
> вошли в бесконечный цикл с нужной паузой.
> ждем пока не завершиться выполнение функции
> выходим из бесконечного цикла
> продолжаем выполнение скрипта с полученными из функции данными.

(остальные циклы, просто показать что там постоянно будет доступ к одним и тем же переменным без синхронизации и вызовет ли рано или поздно это глюк или нет)

#5
13:19, 17 мар 2021
#6
13:34, 17 мар 2021

>#!
Ну если вы скажете способ как дождаться выполнения корутины без завешивания основного потока при этом приостановив выполнение скрипта посередине, тогда я бы это использовал — это именно то что нужно.

Я вчера как раз весь день эти корутины и так и сяк пробовал запускать.

begin stript . . . выполняем func ждем результат . (Все остальное на сцене должно быть полностью активно, все скрипты должны выполняться при вызове) продолжаем . . end script

#7
16:45, 17 мар 2021

Нет, приостановить работу скрипта в Юнити нельзя. Общий смысл работы заключается в следующем: Юнити берет все скрипты и выстраивает из них список, затем последовательно их выполняет. Если мне не изменяет память, то начиная с первого объекта на сцене.
Собственно в этом и заключалась проблема многопоточности для юньки.

Делать инвентарь на отдельных потоках, это блин какое то извращение. Смысл потоков в том, чтобы считать относительно тяжёлые операции без приостановки на эти вычисления основного потока программы, а не пихать в них 2+2. Я даже корутины там всего пару раз использовал и то, для анализа рынка (там что-то около 1000 позиций надо было посчитать). Причём я даже уже не помню где, вроде они по итогу оказались и не нужны.

А так да, задача не ясна и звучит как — где мне найти несколько Белазов что бы перевезти один шкаф.

#8
17:17, 17 мар 2021

Скорее всего это Bad Practice писать результат в глобальную переменную вместо возврата Task, но даже если это реально нужно то тогда нужно городить нечто condition_variable, тогда запись будет последовательной.

#9
17:38, 17 мар 2021

sledo
> Смысл потоков в том, чтобы считать относительно тяжёлые операции без
> приостановки на эти вычисления основного потока программы
ремарочку в многопоточности (реальном) можно считать исключительно данные
которые никак не связанны с MonoBehever
Только из за этого ушел с юнити на годот на котором можно сделать много и быстро(не реклама)
зы поддерживаю выше оратора не пользуйся ты этими «костялями» а делай как тебе указано в доках Юнити
или такие лаги или вылеты будешь отхватывать на разных устройствах (было у меня дело)

#10
20:03, 17 мар 2021

FourGen
> и так и сяк пробовал запускать
а зачем большие скрипты? особенно если просто возня в глобальных переменных
там должна быть ровно одна функция которая на каждую итерацию делает yield

если бы я ещё понимал как работает юнити : )

#11
21:16, 17 мар 2021

>gamedevfor
Я привел пример для того, что бы показать слетит оно если так вызывать или нет? Это был вопрос собственно 3 варианта. #! Уже ответил, что в данном случае асинхронность, а не многопоточность. Если так, то значит не слетит ничего.
Я естественно так не делаю, это было просто для вопроса.

>#!
В fixed update у меня единственное двигается камера, больше у меня нет ни update ни fixed update нигде и думаю пока можно без них обойтись, так оно и будет.

а зачем большие скрипты?

Ну какие получаются.

на каждую итерацию делает yield

Вы имеете в виду окно для деления предметов открывать в корутине?

>DanQuimby
Ну это надо тестить.

>sledo
Просто, скорее всего, я просто не знаю как по-другому.

В итоге в общем получилось следующее:

Запустить видео по клику - Как делать игры

Осталось сделать само перекладывание и симулятор ходьбы будет готов полностью.
(Сорри при записи видео чет подлагивает )
Свет знаю так же надо поворачивать, что бы тени не крутились, никак одну строчку не дописать )

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *