[UE5 C++] Unityと比較しながらView操作の自分用メモ
概要
UnityエンジニアのUE5勉強メモ。UEに関する情報は学び中のため間違っている可能性が高い。
BluePrintが主流っぽそうだがプログラマとしてC++でも作れたらいいなと思って勉強中。
内容は表題の通り。
環境
UnrealEngine5.0.0 EarlyAccess 2
やりたいこと
Viewを組み立てて、コードから細かい操作をしたい。
Unityでは、
- C#のViewクラスを作成
- そのViewクラスの構成を親子関係等々をヒエラルキー上で組み立てを行う
- そのViewクラスの構成要素である子のGameObjectを
SerializeField
で参照し、C#コードで操作
というようなことをすればいい。これをUnrealEngineでしたい。
なお、Unityの具体的な雰囲気が以下。
組み立て自体は見た目の話でUnityEditorから3Dモデルなどを調整したり組むのが便利で、操作するものだけC#で参照をもらい動かす。
public class View : MonoBehaviour
{
[SerializeField] Transform _childView;
public void MoveChildView(Vector3 vector)
{
_childView.position += vector;
}
}
前提(UnityとUEの違いについておさらい)
やりたいことのUnityとUEの作業工程の違いについて。
どちらのゲームエンジンでも、任意のViewの管理単位でプログラムのクラスファイルを作成し、そのViewを3Dのエディタで組み立て、プログラムから参照を取得し、操作をする。
このプログラム側と3D Viewの橋渡しさえできれば、あとはもう自由に動かしてゲームを作るだけ。
この部分はツールであるゲームエンジンの使い方に依存した部分なので、今回UEについてお勉強した。
Unity
- C#のコンポーネントクラスを作成
- GameObjectにアタッチ
- 必要に応じて、それを親にしたGameObjectの親子構造を作成
- それをPrefab化して再利用しやすくできる
- SerializeFieldなどによりGameObjectの参照を得て操作する
UE (C++)
- C++でViewクラスを作成 (Actor継承したとする)
- そのC++クラスを継承したBluePrintを作成
- BluePrintはUnityのPrefabと類似した機能で、親子関係のあるGameObjectの組み立てができる
- BluePrintはUnityのPrefabのように管理・生成が可能
- なお、WorldOutliner(UnityのHierarchy)で組み立てたものをBluePrintにすることもできる。この際は汎用のActorComponentクラスが継承される。
- C++からViewを参照して操作 (今回はこの方法のメモ)
[本編] UEでViewの操作を行う
動作サンプル
KeyboardというViewを作成。Keyboardは子にKeyCapを4つ持ち、それぞれ操作する。
実際のキーボードの↑→↓←に対応してそれぞれ動かした。
3DモデルはBlender/SubstancePainterで簡易作成したものをFBXで用意した。
C++ ヘッダーで変数を用意
UCLASS(Blueprintable)
class TEST_API AKeyboard : public AActor
{
(省略)
private:
UPROPERTY(EditAnywhere)
UStaticMeshComponent* UpKey;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* DownKey;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* LeftKey;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* RightKey;
(省略)
};
3DモデルのKeyCapを動かすためにメッシュのためのコンポーネントであるUStaticMeshComponent
をヘッダーで定義する。
Unityであればここで[SerializeField]
を付けることで、Inspectorタブにドラッグ&ドロップで参照を指すためのEditorUIが表示される。
しかし、UEではDetailsタブにそういったものは表示されない。
MyBluePrintタブにはヘッダー定義した変数が見えるが参照を差し込むような場所はなさそうだった。
UEでC++で、この関連付けを行うには、まずC++側でコンポーネント生成して直接その変数に参照を持たせる。(BluePrintを使うハイブリッドな別の方法もあるので後述する)
C++ 実装 コンストラクタでコンポーネントの作成と変数への参照付け
AKeyboard::AKeyboard()
{
(省略)
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
UpKey = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("UpKey"));
UpKey->SetupAttachment(RootComponent);
DownKey = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DownKey"));
DownKey->SetupAttachment(RootComponent);
LeftKey = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LeftKey"));
LeftKey->SetupAttachment(RootComponent);
RightKey = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RightKey"));
RightKey->SetupAttachment(RootComponent);
}
実装側ではコンストラクタでCreateDefaultSubobject<T>()
を用いて組み立てている。SetupAttachment()
により親子構造の構築も行っている。
さらに3DモデルのMeshデータをロードしてC++で差し込むやり方もあるようだが、文字列等々でハードコードでPathを書くと困りそうなので、Editor側で参照を差し込みたいのでその方法は取らないことにした。
ここまででコンパイルしてBluePrintを見てみる。
Componentsタブを見るとコンストラクタで組み立てた通りに生成されている。(土台の3Dモデルはコード参照しないのでEditor側でコンポーネント作成した)
あとは生成されたUStaticMeshComponent
を選択し、DetailsタブからStaticMeshを設定すればよい。
注意したいのは、BluePrintのDetailsタブから編集するには、ヘッダーの変数定義でBluePrintから編集できるようにUPROPERTY(EditAnywhere)
を付けておく必要がある。なお、今回はEditAnywhere
としたが必要に応じて変更する。
あとは、もう変数に参照を持ってるのでC++で自由に操作するだけでいい。
小話1
Unity民だったのでView構築をクラスのコンストラクタでやるのに違和感があった。
これは単にゲームエンジンの仕組みの違いなだけで、UnityではMonoBehaviourのコンストラクタを使わないというだけなのでなんら深い意味はないがちょっと混乱した。
というのも、ゲームプレイをしたあとに実態が作られてその際にコンストラクタが走ると思っていたが、Editorでの編集中にコンストラクタに書いたのが反映されて、BluePrintと連携されていて、という部分を知らなかったので。「BluePrintのための」感が強いのかな。
この部分に該当するBluePrintの機能がConstruction Scriptだと思われる。
小話2
Find的な方法で動的取得もあるのかもしれないが、できれば最初から決まっているなら静的に取得した方が安心できる。ランタイム検索なら負荷も少なからずありそう。
また今回のように似た用途の同じコンポーネントが複数ある場合に、Viewの見た目と一致したものを動的取得するのが面倒そう。なのでポチポチして静的に対応付けしたい。
(別方法) C++とBluePrintを用いた参照の仕方
コンストラクタでコンポーネントをC++で生成し、変数に持たせることで参照した。
しかし、コンストラクタで組み立てるのは、Viewによっては複雑な親子関係だったりして、C++側で構築するのが大変だったりメンテしづらかったりする場合もありそう。
そのため、View構築はEdiotr側でポチポチやって、動かしたい3DモデルとかだけをC++側で参照して動かしたりもしたい。しかし、Unityの[SerializeField]
のように、BluePrintのDetailタブから参照を指すことはできなかった。
だが、BluePrintを用いて変数に代入することで同じようなことができる。
UCLASS(Blueprintable)
class TEST_API AKeyboard : public AActor
{
(省略)
// BluePrintのためのセッターを用意する
UFUNCTION(BlueprintCallable)
void SetUpKeyStaticMeshComponent(UStaticMeshComponent* Mesh);
UFUNCTION(BlueprintCallable)
void SetDownKeyStaticMeshComponent(UStaticMeshComponent* Mesh);
UFUNCTION(BlueprintCallable)
void SetLeftKeyStaticMeshComponent(UStaticMeshComponent* Mesh);
UFUNCTION(BlueprintCallable)
void SetRightKeyStaticMeshComponent(UStaticMeshComponent* Mesh);
(省略)
// 変数
UPROPERTY(EditAnywhere)
UStaticMeshComponent* UpKey;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* DownKey;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* LeftKey;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* RightKey;
(省略)
};
void AKeyboard::SetUpKeyStaticMeshComponent(UStaticMeshComponent* Mesh)
{
UpKey = Mesh;
}
void AKeyboard::SetDownKeyStaticMeshComponent(UStaticMeshComponent* Mesh)
{
DownKey = Mesh;
}
void AKeyboard::SetLeftKeyStaticMeshComponent(UStaticMeshComponent* Mesh)
{
LeftKey = Mesh;
}
void AKeyboard::SetRightKeyStaticMeshComponent(UStaticMeshComponent* Mesh)
{
RightKey = Mesh;
}
UFUNCTION(BlueprintCallable)
としてBluePrintから呼び出せる変数のセッターを用意する。あとはBluePrint側で作成したUStaticMeshComponent
をこのセッターを用いて紐づけすればよい。
Discussion