Як уникнути жорсткого кодування в ігрових двигунах


22

Моє запитання - не питання кодування; це стосується всього дизайну ігрових двигунів загалом.

Як уникнути жорсткого кодування?

Це питання набагато глибше, ніж здається. Скажіть, якщо ви хочете запустити гру, яка завантажує файли, необхідні для роботи, як уникнути сказати щось на зразок load specificfile.wadу коді двигуна? Крім того, коли файл завантажується, як ви уникаєте сказати load aspecificmap in specificfile.wad?

Це питання стосується майже всієї конструкції двигуна, і якомога менше мотора має бути жорстко закодованим. Який найкращий спосіб досягти цього?

Відповіді:


42

Кодування, кероване даними

Кожна річ, яку ви згадуєте, - це те, що можна вказати в даних. Чому ви завантажуєте aspecificmap? Оскільки конфігурація гри говорить про те, що це перший рівень, коли гравець починає нову гру, або тому, що це ім'я поточної точки збереження у файлі збереження, який він тільки що завантажив, тощо.

Як ви знаходите aspecificmap? Тому що це у файлі даних, в якому перераховані ідентифікатори карт та їх дискові ресурси.

Потрібно лише особливо невеликий набір "основних" ресурсів, які законно важкі або неможливі, щоб уникнути жорсткого кодування. Додавши трохи роботи, це може бути обмежено однією жорстко кодованою назвою активу за замовчуванням на зразок main.wadабо тому подібне. Цей файл потенційно можна змінити під час виконання, передавши в гру аргумент командного рядка, ака game.exe -wad mymain.wad.

Введення коду, керованого даними, спирається на кілька інших принципів. Наприклад, можна уникнути того, щоб системи або модулі запитували певний ресурс, а замість цього інвертувати ці залежності. Тобто не DebugDrawerзавантажуйте його debug.fontв код ініціалізації; натомість DebugDrawerвзяти ручку ресурсу в його код ініціалізації. Ця ручка може бути завантажена з основного файлу конфігурації гри.

В якості конкретних прикладів з нашої бази даних, у нас є об'єкт "глобальних даних", завантажений із бази даних ресурсів (яка сама за замовчуванням є ./resourcesпапкою, але може бути перевантажена аргументом командного рядка). Ідентифікатор бази даних ресурсів цих глобальних даних є єдиним необхідним жорстко кодованим іменем ресурсу в кодовій базі (у нас є інші, оскільки інколи програмісти лінуються, але ми, як правило, виправляємо / видаляємо їх у підсумку). Цей глобальний об'єкт даних переповнений компонентами, єдиною метою яких є надання конфігураційних даних. Одним із компонентів є компонент Global Data UI, який містить обробку ресурсів для всіх основних ресурсів інтерфейсу (шрифти, файли Flash, піктограми, дані локалізації тощо) серед ряду інших елементів конфігурації. Коли розробник інтерфейсу вирішує перейменувати основний актив інтерфейсу з /ui/mainmenu.swfу/ui/lobby.swfвони просто оновлюють цю глобальну посилання на дані; ніякий код двигуна взагалі не потрібно змінювати.

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

Цей підхід має безліч інших переваг. Для одного, це робить упаковку та пакет ресурсів невід'ємною частиною всього процесу. Шляхи жорсткого кодування в двигуні також мають на увазі те, що ці самі шляхи повинні бути жорстко кодовані в будь-яких сценаріях чи інструментах, що запакують ігрові активи, і ці шляхи можуть потім вийти з синхронізації. Замість цього, спираючись на один основний актив та референтні ланцюги, ми можемо створити набір активів за допомогою однієї команди на зразок bundle.exe -root config.data -out main.wadі знати, що вона буде включати всі необхідні нам активи. Далі, оскільки постачальник буде просто дотримуватися посилань на ресурси, ми знаємо, що він буде включати лише потрібні нам активи та пропустити весь залишений пух, який неминуче накопичується протягом життя проекту (плюс ми можемо автоматично генерувати списки цього пух для обрізки).

Хитрий кутовий випадок усього цього - у сценаріях. Зробити керовану даними двигуна концептуально просто, але я бачив дуже багато проектів (хобі до AAA), коли сценарії вважаються даними, а отже, їм "дозволено" просто використовувати шляхи ресурсів без розбору. Не робіть цього. Якщо для файлу Lua потрібен ресурс, і він просто викликає таку функцію, як textures.lua("/path/to/texture.png")тоді, конвеєр активів матиме багато проблем, знаючи, що сценарій потребує /path/to/texture.pngкоректної роботи і може вважати цю текстуру невикористаною та непотрібною. До сценаріїв слід ставитися як до будь-якого іншого коду: будь-які потрібні їм дані, включаючи ресурси або таблиці, повинні бути вказані у записі конфігурації, яку двигун та ресурс трубопроводу можуть перевірити на наявність залежностей. Дані, на яких написано "сценарій завантаження foo.lua", повинні говорити "foo.luaі надайте йому ці параметри "там, де параметри включають будь-які необхідні ресурси. Якщо скрипт випадково породжує ворогів, наприклад, передайте список можливих ворогів у скрипт із цього файлу конфігурації. Потім двигун може попередньо завантажити ворогів рівнем ( оскільки він знає повний перелік можливих нересурсів) і конвеєр ресурсів знає поєднувати всіх ворогів з грою (оскільки вони остаточно посилаються на дані конфігурації). Якщо сценарії генерують рядки імен шляхів і просто викликають loadфункцію, то ні двигун та ресурс не мають жодного способу дізнатися, які саме ресурси може спробувати завантажити сценарій.


