При создании игр разработчики часто сталкиваются с такой задачей — какое событие должно быть выполнено, но не в данный момент, а спустя какое-то время. Решается эта задача по разному. Чаще всего у игровых объектов присутствует собственный внутренний таймер, и нужную задержку можно реализовать с его помощью (добавив лишний код объекту). Но иногда нужно сделать отложенный вызов метода у объекта, не имеющего собственного таймера, скажем скрыть окно, строку, показать иконку или эффектик спустя некоторое время, сделать что-то ещё, но не прямо сейчас, а с задержкой. Однократно или несколько раз.
Вот для таких и подобных целей моим коллегой была разработана универсальная система, которую он назвал 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
}
}