UE4 UnrealC++でのアセットの扱い方についてのメモ

# 概要
UnrealEngine の BPアクターやアセットデータを UnrealC++ で扱うためのメモ書きです。
C++からアクターやアセットをスポーンする場合を書いていますが、実際使う場合そのアセットのロードやパッケージ化時の参照に問題がでることがありますので別途対応が必要です。

## 修正履歴
|日付|内容|
|:---------|:---------|
|2019/08/30|サウンドデータ取得について追記|
|2020/07/03|アクタースポーン時のパラメータについて追記|

## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.22, 4.24


## 参考
以下を参考にさせて頂きました、ありがとうございます。

[アセットの参照](https://docs.unrealengine.com/ja/Programming/Assets/ReferencingAssets/index.html)
[[UE4] C++で動的にアクターを生成（スポーン）する方法で一番実用的だった方法](https://qiita.com/Kuroyanagi96/items/7849dfaed83fc8df5741)
[UE4 スポーン時のアクター（SpawnActor）に引数を渡す方法](http://unrealengine.hatenablog.com/entry/2015/04/08/230000)


# BPアクターをC++から扱う


## BPアクターをC++からスポーンする１

対象のBPファイルクラスのメンバ宣言です。

```cpp:.h
UPROPERTY()
TSubclassOf<AActor> BP_Var;
```

ランタイム時とエディタ時でBPファイルを探すConstructorHelpersでの処理が違います。
また、ファイルパスは /Game/ や /Plugin/ などから開始しないとならないようで、/Content/は省略します。
（例) {Project名}/Content/Test/TestActor.uasset -> /Game/Test/TestActor

```cpp:.cpp
// コンストラクタ
#if UE_GAME
// ランタイム
static ConstructorHelpers::FClassFinder<AActor> BluePrintFile(TEXT("/Game/Test/TestActor"));
if (BluePrintFile.Class) {
	BP_Var = (UClass*)BluePrintFile.Class;
}
#else
// エディター
static ConstructorHelpers::FObjectFinder<UBlueprint> BluePrintFile(TEXT("Blueprint'/Game/Test/TestActor.TestActor'"));
if (BluePrintFile.Object) {
	BP_Var = (UClass*)BluePrintFile.Object->GeneratedClass;
}
#endif


// スポーンするメソッド
// アクターをスポーンする
AActor* MyClass = GetWorld()->SpawnActor<AActor>(BP_Var, FVector(0.0f, 0.0f, 0.0f), FRotator(0.0f, 0.0f, 0.0f));

```

サンプルコードでは取得したBPクラス(BP_Var)のnullptrチェックを行っていないことに注意。


## BPアクターをC++からスポーンする２
対象のBPファイルクラスのメンバ宣言です。

```cpp:.h
UPROPERTY()
TSubclassOf<class AActor> SubClass;
```

ファイルパス設定は TEXT("")マクロを使うか、"/Game/Blueprint/CharaBP.CharaBP_C" のように接尾語 _C を付与します。

```cpp:.cpp
// コンストラクタ
// クラスを取得
SubClass =  TSoftClassPtr<AActor>(FSoftObjectPath (TEXT("/Game/Blueprint/CharaBP"))).LoadSynchronous();

// スポーンするメソッド
// コンストラクタで取得したクラスを用いてアクタを生成
ActorSpawnParameters SpawnInfo;		// パラメータ設定
AActor* Instance = GetWorld()->SpawnActor<AActor>(SubClass , SpawnInfo);

```

この方法ではエディタ時とランタイム時での処理の差異はありませんでした。


## その他
### アクタースポーン時にパラメータを渡す
▼BP
スポーンされるアクターBPの変数の詳細にて[インスタンス編集可能]と[スポーン時に公開]にチェックを入れる。


▼C++
スポーンされるアクタークラスのメンバ変数定義時のメタ情報に `ExposeOnSpawn` を追加する。

```cpp:.h
// スポーン時に公開
UPROPERTY(Category = "Test", EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true))
		int	TestParam;
```

結果、スポーンアクタノードにパラメータピンが追加されます。
この変数はコンストラクションスクリプトから使えるようです。
![SpawnActorNode.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c79c7254-ddba-1642-b08f-6fb4413fac00.jpeg)


▼C++
C++の場合は `SpawnActorDeferred` を使用します。
`BeginPlay` 前に実行したい処理を書いて、`FinishSpawning` を呼びます。
以下コード例。

```cpp:.cpp
FTransform _SpawnTransform;
_SpawnTransform.SetLocation(_Location);
_SpawnTransform.SetRotation(_Rotation.Quaternion());

auto const _Actor = World->SpawnActorDeferred<ATestActor>(_Class, _SpawnTransform);

_Actor->TestParam = 10;		// BeginPlay前に実行したい処理

// パラメータ設定は以下のように名前で指定も可、型名ごとにメソッドが用意されている
// UKismetSystemLibrary::SetIntPropertyByName(_Actor, FName(TEXT("TestParam")), 10);

_Actor->FinishSpawning(_SpawnTransform);	// スポーン処理

```



# アセットデータをC++から扱う

## アセットをロードする
`LoadObject` を使うと同期ロードで取得ができます。
以下コード例。

```cpp:.cpp

UTexture* _Tex = LoadObject<UTexture2D>(NULL, TEXT("/Game/Texture/T_sample00.T_sample00"), NULL, LOAD_None, NULL);

```


## DataTable型のデータをC++から取得する

DataTableの元になる構造体です。
このstructを元にアセットブラウザからデータテーブル型のアセットを作成します。

```cpp:.h
USTRUCT(BlueprintType)
struct FMyDataTable : public FTableRowBase
{
	GENERATED_USTRUCT_BODY()

public:
	// コンストラクタ
	FMyDataTable(){}

public:
	// アイテムID
	UPROPERTY(Category = "Test", EditAnywhere, DisplayName = "Param1")
	int32		Param1;
	// 存続時間[sec]
	UPROPERTY(Category = "Test", EditAnywhere, DisplayName = "Param2")
	float		Param2;

};
```

読み込み処理をするC++クラスです。
`ConstructorHelpers` を使ってオブジェクトを取得します。

```cpp:.h
UDataTable* TestData;
```

TestParamがデータテーブル型のアセットです。

```cpp:.cpp
// コンストラクタ
static ConstructorHelpers::FObjectFinder<UDataTable> DataTableFile(TEXT("/Game/Test/TestParam.TestParam'"));
if(DataTableFile.Object){
	TestData = DataTableFile.Object;
}

// 取得
TArray<FMyDataTable*> OutAllRows;
FString _Context;		// エラー時用
TestData->GetAllRows<FMyDataTable>(_Context, OutAllRows);
for (FMyDataTable* _InfoRow : OutAllRows)
{
	UE_LOG(LogTemp, Warning, TEXT("%d, %f"), _InfoRow->Param1, _InfoRow->Param2);
}

```


## テスクチャデータをC++から取得する

[ui]フォルダにある test00.uasset というテクスチャデータを取得する例です。

```cpp:.cpp
// コンストラクタ
class UTexture2D* Tex;

static ConstructorHelpers::FObjectFinder<UTexture2D> _DataFile(TEXT("/Game/ui/test00"));
if (_DataFile.Object) {
	Tex = _DataFile0.Object;
}
```


## カーブデータをC++から取得する

NewCurveBase.uassetという浮動小数カーブのデータを取得する例です。

```cpp:.cpp
// コンストラクタ
UCurveFloat* Curve = nullptr;
static ConstructorHelpers::FObjectFinder<UCurveBase> _DataCurve(TEXT("/Game/NewCurveBase.NewCurveBase"));
if (_DataCurve.Object) {
	Curve = (UCurveFloat*)_DataCurve.Object;
}

// テスト表示
UE_LOG(LogTemp, Log, TEXT("Curve:(%.2f) = %f"), 1.08f, Curve->GetFloatValue(1.08f) );
```

カーブはこんな感じです。適当に曲線でポイントを繋いでいます。
![CurveFloat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/498d34c3-443b-c644-292c-9dc47c801a52.png)

ポップアップ上は 時間1.08の時に値が0.58ですが、実際に取得すると有効桁数が細かくなります。カーブデータのポップアップの有効桁数とは違うので注意が必要の様です。
![CurveFloat_Output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/f7c28dfd-8092-db06-5d12-9e19af9f9af8.png)


## サウンドデータ（サウンドウェーブ、サウンドキュー）をC++から取得する
se_test というサウンドウェーブデータを取得し再生するテストコードです。
サウンドウェーブ(USoundWave)とサウンドキュー(USoundCue)は、どちらでも 基底クラスのUSoundBase 型でいけます。


```cpp:.h
UPROPERTY()
USoundBase* Sound_Test;
```

```cpp:.cpp
#include "Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h"
// コンストラクタ
static ConstructorHelpers::FObjectFinder< USoundBase > find_sound(TEXT("SoundWave'/Game/Sound/se_test.se_test'"));
if (find_sound.Succeeded()) {
	Sound_Test = find_sound.Object;
}

...
// ↑で取得したサウンドデータを再生する
UGameplayStatics::PlaySoundAtLocation(GetWorld(), Sound_Test, GetActorLocation());

```


# まとめ
C++のみでのアセット取り扱いを書きましたが、パッケージ化をする際には参照がされていないアセットは通常含まれませんので、必要に応じて別途設定をしなければなりません。
方法としては

1.  [プロジェクト設定] -> [パッケージング] -> [Packaging] -> [Additional Asset Directories to cook] に対象ディレクトリを追加する。
1.  ゲームインスタンスクラスに参照を追加しておく。
1. データアセットに参照リストを用意しておく。


などが考えられます。