Як створити AssetManager?


26

Який найкращий підхід до проектування AssestManager, який міститиме посилання на графіку, звуки тощо в грі?

Чи слід зберігати ці активи в парі "ключ / значення"? Тобто я запитую "фоновий" актив і Map повертає пов'язаний растровий файл? Чи є ще кращий спосіб?

Конкретно я пишу гру Android / Java, але відповіді можуть бути загальними.

Відповіді:


16

Це залежить від сфери вашої гри. Менеджер активів абсолютно необхідний для більших назв, тим більше для менших ігор.

Для більших назв вам потрібно керувати проблемами, такими як:

  • Спільні активи - чи є ця текстура цегли, яка використовується у декількох моделях?
  • Термін експлуатації активів - той актив, який ви завантажили 15 хвилин тому, більше не потрібен? Посилання на підрахунок ваших активів, щоб переконатися, що ви знаєте, коли щось закінчено з тощо
  • У DirectX 9, якщо певні типи активів завантажуються, а ваш графічний пристрій втрачається (це трапляється, якщо серед іншого натиснути Ctrl + Alt + Del) - вашій грі потрібно буде відтворити їх
  • Завантаження активів заздалегідь потребуючи - без цього ви не могли б створити великі ігри з відкритим світом
  • Масове завантаження активів - Ми часто пакуємо багато активів в один файл, щоб покращити час завантаження - пошук диска займає багато часу

Що стосується менших назв, то це менше питання, а такі структури, як XNA, мають у собі менеджери активів - дуже мало сенсу повторно їх вигадувати.

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

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

  • Як кумедна сторона, ви зазвичай отримуєте одне хеш-зіткнення за проект.

Тільки тому, що вам потрібно керувати активами, це не вимагає AssetManagers, великого іменника з великим літери, який, мабуть, має занадто багато методів, низьку продуктивність і мутну семантику пам'яті. Для порівняння подумайте, що станеться, якщо у вас багато управління проектами (як правило, добре), а потім, коли у вас багато керівників проектів (як правило, погано).

2
@Joe Wreschnig - як би ви вирішили п’ять вимог, згаданих icStatic, не використовуючи менеджера активів?
antinome

8

(Намагаючись уникнути обговорення "не використовувати менеджера активів", оскільки я вважаю це офтопічним.)

Карта ключа / значення - дуже зручний підхід.

У нас є одна реалізація ResourceManager, де Фабрики для різних типів ресурсів можуть реєструватися.

Метод "getResource" використовує шаблони, щоб знайти правильний Factory для потрібного типу ресурсу і повертає певний ResourceHandle (знову використовуючи шаблон для повернення SpecificResourceHandle).

Ресурси перераховуються ResourceManager (всередині ResourceHandle) і вивільняються, коли вони більше не потрібні.

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

Більшу частину часу ми маємо лише на екземплярі ResourceManager, але іноді ми створюємо новий екземпляр лише для рівня або карти. Таким чином, ми можемо просто викликати "відключення" на levelResourceManager і переконатися, що нічого не протікає.

(короткий) приклад

// very abbreviated!
// this code would never survive our coding guidelines ;)

ResourceManager* pRm = new ResourceManager;
pRm->initialize( );
pRm->registerFactory( new TextureFactory );
// [...]
TextureHandle tex = pRm->getResource<Texture>( "test.otx" ); // in real code we use some macro magic here to use CRCs for filenames
tex->storeToHardware( 0 ); // channel 0

pRm->releaseResource( pRm );

// [...]
pRm->shutdown(); // will log any leaked resource

6

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

Стів Йегґе (серед багатьох, багатьох інших) написав гарну історію про те, як марні класи менеджерів, внаслідок одинарного шаблону, закінчуються. http://sites.google.com/site/steveyegge2/singleton-considered-stupid


2
Гаразд, звичайно. Але у таких випадках, як Android (або інші ігри), вам потрібно завантажити багато графіки / звуків у пам'ять перед початком гри, а не під час. Як я можу використовувати те, що ви говорите (фабрики), щоб зробити це під час завантаження екрана? Просто натисніть на кожен заводський об'єкт на екрані завантаження, щоб він кешував їх?
Брайан Денні

