大量のアセットの管理方法について

# 概要

私がよく使用する、アセットの管理方法について記載します。
また、想定するプロジェクトの規模としては、アセット管理機能を使用する中～大規模を想定しています。

# TextureAsset、AudioAsset、FontAsset

本稿で基本的に使用するOpenSiv3Dの機能です。
この機能を使用すると、プログラムのあらゆるところから登録したアセットにアクセスできるようになります。
詳細なリファレンスは（ https://siv3d.github.io/ja-jp/tutorial/asset/ ）を参照してください。

## アセット用の文字列を管理する

アセット管理では、アセットの登録/アクセスに文字列を使用します。
つまり文字列を管理できれば、アセットを管理することができます。

### 一箇所にまとめて管理する

フォントやオーディオなど、大体どの場所でも使用するものに対してよく使う管理方法です。
必ず読みこむヘッダなどに、グローバル変数として文字列を定義してそれを使用します。

利点：導入が非常に簡単です。
欠点：大量に存在する場合、登録漏れが発生しやすいです。

```:C++
const String Font_Number = U"FontNumber";
const String Font_Barrage = U"FontBarrage";
const String Font_Name = U"FontName";
const String Font_16 = U"Font16";

void Main() {
	FontAsset::Register(Font_Number, 8);
	FontAsset::Register(Font_Barrage, 8, U"あずきフォントLB");
	FontAsset::Register(Font_Name, 6, U"あずきフォントLB");
	FontAsset::Register(Font_16, 16, U"YomogiFont", FontStyle::Bold);

	while (System::Update())
	{
		FontAsset(Font_Number)(U"文字列").draw();
	}
}

```

### 各オブジェクトごとに管理する

キャラクターの種類ごとにクラスを作成した際に使用する管理方法です。
クラス内部で登録する方法と、外部で登録する方法があります。

利点：オブジェクト毎に登録されているのでプリロードやリリースもオブジェクト毎に管理できることです。
欠点：アセット名がオブジェクト毎に記載されるので、登録名の重複を一層意識する必要があります。

```:C++
// 内部登録
class PlayerA {
private:
	// 登録するアセット名
	inline static const String PlayerTexture = U"PlayerATexture";

public:

	// アセットの登録
	static void Register() {
		TextureAsset::Register(PlayerTexture, U"example/windmill.png");
	}

	// アセットの使用
	void draw() const {
		TextureAsset(PlayerTexture).draw(Vec2(0.0, 0.0));
	}

};

// 外部登録
class PlayerB {
public:

	// 登録するアセット名
	inline static const String PlayerTexture = U"PlayerBTexture";
	inline static const String PlayerTexturePath = U"example/windmill.png";

	// アセットの使用
	void draw() const {
		TextureAsset(PlayerTexture).draw(Vec2(400.0, 0.0));
	}

};

void Main() {

	PlayerA::Register();
	PlayerA playerA;

	TextureAsset::Register(PlayerB::PlayerTexture, PlayerB::PlayerTexturePath);
	PlayerB playerB;

	while (System::Update())
	{
		playerA.draw();
		playerB.draw();
	}
}

```

外部登録では、アセット名などが統一されている場合に限りテンプレートを使用して登録できます。

```:C++
// 登録関数
template <class Type>
void textureRegister() {
	TextureAsset::Register(Type::PlayerTexture, Type::PlayerTexturePath);
}

void Main() {
	// PlayerBの登録
	textureRegister<PlayerB>();
}
```

## アセットにアクセスするクラスを管理する

上記までは、アセットにアクセスするための文字列を管理しようとしていましたが、この章ではアセットにアクセスするためのクラスを作成し、そのクラスの管理を行います。

利点：アセットの登録漏れが絶対に起きないことです。
欠点：アクセスするクラスのインスタンスが起動～終了まで1つである必要があります。

以下に各アセットへのアクセス用のクラスを記載します。
実装内容としては、各コンストラクタで指定されたアセット名を保存し、アクセス時にそのアセット名を使用しているだけです。
アセットのアクセスには()オペレータを使用しています。

