Я уже некоторое время работаю над 2-й RPG и понял, что принял несколько плохих дизайнерских решений. В частности, есть несколько вещей, которые вызывают у меня проблемы, поэтому мне было интересно, какие конструкции использовали другие люди, чтобы их преодолеть или использовать.
Для небольшой предыстории я начал работать над этим в свободное время прошлым летом. Я изначально делал игру на C #, но около 3 месяцев назад решил перейти на C ++. Я хотел получить хорошее представление о C ++, так как прошло много времени с тех пор, как я активно использовал его, и подумал, что такой интересный проект будет хорошим мотиватором. Я широко использую библиотеку наддува и использую SFML для графики и FMOD для аудио.
У меня есть немало написанного кода, но я собираюсь отказаться от него и начать все сначала.
Вот основные проблемные области, которые у меня есть, и я хотел бы получить некоторые мнения о том, как другие решили или решили бы их.
1. Циклические зависимости Когда я делал игру на C #, мне не пришлось об этом беспокоиться, так как это не проблема. Переход на C ++, это стало довольно серьезной проблемой и заставило меня думать, что я, возможно, спроектировал вещи неправильно. Я не могу себе представить, как разделить мои классы, и при этом заставить их делать то, что я хочу. Вот несколько примеров цепочки зависимостей:
У меня есть статус эффекта класса. В классе есть несколько методов (Apply / Unapply, Tick и т. Д.) Для применения его эффектов к персонажу. Например,
virtual void TickCharacter(Character::BaseCharacter* character, Battles::BattleField *field, int ticks = 1);
Эти функции будут вызываться каждый раз, когда персонаж, на которого наложен эффект статуса, принимает ход. Он будет использоваться для реализации таких эффектов, как Regen, Poison и т. Д. Однако он также вводит зависимости от класса BaseCharacter и класса BattleField. Естественно, класс BaseCharacter должен отслеживать, какие эффекты состояния в настоящее время активны для них, так что это циклическая зависимость. Battlefield должен отслеживать сражающиеся стороны, а у класса участников есть список BaseCharacters, представляющий другую циклическую зависимость.
2 - События
В C # я широко использовал делегатов для привязки к событиям на персонажах, полях сражений и т. Д. (Например, был делегат для изменения состояния персонажа, изменения статистики, добавления / удаления эффекта состояния и т. Д. .) и поле битвы / графические компоненты будут подключаться к этим делегатам для усиления их эффектов. В C ++ я сделал нечто подобное. Очевидно, что нет прямого эквивалента делегатам C #, поэтому вместо этого я создал что-то вроде этого:
typedef boost::function<void(BaseCharacter*, int oldvalue, int newvalue)> StatChangeFunction;
и в моем классе персонажей
std::map<std::string, StatChangeFunction> StatChangeEventHandlers;
всякий раз, когда менялась статистика персонажа, я повторял и вызывал каждую функцию StatChangeFunction на карте. Хотя это работает, я боюсь, что это плохой подход к выполнению задач.
3 - Графика
Это большая вещь. Это не связано с графической библиотекой, которую я использую, но скорее концептуально. В C # я сочетал графику со многими своими уроками, что, как я знаю, ужасная идея. Желая сделать это развязанным на этот раз, я попробовал другой подход.
Чтобы реализовать мою графику, я представлял всю графику, связанную с игрой, как серию экранов. То есть есть экран заголовка, экран статуса персонажа, экран карты, экран инвентаря, экран битвы, экран графического интерфейса битвы, и, в принципе, я мог бы размещать эти экраны поверх друг друга по мере необходимости для создания игровой графики. Каким бы ни был активный экран, он обладает входом в игру.
Я разработал менеджер экранов, который будет выдвигать и выдвигать экраны на основе пользовательского ввода.
Например, если вы находитесь на экране карты (обработчик ввода / визуализатор для карты листов) и нажали кнопку «Пуск», это вызовет диспетчер экрана, чтобы переместить экран главного меню на экран карты и отметить карту. экран не будет нарисован / обновлен. Проигрыватель будет перемещаться по меню, которое будет выдавать дополнительные команды диспетчеру экрана, в зависимости от ситуации, чтобы вставить новые экраны в стек экрана, а затем выскочит их по мере того, как пользователь изменяет экраны / отменяет их. Наконец, когда игрок выходит из главного меню, я выскакиваю его и возвращаюсь к экрану карты, отмечаю, что его нужно рисовать / обновлять, и оттуда идти.
Боевые экраны были бы более сложными. У меня был бы экран в качестве фона, экран для визуализации каждой стороны в битве и экран для визуализации интерфейса битвы. Пользовательский интерфейс будет подключаться к символьным событиям и использовать их, чтобы определить, когда обновлять / перерисовывать компоненты пользовательского интерфейса. Наконец, каждая атака, имеющая доступный сценарий анимации, будет вызывать дополнительный слой, чтобы анимировать себя перед тем, как выскочить из экрана. В этом случае каждый слой последовательно помечается как рисуемый и обновляемый, и я получаю стек экранов, обрабатывающих мою боевую графику.
Хотя я еще не смог заставить менеджер экрана работать идеально, я думаю, что смогу со временем. У меня вопрос по этому поводу: стоит ли вообще этого делать? Если это плохой дизайн, я хочу знать сейчас, прежде чем тратить слишком много времени на создание всех необходимых мне экранов. Как вы строите графику для своей игры?