Unreal Engine 4 では アセット のロードとアンロードを自動的に処理します。これにより、各アセットがまさに必要になるタイミングでエンジンがロードするように、デベロッパーがシステムをコーディングする苦労から解放されます。ただし、アセットを検出、ロード、監査する時期と方法について、デベロッパーが細かくコントロールしたい場合もあります。このような場合は、アセット マネージャー が役に立ちます。アセット マネージャーとは、エディタやパッケージ化ゲームにある唯一のグローバル オブジェクトです。これはどのプロジェクトにもオーバーライドしカスタマイズできます。コンテンツをチャンクに分割できるアセットを管理するためのフレームワークを提供します。チャンクはプロジェクトのそれぞれの状況で意味があるもので、Unreal Engine 4 の 緩やかなパッケージ アーキテクチャ のメリットが失われません。さらにディスクやメモリ使用状況を監査できるツールセットも用意されています。これにより、ゲームをデプロイするときの クック処理とチャンク化 向けにアセットの編成を最適化するために必要な情報が得られます。
プライマリ アセットとセカンダリ アセット
理論的に、Unreal Engine 4 のアセット管理システムではすべてのアセットを次の 2 つのタイプ、プライマリ アセット と セカンダリ アセット に分けます。プライマリ アセットはアセット マネージャーで直接操作できます。このとき GetPrimaryAssetId
を呼び出して取得する プライマリ アセット ID を使用します。特定の UObject
クラスから作成されたアセットをプライマリ アセットとして指定するには、GetPrimaryAssetId
をオーバーライドして、有効な FPrimaryAssetId
構造体を返します。セカンダリ アセットはアセット マネージャーで直接操作できませんが、プライマリ アセットが参照または使用されると、それに応じてエンジンにより自動的にロードされます。デフォルトでは、UWorld
アセット (レベル) のみがプライマリで、他のすべてのアセットはセカンダリです。セカンダリ アセットをプライマリ アセットに変えるには、対応クラスの GetPrimaryAssetId
関数をオーバーライドして、有効な FPrimaryAssetId
構造体を返す必要があります。プライマリ アセット ID は次の 2 つの部分で構成されます。アセットのグループを識別する固有のプライマリ アセット タイプ、および コンテンツブラウザ に表示されるアセット名のデフォルトになる、個別のプライマリ アセットの名前です。
ブループリント クラス アセットおよびデータ アセット
アセット マネージャーでは次の異なる 2 つのタイプのアセット、ブループリント クラスおよびレベルおよびデータ アセットなどブループリント以外のアセット (UDataAsset
クラスのアセット インスタンス) を扱います。それぞれのプライマリ アセット タイプは特定の基本クラスに関連付けられ、ブループリント クラスを格納するかどうかを、次に説明するコンフィギュレーションに指定します。
ブループリント クラス
新規ブループリント プライマリ アセットを作成するには、コンテンツブラウザ で ブループリント クラスを新規作成 します。このクラスは GetPrimaryAssetId
関数をオーバーライドするクラスの子孫です。この基本クラスはプライマリ データ アセットまたはその子、あるいは GetPrimaryAssetId
をオーバーライドするアクタ サブクラスです。ブループリント プライマリ アセットにアクセスするには、C++ コードから GetPrimaryAssetObjectClass
などの関数を呼び出す、またはその名前に「Class」を含むブループリント アセット マネージャー関数を使用します。クラスがある場合、他のブループリント クラスと同様に扱い、新しいインスタンスをスポーンするために使用できます。あるいは Get Defaults 関数を使用して、ブループリントに関連付けられたクラス デフォルト オブジェクトから読み取り専用データにアクセスできます。
インスタンス化がまったく必要ないブループリント クラスでは、データを データ専用ブループリント (UPrimaryDataAsset
から継承) に格納できます。使用する基本クラスから、ブループリントベースの子を含め、子クラスを派生させることもできます。たとえば、C++ で UPrimaryDataAsset
を拡張した UMyShape
のような基本クラスを作成でき、親が UMyShape
である BP_MyRectangle
というブループリントベースのサブクラス、さらに BP_MySquare
という、ブループリントベースの BP_MyRectangle
の子を作成できます。デフォルト設定では、作成した最後のクラスの PrimaryAssetId は MyShape:BP_MySquare
になります。
非ブループリント アセット
プライマリ アセット タイプでブループリント データを格納する必要がない場合、非ブループリント アセットを使用できます。非ブループリント アセットはコードで簡単にアクセスでき、メモリ効率が高くなっています。エディタで非ブループリント プライマリ アセットを新規作成するには、コンテンツブラウザの詳細ウィンドウからデータ アセットを新規作成する、または新規レベルなどを作成するときのカスタム UI を使用します。このようにアセットを作成することは、ブループリント クラスの作成と同じではありません。作成するアセットは、クラス自体ではなく、クラスのインスタンスです。このクラスにアクセスするには、GetPrimaryAssetObject
などの C++ 関数、または名前に Class がないブループリント関数でロードします。ロードされたら、直接アクセスして、そのデータを読み取ることができます。
これらのアセットはクラスではなくインスタンスなので、これらからクラスや他のアセットを継承できません。継承が必要な場合、たとえば、明示的にオーバーライドしたものを除き、親の値を継承する子アセットを作成する場合、代わりにブループリント クラスを使用する必要があります。
アセット マネージャーおよびストリーミング可能マネージャー
アセット マネージャー オブジェクトはシングルトンで、プライマリ アセットの検出とロードを管理します。エンジンに含まれるアセットマネージャーの基本クラスには、管理の基本機能が用意されていますが、プロジェクト固有のニーズを満たすように拡張できます。ストリーミング可能マネージャー 構造体 (アセット マネージャー内に含まれる) はオブジェクトの非同期ロードに関わる処理を実行します。さらに、必要なくなり、アンロードできるまで ストリーミング可能ハンドル (Streamable Handle) を使用して、メモリにオブジェクトを維持します。シングルトンのアセット マネージャーと異なり、エンジンの各部や異なるユースケースで利用できる、複数のストリーミング可能マネージャーがあります。
アセット バンドル
アセット バンドル は、プライマリ アセットに関連付けられた特定アセットの名前付きリストです。アセット バンドルを作成するには、UObject
の TSoftObjectPtr
または FStringAssetReference
メンバーの UPROPERTY
セクションに「AssetBundles」メタ タグを付けます。タグの値は、セカンダリ アセットを格納するバンドルの名前を示します。たとえば、次のスタティック メッシュ アセット (MeshPtr
というメンバー変数に格納) は、UObject が保存されるときに「TestBundle」というアセット バンドルに追加されます。
/** Mesh */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Display, AssetRegistrySearchable, meta = (AssetBundles = "TestBundle"))
TSoftObjectPtr<UStaticMesh> MeshPtr;
アセット バンドルを使用する 2 番目の方法は、ランタイム時にプロジェクトのアセット マネージャー クラスとともに登録することです。この場合、プログラマーは、FAssetBundleData
構造体にすべてデータが入ったコードを記述し、この構造体をアセット マネージャーに渡します。これを実行するには、UpdateAssetBundleData
関数をオーバーライドする、または、バンドルのセカンダリ アセットに関連付けるプライマリ アセット ID で AddDynamicAsset
を呼び出します。
プライマリ アセットを登録する、およびディスクからロードする
多くのプライマリ アセットは コンテンツブラウザ に表示され、ディスクに格納されたアセット ファイルとして存在します。これによりアーティストまたはデザイナーが編集できます。このように使用できるクラスをプログラマーが作成する最も簡単な方法は、UDataAsset
子クラス、UPrimaryDataAsset
から継承することです。これにはアセット バンドル データをロードおよび保存するためのビルトイン機能があります。別の基本クラス (APawn
など) を使用する場合、UPrimaryDataAsset
を調べることが便利です。自分のクラスに対してアセット バンドルを機能させるために実装する必要がある機能を最小限揃えたサンプルだからです。次のクラスは、想定されるゲームのゾーンのタイプを指定する方法の一例です。
このゾーン タイプはゲーム全体のマップ画面でワールドのビジュアル表現を構築するとき、使用するアート アセットの種類をゲームに伝えます。
UCLASS(Blueprintable)
class MYGAME_API UMyGameZoneTheme : public UPrimaryDataAsset
{
GENERATED_BODY()
/** Name of the zone */
UPROPERTY(EditDefaultsOnly, Category=Zone)
FText ZoneName;
/** The Level that will be loaded when entering this zone */
UPROPERTY(EditDefaultsOnly, Category=Zone)
TSoftObjectPtr<UWorld> LevelToLoad;
/** The Blueprint class used to represent this zone on the map */
UPROPERTY(EditDefaultsOnly, Category=Visual, meta=(AssetBundles = "Menu"))
TSoftClassPtr<class AGameMapTile> MapTileClass;
};
このクラスは UPrimaryDataAsset
から継承されるので、アセットの短い名前とネイティブ クラスを使用する GetPrimaryAssetId
の作業バージョンがあります。たとえば、「Forest」の名前で保存された UMyGameZoneTheme
には、「MyGameZoneTheme:Forest」のプライマリ アセット ID があります。UMyGameZoneTheme
アセットがエディタで保存されるときは常に、PrimaryDataAsset
の AssetBundleData
メンバーはセカンダリ アセットとしてそれを含めるように更新されます。
プライマリ アセットの登録とロードには、次の操作が必要です。
-
エンジンに、プロジェクトのカスタム アセット マネージャー クラスが存在する場合はそれを認識させます。デフォルトのアセット マネージャー クラス
UAssetManager
をオーバーライドするだけです (プロジェクトで特別な機能が必要な場合)。プロジェクトで特別な機能が必要ない場合、このステップをスキップできます。オーバーライドするには、プロジェクトのDefaultEngine.ini
ファイルを変更し、AssetManagerClassName
変数を[/Script/Engine.Engine]
セクションに設定します。最終値は次の形式になります。[/Script/Engine.Engine] AssetManagerClassName=/Script/Module.UClassName
ここで「Module」はプロジェクトのモジュール名、「UClassName」は使用する UClass
の名前です。例として、プロジェクトのモジュール名は「MyGame」、使用するクラスは UFortAssetManager
(つまり UClass
名は FortAssetManager
) とすると、2 行目は次のとおりになります。
AssetManagerClassName=/Script/FortniteGame.FortAssetManager
- 使用するプライマリ アセットをアセット マネージャーで登録します。 これを実行するには、[Project Settings (プロジェクト設定)] メニューで構成する、または起動時にプライマリ アセットを登録するようにアセット マネージャー クラスをプログラムします。
- [Project Settings] (Game / Asset Manager セクション) での構成は次のようになります。
プライマリ アセットをスキャンするパスを設定できます。
設定 | 機能 |
---|---|
Primary Asset Types to Scan (スキャンするプライマリアセットタイプ) | 検出して登録するプライマリ アセットのタイプ、さらに検索場所、実行内容をリストします。 |
Directories to Exclude (除外するディレクトリ) | プライマリ アセットを明示的にスキャンしないディレクトリ。これはテスト アセットを除外する場合に便利です。 |
Primary Asset Rules (プライマリアセットルール) | アセットの処理方法を指定する特定のルール オーバーライドをリスト。詳細については、「クック処理とチャンク化」を参照してください。 |
Only Cook Production Assets (プロダクションアセットのみをクック) | DevelopmentCook と指定されたアセットは、これがオンの場合に、クック プロセス中にエラーを発生します。最終のシッピング ビルドにテスト アセットが含まれないことを確認するのに最適です。 |
Primary Asset ID Redirects (プライマリアセットIDリダイレクト) | アセット マネージャーがこのリストにある ID のプライマリ アセットに関するデータを検索するとき、その ID が指定した別の ID で置き換えられます。 |
Primary Asset Type Redirects (プライマリアセットタイプリダイレクト) | アセット マネージャーがプライマリ アセットに関するデータを検索するとき、このリストにあるタイプ名がネイティブ タイプの代わりに使われます。 |
Primary Asset Name Redirects (プライマリアセット名リダイレクト) | アセット マネージャーがプライマリ アセットに関するデータを検索するとき、このリストにあるアセット名がネイティブ名の代わりに使われます。 |
* コードでプライマリ アセットを直接登録する場合は、StartInitialLoading
関数をアセット マネージャー クラスでオーバーライドし、そこから ScanPathsForPrimaryAssets
を呼び出します。この場合、単一のサブフォルダに同じタイプのすべてのプライマリ アセットを配置することを推奨します。これにより検出と登録が速くなります。
- アセットをロードします。アセット マネージャーの各関数
LoadPrimaryAssets
、LoadPrimaryAsset
、LoadPrimaryAssetsWithType
を使用して、適切なタイミングでプライマリ アセットをロードします。その後、UnloadPrimaryAssets
、UnloadPrimaryAsset
、UnloadPrimaryAssetsWithType
でアセットをアンロードします。これらのロード関数を使用するとき、アセット バンドルのリストを指定できます。このようにロードすると、前に説明したとおり、アセット バンドルが参照しているセカンダリ アセットをアセット マネージャーがロードします。
動的に作成されたプライマリ アセットを登録しロードする
プライマリ アセット バンドルは、ランタイム時に動的に登録しロードできます。実行を理解するのに役に立つ 2 つのアセット マネージャー関数があります。
ExtractSoftObjectPaths
では、指定されたUScriptStruct
のすべてのUPROPERTY
メンバーを調べ、アセット参照を特定します。続いてこれらがアセット名の配列に格納されます。この配列は、アセット バンドルを作成するときに使用できます。ExtractSoftObjectPaths
パラメータ:
パラメータ | 目的 |
---|---|
Struct |
アセット参照を検索するための UStruct。 |
StructValue |
構造体への void pointer |
FoundAssetReferences |
構造体に見つかったアセット参照を返すために使用する配列。 |
PropertiesToSkip |
返される配列から除外されるプロパティ名の配列。 |
RecursivelyExpandBundleData
はプライマリ アセットのすべての参照を検索します。アセット バンドルのすべての依存関係を見つけるために再帰的に展開します。この場合、上の ZoneTheme で参照される TheaterMapTileClass は AssetBundleData に追加されるということです。 それから、名前付きダイナミック アセットを登録し、そのロードを開始します。RecursivelyExpandBundleData
のパラメータ
パラメータ | 目的 |
---|---|
BundleData |
アセットの参照を含むバンドル データ。再帰的に展開されます。関連アセット一式をロードするときに便利です。 |
たとえば、「MyGame」プロジェクトは、カスタム アセット マネージャー クラスで次のコードを使用して、ゲーム中にダウンロードされた「theater」データに基づいて、アセットを構築し、ロードします。
// Construct the name from the theater ID
UMyGameAssetManager& AssetManager = UMyGameAssetManager::Get();
FPrimaryAssetId WorldMapAssetId = FPrimaryAssetId(UMyGameAssetManager::WorldMapInfoType, FName(*WorldMapData.UniqueId));
TArray<FSoftObjectPath> AssetReferences;
AssetManager.ExtractSoftObjectPaths(FMyGameWorldMapData::StaticStruct(), &WorldMapData, AssetReferences);
FAssetBundleData GameDataBundles;
GameDataBundles.AddBundleAssets(UMyGameAssetManager::LoadStateMenu, AssetReferences);
// Recursively expand references to pick up tile Blueprints in Zone
AssetManager.RecursivelyExpandBundleData(GameDataBundles);
// Register a dynamic Asset
AssetManager.AddDynamicAsset(WorldMapAssetId, FSoftObjectPath(), GameDataBundles);
// Start preloading
AssetManager.LoadPrimaryAsset(WorldMapAssetId, AssetManager.GetDefaultBundleState());