```:C++
class TextureAssetManager {
private:

	const String name;

public:

	TextureAssetManager(const AssetName& name, const FilePath& path, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		TextureAsset::Register(name, path, parameter);
	}

	TextureAssetManager(const AssetName& name, const FilePath& path, TextureDesc desc, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		TextureAsset::Register(name, path, desc, parameter);
	}

	TextureAssetManager(const AssetName& name, const Icon& icon, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		TextureAsset::Register(name, icon, parameter);
	}

	TextureAssetManager(const AssetName& name, const Icon& icon, TextureDesc desc, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		TextureAsset::Register(name, icon, desc, parameter);
	}

	TextureAssetManager(const AssetName& name, const Emoji& emoji, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		TextureAsset::Register(name, emoji, parameter);
	}

	TextureAssetManager(const AssetName& name, const Emoji& emoji, TextureDesc desc, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		TextureAsset::Register(name, emoji, desc, parameter);
	}

	TextureAssetManager(const AssetName& name, const TextureAssetData& data) : name(name) {
		TextureAsset::Register(name, data);
	}

	[[nodiscard]] TextureAsset operator()() const { return TextureAsset(name); }
	[[nodiscard]] TextureAsset operator()(const Texture& dummy) const { return TextureAsset(name, dummy); }

};

class AudioAssetManager {
private:

	const String name;

public:

	AudioAssetManager(const AssetName& name, const FilePath& path, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		AudioAsset::Register(name, path, parameter);
	}

	AudioAssetManager(const AssetName& name, const FilePath& path, const Optional<AudioLoopTiming>& loop, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		AudioAsset::Register(name, path, loop, parameter);
	}

	AudioAssetManager(const AssetName& name, GMInstrument instrumrnt, uint8 key, const Duration& duration, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		AudioAsset::Register(name, instrumrnt, key, duration, parameter);
	}

	AudioAssetManager(const AssetName& name, GMInstrument instrumrnt, uint8 key, const Duration& duration, double velocity, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		AudioAsset::Register(name, instrumrnt, key, duration, velocity, parameter);
	}

	AudioAssetManager(const AssetName& name, GMInstrument instrumrnt, uint8 key, const Duration& duration, double velocity, Arg::samplingRate_<uint32> samplingRate, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		AudioAsset::Register(name, instrumrnt, key, duration, velocity, samplingRate, parameter);
	}

	AudioAssetManager(const AssetName& name, GMInstrument instrumrnt, uint8 key, const Duration& duration, double velocity, Arg::samplingRate_<uint32> samplingRate, float silenceValue, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		AudioAsset::Register(name, instrumrnt, key, duration, velocity, samplingRate, silenceValue, parameter);
	}

	AudioAssetManager(const AssetName& name, const AudioAssetData& data) : name(name) {
		AudioAsset::Register(name, data);
	}

	[[nodiscard]] AudioAsset operator()() const { return AudioAsset(name); }

};

class FontAssetManager {
private:

	const String name;

public:

	FontAssetManager(const AssetName& name, int32 fontSize, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		FontAsset::Register(name, fontSize, parameter);
	}

	FontAssetManager(const AssetName& name, int32 fontSize, Typeface typeface, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		FontAsset::Register(name, fontSize, typeface, parameter);
	}

	FontAssetManager(const AssetName& name, int32 fontSize, Typeface typeface, FontStyle style, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		FontAsset::Register(name, fontSize, typeface, style, parameter);
	}

	FontAssetManager(const AssetName& name, int32 fontSize, const FilePath& path, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		FontAsset::Register(name, fontSize, path, parameter);
	}

	FontAssetManager(const AssetName& name, int32 fontSize, const FilePath& path, FontStyle style, const AssetParameter& parameter = AssetParameter{}) : name(name) {
		FontAsset::Register(name, fontSize, path, style, parameter);
	}

	FontAssetManager(const AssetName& name, const FontAssetData& data) : name(name) {
		FontAsset::Register(name, data);
	}

	[[nodiscard]] FontAsset operator()() const { return FontAsset(name); }

};

```

また、上記クラスを管理するシングルトンのクラスも作成する必要があります。
シングルトンでない場合、管理クラスのインスタンスを作成するたびに登録処理が行われ、多重登録のログが出力されます。

```:C++
class AssetManager {
private:

	inline static std::unique_ptr<AssetManager> instance;

public:

	static void Create() { instance = std::make_unique<AssetManager>(); }
	static const AssetManager& Get() { return *instance; }

	AssetManager() {}

	// 管理したいアセット
	const TextureAssetManager texturePlayer{ U"PlayerTexture", U"example/windmill.png" };
	const AudioAssetManager audioTest{ U"BGM", U"example/test.mp3" };
	const FontAssetManager fontDebug{ U"font_debug", 24 };

};

void Main() {

	AssetManager::Create();

	const auto& asset = AssetManager::Get();

	while (System::Update())
	{
		asset.texturePlayer().draw();
	}
}
```