AssetManagerの設計方法は?


26

ゲームのグラフィックス、サウンドなどへの参照を保持するAssestManagerを設計するための最良のアプローチは何ですか?

これらのアセットをキー/値のマップペアに保存する必要がありますか?つまり、「バックグラウンド」アセットを要求すると、マップは関連するビットマップを返しますか?もっと良い方法はありますか?

具体的には、Android / Javaゲームを書いていますが、答えは一般的なものです。

回答:


16

ゲームの範囲に依存します。アセットマネージャーは、タイトルが大きい場合には不可欠ですが、ゲームが小さい場合にはそれほど重要ではありません。

大きなタイトルの場合、次のような問題を管理する必要があります。

  • 共有アセット-そのブリックテクスチャは複数のモデルで使用されていますか?
  • アセットの有効期間-15分前にロードしたアセットは不要になりましたか?資産のカウントを参照して、何かが終了したときなどを確認します
  • DirectX 9では、特定のアセットタイプが読み込まれ、グラフィックデバイスが「失われた」場合(これは、特にCtrl + Alt + Delを押すと発生します)-ゲームでそれらを再作成する必要があります
  • アセットを必要とする前にロードする-これなしでは大きなオープンワールドゲームを構築できませんでした
  • アセットの一括読み込み-読み込み時間を改善するために、多くのアセットを1つのファイルにパックすることがよくあります-ディスクを探し回るのは非常に時間がかかります

小さいタイトルの場合、これらの問題はそれほど問題ではありません。XNAのようなフレームワークにはアセットマネージャーがあります-再発明する意味はほとんどありません。

アセットマネージャーが必要な場合は、万能のソリューションはありませんが、ファイル名のハッシュ*としてキーを使用したハッシュマップ(小文字で区切り、すべて「固定」)が見つかりました私が取り組んできたプロジェクトに適しています。

通常、アプリでファイル名をハードコードすることはお勧めできません。通常、別のデータ形式(xmlなど)でファイル名を「ID」に表現することをお勧めします。

  • おもしろいことに、通常、プロジェクトごとに1つのハッシュ衝突が発生します。

アセットを管理する必要があるからといって、AssetManagersは必要ありません。これは、多すぎるメソッド、パフォーマンスの低下、泥だらけのメモリセマンティクスを持つおそらく大文字の重要な名詞です。比較のために、多くのプロジェクト管理(通常は良い)がある場合、そして多くのプロジェクトマネージャー(通常は悪い)がある場合に何が起こるかを考えてください。

2
@Joe Wreschnig-アセットマネージャーを使用せずに、icStaticで言及されている5つの要件にどのように対処しますか?
アンチノーム

8

(ここでは「アセットマネージャーを使用しないでください」という議論を避けようとしています。これはトピック外だと考えているためです。)

キー/値マップは非常に便利なアプローチです。

さまざまなリソースタイプのファクトリを登録できるResourceManager実装が1つあります。

「getResource」メソッドは、テンプレートを使用して必要なリソースタイプの正しいファクトリを見つけ、特定のResourceHandleを返します(再びテンプレートを使用してSpecificResourceHandleを返します)。

リソースは(ResourceHandle内の)ResourceManagerによって再カウントされ、不要になったときに解放されます。

最初に作成したアドオンは「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の貧弱なRAMに収まる以上のテクスチャを使用することはできません。

@Joeは、「画面の読み込み」に関する他の質問をご覧ください。gamedev.stackexchange.com / questions / 1171 / ...空のキャッシュをヒットすると、ディスクに移動する時間が長くなり、最初の呼び出しでFPSパフォーマンスが低下する可能性があります。事前に何をヒットするかをすでに知っている場合は、ロード中にヒットしてプリキャッシュすることもできますか?
ブライアンデニー

繰り返しますが、Androidに話すことはできませんが、通常、ディスクに行くスレッドはFPSヒットを取得せずに行うことができます。ディスクに行くスレッドはCPUをまったく使用しないためです。ポップインしないように、事前に予算を立てる必要があります。必要なものが事前にわかっているため、すべてを事前にキャッシュする場合、AssetManagerは必要ありません。資産を管理する必要がないため、すでにすべて手元にあります。

