[C#] Элементарный таймер руками корутин

Научился сам? Помоги начинающему.

[C#] Элементарный таймер руками корутин

Сообщение llka 08 янв 2014, 06:29

В этой теме хотел бы рассказать, может даже научить, использовать корутину как таймер одним движением.
Кароче суть фигни: как всем известно корутина работает за счет MonoBehavior в компоненте и является(могут не все согласиться) некой реализацией потоков силами юнити, кароче предлагает задержку выполнения команд с запоминанием положения счетчика команд. Ну и вот много кто их и как использует, а я столкнувщись с реализацией простого таймера, который как все начинающие лепят через разные Апдейты(Fixed\Late\Update) решил сделать таймер через корутину. По сложности урок можно сравнить с написанием компонента.

Для кого будет полезно:
- для тех кому вообще интересно изучать
- для расширения познаний о корутинах

Метод работы: по истечению времени(таймера) выполняется заданная пользователем команда.

И так если все еще интересно изучить данную технология, приступаю.

Для начала напишем рабочую область

Создадим где угодно в редакторе скрипт Progrressor.cs
В нем удалим все что было написано...
Все что выделено на картинке удаляем!
lessPic3.png


Далее...
Напишем делегат который будет помогать запоминать команду которую впоследующем будет выполнять корутина.
Синтаксис:
Используется 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<Компонент>();
 }
}


Далее...
И вот когда у нас есть уже рабочее тело которое будет добавлять компонент на объект переходим к написанию самого компонента.
Создаем ниже в этом же скрипте новый класс который уже будет наследоваться от MonoBehavior который и будет содержать корутину.
Назовем его CorutineLoader
Синтаксис:
Используется csharp
public class CorutineLoader : MonoBehaviour {

}


Далее...
Внутри класса CorutineLoader определим некоторый свойства(переменные)
Синтаксис:
Используется csharp
private float timeSec = 0;
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;
}

Метод StartCast по принимаемым параметрам похож на метод StartNewProgress класса Progress, этот метод (StartCast) и будет стартовать корутину.

Далее...
Переходим к написанию самой корутины:
Синтаксис:
Используется csharp
private IEnumerator Corutine() {
 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);}
}

В чем его суть: данный метод будет вызываться по выполнению корутины, далее он будет выполнять нашу функции которая представлена в виде делагета
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);}
 }
}


Ничего сложного в целом
Теперь когда создан компонент и он работоспособен перенесем его имя 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);
}


Теперь объекту 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);}
 }
}


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

Создадим любой объект допустим куб
lessPic2.png

Далее создадим небольшой компонент, к примеру я создал такой
Синтаксис:
Используется 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");
        }
}

Здесь у меня метод Start который будет при старте сцены через Progress.StartNewProgress создавать единократный таймер
с такими параметрами как this.gameObject - что есть этим самым кубом в виде ГеймОбъекта, время действия таймера - одна секунда, false - меткой которая дает определение что таймер является однократным и CastedFunc что является параметром в виде функции что мы хотим выполнять по завершении таймера, описанная ниже в этом же классе.

Далее... добавим этот компонент к кубу как здесь
lessPic1.png


И так что имеем:
когда запуститься сцена, запуститься и таймер(корутина) как компонент к этому объекту и через секунду после завершения таймера выполниться функция 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;
        }
       
}

Скрипт таймера
Синтаксис:
Используется 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();
 }
}


Ну и конечно у этой системы есть недостатки
-есть возможность создавать только один таймер для одного объекта, в случае задания нового таймера к этому же объекту когда выполняется предыдущий, предыдущий будет обнулен.
-таймер будет выполнять только если объект будет активен.
-да это же аналог Invoke - очень даже.
В общем то это легко поправимо
Скрытый текст:
хотя нет не легко ;)


Автор - этот хрен, он же Porcha он же Lawson
Всем спасибо!
У вас нет доступа для просмотра вложений в этом сообщении.
Последний раз редактировалось llka 08 янв 2014, 13:59, всего редактировалось 3 раз(а).
llka
UNIверсал
 
Сообщения: 359
Зарегистрирован: 08 янв 2014, 05:00

