Консультации по игровой архитектуре / шаблонам дизайна

16

Я уже некоторое время работаю над 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 # я сочетал графику со многими своими уроками, что, как я знаю, ужасная идея. Желая сделать это развязанным на этот раз, я попробовал другой подход.

Чтобы реализовать мою графику, я представлял всю графику, связанную с игрой, как серию экранов. То есть есть экран заголовка, экран статуса персонажа, экран карты, экран инвентаря, экран битвы, экран графического интерфейса битвы, и, в принципе, я мог бы размещать эти экраны поверх друг друга по мере необходимости для создания игровой графики. Каким бы ни был активный экран, он обладает входом в игру.

Я разработал менеджер экранов, который будет выдвигать и выдвигать экраны на основе пользовательского ввода.

Например, если вы находитесь на экране карты (обработчик ввода / визуализатор для карты листов) и нажали кнопку «Пуск», это вызовет диспетчер экрана, чтобы переместить экран главного меню на экран карты и отметить карту. экран не будет нарисован / обновлен. Проигрыватель будет перемещаться по меню, которое будет выдавать дополнительные команды диспетчеру экрана, в зависимости от ситуации, чтобы вставить новые экраны в стек экрана, а затем выскочит их по мере того, как пользователь изменяет экраны / отменяет их. Наконец, когда игрок выходит из главного меню, я выскакиваю его и возвращаюсь к экрану карты, отмечаю, что его нужно рисовать / обновлять, и оттуда идти.

Боевые экраны были бы более сложными. У меня был бы экран в качестве фона, экран для визуализации каждой стороны в битве и экран для визуализации интерфейса битвы. Пользовательский интерфейс будет подключаться к символьным событиям и использовать их, чтобы определить, когда обновлять / перерисовывать компоненты пользовательского интерфейса. Наконец, каждая атака, имеющая доступный сценарий анимации, будет вызывать дополнительный слой, чтобы анимировать себя перед тем, как выскочить из экрана. В этом случае каждый слой последовательно помечается как рисуемый и обновляемый, и я получаю стек экранов, обрабатывающих мою боевую графику.

Хотя я еще не смог заставить менеджер экрана работать идеально, я думаю, что смогу со временем. У меня вопрос по этому поводу: стоит ли вообще этого делать? Если это плохой дизайн, я хочу знать сейчас, прежде чем тратить слишком много времени на создание всех необходимых мне экранов. Как вы строите графику для своей игры?

user127817
источник

Ответы:

15

В целом, я бы не сказал, что все, что вы перечислили, должно привести к отказу системы и запуску заново Это то, что каждый программист хочет сделать на 50-75% пути любого проекта, над которым он работает, но это ведет к бесконечному циклу разработки и никогда не заканчивает ничего. Таким образом, с этой целью некоторые обратная связь по каждому разделу.

  1. Это может быть проблемой, но обычно больше раздражает, чем что-либо еще. Используете ли вы один раз #pragma или #ifndef MY_HEADER_FILE_H #define MY_HEADER_FILE_H ... #endif вверху или окружаете ваши файлы .h соответственно? Таким образом .h файл существует только один раз в каждой области? Если да, то я рекомендую удалить все операторы #include и собрать их, добавив их по мере необходимости, чтобы снова скомпилировать игру.

  2. Я фанат таких систем и не вижу в этом ничего плохого. Что такое событие в C #, обычно заменяется системой событий или системой обмена сообщениями (здесь можно найти вопросы для получения дополнительной информации). Ключевым моментом здесь является сведение их к минимуму, когда что-то должно произойти, что уже звучит так, как будто вы это делаете, не должно вызывать здесь минимальных забот.

  3. Это также кажется мне правильным и это то, что я делаю для своих собственных двигателей, как лично, так и профессионально. Это превращает систему меню в систему состояний, в которой либо есть корневое меню (до начала игры), либо HUD игрока в качестве отображаемого «корневого» экрана, в зависимости от того, как вы его настроили.

Подводя итог, я не вижу ничего достойного перезапуска в том, с чем вы сталкиваетесь. Возможно, вы захотите более официальную замену системы событий в будущем, но это произойдет вовремя. Циклические включения - это препятствие, с которым все программисты на C / C ++ постоянно сталкиваются, и работа по разделению графики кажется логичным «следующим шагом».

Надеюсь это поможет!

Джеймс
источник
#ifdef не помогает циклически включать проблемы.
Коммунистическая утка
Просто покрывал мою базу, ожидая, что она будет там, прежде чем выследить циклические включения. Может быть совершенно другой чайник рыбы, когда у вас есть несколько символов, а не файл, который должен включать файл, который включает себя. (хотя из того, что он описал, если включения находятся в файлах .CPP, а не в файлах .H, он должен быть в порядке с двумя базовыми объектами, знающими друг о друге)
Джеймс
Спасибо за совет :) Рад знать, что я на правильном пути
user127817
4

Ваши циклические зависимости не должны быть проблемой, если вы будете заранее объявлять классы в заголовочных файлах и включать их в .cpp (или любые другие) файлы.

Для системы событий, два предложения:

1) Если вы хотите сохранить шаблон, который используете сейчас, подумайте о переключении на boost :: unordered_map вместо std :: map. Отображение строк в качестве ключей медленное, тем более что .NET делает некоторые полезные вещи, чтобы помочь ускорить процесс. Использование unordered_map хеширует строки, поэтому сравнение обычно происходит быстрее.

2) Подумайте о переключении на что-то более мощное, например, boost :: сигналы. Если вы сделаете это, вы можете сделать приятные вещи, например, сделать ваши игровые объекты отслеживаемыми, производными от boost :: signal :: trackable, и позволить деструктору позаботиться об очистке всего, вместо того, чтобы вручную отменить регистрацию в системе событий. Вы также можете иметь несколько сигналов , указывающих на каждый слот (или наоборот, я не помню точную номенклатуру) , так что это очень похоже делать +=на delegateв C #. Самая большая проблема с boost :: signal заключается в том, что он должен быть скомпилирован, а не просто заголовками, поэтому, в зависимости от вашей платформы, может возникнуть боль при запуске и запуске.

тетрада
источник