Хорошо: я довольно плохо знаком с C++ и статическими языками на целом. При прибытии с лет рубина (и другие динамические языки) я не знаю, возможно ли это.
Я делал игровую систему состояния для... хорошо игры. Я хочу сделать систему легкой для меня вырезать и вставить в другие игры ни с кем (или очень немногие) изменения.
Этими двумя вещами, которые я желаю улучшить, является путь, в который состояния переключаются и путь, которым сохранены указатели состояния.
Могло быть любое количество состояний, но всегда будет по крайней мере 2 - 3 состояния, активные в памяти.
Уродство № 1.
В настоящее время у меня есть класс менеджера состояния с чем-то вроде этого в нем:
void StateManager::changeState(StateID nextStateID)
{
// UNFOCUS THE CURRENT STATE //
if (currentState())
{
currentState()->onUnFocus();
// DESTROY THE STATE IF IT WANTS IT //
if(currentState()->isDestroyedOnUnFocus()) {
destroyCurrentState();
}
}
if (m_GameStates[nextStateID]) {
// SWITCH TO NEXT STATE //
setCurrentState(nextStateID);
}
else
{
// CREATE NEW STATE //
switch (nextStateID)
{
case MainMenuStateID:
m_GameStates[MainMenuStateID] = new MainMenuState;
break;
case GameStateID:
m_GameStates[MainMenuStateID] = new GameStates;
break;
};
setCurrentState(nextStateID);
}
// FOCUS NEXT STATE //
currentState()->onFocus();
}
Этот подход работает, но я не чувствую, что это очень хорошо.
Действительно ли возможно передать тип? И затем назовите новыми на нем?
new NextGameState; // Whatever type that may be.
poloymophism может помочь здесь? Все состояния получены из a class State
.
Уродство № 2.
Другая вещь я думаю, нуждается в некотором улучшении, способ, которым я хранил состояния.
State* m_GameStates[MaxNumberOfStates];
Все состояния инициализируются к ПУСТОМУ УКАЗАТЕЛЮ, таким образом, я могу протестировать, если состояние там, и если не это создает тот при необходимости.
Это работает хорошо, как я могу назвать текущее состояние:
m_GameStates[m_CurrentState];
Однако мне не нравится это по двум причинам. Кажется чем-то вроде отходов, имеющих массив, полный Нулевых указателей, когда только будет 2 или трехочковые, активные в любой момент. [Примечание редактора: какова вторая причина?]
Я думал о смещении этого в a vector_ptr
, но не сделал, поскольку это создаст дополнительные сложности с проверкой, чтобы видеть, существует ли состояние. И вектор, кажется, укрепляет Уродство № 1. поскольку у меня должен быть список для проверки каждого состояния.
Любой совет или направление ценятся.
Спасибо, Phil.
Для вашей первой проблемы, да, вы можете передать тип с некоторыми оговорками.
Я добавил комментарий к вашему вопросу с просьбой предоставить дополнительную информацию. Пока мы этого не добьемся, я не могу сказать , как это должно быть сделано, но почитайте о шаблонах.
Вы можете создать шаблон функции, которому можно передать тип, например, следующим образом:
template <typename T>
void Foo() {
T* x = new T();
...
}
Foo<int>() // call Foo with the type T set to 'int'
Здесь есть некоторые ограничения, так как типы должны быть указаны во время компиляции, но это очень мощный языковая особенность.
Другой вариант, который может работать лучше, поскольку у вас есть связь между переменной ( MainState
) и типом ( MainMenu
), может быть использование классов признаков. Опять же, я не уверен, как именно это будет сделано в вашем случае, поскольку мы не видели всю функцию (в частности, какой тип MainState
, и как / когда это created?)
Также возможно решить проблему с помощью полиморфизма, но опять же, мне нужно увидеть немного больше контекста, чтобы предложить решение.
Для решения второй проблемы вы можете использовать стандартную библиотеку map
:
#include <map>
// I'm not sure what type m_CurrentState is, so use its type instead of KeyType below
std::map<KeyType, State*> m_GameStates;
// and to perform a lookup in the map:
GameStates[m_CurrentState];
И, наконец, очень важный совет:
Прекратите использовать указатели повсюду. Прекратите вызывать новый
для создания новых объектов.
Как правило, объекты должны создаваться в стеке (вместо Foo * f = new Foo;
просто выполните Foo f;
И вместо использования указателей вы часто хотите просто скопировать сам объект или создать ссылки вместо указателей.
И когда вам действительно нужно использовать динамическое распределение памяти, вы по-прежнему не должны использовать new
напрямую. Вместо этого создайте объект-оболочку, который внутренне выделяет то, что ему нужно, с помощью new
в своем конструкторе и снова освобождает его в деструкторе.
Если вы сделаете это правильно, это в значительной степени решит все проблемы управления памятью.
Общая методика называется RAII .
Как только вы говорите "Состояния", я думаю о схеме состояний .
По сути, вы можете получить множество объектов из базового класса State. Все действия, связанные с состоянием, происходят против текущего состояния, поддерживаемого менеджером состояний. Состояния будут переходить из состояния в состояние через менеджера.
Например, у вас может быть состояние Paused и Unpaused, каждое из которых имеет событие buttonPressed. Когда вы нажимаете кнопку, текущее состояние доставляется событию. Если он находится в режиме «Приостановлено», а кнопка была кнопкой паузы, перейдите к «Без паузы». И наоборот для Unpaused.
void StateManager::changeState(StateID nextStateID)
{
leaveState(actualState);
enterState(nextStateID);
}
Мне очень нравится этот - настолько простой, насколько это возможно. ; -)
Что я хочу вам сказать - я думаю, что создание / удаление вашей статистики в функции changeState слишком логично - это просто должно изменить состояние, верно?
Edit: Чтобы перейти к вашим двум вопросам - я не думаю, что использование этого массива действительно бесполезно - вы говорите о трех полях, а не о 300 или около того. Так что если вам нравится использовать массивы - дерзайте. Если вы этого не сделаете, я выберу карту, она упрощает задачу, если вы хотите проверить, создано ли состояние или нет, и вы не ограничены магическим числом «maxStates». Вы можете проверить, достаточно ли оперативной памяти, а затем создать состояния X, а не фиксированные 2-3.
Для генерации состояний вам нужна фабрика. Таким образом, идентификатор состояния остается универсальным. Для хранения состояний я бы использовал std :: map
Используйте перечисление (eration) для определения всех возможных состояний (это что-то вроде списка с константами). Просто создайте для одного объекта одну переменную, которая хранит состояние, и меняйте ее всякий раз, когда вам нужно ее изменить.