При создании игр перед разработчиками часто возникает следующая задача: какое событие должно завершиться не в данный момент, а через какое-то время.
Эта проблема решается разными способами.
Чаще всего игровые объекты имеют собственный внутренний таймер, и нужную задержку можно реализовать с его помощью (добавив к объекту дополнительный код).
Но иногда нужно сделать отложенный вызов метода на объекте, не имеющем своего таймера, скажем, скрыть окно, строку, показать иконку или эффект через какое-то время, сделать что-то еще, но не прямо сейчас, но с задержкой.
Один или несколько раз.
Для таких и подобных целей мой коллега разработал универсальную систему, которую назвал Functor Manager (возможно, есть другой устоявшийся термин для названия таких систем, не знаю, буду рад, если мне подскажут).
Изначально код был написан на C++ и использовался в наших прошлых проектах, сейчас у нас есть проект на C#, поэтому я приведу реализацию на C#.
Краткая концепция и код реализации (C#) под катом.
Функтор
Фанктор — это классика, которая запоминает, через какое время и какую функцию следует вызвать.Итак, сам фактор состоит из: Руководство — уникальный идентификатор, который генерируется при создании фанктора.
Этот идентификатор нужен для того, чтобы фанктор можно было удалить, если он больше не нужен.
_deltaTime - внутренний таймер.
Там записано время, через которое должен произойти вызов функции.
_функ — указатель на функцию, код которой должен выполниться по истечении таймера.
Функция возвращает значение — через какой период (в секундах) фанктор должен повторить вызов метода.
Если функция вернула значение 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; } } }
Менеджер функторов
Менеджер фанкторов — это класс, синглтон, который хранит список фанкторов (на самом деле 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(дельта); в игровом цикле, естественно, предварительно добавив в проект класс менеджера фанкторов.
В моем случае (движок NeoAxis) это защищенный метод переопределения void OnTick(float 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 раз, отображая на экране тест слова с интервалом в 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
}
}
Теги: #gamedev #менеджер функторов #советы и подсказки #Разработка игр
-
Дельта
19 Oct, 24 -
Краткость В Описании
19 Oct, 24 -
Ипотека Для Гражданского Брака
19 Oct, 24