Поради щодо ігрової архітектури / моделей дизайну


16

Я деякий час працюю над 2d 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 повинен вести облік бойових сторін, і партійний клас має список базових характеристик, що вводить ще одну циклічну залежність.

2 - Події

У C # я широко використовував делегатів, щоб підключитися до подій на персонажах, полях бою тощо (наприклад, був делегат, коли змінювалося здоров'я персонажа, коли змінювалася статистика, коли додавались / видалялися ефект стану тощо) .) і графічні компоненти поля битви причепляться до цих делегатів для забезпечення їх ефектів. У C ++ я зробив щось подібне. Очевидно, що немає прямого еквівалента делегатам C #, тому натомість я створив щось подібне:

typedef boost::function<void(BaseCharacter*, int oldvalue, int newvalue)> StatChangeFunction;

і в моєму класі персонажів

std::map<std::string, StatChangeFunction> StatChangeEventHandlers;

всякий раз, коли статистика персонажа змінюється, я б повторював і закликав кожну StatChangeFunction на карті. Хоча це працює, я переживаю, що це поганий підхід до виконання справ.

3 - Графіка

Це велика річ. Це не пов’язано з графічною бібліотекою, яку я використовую, але це більше концептуальна річ. У C # я поєднав графіку з багатьма своїми класами, про які я знаю, це жахлива ідея. Бажаючи зробити це нероздільно, цього разу я спробував інший підхід.

Для того, щоб реалізувати свою графіку, я уявляв все, що стосується гри, як серія екранів. Тобто є заголовок, екран статусу символів, екран карти, екран інвентаря, бойовий екран, бойовий графічний інтерфейс, і я в основному можу скласти ці екрани один на одного, як потрібно для створення ігрової графіки. Незалежно від активного екрана є вхід гри.

Я створив диспетчер екранів, який буде натискати та з'являтися на екрані на основі введення користувача

Наприклад, якщо ви знаходилися на екрані карти (обробник вводу / візуалізатор для карти плитки) і натискали кнопку запуску, він надішле дзвінок менеджеру екрана, щоб натиснути екран головного меню через екран карти та позначити карту екран не малювати / оновлювати. Гравець буде орієнтуватися по меню, яке видасть більше команд керуючий екраном, як це підходить для натискання нових екранів на стек екрана, а потім з'являється, коли користувач змінює екрани / скасовує. Нарешті, коли гравець виходить з головного меню, я вискочу його та повернусь до екрана карти, зауважую, що його буде намальовано / оновлено та йду звідти.

Бойові екрани були б складнішими. У мене був би екран, який виступав би як фон, екран для візуалізації кожної сторони в битві та екран для візуалізації інтерфейсу користувача для битви. Користувальницький інтерфейс підключається до символьних подій і використовує ті, щоб визначити, коли потрібно оновлювати / перемальовувати компоненти інтерфейсу. Нарешті, кожна атака, яка має доступний сценарій анімації, вимагатиме додаткового шару для анімації перед тим, як вискочити з екрану. У цьому випадку кожен шар послідовно позначається як графічний і оновлений, і я отримую стек екранів, що обробляють мою бойову графіку.

Поки мені ще не вдалося змусити менеджера екранів працювати бездоганно, я думаю, що можу з певним часом. Моє запитання щодо цього, чи взагалі такий гідний підхід? Якщо це поганий дизайн, я хочу знати вже зараз, перш ніж вкладати занадто багато часу, роблячи всі екрани, які мені знадобляться. Як ви збираєте графіку для своєї гри?

Відповіді:


15

В цілому я б не сказав, що все, що ви перерахували, повинно призвести до того, що ви бракуєте систему та починаєте спочатку. Це те, що кожен програміст хоче пройти приблизно 50-75% шляху через будь-який проект, над яким він працює, але це призводить до нескінченного циклу розвитку і ніколи нічого не закінчуючи. Отже, для цього деякі відгуки про кожен розділ.

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

  2. Я шанувальник таких типів систем і не бачу в цьому нічого поганого. Що таке подія в C #, зазвичай замінюється системою подій або системою обміну повідомленнями (тут можна знайти питання, щоб дізнатися більше про ці речі). Головне тут - звести їх до мінімуму, коли речі повинні статися, що вже звучить так, як ви це робите, не повинно бути тут мінімальними турботами.

  3. Це також здається мені на правильному шляху, і це я роблю для власних двигунів, як особисто, так і професійно. Це перетворює систему меню в систему стану, яка або має кореневе меню (перед початком гри), або HUD програвача, як відображається екран «root», залежно від способу його налаштування.

Отже, підводячи підсумок, я не бачу нічого гідного перезапуску у тому, у що ви стикаєтесь. Ви можете захотіти більш офіційну заміну системи подій у дорозі, але це прийде вчасно. Циклічне включення - це перешкода, через яку всі програмісти на C / C ++ повинні постійно проскакувати, а робота з роз'єднання графіки - все це як логічне "наступні кроки".

Сподіваюся, це допомагає!


#ifdef не допомагає круговим включити проблеми.
Качка комуніста

Просто покривав мою основу, розраховуючи, що буде там, перш ніж відстежувати циклічне включення. Це може бути цілий інший чайник з рибою, коли у вас є кілька визначень символів на відміну від файлу, який повинен містити файл, що включає сам. (хоча з того, що він описав, якщо включається у файли .CPP, а не у файли .H, у нього повинно бути гаразд з двома базовими об'єктами, що знають один про одного)
Джеймс,

Дякую за пораду :) Радий знати, що я на правильному шляху
користувач127817

4

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

Для системи заходів два пропозиції:

1) Якщо ви хочете зберегти шаблон, який ви використовуєте зараз, розгляньте можливість переключення на boost :: unordered_map замість std :: map. Зображення з рядками як клавішами відбувається повільно, тим більше, що .NET робить деякі приємні речі під кришкою, щоб допомогти прискорити роботу. Використання unororder_map хеширує рядки, тому порівняння, як правило, швидше.

2) Подумайте про перехід на щось більш потужне, як boost :: сигнали. Якщо ви це зробите, ви можете робити такі приємні речі, як зробити так, щоб ваші ігрові об’єкти відстежувались, виходячи з boost :: сигналів :: відстежуваних, і дозвольте деструктору подбати про очищення всього, а не про необхідність вручну відреєстрації системи подій. Ви також можете мати декілька сигналів, що вказують на кожен слот (або навпаки, я не пам’ятаю точну номенклатуру), тому це дуже схоже на те, щоб робити +=на delegateC #. Найбільшою проблемою при boost :: сигналах є те, що його потрібно зібрати, це не лише заголовки, тому залежно від вашої платформи може бути біль встати та працювати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.