拡張可能なアセット読み込みシステムをどのように構築すればよいですか?


19

Javaの趣味のゲームエンジンの場合、シンプルだが柔軟なアセット/リソースマネージャーをコーディングしたいと思います。資産は、音、画像、アニメーション、モデル、テクスチャなどです。数時間のブラウジングといくつかのコードの実験の後、私はまだこのことを設計する方法がわかりません。

具体的には、特定の資産タイプがどのようにロードされ、どこから資産がロードされるかを抽象化するような方法でマネージャーを設計する方法を探しています。他のプログラムがそれについて知る必要なしに、ファイルシステムとRDBMSストレージの両方をサポートできるようにしたいと思います。同様に、XMLであるアニメーション記述アセット(FPS、レンダリングするフレーム、スプライトイメージへの参照など)を追加したいと思います。XMLファイルを見つけて読み取り、AnimationAssetその情報を持つクラスを作成して返す機能を備えたクラスを作成できるはずです。データ駆動型の設計を探しています

私は上の多くの情報を見つけることができますどのような資産運用会社がすべきではなく、上でどのようにそれを行います。関連するジェネリックは、何らかの形のクラスのカスケード、または何らかの形のヘルパークラスをもたらすようです。しかし、個人的なハックやコンセンサスのように見えない明確な例を見たことはありません。

回答:


23

資産運用会社について考えないことから始めます。大まかに定義された用語(「マネージャー」など)でアーキテクチャを考えると、敷居の下で多くの詳細を精神的に一掃する傾向があり、その結果、解決策を見つけるのが難しくなります。

特定のニーズに焦点を当てます。これは、基になるオリジンストレージを抽象化し、サポートされているタイプセットの拡張性を可能にするリソースロードメカニズムの作成に関係しているようです。たとえば、既にロードされたリソースのキャッシュに関する質問には本当に何もありません-これは問題ありません。単一の責任原則に従って、おそらくアセットキャッシュを別のエンティティとして構築し、2つのインターフェイスを別の場所に集約する必要があるためです、 適切に。

特定の懸念に対処するには、アセット自体の読み込みを行わず、特定の種類のアセットの読み込みに合わせたインターフェイスにその責任を委任するようにローダーを設計する必要があります。例えば:

interface ITypeLoader {
  object Load (Stream assetStream);
}

このインターフェイスを実装する新しいクラスを作成できます。新しいクラスはそれぞれ、ストリームから特定のタイプのデータをロードするように調整されます。ストリームを使用することにより、タイプローダーは一般的なストレージに依存しないインターフェイスに対して記述でき、ディスクまたはデータベースからロードするためにハードコーディングする必要はありません。これにより、ネットワークストリームからアセットをロードできるようになります(ゲームがコンソールで実行され、編集ツールがネットワークに接続されたPCで実行されている場合、アセットのホットリロードを実装するのに非常に役立ちます)。

メインのアセットローダーは、これらのタイプ固有のローダーを登録および追跡できる必要があります。

class AssetLoader {
  public void RegisterType (string key, ITypeLoader loader) {
    loaders[key] = loader;
  }

  Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}

ここで使用する「キー」は好きなものにできます。文字列である必要はありませんが、簡単に開始できます。キーは、ユーザーが特定の資産を識別する方法を考慮し、適切なローダーを検索するために使用されます。実装がファイルシステムまたはデータベースを使用している可能性があるという事実を隠したいので、ファイルシステムパスまたはそのようなものでアセットを参照するユーザーを作成することはできません。

ユーザーは最小限の情報でアセットを参照する必要があります。場合によっては、ファイル名だけで十分な場合もありますが、すべてが非常に明確になるように、タイプと名前のペアを使用することが望ましい場合が多いことがわかりました。したがって、ユーザーは、アニメーションXMLファイルの名前付きインスタンスをとして参照する場合があります"AnimationXml","PlayerWalkCycle"