Re: [C#] Элементарный таймер руками корутин

Сообщение BornFoRdeatH 08 янв 2014, 06:51

(3A4OT) можно считать эталоном оформления уроков, все красиво оформлено и доходчиво расписано(ну кроме делегатов, новичек растеряется что это такое и зачем оно нужно)... В видеоуроке это бы на пол часа-час расстянулось, а так все под рукой, глазами нащупал нужное, в памяти отложилось, так держать.
Не бойся, если ты один, бойся, если ты ноль.
BornFoRdeatH
Адепт
 
Сообщения: 2377
Зарегистрирован: 22 окт 2011, 23:41
Откуда: Украина
Skype: bornfordeath

Re: [C#] Элементарный таймер руками корутин

Сообщение llka 08 янв 2014, 07:12

ну кроме делегатов, новичек растеряется что это такое и зачем оно нужно

Ну я же не буду расписывать здесь 1000 и 1 применение делагатов, в данном уроке я написал именно для передачи выполняемой функции как параметра.
llka
UNIверсал
 
Сообщения: 359
Зарегистрирован: 08 янв 2014, 05:00

Re: [C#] Элементарный таймер руками корутин

Сообщение BornFoRdeatH 08 янв 2014, 07:38

Этот класс (Progress) будет реализовать реализовывать парочку статических методов:
Не бойся, если ты один, бойся, если ты ноль.
BornFoRdeatH
Адепт
 
Сообщения: 2377
Зарегистрирован: 22 окт 2011, 23:41
Откуда: Украина
Skype: bornfordeath

Re: [C#] Элементарный таймер руками корутин

Сообщение seaman 08 янв 2014, 11:03

Урок хороший, решение так себе.
На объекте лишний компонент. Тогда уж все связанное с таймером всунуть в CubeScript.
Но вообще я бы сделал так.
1. Пишем скрипт таймера, который будет Singleton.
Синтаксис:
Используется csharp
using System;
using UnityEngine;
using System.Collections;

public class Timer : MonoBehaviour
{
    private static Timer __instance;

    public IEnumerator TimerStart(float time, Action act)
    {
        yield return new WaitForSeconds(time);
        if (act != null) act();
    }

    public static Timer Instance
    {
        get
        {
            if (__instance == null)
            {
                GameObject t = GameObject.Find("Timer") ?? new GameObject("Timer");
                __instance = t.GetComponent<Timer>();
                if (__instance == null) __instance = t.AddComponent<Timer>();
            }
            return __instance;
        }
    }
}

Здесь два статических члена, которые и делают синглтон.
Один - приватный для сохранения ссылки на сам скрипт таймера, второй для создания этого самого таймера при необходимости.
Что мы делаем во втором. Сначала проверяем - нет ли у нас уже таймера? Если нет - ищем объект на который будем вешать скрипт таймера (а вдруг его вручную создали?) Если найдем - t - будет ссылкой на этот найденный объект, если не найдем - создадим новый объект. Это записывается одной строчкой:
Синтаксис:
Используется csharp
GameObject t = GameObject.Find("Timer") ?? new GameObject("Timer");

Далее пытаемся получить с него компонент - наш таймер. Если получили - значит кто-то его вручную накинул на объект. Если не получили - добавляем этот компонент и сразу присваиваем его нашей приватной ссылке.
Ну и возвращаем эту ссылку.
Также в этом скрипте есть корутина, в которую передается необходимое время задержки и ссылка на метод, который нужно выполнить после задержки. Action - это предварительно определенный для Вашего удобства делегат без параметров, ничего не возвращающий.
2. В нужном нам месте используем этот таймер так:
Синтаксис:
Используется csharp
using UnityEngine;

public class TestTimer1 : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(Timer.Instance.TimerStart(4f, Act));
    }

    public void Act()
    {
        Debug.Log("TEST 1");
    }
}

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

Обратите внимание как мы вызываем корутину в другом скрипте. Мы используем статическое свойствоTimer.Instance для доступа к скрипту таймера. Даже если у нас на сцене нет ни объекта ни скрипта - он при первом же вызове Timer.Instance создастся!

Обращу внимание, что точно так же можно вызывать корутины даже из скрипта не унаследованного от MonoBehaviour! Это делается так:
Синтаксис:
Используется csharp
Timer.Instance.StartCoroutine(Timer.Instance.TimerStart(4f, Act));


Что тут нет? Нет повтора таймера.Но я думаю, Вам будет легко его создать.
seaman
Адепт
 
Сообщения: 8352
Зарегистрирован: 24 янв 2011, 12:32
Откуда: Самара

Re: [C#] Элементарный таймер руками корутин

Сообщение seaman 08 янв 2014, 14:19

лишний объект на сцене

Но не куча компонентов на Ваших объектах. Часто делают именно так - некий объект "Менеджер игры". На нем несколько скриптов, управляющих игрой. Один их них может быть этот таймер.
создание нескольких таймеров на одном объекте

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

Не понял. Глюк. Пока писал сообщение в ответ - Ваше куда то делось...
seaman
Адепт
 
Сообщения: 8352
Зарегистрирован: 24 янв 2011, 12:32
Откуда: Самара

Re: [C#] Элементарный таймер руками корутин

Сообщение llka 08 янв 2014, 15:23

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

Самая большая здесь проблема которую я еще не знаю как элегантно решить это - создание нескольких таймеров на одном объекте, будь то через отдельный объект или через те же компоненты, т к добавлять десятки одного и того же компонента, думаю, не выход.
llka
UNIверсал
 
Сообщения: 359
Зарегистрирован: 08 янв 2014, 05:00

Re: [C#] Элементарный таймер руками корутин

Сообщение llka 08 янв 2014, 15:26

Но не куча компонентов на Ваших объектах

Так я специально сделал проверку на уже существующий компонент в случае если будет задан новый таймер без проверки на выполнение предыдущего:
в конце урока указаны конечные варианты всех скриптов
Синтаксис:
Используется csharp
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);
 }

Это все очевидно, еще при написание самой системы я понимал что, десятки компонентов(таймеров) на одном объекте - не решение проблемы.
llka
UNIверсал
 
Сообщения: 359
Зарегистрирован: 08 янв 2014, 05:00

Re: [C#] Элементарный таймер руками корутин

Сообщение lawson 09 янв 2014, 23:48

Доделал то что посоветовал МОРЯК мои благодарности, точнее украв его идею дописал на коленке более рабочий вариант.

В новой системе:
- теперь можно создавать таймер во время игры из какой либо угодной области(части кода)
- теперь его можно остановить
- и после остановки можно возобновить.
- также находу менять настройки таймера.

Ассет:
Timer.unitypackage


Представляю весь код
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;

namespace TimerSpace {
 
 public delegate void FunctionToCast();
 
 public class ProcessCoroutine : MonoBehaviour {
 
  private static ProcessCoroutine process = null;
  private static string objName = "Global Timer";
  private static GameObject globalTimerObj = null;
 
  public static ProcessCoroutine control {
   get {
    if (process == null || globalTimerObj == null) {
     globalTimerObj = (GameObject.Find(objName)) ?? (new GameObject(objName));
     process = globalTimerObj.GetComponent<ProcessCoroutine>();
     if (process == null) process = globalTimerObj.AddComponent<ProcessCoroutine>();
    }
    globalTimerObj.active = true;
    return process;
   }
  }
 
  public void StartNewCorutine(ProcessTimer timer) {
   if (timer != null && globalTimerObj.active) {
    if (timer.onProcess == false && timer.expired > 0) timer.cast = StartCoroutine(timer.numerator);
    else timer.Dispose();
   }
  }
 
  private void OnDestroy() {
   StopAllCoroutines();
  }
 
 }
 
 public class ProcessTimer : System.IDisposable {
 
  public bool repeatTimer {get; set;}
  public bool onProcess { get; private set;}
 
  private FunctionToCast function = null;
  private float timerTime = 0;
  private Coroutine coroutine = null;
  private IEnumerator coroutineNumerator = null;
  private bool breakAfterDone = false;
 
  public static ProcessTimer StartNewTimer(float timeSec, FunctionToCast function) {
   return ProcessTimer.StartNewTimer(timeSec, false, function);
  }
 
  public static ProcessTimer StartNewTimer(float timeSec, bool repeat, FunctionToCast function) {
   ProcessTimer timer = new ProcessTimer();
   timer.expired = timeSec;
   timer.repeatTimer = repeat;
   timer.action = function;
   ProcessCoroutine.control.StartNewCorutine(timer);
   return timer;
  }
 
  public bool start() {
   if (this.coroutineNumerator == null) { ProcessCoroutine.control.StartNewCorutine(this); return true;}
   else return false;
  }
 
  public bool stop() {
   if (this.coroutineNumerator != null) { this.Dispose(); return true;}
   else return false;
  }
 
  public Coroutine cast {
   get { return this.coroutine;}
   set { this.coroutine = value;}
  }
 
  public IEnumerator numerator {
   get {
    this.coroutineNumerator = this.LocalCoroutine();
    return this.coroutineNumerator;
   }
   set { this.coroutineNumerator = value;}
  }
 
  private IEnumerator LocalCoroutine() {
   if (this.repeatTimer == false) {
    this.onProcess = true;
    yield return new WaitForSeconds(this.expired);
    yield return "";
    if (this.breakAfterDone) { this.breakAfterDone = false; yield break;}
    else CastDone();
    if (this.repeatTimer == false) yield break;
   }
   
   while(this.repeatTimer) {
    this.onProcess = true;
    yield return new WaitForSeconds(this.expired);
    yield return "";
    if (this.breakAfterDone) { this.breakAfterDone = false; yield break;}
    else CastDone();
   }
  }
 
  private void CastDone() {
   if (this.function != null) function();
   if (this.repeatTimer == false) this.Dispose();
  }
 
  public FunctionToCast action {
   get { return this.function;}
   set { if (value != null) this.function = value;}
  }
 
  public float expired {
   get { return this.timerTime;}
   set { this.timerTime = (value < 0) ? 0 : value;}
  }
 
  public void Dispose() {
   this.breakAfterDone = true;
   if (this.coroutineNumerator.Current.GetType() == typeof(WaitForSeconds)) this.coroutineNumerator.MoveNext();
   this.coroutineNumerator = null;
   this.onProcess = false;
  }
 
  private ProcessTimer() {}
 
 }
 
}


Инструкция:
Для инициализации экземпляра достаточно вызвать
Синтаксис:
Используется csharp
ProcessTimer.StartNewTimer(1, false, ActionFunction);

где:
- время действия корутины
- метка однократный таймер или многократный
- функция которая должна быть выполнена

Также есть рабочие методы stop() который останавливает корутину и start() - который возобновляет ее выполнение.

Код конечно не очень симпатичный, т к сказать рабочий вариант.
У вас нет доступа для просмотра вложений в этом сообщении.
Последний раз редактировалось lawson 13 янв 2014, 00:47, всего редактировалось 3 раз(а).
lawson
UNIверсал
 
Сообщения: 481
Зарегистрирован: 14 сен 2012, 21:20

Re: [C#] Элементарный таймер руками корутин

Сообщение Nolex 10 янв 2014, 00:42

Глянул на код, жесть какая-то. Зачем так извращаться?

Вот элементарный таймер:

Синтаксис:
Используется csharp
private float start_time = 0;
void Update() {  float timer = Time.time - start_time; }
void StartTimer() { start_time = Time.time; }


На мой взгялд ваш метод плох потому что:
1. Коротины жрут много ресурсов (для мобильных платформ актуально);
2. Нету реального отсчёта, т.е. есть только финальный триггер.
Разработка игр в студии Brinemedia .
Аватара пользователя
Nolex
UNIверсал
 
Сообщения: 483
Зарегистрирован: 17 окт 2010, 12:26
Откуда: Украина
Skype: exlumen
  • Сайт
  • ICQ

Re: [C#] Элементарный таймер руками корутин

Сообщение lawson 10 янв 2014, 00:51

Вот элементарный таймер:

Интересный экземпляр, только его использовать неудобно и контролировать сложно.
Коротины жрут много ресурсов

Я слышал такая фигня только на одноядерных процессорах. Причем даже проводились тесты. Да и вроде все апдейты это те же самые по сути корутины.
Нету реального отсчёта, т.е. есть только финальный триггер.

согласен это минус корутин. Подходят только для получения конечного результата. А вообще конечно это непонятный итератор.
lawson
UNIверсал
 
Сообщения: 481
Зарегистрирован: 14 сен 2012, 21:20

Re: [C#] Элементарный таймер руками корутин

Сообщение lwe 26 сен 2014, 11:15

Охх ну наконец руки дошли собрать маленькую сцену с таймером а то уже месяцев так 3 назад закончил окончательные изменения в системе.
Теперь для тех кто ныл что нельзя отсчитывать время в корутинах - теперь можно.
Сама сцена с таймером.

https://dl.dropboxusercontent.com/u/233 ... Build.html

Основные изменения:
-теперь таймер кидается на камеру т к камера есть всегда на сцене + не отключается
-теперь нет необходимости держать лишний объект.
-теперь можно задать кол во раз повторений таймера
-в процессе выполнения таймера можно отсчитывать время методом getTime
Синтаксис:
Используется csharp
ProcessTimer.StartNewTimer(1f, 10, Action);

private void Action(ProcessTimer e) {
 Debug.Log("Finished in: " + e.expired + ", finished times: " + e.timesFinished );
}
 


И сам код обновленного таймера.
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;

namespace TimerSpace {
 
 internal class ProcessCoroutine : MonoBehaviour {
 
  private static ProcessCoroutine process = null;
 
  internal static ProcessCoroutine control {
   get {
    if (process == null) {
     process = Camera.main.gameObject.GetComponent<ProcessCoroutine>();
     if (process == null)
      process = Camera.main.gameObject.AddComponent<ProcessCoroutine>();
    }
    return process;
   }
  }
 
  internal void StartTimer(ProcessTimer timer) {
    if (timer.onProcess == false && timer.expired > 0)
     StartCoroutine(timer.numerator);
    else if (timer.expired <= 0f)
     timer.Dispose();
  }
 
  internal void StopTimer(ProcessTimer timer) {
   if (timer.numerator != null)
    this.StopCoroutine(timer.numerator);
  }
 
  private void OnDestroy() {
   StopAllCoroutines();
  }
               
  private void OnDisable() {
   OnDestroy();
  }
 
 }
 
 [System.NonSerialized]
 public sealed class ProcessTimer : System.IDisposable {
 
  public bool repeatTimer {get; set;}
  public bool onProcess { get; private set;}
  public int timesFinished { get; private set;}
  public int repeatTimes { get; private set;}
 
  private System.Action<ProcessTimer> function = null;
  private float timerTime = 0;
  private IEnumerator coroutineNumerator = null;
  private bool breakAfterDone = false;
  private double timeSinceStart = 0d;
 
  public static ProcessTimer StartNewTimer(float timeSec, System.Action<ProcessTimer> function) {
   return ProcessTimer.StartNewTimer(timeSec, false, function);
  }
 
  public static ProcessTimer StartNewTimer(float timeSec, bool repeat, System.Action<ProcessTimer> function) {
   return ProcessTimer.StartNewTimer(timeSec, repeat, 0, function);
  }
 
  public static ProcessTimer StartNewTimer(float timeSec, int repeatTimes, System.Action<ProcessTimer> function) {
   return ProcessTimer.StartNewTimer(timeSec, (repeatTimes > 0) ? true : false, repeatTimes, function);
  }
 
  private static ProcessTimer StartNewTimer(float timeSec, bool repeat, int repeatTimes, System.Action<ProcessTimer> function) {
   ProcessTimer timer = new ProcessTimer();
   timer.expired = timeSec;
   timer.action = function;
   timer.repeatTimer = repeat;
   timer.repeatTimes = (repeatTimes > 0) ? repeatTimes : 0;
   ProcessCoroutine.control.StartTimer(timer);
   return timer;
  }
 
  public bool start() {
   if (this.coroutineNumerator == null && this.onProcess == false) {
    ProcessCoroutine.control.StartTimer(this);
    return true;
   }
   else return false;
  }
 
  public bool stop() {
   if (this.coroutineNumerator != null) {
    ProcessCoroutine.control.StopTimer(this);
    this.coroutineNumerator = null;
    this.onProcess = false;
    return true;
   }
   else return false;
  }
 
  public IEnumerator numerator {
   get {
    this.coroutineNumerator = this.LocalCoroutine();
    return this.coroutineNumerator;
   }
  }
 
  public double getTime {
   get {
    if (this.onProcess)
      return System.Math.Round((double)(Time.time - this.timeSinceStart), 2);
    else
     return 0d;
   }
  }
 
  private IEnumerator LocalCoroutine() {
   this.onProcess = true;
   if (this.repeatTimer == false) {
    this.timeSinceStart = (double)Time.time;
    yield return new WaitForSeconds(this.expired);
    if (this.breakAfterDone) {
     this.breakAfterDone = false;
     yield break;
    } else CastDone();
    if (this.repeatTimer == false) yield break;
   }
   
   while(this.repeatTimer && this.breakAfterDone == false) {
    this.onProcess = true;
    this.timeSinceStart = (double)Time.time;
    yield return new WaitForSeconds(this.expired);
    if (this.breakAfterDone) {
     this.breakAfterDone = false;
     yield break;
    } else CastDone();
   }
  }
 
  private void CastDone() {
   this.onProcess = false;
   this.timesFinished += 1;
   if (this.function != null) function(this);
   if (this.repeatTimer == false) this.Dispose();
   else if (this.repeatTimes > 0 && this.timesFinished >= this.repeatTimes) this.Dispose();
  }
 
  public System.Action<ProcessTimer> action {
   get { return this.function;}
   set { if (value != null) this.function = value;}
  }
 
  public float expired {
   get { return this.timerTime;}
   set { this.timerTime = (value < 0) ? 0 : value;}
  }
 
  public void Dispose() {
   this.breakAfterDone = true;
   this.coroutineNumerator = null;
   this.onProcess = false;
   ProcessCoroutine.control.StopTimer(this);
  }
 
  private ProcessTimer() {}
 
 }
 
}
lwe
UNITрон
 
Сообщения: 261
Зарегистрирован: 24 авг 2014, 14:20
Skype: lawsonilka


Вернуться в Уроки

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 3