Каждый разработчик находит свои преимущества и недостатки использования корутин в Unity. И сам решает в каких сценариях их применить, а в каких отдать предпочтение альтернативам. В повседневной практике я достаточно часто использую корутины для различных видов задач. Однажды, я понял, что именно меня в них раздражает и отторгает многих новичков. Движок предоставляет всего пару методов и несколько их перегрузок для работы с корутинами: Запуск (docs) Остановка (docs) Перегрузки со строковыми параметрами (не смотря на их обманчивое удобство) можно сразу отправить на помойку забыть как минимум по трем причинам. С одной стороны, предоставленных методов вполне достаточно, чтобы покрыть базовые потребности. Но со временем я начал замечать, что при активном использовании приходится писать большое количество шаблонного кода — это утомляет, и ухудшает его читаемость. В этой статье я хочу описать небольшую обертку, которую я применяю уже давно. Благодаря ей, с мыслями о корутинах у меня в голове больше не возникают фрагменты шаблонного кода, с которым приходилось вокруг них плясать. Кроме этого, всей команде стало проще читать и понимать компоненты, где используются корутины. Допустим, перед нами стоит следующая задача — написать компонент, который позволяет перемещать объект к заданной точке. В данным момент не играет роли, каким именно методом будет выполняться передвижение и в каких координатах. Мной будет выбран лишь один из множества вариантов — это интерполяция и глобальные координаты. Обратите внимание, крайне не рекомендуется перемещать объект путем изменения его координат «в лоб», то есть конструкцией transform.position = newPosition, если с ним используется компонент RigidBody (тем более в методе Update)(docs). Предлагаю следующий вариант реализации необходимого компонента: В методе Move очень важно запускать корутину только в тому случае, когда она еще еще не запущена. Иначе их можно будет запустить сколько угодно и каждая из них будет перемещать объект. threshold — допуск. Другими словами, расстояние к точке, приблизившись на которое мы будем считать, что достигли цели. Для чего это нужно Учитывая, что все компоненты (x, y, z) структуры Vector3 имеют тип float, использовать результат проверки на равенство расстояния к цели и допуска в качестве условия цикла — плохая затея. Мы проверяем расстояние к цели на больше/меньше, что позволяет нам избежать данной проблемы. Также, при желании можно применить метод Mathf.Approximately(docs) для примерной проверки на равенство. Стоит заметить, что при некоторых способах перемещения скорость может оказаться достаточно большой для того, чтобы объект за один кадр «перепрыгнул» цель. Тогда цикл никогда не завершится. Например, если вы используете метод Vector3.MoveTowards. На сколько мне известно, в движке Unity для структуры Vector3 оператор == уже переопределен таким образом, что для покомпонентной проверки на равенство вызывается Mathf.Approximately. Пока что это всё, наш компонент достаточно прост. И в текущий момент никаких проблем нет. Но, что это за компонент, позволяющий двигать объект к точке, но не предоставляющий возможность его остановить. Давайте исправим эту несправедливость. Так как мы с вами решили не переходить на сторону зла, и не использовать перегрузки со строковыми параметрами, теперь нам нужно сохранить где-то ссылку на запущенную корутину. Иначе как потом остановить её?  Добавим поле: Подправим Move: Добавим метод остановки движения: Совсем другое дело! Хоть к ране прикладывай. Итак. Мы имеем небольшой компонент, который выполняет поставленную задачу. В чем же моё негодование? Со временем, проект растёт, а вместе с ним и количество компонентов, в том числе использующих корутины. И с каждым разом мне всё больше не дают покоя вот какие вещи: Один их вид заставляет мой глаз дергаться, да и читать такой код сомнительное удовольствие (согласен, бывает и хуже). Но ведь куда приятнее и нагляднее было бы иметь что-нибудь в таком роде: Иначе, по причине отсутствия ссылки на корутину, вы попросту не сможете ее остановить. Если забудете, то получите одноразовую корутину. После первого запуска в moveRoutine так и останется ссылка на корутину, и новую запустить уже не выйдет. Точно так же нужно сделать и в случае принудительной остановки: В один прекрасный момент, очень хочется единожды куда-то вынести вот этот весь маскарад, а для себя оставить только нужные методы: Start, Stop и еще пару событий и свойств. Давайте же наконец-то это сделаем! Owner — ссылка на экземпляр MonoBehaviour, к которому будет привязана корутина. Как известно, она должна выполняться в контексте конкретного компонента, так как именно ему принадлежат методы StartCoroutine и StopCoroutine. Соответственно, нам нужна ссылка на компонент, который будет владельцем корутины. Coroutine — аналог поля moveRoutine в компоненте MoveToPoint, содержит ссылку на текущую корутину. Routine — делегат, с которым будет сообщен метод, выполняющий роль корутины. Process() — небольшая обертка над основным методом Routine. Нужен для того, чтобы иметь возможность проследить, когда же завершится выполнение корутины, сбросить ссылку на неё и выполнить в этот момент другой код (если понадобится). IsProcessing — позволяет узнать, выполняется ли корутина в текущий момент. Таким образом мы избавляемся от большого количества головной боли, а наш компонент приобретает совсем другой вид: Осталась лишь сама корутина и несколько строчек кода для работы с ней. Значительно лучше. Допустим, пришла новая задача — нужно добавить возможность выполнить любой код после того, как объект достиг цели. В изначальном варианте нам пришлось бы в каждую корутину добавлять дополнительный параметр-делегат, который можно дернуть по ее завершении. И вызывать следующим образом: А если еще в качестве обработчика будет какая-нибудь лямбда, то смотрится еще страшнее. С нашей же оберткой достаточно лишь один раз добавить в нее это событие. А затем, при необходимости, подписаться. Полагаю, вы уже обратили внимание на то, что текущий вариант обертки предоставляет возможность работать только с корутинами без параметров. Поэтому мы можем написать обобщенную обертку для корутин с одним параметром. Остальные делаются по аналогии. Но, по хорошему, было бы неплохо сначала вынести код, который будет одинаковым для всех оберток, в какой-то базовый класс, чтобы не писать одно и то же. Мы ведь с этим и боремся. В него уберем: И теперь, собственно говоря, обертка для корутин с одним параметром: Как видим, код практически такой же. Лишь в некоторых местах добавились фрагменты, зависящие от количества аргументов. Допустим, нас попросили обновить компонент MoveToPoint таким образом, чтобы точку можно было задавать не через окно Inspector в редакторе, а кодом при вызове метода Move. Есть множество вариантов, как можно еще больше расширить функциональность этой обертки: добавить запуск с задержкой, события с параметрами, возможное отслеживание прогресса корутины и прочее. Но я предлагаю на этом этапе остановиться. Цель данной статьи — желание поделиться насущными проблемами, с которыми я столкнулся и предложить вариант их решения, а не покрыть возможные потребности всех разработчиков. Надеюсь, как новички, так и опытные товарищи почерпнут пользу из моего опыта. Возможно, поделятся своим в комментариях или укажут на ошибки, которые я мог допустить. Разработчик