Я новичок в Unity. Я изучал сопрограммы, и я написал это.
private void Fire()
{
if(Input.GetButtonDown("Fire1"))
{
StartCoroutine(FireContinuously());
}
if(Input.GetButtonUp("Fire1"))
{
StopAllCoroutines();
}
}
IEnumerator FireContinuously()
{
while(true)
{
GameObject laser = Instantiate(LaserPrefab, transform.position, Quaternion.identity) as GameObject;
laser.GetComponent<Rigidbody2D>().velocity = new Vector2(0, 10f);
yield return new WaitForSeconds(firetime);
}
}
Когда кнопка нажата, вызывается сопрограмма, и она входит в цикл while. Когда я оставляю кнопку, она останавливает сопрограмму. Разве он не должен застрять в цикле 'while', поскольку это бесконечный цикл? Почему?
unity
c#
coroutines
babybrain
источник
источник
"Fire1"
Это то, что вы можете настроить в движке, чтобы разрешить перераспределение клавиш вместо того, чтобы печататьKeycode.Foo
?yield
это фактически означает «контроль доходности для вызывающего до тех пор, пока не будет запрошен следующий элемент в Enumerable».StopAllCoroutines()
в этом случае. Это хорошо, когда вы используете только одну сопрограмму, но если вы когда-либо планировали иметь более одной, это может иметь нежелательные эффекты. Вместо этого вы должны использоватьStopCoroutine()
и просто остановить тот, который имеет значение, а не все из них. (StopAllCoroutines()
было бы полезно, например, при завершении уровня или загрузке новой области и т. д., но не для конкретных вещей, таких как «Я больше не стреляю».)Ответы:
Причиной является ключевое слово,
yield
которое имеет конкретное значение в C #.При встрече со словами
yield return
функция в C # возвращается, как и следовало ожидать.Так что бесконечного цикла нет. Существует функция / итератор, которую можно вызывать бесконечное количество раз.
Функция Unity
StartCoroutine()
заставляет инфраструктуру Unity вызывать функцию / итератор один раз за кадр.Функция Unity
StopAllCoroutines
позволяет инфраструктуре Unity перестать вызывать функцию / итератор.А возвращение
WaitForSeconds(time)
из итератора заставляет платформу Unity приостанавливать вызов функции / итератораtime
.Запутанный комментарий и столь же запутанный ответ на этот комментарий побудили меня более подробно остановиться на том, что ключевое слово
yield
делает и не делает.Если вы напишите это:
Вместо этого вы также можете написать это:
Отсюда следует, что ключевое слово
yield
не относится к многопоточности и абсолютно не вызываетSystem.Threading.Thread.Yield()
.источник
On encountering the words yield return a function in C# returns
" Нет. Текст, который вы цитируете, объясняет это, как и Википедия - "In computer science, yield is an action that occurs in a computer program during multithreading, of forcing a processor to relinquish control of the current running thread, and sending it to the end of the running queue, of the same scheduling priority.
". По сути, «пожалуйста, остановите меня там, где я нахожусь, и пусть кто-нибудь еще побежит».Когда кнопка огня нажата, вводится второй оператор if и запускается StopAllCoroutines. Это означает, что Coroutine, в котором запущен цикл while, закончен, поэтому бесконечного цикла больше нет. Сопрограмма подобна контейнеру для выполнения кода.
Я могу порекомендовать Руководство по Unity и API сценариев Unity, чтобы лучше понять, какие сопрограммы и насколько они эффективны.
Этот блог и поисковый пост на YouTube также помогли мне лучше использовать сопрограммы.
источник
Сопрограммы странный зверь. Возврат возврата приводит к тому, что метод приостанавливает выполнение до тех пор, пока он не будет изменен. За кулисами это может выглядеть примерно так:
А внутри Unity / C # (так как yield return является встроенной функцией c #), когда вы вызываете StartCoroutine, он создает
FireContinuouslyData
объект и передает его в метод. Основываясь на возвращаемом значении, он определяет, когда вызывать его позже, просто сохраняя объект FireContinuouslyData, чтобы передать его в следующий раз.Если вы когда-либо делали разрыв доходности, он мог бы просто установить его,
data.shouldBreak = true
и тогда Unity просто выбросил бы данные и не планировал их снова.И если бы были какие-либо данные, которые необходимо было сохранить между выполнениями, они также были бы сохранены в данных для дальнейшего использования.
Пример того, как Unity / C # может реализовать функциональность сопрограммы:
источник
В другом ответе упоминается, что вы прекращаете совместную работу, когда
"Fire1"
она активна - это совершенно правильно, поскольку сопрограмма не продолжает создавать экземпляры GameObjects после первого нажатия кнопки"Fire1"
.Однако в вашем случае этот код не будет «зависать» в бесконечном цикле, что выглядит так, как будто вы ищете ответ - то есть
while(true) {}
цикл, даже если вы не остановили его извне.Это не застрянет, но ваша сопрограмма не закончится (без вызова
StopCoroutine()
илиStopAllCoroutines()
) либо. Это происходит потому , что Unity сопрограмма выхода управления их абонент.yield
ing отличается отreturning
:return
заявление прекратит выполнение функции, даже если есть больше кода после негоyield
оператор приостанавливает функцию, начиная со следующей строкой после ,yield
когда возобновился.Обычно сопрограммы возобновляются в каждом кадре, но вы также возвращаете
WaitForSeconds
объект.Строка
yield return new WaitForSeconds(fireTime)
примерно переводится как «теперь приостанови меня и не возвращайся, пока не пройдутfireTime
секунды».Если не остановить, это сопрограмма, которая после запуска будет выполнять весь цикл раз в
fireTime
секунду.источник
Простое объяснение: под капотом Unity перебирает коллекцию (из YieldInstruction s или null или чего угодно
yield return
), используя то,IEnumerator
что возвращает ваша функция.Поскольку вы используете
yield
ключевое слово, ваш метод является итератором . Это не вещь Unity, это особенность языка C #. Как это работает?Он ленив и не генерирует всю коллекцию одновременно (а коллекция может быть бесконечной и ее невозможно создать сразу). Элементы коллекции генерируются по мере необходимости. Ваша функция возвращает итератор для работы с Unity. Он вызывает свой
MoveNext
метод для генерации нового элемента иCurrent
свойства для доступа к нему.Таким образом, ваш цикл не является бесконечным, он запускает некоторый код, возвращает элемент и возвращает управление обратно в Unity, чтобы он не завис и мог выполнять другую работу, такую как обработка ввода, чтобы остановить сопрограмму.
источник
Подумайте о том, как
foreach
работает:Контроль над итерацией находится на вызывающей стороне - если вы останавливаете итерацию (здесь с
break
), это все.yield
Ключевое слово является простым способом сделать перечислимую в C #. Имя намекает на это -yield return
возвращает управление вызывающей стороне (в данном случае нашейforeach
); звонящий решает, когда перейти к следующему пункту. Таким образом, вы можете создать такой метод:Это наивно выглядит так, будто оно будет работать вечно; но на самом деле все зависит от абонента. Вы можете сделать что-то вроде этого:
Это может немного сбивать с толку, если вы не привыкли к этой концепции, но я также надеюсь, что это очень полезное свойство. Это был самый простой способ, которым вы могли предоставить контроль своему вызывающему, и когда вызывающий абонент решает продолжить, он может просто сделать следующий шаг (если бы Unity был сделан сегодня, он, вероятно, использовал бы
await
вместоyield
; ноawait
не существовал обратно тогда).Все, что вам нужно для реализации ваших собственных сопрограмм (само собой разумеется, самые простые глупые сопрограммы), это:
Чтобы добавить очень простую
WaitForSeconds
реализацию, вам просто нужно что-то вроде этого:И соответствующий код в нашем основном цикле:
Та-да - это все, что нужно простой системе сопрограмм. А передавая управление вызывающему, вызывающий может принять решение по любому количеству вещей; у них может быть отсортированная таблица событий, вместо того, чтобы перебирать все сопрограммы в каждом кадре; у них могут быть приоритеты или зависимости. Это позволяет очень просто реализовать совместную многозадачность. И просто посмотрите, как это просто, спасибо
yield
:)источник