[C#] Элементарный таймер руками корутин
Добавлено: 08 янв 2014, 06:29
В этой теме хотел бы рассказать, может даже научить, использовать корутину как таймер одним движением.
Кароче суть фигни: как всем известно корутина работает за счет MonoBehavior в компоненте и является(могут не все согласиться) некой реализацией потоков силами юнити, кароче предлагает задержку выполнения команд с запоминанием положения счетчика команд. Ну и вот много кто их и как использует, а я столкнувщись с реализацией простого таймера, который как все начинающие лепят через разные Апдейты(Fixed\Late\Update) решил сделать таймер через корутину. По сложности урок можно сравнить с написанием компонента.
Для кого будет полезно:
- для тех кому вообще интересно изучать
- для расширения познаний о корутинах
Метод работы: по истечению времени(таймера) выполняется заданная пользователем команда.
И так если все еще интересно изучить данную технология, приступаю.
Для начала напишем рабочую область
Создадим где угодно в редакторе скрипт Progrressor.cs
В нем удалим все что было написано...
Все что выделено на картинке удаляем!
Далее...
Напишем делегат который будет помогать запоминать команду которую впоследующем будет выполнять корутина.
Здесь я выбрал простейший делегат который ни чего не берет в виде аргументов(параметров) и ни чего не возвращает.
Далее...
Создадим в этом скрипте класс Progress вот так
Этот класс (Progress) будет реализовывать парочку статических методов:
Пояснения:
Первый аргумент(параметр): GameObject obj - это объект который будет передаваться в метод и именно на него будет вещаться скрипт с корутиной
Второй аргумент(параметр): float timeSec - просто время в секундах, через которое будет выполняться корутина.
Третий аргумент: bool repeat - метка(флаг) простой параметр который будет давать понять - повторяться ли таймеру или выполнить его один раз.
Четвертый аргумент: CastedFunction func - функция как делегат которая будет выполняться по истечению таймера(корутины)
Так далее...
Рассмотрим работу метода ->
с начала проверим есть ли у нас параметр GameObject obj чтобы убедится что объект на который будет вещаться скрипт существует простой проверкой
Если объекта не существует то отключаем дальнейшее выполнение метода StartNewProgress
Следующей командой будет: добавление компонента с корутиной на выбранный объект
Элементарная команда добавления компонента к объекту ни чего нового.
Вот как будет выглядеть класс Progress в целом
Далее...
И вот когда у нас есть уже рабочее тело которое будет добавлять компонент на объект переходим к написанию самого компонента.
Создаем ниже в этом же скрипте новый класс который уже будет наследоваться от MonoBehavior который и будет содержать корутину.
Назовем его CorutineLoader
Далее...
Внутри класса CorutineLoader определим некоторый свойства(переменные)
Пояснения:
- свойство float timeSec - число которое будет определять время действия "таймера"
- свойство bool repeat - метка которая будет служить определителем для однократкого или многократного таймера
- свойство CastedFunction func - наш делегат который будет ссылаться на команду что должна будет выполниться после завершения таймера.
Далее...
Создадим несколько методов внутри данного (CorutineLoader) класса
Метод StartCast по принимаемым параметрам похож на метод StartNewProgress класса Progress, этот метод (StartCast) и будет стартовать корутину.
Далее...
Переходим к написанию самой корутины:
где yield return new WaitForSeconds(timeSec); - это и есть вся суть темы, эта команда будет выполнять задержку с помощью WaitForSeconds где
timeSec и есть время задержки в секундах.
Далее...
Опишем некий метод CastDone который будет служить нам определителем выполнения корутины
В чем его суть: данный метод будет вызываться по выполнению корутины, далее он будет выполнять нашу функции которая представлена в виде делагета
CastedFunction func , далее идет проверка if (repeat && this.active) что является проверкой того многократный ли у нас таймер и (&&) активен ли наш компонент что очень важно !!! т к корутина может существовать только в активном компоненте, при выполнении всех этих проверок идет повторный запуск корутины StartCoroutine(Corutine());
иначе если одна из проверок не прошла else следует удалить данный компонент с объекта Destroy(this);, но перед этим остановим все корутины данного компонента StopAllCoroutines();, т к повторюсь, что корутины являются неким аналогом потоков а они довольно таки утечны.
Данный метод CastDone помещаем в нашу корутину после задержки:
private IEnumerator Corutine() {
yield return new WaitForSeconds(timeSec);
CastDone();
}
И вот как в итоге должен выглядеть наш компонент CorutineLoader в целом:
Ничего сложного в целом
Теперь когда создан компонент и он работоспособен перенесем его имя CorutineLoader в метод StartNewProgress класса Progress
вот так:
Теперь объекту obj будет добавляться данный компонент CorutineLoader при этом будет вызываться его метод StartCast который в своем случае и будет неким инциатором корутины.
Так как описание рабочей области подходит к концу вот полная реализация классов CorutineLoader и Progress
Далее время проверки на работоспособность...
Для того чтобы проверить эту всю фигню что была написана выше нам будет необходим любой объекта на сцене и маленький компонент который и будет реализовывать работу корутины, действуем!
Создадим любой объект допустим куб
Далее создадим небольшой компонент, к примеру я создал такой
Здесь у меня метод Start который будет при старте сцены через Progress.StartNewProgress создавать единократный таймер
с такими параметрами как this.gameObject - что есть этим самым кубом в виде ГеймОбъекта, время действия таймера - одна секунда, false - меткой которая дает определение что таймер является однократным и CastedFunc что является параметром в виде функции что мы хотим выполнять по завершении таймера, описанная ниже в этом же классе.
Далее... добавим этот компонент к кубу как здесь
И так что имеем:
когда запуститься сцена, запуститься и таймер(корутина) как компонент к этому объекту и через секунду после завершения таймера выполниться функция CastedFunc которая в свою очередь выведет сообщение "Casted" в консоли, что и будет свидетельствовать о успешном завершении таймера.
В завершении урока скажу как я использую данную "технологи":
- елементарно, к примеру реализация перезагрузки объекта, когда игрок поднимает некий предмет и если этот предмет нужно создать в этой же позиции через определенное время то в данном случае подходит данный "таймер", т к удалять и инстансить объекты заново на сцене бьет по памяти и можно просто отключать их видимость через this.gameObject.renderer.enabled = false и включать их видимости по завершению таймера что даст уверенность в том что объект действительно был удален.
Вот в общем все скрипты которые я использую:
Скрипт куба
Скрипт таймера
Ну и конечно у этой системы есть недостатки
-есть возможность создавать только один таймер для одного объекта, в случае задания нового таймера к этому же объекту когда выполняется предыдущий, предыдущий будет обнулен.
-таймер будет выполнять только если объект будет активен.
-да это же аналог Invoke - очень даже.
В общем то это легко поправимо
Автор - этот хрен, он же Porcha он же Lawson
Всем спасибо!
Кароче суть фигни: как всем известно корутина работает за счет 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
Всем спасибо!