ここでAnimationXmlAnimationXmlLoader、を実装するキーを登録しますIAssetLoader。明らかにPlayerWalkCycle、特定の資産を識別します。タイプ名とリソース名を指定すると、アセットローダーは、そのアセットの未加工バイトについて永続ストレージを照会できます。ここでは最大限の汎用性を求めているため、ローダーを作成するときにローダーにストレージアクセスの手段を渡すことでこれを実装でき、ストレージメディアを後でストリームを提供できるものに置き換えることができます。

interface IAssetStreamProvider {
  Stream GetStream (string type, string name);
}

class AssetLoader {
  public AssetLoader (IAssetStreamProvider streamProvider) {
    provider = streamProvider;
  }

  object LoadAsset (string type, string name) {
    var loader = loaders[type];
    var stream = provider.GetStream(type, name);

    return loader.Load(stream);
  }

  public void RegisterType (string type, ITypeLoader loader) {
    loaders[type] = loader;
  }

  IAssetStreamProvider provider;
  Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}

非常に単純なストリームプロバイダーは、指定されたアセットのルートディレクトリで名前の付いたサブディレクトリを探し、指定されtypeたファイルの未加工バイトをnameストリームにロードして、それを返します。

つまり、ここにあるのは次のようなシステムです。

  • ある種のバックエンドストレージ(ディスク、データベース、ネットワークストリームなど)から生のバイトを読み取る方法を知っているクラスがあります。
  • 生のバイトストリームを特定の種類のリソースに変換して返す方法を知っているクラスがあります。
  • 実際の「アセットローダー」には上記のコレクションがあり、ストリームプロバイダーの出力をタイプ固有のローダーにパイプして具体的なアセットを生成する方法を知っています。ストリームプロバイダーとタイプ固有のローダーを構成する方法を公開することにより、実際のアセットローダーコードを変更することなく、クライアント(またはユーザー)によって拡張できるシステムができます。

いくつかの注意事項と最終ノート:

  • 上記のコードは基本的にC#ですが、最小限の労力でほぼすべての言語に翻訳する必要があります。これを容易にするために、エラーチェックや適切な使用IDisposableなど、他の言語に直接適用されない可能性のある他のイディオムなど、多くのことを省略しました。それらは読者の宿題として残されています。

  • 同様に、object上記の具体的なアセットを返しますが、必要に応じてジェネリックやテンプレートなどを使用して、より具体的なオブジェクトタイプを生成できます(作業するのは良いことです)。

  • 上記のように、ここではキャッシュをまったく扱いません。ただし、同じ種類の一般性と構成可能性を備えたキャッシングを簡単に追加できます。試してみてください!

  • これを行うにはたくさんの方法がたくさんあり、確かに一つの方法もコンセンサスもありません。だからあなたはそれを見つけることができなかったのです。この答えを非常に長いコードの壁に変えることなく、特定のポイントを理解するのに十分なコードを提供しようとしました。すでに非常に長いです。明確な質問がある場合は、気軽にコメントしたり、チャットで私を見つけてください。


1
ソリューションをデータ駆動型の設計だけでなく、データ駆動型の考え方
パトリックヒューズ

非常に素晴らしく詳細な答え。あなたが私の質問をどのように解釈し、非常に不十分に定式化する間、私が知る必要があることを正確に教えてくれたのが大好きです。ありがとう!万が一、Streamsに関するリソースを教えていただけますか?
user8363

「ストリーム」は、バイトまたはデータの単なるシーケンスです(潜在的に決定可能な終了がない)。私は特にC#のStreamについて考えていましたが、おそらくJavaのストリームクラスにもっと興味があります-警告されますが、Java はあまり知らないので、使用するのに理想的なクラスではないかもしれません。

通常、ストリームはステートフルです。ストリームオブジェクトは通常、ストリーム内の現在の読み取りまたは書き込み位置を持ち、その上で実行するIOはその位置から発生するため、上記のアセットインターフェイスへの入力として使用しました。彼らは本質的に「ここに生データがあり、どこから読み始め、それから読み、あなたのことをするのか」と言っているからです。

このアプローチは、SOLIDOOPの両方の中核的な原則の一部を尊重します。ブラボー。
アダムネイラー14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.