1
@ジョー、工場は「専任マネージャー」でもないのですか?
MSN

2

私は常に、優れた資産管理者にはいくつかの操作モードが必要だと考えてきました。これらのモードは、多くの場合、共通のインターフェースに準拠した別個のソースモジュールです。操作の2つの基本モードは次のとおりです。

  • 本番モード-すべてのアセットはローカルであり、すべてのメタデータが削除されます
  • 開発モード-アセットは、追加のメタデータとともにデータベース(MySQLなど)に保存されます。データベースは、共有データベースをキャッシュするローカルデータベースを持つ2層システムになります。コンテンツ作成者は、共有データベースと、開発者/ QAシステムに自動的に伝達される更新を編集および更新できます。プレースホルダーコンテンツを作成することも可能です。すべてがデータベースにあるため、データベースに対してクエリを実行し、生成されたレポートを生成して、本番の状態を分析できます。

共有データベースからすべてのassestを取得し、運用データセットを作成できるツールが必要です。

開発者としての数年間、このようなことは一度もありませんでしたが、私はほんの一握りの会社でしか働いていなかったので、私の見解は実際に代表的なものではありません。

更新

OK、いくつかの否定的な投票。この設計を拡張します。

第一に、あなたが持っているのであれば、あなたは本当にファクトリークラスを必要としません:

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

あなたはタイプを知っているので、そうしてください:

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

しかし、その後、私が言いたかったのは、とにかく明示的なファイル名を使用しないことです。ロードするテクスチャは、テクスチャが使用されるモデルによって指定されるため、実際に人間が読める名前は必要ありません。 32ビットの整数値にすることもできますが、これはCPUでの処理がはるかに簡単です。そのため、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を使用して)データベース内のIDを検索してファイル名を取得し、ファイルを開きます。ファイルはローカルにキャッシュするか、ローカルファイルが存在しないか、時代遅れ。

リリース:ストリームはキー/値テーブルでIDを検索し、オフセット/サイズを大きなパックファイル(DoomのWADファイルなど)に取得します。


実際のVCSを使用するのではなく、主キーを使用してすべてをSQLテーブルにシューホーンすることを提案したため、私はあなたに投票しました。また、文字列名の早期最適化ではなく、不透明なIDの使用を検討します。翻訳キー以外のすべてのアセットに2つの大きなプロジェクトで文字列を使用しましたが、そのうち数十万もの非常に長い文字列キーがありました(そしてコンソールにのみ移植しました)。通常は正規化されているため、文字列比較ではなくポインタ比較を使用できますが、文字列比較は実際の比較ではなくメモリフェッチのコストに左右されることがよくあります。

@Joe:SQLを例として挙げましたが、開発環境でのみVCSを使用することもできます。格納されたオブジェクトに追加の情報を追加し、SQL関数を使用してデータベースから情報を照会できるため、SQLデータベースのみを推奨しました(他の何よりも管理が向上します)。時期尚早な最適化としての不透明なIDについては、そのように見える人もいるかもしれませんが、開発の後半でそれを示すよりも、それから始める方が簡単だと思います。IDまたは文字列を使用した場合、開発に大きな影響はないと思います。
スキズ

2

私が資産のためにしたいことは、一括マネージャーを設定することです。ドゥームエンジンに触発、塊はに格納された資産を含むデータの部分である一括ファイル塊の名前、長さ、タイプ(ビットマップ、サウンド、シェーダなど)を宣言し、コンテンツタイプ(ファイル、別の塊、内部が一括ファイル自体)。起動時に、これらのランプはバイナリツリーに入力されますが、まだロードされていません。各マップ(これも塊である)には依存関係のリストがあります。これは、マップが機能するために必要な塊の名前です。これらの塊は、既にロードされていない限り、マップのロード時にロードされます。さらに、マップの隣接するマップの塊がロードされるのは、同時にではなく、何らかの理由でエンジンがアイドリングしているときです。これにより、マップがシームレスになり、ロード画面がなくなります。

私の方法はオープンワールドマップに最適ですが、レベルベースのゲームはこの方法で得られるシームレスさの恩恵を受けません。お役に立てれば!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.