Как спроектировать AssetManager?


26

Каков наилучший подход к разработке AssestManager, который будет содержать ссылки на графику, звуки и т. Д. Игры?

Должны ли эти активы храниться в паре карта ключ / значение? Т.е. я запрашиваю "фоновый" актив, а карта возвращает связанный растровый рисунок? Есть ли еще лучший способ?

В частности, я пишу игру для Android / Java, но ответы могут быть общими.

Ответы:


16

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

Для больших названий вы должны решить следующие проблемы:

  • Общие активы - эта кирпичная текстура используется несколькими моделями?
  • Срок действия актива - больше не нужен тот актив, который вы загрузили 15 минут назад? Подсчет ссылок ваших активов, чтобы убедиться, что вы знаете, когда что-то закончено и т. Д.
  • В DirectX 9, если загружены определенные типы ресурсов, и ваше графическое устройство «теряется» (это происходит, если вы нажимаете Ctrl + Alt + Del среди прочего) - вашей игре нужно будет воссоздать их
  • Загрузка ресурсов до того, как они понадобятся - без этого вы не сможете создать большие игры с открытым миром
  • Массовая загрузка ресурсов - мы часто упаковываем множество ресурсов в один файл, чтобы сократить время загрузки - поиск по диску занимает очень много времени

Для небольших изданий такие вещи не так важны, так как в таких средах, как XNA, есть управляющие активами - повторного изобретения нет особого смысла.

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

Обычно не рекомендуется жестко кодировать имена файлов в вашем приложении, обычно лучше, чтобы другой формат данных (например, xml) отображал имена файлов в «идентификаторы».

  • Как забавное примечание, вы обычно получаете одно коллизию хешей на проект.

Просто потому, что вам нужно управлять активами, не требуется AssetManagers, существительное существительное с большой буквы, которое, вероятно, имеет слишком много методов, плохую производительность и грязную семантику памяти. Для сравнения подумайте о том, что происходит, если у вас много управления проектами (обычно это хорошо), а затем, когда у вас много менеджеров проектов (как правило, плохо).

2
@Joe Wreschnig - как бы вы справились с пятью требованиями, упомянутыми icStatic, без использования менеджера активов?
Антином

8

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

Карта ключ / значение - очень удобный подход.

У нас есть одна реализация ResourceManager, в которой фабрики для разных типов ресурсов могут регистрироваться.

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

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

Первым дополнением, которое мы написали, был метод «reload (XYZ)», который позволяет нам изменять ресурсы извне работающего движка без изменения кода или перезагрузки игры. (Это важно, когда художники работают на консолях;))

Большую часть времени мы имеем только на экземпляре ResourceManager, но иногда мы создаем новый экземпляр только для уровня или карты. Таким образом, мы можем просто вызвать «shutdown» на 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

Классы Dedicated Manager почти никогда не являются правильным инженерным инструментом. Если вам нужен актив только один раз (например, фон или карта), вы должны запросить его только один раз и позволить ему умереть нормально, когда вы закончите с ним. Если вам нужно кэшировать объект определенного типа, вы должны использовать фабрику, которая сначала проверяет кеш, а затем загружает что-то, помещает его в кеш, а затем возвращает его, и эта фабрика может быть просто статической функцией, обращающейся к статической переменной. , а не своего рода.

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


2
Да конечно. Но в таких случаях, как Android (или другие игры), вам нужно загрузить много графики / звуков в память до начала игры, а не во время. Как я могу использовать то, что вы говорите (фабрики), чтобы сделать это во время загрузки экрана? Просто нажмите каждый объект на заводе на экране загрузки, чтобы он их кешировал?
Брайан Денни

Я не знаком с деталями Android, но понятия не имею, что вы подразумеваете под «до запуска игры». Действительно ли невозможно загрузить ресурс, когда он вам нужен (или когда он понадобится «скоро»), а не когда вы запускаете программу? Я нахожу это крайне маловероятным, в противном случае, например, вы никогда не сможете получить больше текстур, чем умещается в скудной памяти Android.

@ Джо, взгляните на другой мой вопрос о «загрузке экранов»: gamedev.stackexchange.com/questions/1171/… Попадание в пустой кеш означает длительное время перехода на диск и может привести к некоторому снижению производительности FPS при этих первых вызовах. , Если вы уже знаете, что собираетесь ударить раньше времени, то можете также ударить по нему во время загрузки, чтобы предварительно кэшировать его, верно?
Брайан Денни

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

1
@ Джо, разве фабрика не является «Выделенным менеджером»?
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 или строки.
Skizz

2

Что мне нравится делать с активами, так это настроить единовременного менеджера . Вдохновленные движком Doom, куски - это фрагменты данных, которые содержат ресурсы, хранящиеся в файле кусков, который объявляет имена кусков, длину, тип (растровое изображение, звук, шейдер и т. Д.) И тип содержимого (файл, другой кусочек, внутри сам файл комка). При запуске эти куски вводятся в двоичное дерево, но еще не загружены. Каждая карта (которая также является комом) имеет список зависимостей, которые являются просто названиями комков, с которыми карта должна работать. Эти комки, если они еще не были загружены, загружаются во время загрузки карты. Кроме того, куски соседних карт загружаются, но не одновременно, а по какой-то причине, когда двигатель работает на холостом ходу. Это может сделать карты без шва, и нет экрана загрузки.

Мой метод идеально подходит для карт с открытым миром, но игра, основанная на уровнях, не выиграет от беспроблемности этого метода. Надеюсь это поможет!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.