Мені незнайомі деталі Android, але я не маю уявлення, що ви маєте на увазі під "перед початком гри". Невже неможливо завантажити ресурс тоді, коли вам це потрібно (або коли вам це знадобиться «незабаром»), а не при запуску програми? Я вважаю це вкрай малоймовірним, інакше, наприклад, ви ніколи не могли мати більше текстур, ніж вмістити в мізерну оперативну пам'ять Android.

@Joe погляньте на інше моє запитання щодо "завантаження екранів": gamedev.stackexchange.com/questions/1171/… Попадання порожнього кеша означає довго переходити до диска, і це може призвести до певних звернень FPS щодо перших викликів . Якщо ви вже знаєте, що ви збираєтесь вдарити достроково, може також вдарити його під час завантаження, щоб попередньо кешувати його, правда?
Брайан Денні

Знову я не можу розмовляти з Android, але, як правило, перехід на диск - це саме те, що можна зробити, не приймаючи FPS-хіти, тому що нитка, що йде на диск, взагалі не буде використовувати жоден процесор. Вам просто потрібно заздалегідь сплатити бюджет, щоб ви не отримали спливаючі вікна. Якщо ви збираєтеся попередньо кешувати все, тому що ви заздалегідь знаєте, що вам потрібно, вам насправді не потрібен AssetManager, тому що вам взагалі не потрібно керувати активами - вони вже всі під рукою.

1
@Joe, чи не фабрика є також "виділеним менеджером"?
MSN

2

Я завжди думав, що хороший менеджер активів повинен мати кілька режимів роботи. Ці режими, швидше за все, будуть окремими вихідними модулями, що приєднуються до загального інтерфейсу. Два основних режими роботи:

  • Режим виробництва - усі активи локальні та позбавлені всіх метаданих
  • Режим розвитку - асети зберігаються в базі даних (наприклад, MySQL тощо) з додатковими метаданими. База даних буде дворівневою системою з локальною базою даних, кешуючи спільну базу даних. Творці вмісту зможуть редагувати та оновлювати спільну базу даних та оновлення, що автоматично надходять до систем розробника / QA. Слід також мати можливість створювати вміст заповнювачів. Оскільки все знаходиться в базі даних, на базі даних можна робити запити та формувати звіти для аналізу стану виробництва.

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

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

Оновлення

Гаразд, кілька негативних голосів. Я розширю на цю конструкцію.

По-перше, вам не потрібні фабричні заняття, тому що якщо у вас є:

TextureHandle tex = pRm->getResource<Texture>( "test.otx" );

ви знаєте тип, так що просто робіть:

TextureHandle tex = new TextureHandle ("test.otx");

але тоді, що я намагався сказати вище, це те, що ви б не використовували явні назви файлів, текстура для завантаження визначалася б моделлю, на якій використовується текстура, тож вам насправді не потрібно читати людського імені, це може бути 32-бітове ціле значення, що набагато простіше обробляти процесор. Отже, у конструкторі для TextureHandle у вас є:

if (texture already loaded)
  update texture reference count
else
  asset_stream = new AssetStream (resource_id)
  asset_stream->ReadBytes
  create texture
  set texture ref count to 1

AssetStream використовує параметр resource_id для пошуку місця розташування даних. Як це зробити, це залежатиме від середовища, в якому ви працюєте:

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

У випуску: потік шукає ідентифікатор у таблиці ключ / значення, щоб отримати зміщення / розмір у великому, упакованому файлі (як WAD-файл Doom).


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

@Joe: Я наводив лише SQL як приклад, і тоді лише в середовищі розробки ви могли однаково використовувати VCS. Я лише запропонував базу даних SQL, оскільки потім можна додати додаткову інформацію до збережених об'єктів і використовувати функції SQL для запиту інформації з бази даних (більше виграшів від управління, ніж будь-що інше). Що стосується непрозорих ідентифікаторів як передчасної оптимізації - деякі, можливо, бачать саме так, я думаю, але, думаю, було б легше почати з цього, а не показувати його на більш пізньому етапі розвитку. Я не думаю, що це значно вплине на розвиток, якщо ви використовували ID або рядки.
Скізз

2

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

Мій метод ідеально підходить для карт відкритого світу, але гра на основі рівня не виграє від безшовності цього методу. Сподіваюся, це допомагає!

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