Гарна відповідь, дуже практична, а також пояснює підводні камені та помилки, які люди роблять при здійсненні цього! +1
WHN

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

12

Таким же чином ви уникаєте жорсткого кодування в загальних функціях.

Ви передаєте параметри і зберігаєте свою інформацію у файлах конфігурації.

У цій ситуації між написанням двигуна та написанням класу абсолютно немає різниці в інженерії програмного забезпечення.

MgrAssets
public:
  errorCode loadAssetFromDisk( filePath )
  errorCode getMap( mapName, map& )

private:
  maps[name, map]

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

Звідти все керується файлом конфігурації "master".


1
Так, це плюс якийсь механізм для введення в користувальницьку логіку. Можливо, вбудуючи таку мову, як C #, python тощо, щоб розширити основні функції двигуна за визначеною користувачем функціональністю
qCring

3

Мені подобаються інші відповіді, тому я буду трохи навпаки. ;)

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

"Чистий" підхід, керований даними, дозволить вам запустити виконуваний файл з параметрами командного рядка, необхідними для завантаження початкової конфігурації, але двигун повинен бути закодований, щоб знати, як інтерпретувати цю інформацію. Наприклад , якщо ваші файли конфігурації в форматі JSON, ви повинні жорстко закодувати змінні , шукати, наприклад , двигун повинен знати , шукати "intro_movies"і "level_list"і так далі.

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

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

На відміну від підходу до файлів даних (який я щиро підтримую), можливо, ви все гаразд збираєте дані у свій движок. Якщо "вартість" цього зробити нижче, то реальної шкоди немає; якщо ви єдиний, хто працює над цим, ви можете відкласти обробку файлів на більш пізню дату і не обов’язково викручувати себе. У моїх перших декількох ігрових проектах були великі таблиці даних, що вводяться в саму гру, наприклад, перелік зброї та їх різноманітні дані:

struct Weapon
{
    enum IconID icon;
    enum ModelID model;
    int damage;
    int rateOfFire;
    // etc...
};

const struct Weapon g_weapons[] =
{
    { ICON_PISTOL, MODEL_PISTOL, 5, 6 },
    { ICON_RIFLE, MODEL_RIFLE, 10, 20 },
    // etc...
};

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


Розібрати json не дуже важко. Єдина «вартість», яка пов'язана з цим, - це навчання. (Зокрема, навчитися користуватися відповідним модулем чи бібліотекою. Наприклад, Go має хорошу підтримку json.)
Wildcard

Це не страшно складно, але це вимагає того, щоб не тільки вчитися. Наприклад, я знаю, як технічно проаналізувати JSON, я написав аналізатори для багатьох інших форматів файлів, але мені потрібно або знайти і встановити стороннє рішення (і з'ясувати залежності і як його побудувати), або прокрутити моє власне. Займає більше часу, ніж цього не робити.
dash-tom-bang

4
На все потрібно більше часу, ніж не робити цього. Але потрібні вам інструменти вже написані. Так само, як вам не потрібно розробляти компілятор, щоб написати гру або поспілкуватися з машинним кодом, але ви повинні вивчити мову для платформи, з якою працюєте. Отже, навчіться також використовувати json-аналізатор.
Wildcard

Я не впевнений, у чому ваш аргумент. У цій відповіді я виступаю за YAGNI; якщо вам не потрібно витрачати / витрачати час, роблячи щось, що не допоможе вам, тоді не робіть. Якщо ви хочете витратити час на це, то чудово. Можливо, вам доведеться витратити час пізніше, можливо, ви цього не зробите, але робити це попереду лише відволікає вас від завдання фактично зробити гру. Розвиток гри тривіальний; кожне завдання, яке перетворюється на створення гри, є простим. Просто в більшості ігор є мільйон простих завдань, і відповідальний розробник вибирає ті, які досягають цієї мети найшвидше.
dash-tom-bang

2
Власне, я підтримав вашу відповідь; реального аргументу як такого немає. Я просто хотів зазначити, що JSON не важко розібратися. Читаючи ще раз, я вважаю, що я в основному відповідав на фрагмент "але тоді вам потрібно зробити розбір і переклад і весь цей джаз". Але я погоджуюсь, що для особистих ігор для проектів і таких, YAGNI :)
Wildcard
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.