Кароче суть фигни: как всем известно корутина работает за счет MonoBehavior в компоненте и является(могут не все согласиться) некой реализацией потоков силами юнити, кароче предлагает задержку выполнения команд с запоминанием положения счетчика команд. Ну и вот много кто их и как использует, а я столкнувщись с реализацией простого таймера, который как все начинающие лепят через разные Апдейты(Fixed\Late\Update) решил сделать таймер через корутину. По сложности урок можно сравнить с написанием компонента.
Для кого будет полезно:
- для тех кому вообще интересно изучать
- для расширения познаний о корутинах
Метод работы: по истечению времени(таймера) выполняется заданная пользователем команда.
И так если все еще интересно изучить данную технология, приступаю.
Для начала напишем рабочую область
Создадим где угодно в редакторе скрипт Progrressor.cs
В нем удалим все что было написано...
Все что выделено на картинке удаляем!
Далее...
Напишем делегат который будет помогать запоминать команду которую впоследующем будет выполнять корутина.
Синтаксис:
Используется csharp
public delegate void CastedFunction();
Здесь я выбрал простейший делегат который ни чего не берет в виде аргументов(параметров) и ни чего не возвращает.
Далее...
Создадим в этом скрипте класс Progress вот так
Синтаксис:
Используется csharp
public class Progress {
}
}
Этот класс (Progress) будет реализовывать парочку статических методов:
Синтаксис:
Используется csharp
public static void StartNewProgress(GameObject obj, float timeSec, bool repeat, CastedFunction func) {
}
}
Пояснения:
Первый аргумент(параметр): GameObject obj - это объект который будет передаваться в метод и именно на него будет вещаться скрипт с корутиной
Второй аргумент(параметр): float timeSec - просто время в секундах, через которое будет выполняться корутина.
Третий аргумент: bool repeat - метка(флаг) простой параметр который будет давать понять - повторяться ли таймеру или выполнить его один раз.
Четвертый аргумент: CastedFunction func - функция как делегат которая будет выполняться по истечению таймера(корутины)
Так далее...
Рассмотрим работу метода ->
с начала проверим есть ли у нас параметр GameObject obj чтобы убедится что объект на который будет вещаться скрипт существует простой проверкой
Синтаксис:
Используется csharp
if (obj == null) return;
Если объекта не существует то отключаем дальнейшее выполнение метода StartNewProgress
Следующей командой будет: добавление компонента с корутиной на выбранный объект
Синтаксис:
Используется csharp
obj.AddComponent<Компонент>()
Элементарная команда добавления компонента к объекту ни чего нового.
Вот как будет выглядеть класс Progress в целом
Синтаксис:
Используется csharp
public class Progress {
public static void StartNewProgress(GameObject obj, float timeSec, bool repeat, CastedFunction func) {
if (obj == null) return;
obj.AddComponent<Компонент>();
}
}
public static void StartNewProgress(GameObject obj, float timeSec, bool repeat, CastedFunction func) {
if (obj == null) return;
obj.AddComponent<Компонент>();
}
}
Далее...
И вот когда у нас есть уже рабочее тело которое будет добавлять компонент на объект переходим к написанию самого компонента.
Создаем ниже в этом же скрипте новый класс который уже будет наследоваться от MonoBehavior который и будет содержать корутину.
Назовем его CorutineLoader
Синтаксис:
Используется csharp
public class CorutineLoader : MonoBehaviour {
}
}
Далее...
Внутри класса CorutineLoader определим некоторый свойства(переменные)
Синтаксис:
Используется csharp
private float timeSec = 0;
private bool repeat = false;
private CastedFunction func = null;
private bool repeat = false;
private CastedFunction func = null;
Пояснения:
- свойство float timeSec - число которое будет определять время действия "таймера"
- свойство bool repeat - метка которая будет служить определителем для однократкого или многократного таймера
- свойство CastedFunction func - наш делегат который будет ссылаться на команду что должна будет выполниться после завершения таймера.
Далее...
Создадим несколько методов внутри данного (CorutineLoader) класса
Синтаксис:
Используется csharp
public void StartCast(float time, bool repeat, CastedFunction function) {
timeSec = time;
this.repeat = repeat;
func = function;
}
timeSec = time;
this.repeat = repeat;
func = function;
}
Метод StartCast по принимаемым параметрам похож на метод StartNewProgress класса Progress, этот метод (StartCast) и будет стартовать корутину.
Далее...
Переходим к написанию самой корутины:
Синтаксис:
Используется csharp
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
}
yield return new WaitForSeconds(timeSec);
}
где yield return new WaitForSeconds(timeSec); - это и есть вся суть темы, эта команда будет выполнять задержку с помощью WaitForSeconds где
timeSec и есть время задержки в секундах.
Далее...
Опишем некий метод CastDone который будет служить нам определителем выполнения корутины
Синтаксис:
Используется csharp
private void CastDone() {
if (func != null) func();
if (repeat && this.active) StartCoroutine(Corutine());
else { StopAllCoroutines(); Destroy(this);}
}
if (func != null) func();
if (repeat && this.active) StartCoroutine(Corutine());
else { StopAllCoroutines(); Destroy(this);}
}
В чем его суть: данный метод будет вызываться по выполнению корутины, далее он будет выполнять нашу функции которая представлена в виде делагета
CastedFunction func , далее идет проверка if (repeat && this.active) что является проверкой того многократный ли у нас таймер и (&&) активен ли наш компонент что очень важно !!! т к корутина может существовать только в активном компоненте, при выполнении всех этих проверок идет повторный запуск корутины StartCoroutine(Corutine());
иначе если одна из проверок не прошла else следует удалить данный компонент с объекта Destroy(this);, но перед этим остановим все корутины данного компонента StopAllCoroutines();, т к повторюсь, что корутины являются неким аналогом потоков а они довольно таки утечны.
Данный метод CastDone помещаем в нашу корутину после задержки:
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
CastDone();
}
И вот как в итоге должен выглядеть наш компонент CorutineLoader в целом:
Синтаксис:
Используется csharp
public class CorutineLoader : MonoBehaviour {
private float timeSec = 0;
private bool repeat = false;
private CastedFunction func = null;
public void StartCast(float time, bool repeat, CastedFunction function) {
timeSec = time;
this.repeat = repeat;
func = function;
if (timeSec > 0 && this.active) StartCoroutine(Corutine());
else Destroy(this);
}
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
CastDone();
}
private void CastDone() {
if (func != null) func();
if (repeat && this.active) StartCoroutine(Corutine());
else { StopAllCoroutines(); Destroy(this);}
}
}
private float timeSec = 0;
private bool repeat = false;
private CastedFunction func = null;
public void StartCast(float time, bool repeat, CastedFunction function) {
timeSec = time;
this.repeat = repeat;
func = function;
if (timeSec > 0 && this.active) StartCoroutine(Corutine());
else Destroy(this);
}
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
CastDone();
}
private void CastDone() {
if (func != null) func();
if (repeat && this.active) StartCoroutine(Corutine());
else { StopAllCoroutines(); Destroy(this);}
}
}
Ничего сложного в целом
Теперь когда создан компонент и он работоспособен перенесем его имя CorutineLoader в метод StartNewProgress класса Progress
вот так:
Синтаксис:
Используется csharp
public static void StartNewProgress(GameObject obj, float timeSec, bool repeat, CastedFunction func) {
if (obj == null) return;
obj.AddComponent<CorutineLoader>().StartCast(timeSec, repeat, func);
}
if (obj == null) return;
obj.AddComponent<CorutineLoader>().StartCast(timeSec, repeat, func);
}
Теперь объекту obj будет добавляться данный компонент CorutineLoader при этом будет вызываться его метод StartCast который в своем случае и будет неким инциатором корутины.
Так как описание рабочей области подходит к концу вот полная реализация классов CorutineLoader и Progress
Синтаксис:
Используется csharp
public class Progress {
public static void StartNewProgress(GameObject obj, float timeSec, bool repeat, CastedFunction func) {
if (obj == null) return;
obj.AddComponent<CorutineLoader >();
}
}
public class CorutineLoader : MonoBehaviour {
private float timeSec = 0;
private bool repeat = false;
private CastedFunction func = null;
public void StartCast(float time, bool repeat, CastedFunction function) {
timeSec = time;
this.repeat = repeat;
func = function;
if (timeSec > 0 && this.active) StartCoroutine(Corutine());
else Destroy(this);
}
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
CastDone();
}
private void CastDone() {
if (func != null) func();
if (repeat && this.active) StartCoroutine(Corutine());
else { StopAllCoroutines(); Destroy(this);}
}
}
public static void StartNewProgress(GameObject obj, float timeSec, bool repeat, CastedFunction func) {
if (obj == null) return;
obj.AddComponent<CorutineLoader >();
}
}
public class CorutineLoader : MonoBehaviour {
private float timeSec = 0;
private bool repeat = false;
private CastedFunction func = null;
public void StartCast(float time, bool repeat, CastedFunction function) {
timeSec = time;
this.repeat = repeat;
func = function;
if (timeSec > 0 && this.active) StartCoroutine(Corutine());
else Destroy(this);
}
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
CastDone();
}
private void CastDone() {
if (func != null) func();
if (repeat && this.active) StartCoroutine(Corutine());
else { StopAllCoroutines(); Destroy(this);}
}
}
Далее время проверки на работоспособность...
Для того чтобы проверить эту всю фигню что была написана выше нам будет необходим любой объекта на сцене и маленький компонент который и будет реализовывать работу корутины, действуем!
Создадим любой объект допустим куб
Далее создадим небольшой компонент, к примеру я создал такой
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;
public class CubeScript : MonoBehaviour {
private void Start () {
Progress.StartNewProgress(this.gameObject, 1, false, CastedFunc);
}
public void CastedFunc() {
Debug.LogError("Casted");
}
}
using System.Collections;
public class CubeScript : MonoBehaviour {
private void Start () {
Progress.StartNewProgress(this.gameObject, 1, false, CastedFunc);
}
public void CastedFunc() {
Debug.LogError("Casted");
}
}
Здесь у меня метод Start который будет при старте сцены через Progress.StartNewProgress создавать единократный таймер
с такими параметрами как this.gameObject - что есть этим самым кубом в виде ГеймОбъекта, время действия таймера - одна секунда, false - меткой которая дает определение что таймер является однократным и CastedFunc что является параметром в виде функции что мы хотим выполнять по завершении таймера, описанная ниже в этом же классе.
Далее... добавим этот компонент к кубу как здесь
И так что имеем:
когда запуститься сцена, запуститься и таймер(корутина) как компонент к этому объекту и через секунду после завершения таймера выполниться функция CastedFunc которая в свою очередь выведет сообщение "Casted" в консоли, что и будет свидетельствовать о успешном завершении таймера.
В завершении урока скажу как я использую данную "технологи":
- елементарно, к примеру реализация перезагрузки объекта, когда игрок поднимает некий предмет и если этот предмет нужно создать в этой же позиции через определенное время то в данном случае подходит данный "таймер", т к удалять и инстансить объекты заново на сцене бьет по памяти и можно просто отключать их видимость через this.gameObject.renderer.enabled = false и включать их видимости по завершению таймера что даст уверенность в том что объект действительно был удален.
Вот в общем все скрипты которые я использую:
Скрипт куба
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;
public class CubeScript : MonoBehaviour {
private void Start () {
this.renderer.enabled = false;
Progress.StartNewProgress(this.gameObject, 1, CastedFunc);
}
public void CastedFunc() {
Debug.LogError("Casted");
this.renderer.enabled = true;
}
}
using System.Collections;
public class CubeScript : MonoBehaviour {
private void Start () {
this.renderer.enabled = false;
Progress.StartNewProgress(this.gameObject, 1, CastedFunc);
}
public void CastedFunc() {
Debug.LogError("Casted");
this.renderer.enabled = true;
}
}
Скрипт таймера
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;
public delegate void CastedFunction();
public class Progress {
public static void StartNewProgress(GameObject obj, float timeSec, CastedFunction func) {
Progress.StartNewProgress(obj, timeSec, false, func);
}
public static void StartNewProgress(GameObject obj, float timeSec, bool repeat, CastedFunction func) {
if (obj == null) return;
CorutineLoader prevComp = obj.GetComponent<CorutineLoader>();
if (prevComp != null) { prevComp.StopAllCoroutines(); prevComp.StartCast(timeSec, repeat, func);}
else obj.AddComponent<CorutineLoader>().StartCast(timeSec, repeat, func);
}
private Progress() {}
}
public class CorutineLoader : MonoBehaviour {
private float timeSec = 0;
private bool repeat = false;
private CastedFunction func = null;
public void StartCast(float time, bool repeat, CastedFunction function) {
timeSec = time;
this.repeat = repeat;
func = function;
if (timeSec > 0 && this.active) StartCoroutine(Corutine());
else Destroy(this);
}
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
CastDone();
}
private void CastDone() {
if (func != null) func();
if (repeat && this.active) StartCoroutine(Corutine());
else { StopAllCoroutines(); Destroy(this);}
}
private void OnDestroy() {
StopAllCoroutines();
}
}
using System.Collections;
public delegate void CastedFunction();
public class Progress {
public static void StartNewProgress(GameObject obj, float timeSec, CastedFunction func) {
Progress.StartNewProgress(obj, timeSec, false, func);
}
public static void StartNewProgress(GameObject obj, float timeSec, bool repeat, CastedFunction func) {
if (obj == null) return;
CorutineLoader prevComp = obj.GetComponent<CorutineLoader>();
if (prevComp != null) { prevComp.StopAllCoroutines(); prevComp.StartCast(timeSec, repeat, func);}
else obj.AddComponent<CorutineLoader>().StartCast(timeSec, repeat, func);
}
private Progress() {}
}
public class CorutineLoader : MonoBehaviour {
private float timeSec = 0;
private bool repeat = false;
private CastedFunction func = null;
public void StartCast(float time, bool repeat, CastedFunction function) {
timeSec = time;
this.repeat = repeat;
func = function;
if (timeSec > 0 && this.active) StartCoroutine(Corutine());
else Destroy(this);
}
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
CastDone();
}
private void CastDone() {
if (func != null) func();
if (repeat && this.active) StartCoroutine(Corutine());
else { StopAllCoroutines(); Destroy(this);}
}
private void OnDestroy() {
StopAllCoroutines();
}
}
Ну и конечно у этой системы есть недостатки
-есть возможность создавать только один таймер для одного объекта, в случае задания нового таймера к этому же объекту когда выполняется предыдущий, предыдущий будет обнулен.
-таймер будет выполнять только если объект будет активен.
-да это же аналог Invoke - очень даже.
В общем то это легко поправимо
Скрытый текст:
Автор - этот хрен, он же Porcha он же Lawson
Всем спасибо!