При создании игр разработчики часто сталкиваются с такой задачей — какое событие должно быть выполнено, но не в данный момент, а спустя какое-то время. Решается эта задача по разному. Чаще всего у игровых объектов присутствует собственный внутренний таймер, и нужную задержку можно реализовать с его помощью (добавив лишний код объекту). Но иногда нужно сделать отложенный вызов метода у объекта, не имеющего собственного таймера, скажем скрыть окно, строку, показать иконку или эффектик спустя некоторое время, сделать что-то ещё, но не прямо сейчас, а с задержкой. Однократно или несколько раз.
Вот для таких и подобных целей моим коллегой была разработана универсальная система, которую он назвал Functor Manager (возможно для названия подобных систем есть устоявшийся другой термин, я не знаю, буду рад если подскажут).
Изначально код был написан на C++, и использовался в наших прошлых проектах, сейчас у нас проект на C#, поэтому реализацию приведу на C#. Краткая концепция и код реализации (C#) под катом.
Фанктор — это классик, который помнит, через сколько времени и какую функцию надо вызвать.
Итак, собственно фанктор состоит из:
Guid — уникального идентификатора, который генерируется при создании фанктора. Идентификатор этот нужен чтобы можно было удалить фанктор в случае ненадобности.
_deltaTime — внутренний таймер. Туда записывается время, через которое должен произойти вызов функции.
_func — указатель на функцию, код которой должен быть выполнен по истечению времени таймера. Функция возвращает значение — через какой период (в секундах) фанктор должен повторить вызов метода. В случае, если функция вернула значение 0.0f, то фанктор прибивается.
_funcArg — аргумент функции. Сюда можно передать скажем указатель на объект, или любую другую нужную информацию.
В коде фанктора всё просто — конструкторы, заполняющие поля, и единственный метод, который стоит пояснения — это метод Process. Счётчик уменьшается на количество тиков, если достиг нуля — вызывается функция. Возвращаемое значение присваивается таймеру.
Код фанктора:
Фанктор менеджер — это класс, синглтон, хранящий список фанкторов (на самом деле 2 списка, об этом подробнее ниже).
Итак, в менеджере есть 2 списка, один из которых активный на данный момент, а второй заполняется вновь добавленными фанкторами, в том числе и добавленными на момент исполнения активного списка. Так же есть переменная _currentIndex, она и определяется какой из списков сейчас активен.
Большинство методов так же интуитивно понятны (AddFunctor(...) — добавляет фанктор в список, RemoveFunctor() — удаляет из списка). Еднственное, что стоит пояснения — это метод ProcessFunctors(float delta). Берётся текущий список, у каждого фанктора вызывается метод Process, чтобы он отсчитал время и если надо — выполнился, если фанктор вернул ненулевое значение (то есть время, через которое его надо вызвать повторно) — то он добавляется в новый список. В конце активный список очищается.
Код менеджера:
Интеграция в проект производится 1 строкой. Нужно добавить вызов
HOFunctorMgr.Instance.ProcessFunctors(delta);
в игровом цикле, естественно предварительно добавив класс фанктор менеджера в проект. В моём случае (движок NeoAxis) это метод protected override void OnTick(float delta) игрового окна.
delta — это время в секундах, прошедшее с предыдущего вызова.
Всё, так просто.
Однократный вызов:
Через 2 секунды вызовется HidePuzzleWindow() и скроется окно с головоломкой.
Многократный вызов:
5 раз вызовется фанктор, выводящий на экран слово test, с интервалом в 5 секунд каждый раз. Интервал, кстате, можно менять.
Вызов статичного метода:
Через 10 секунд вызовется статичный метод MyMethod и ему в качестве аргумента будет передан someObject.
Вы можете модифицировать и/или использовать код в ваших проектах, как некоммерческих, так и коммерческих.
Код класса, неймспейс заточен для NeoAxis-а.
Вот для таких и подобных целей моим коллегой была разработана универсальная система, которую он назвал Functor Manager (возможно для названия подобных систем есть устоявшийся другой термин, я не знаю, буду рад если подскажут).
Изначально код был написан на C++, и использовался в наших прошлых проектах, сейчас у нас проект на C#, поэтому реализацию приведу на C#. Краткая концепция и код реализации (C#) под катом.
Functor
Фанктор — это классик, который помнит, через сколько времени и какую функцию надо вызвать.
Итак, собственно фанктор состоит из:
Guid — уникального идентификатора, который генерируется при создании фанктора. Идентификатор этот нужен чтобы можно было удалить фанктор в случае ненадобности.
_deltaTime — внутренний таймер. Туда записывается время, через которое должен произойти вызов функции.
_func — указатель на функцию, код которой должен быть выполнен по истечению времени таймера. Функция возвращает значение — через какой период (в секундах) фанктор должен повторить вызов метода. В случае, если функция вернула значение 0.0f, то фанктор прибивается.
_funcArg — аргумент функции. Сюда можно передать скажем указатель на объект, или любую другую нужную информацию.
В коде фанктора всё просто — конструкторы, заполняющие поля, и единственный метод, который стоит пояснения — это метод Process. Счётчик уменьшается на количество тиков, если достиг нуля — вызывается функция. Возвращаемое значение присваивается таймеру.
Код фанктора:
public delegate float Func(object funcArg); public class HOFunctor { float _deltaTime = 0; Func _func = null; object _funcArg = null; Guid _id = Guid.Empty; public HOFunctor(float deltaTime, Func func, object funcArg) { _id = Guid.NewGuid(); _deltaTime = deltaTime; _func = func; _funcArg = funcArg; } public HOFunctor(float deltaTime, Func func) : this(deltaTime, func, null) { } public bool Process(float deltaTime) { if (_func != null) { _deltaTime -= deltaTime; if (_deltaTime <= 0) { _deltaTime = _func(_funcArg); } } return _deltaTime > 0; } public Guid ID { get { return _id; } } }
Functor Manager
Фанктор менеджер — это класс, синглтон, хранящий список фанкторов (на самом деле 2 списка, об этом подробнее ниже).
Итак, в менеджере есть 2 списка, один из которых активный на данный момент, а второй заполняется вновь добавленными фанкторами, в том числе и добавленными на момент исполнения активного списка. Так же есть переменная _currentIndex, она и определяется какой из списков сейчас активен.
Большинство методов так же интуитивно понятны (AddFunctor(...) — добавляет фанктор в список, RemoveFunctor() — удаляет из списка). Еднственное, что стоит пояснения — это метод ProcessFunctors(float delta). Берётся текущий список, у каждого фанктора вызывается метод Process, чтобы он отсчитал время и если надо — выполнился, если фанктор вернул ненулевое значение (то есть время, через которое его надо вызвать повторно) — то он добавляется в новый список. В конце активный список очищается.
Код менеджера:
public class HOFunctorMgr { #region Private fields private static HOFunctorMgr _instance = null; protected Dictionary<Guid, HOFunctor>[] _functors = { new Dictionary<Guid, HOFunctor>(), new Dictionary<Guid, HOFunctor>() }; int _currentIndex = 0; private HOFunctorMgr() { } #endregion public static HOFunctorMgr Instance { get { if (_instance == null) { _instance = new HOFunctorMgr(); } return _instance; } } #region Public methods public Guid AddFunctor(float deltaTime, Func func, object funcArg) { return AddFunctor(new HOFunctor(deltaTime, func, funcArg)); } public Guid AddFunctor(float deltaTime, Func func) { return AddFunctor(new HOFunctor(deltaTime, func, null)); } public Guid AddFunctor(HOFunctor functor) { if (functor != null && !_functors[_currentIndex].ContainsKey(functor.ID)) { _functors[_currentIndex].Add(functor.ID, functor); return functor.ID; } return Guid.Empty; } public void ProcessFunctors(float delta) { int indexToProcess = _currentIndex; _currentIndex ^= 1; foreach (HOFunctor f in _functors[indexToProcess].Values) { if (f.Process(delta)) { AddFunctor(f); } } _functors[indexToProcess].Clear(); } public void RemoveFunctor(Guid id) { if (_functors[0].ContainsKey(id)) { _functors[0].Remove(id); } if (_functors[1].ContainsKey(id)) { _functors[1].Remove(id); } } #endregion }
Интеграция в проект
Интеграция в проект производится 1 строкой. Нужно добавить вызов
HOFunctorMgr.Instance.ProcessFunctors(delta);
в игровом цикле, естественно предварительно добавив класс фанктор менеджера в проект. В моём случае (движок NeoAxis) это метод protected override void OnTick(float delta) игрового окна.
delta — это время в секундах, прошедшее с предыдущего вызова.
Всё, так просто.
Примеры использования
Однократный вызов:
GameEntities.HOFunctorMgr.Instance.AddFunctor(2.0f, arg => { HidePuzzleWindow(); return 0.0f; }, null );
Через 2 секунды вызовется HidePuzzleWindow() и скроется окно с головоломкой.
Многократный вызов:
... int k = 5; HOFunctorMgr.Instance.AddFunctor(5, arg => { GameMap.Instance.AddScreenMessage(string.Format("{0}", arg)); if (k-- >= 0) { return 5; } return 0; }, "test");
5 раз вызовется фанктор, выводящий на экран слово test, с интервалом в 5 секунд каждый раз. Интервал, кстате, можно менять.
Вызов статичного метода:
static float MyMethod(object arg) { if (arg != null) { ... } return 0; } HOFunctorMgr.Instance.AddFunctor(10, MyMethod, someObject);
Через 10 секунд вызовется статичный метод MyMethod и ему в качестве аргумента будет передан someObject.
О использовании
Вы можете модифицировать и/или использовать код в ваших проектах, как некоммерческих, так и коммерческих.
Полный код класса
Код класса, неймспейс заточен для NeoAxis-а.
using System; using System.Collections.Generic; using System.Text; namespace GameEntities { public delegate float Func(object funcArg); public class HOFunctor { float _deltaTime = 0; Func _func = null; object _funcArg = null; Guid _id = Guid.Empty; public HOFunctor(float deltaTime, Func func, object funcArg) { _id = Guid.NewGuid(); _deltaTime = deltaTime; _func = func; _funcArg = funcArg; } public HOFunctor(float deltaTime, Func func) : this(deltaTime, func, null) { } public bool Process(float deltaTime) { if (_func != null) { _deltaTime -= deltaTime; if (_deltaTime <= 0) { _deltaTime = _func(_funcArg); } } return _deltaTime > 0; } public Guid ID { get { return _id; } } } public class HOFunctorMgr { #region Private fields private static HOFunctorMgr _instance = null; protected Dictionary<Guid, HOFunctor>[] _functors = { new Dictionary<Guid, HOFunctor>(), new Dictionary<Guid, HOFunctor>() }; int _currentIndex = 0; private HOFunctorMgr() { } #endregion public static HOFunctorMgr Instance { get { if (_instance == null) { _instance = new HOFunctorMgr(); } return _instance; } } #region Public methods public Guid AddFunctor(float deltaTime, Func func, object funcArg) { return AddFunctor(new HOFunctor(deltaTime, func, funcArg)); } public Guid AddFunctor(float deltaTime, Func func) { return AddFunctor(new HOFunctor(deltaTime, func, null)); } public Guid AddFunctor(HOFunctor functor) { if (functor != null && !_functors[_currentIndex].ContainsKey(functor.ID)) { _functors[_currentIndex].Add(functor.ID, functor); return functor.ID; } return Guid.Empty; } public void ProcessFunctors(float delta) { int indexToProcess = _currentIndex; _currentIndex ^= 1; foreach (HOFunctor f in _functors[indexToProcess].Values) { if (f.Process(delta)) { AddFunctor(f); } } _functors[indexToProcess].Clear(); } public void RemoveFunctor(Guid id) { if (_functors[0].ContainsKey(id)) { _functors[0].Remove(id); } if (_functors[1].ContainsKey(id)) { _functors[1].Remove(id); } } #endregion } }
