見出し画像

【3Dチーム研究開発】UnrealEngine4を活用したリアルタイムアクションゲーム~Blueprint、AnimationBlueprint、c++による設計編~

近年モバイルゲーム開発においてUnrealEngine4を採用したタイトルが増えてきています。
G2 Studios 3Dチームでは、Unreal Engine4を採用した3Dアクションゲームの研究開発に取り組んでいます。今回は開発における、基本設計やソースコードからBlueprint連携について纏めたいと思います。


はじめに

初めまして!3DチームテクニカルアーティストのNagaです。
私は入社後、3DチームによるUnrealEngine4を使ったアクションゲーム研究開発に参画し、主にエンジニアリング、グラフィック周りを担当致しました。

▼チーム発足や開発経緯はこちらをご確認下さい。

開発環境について

VisualStudio 2019
UnrealEngine 4.25

*開発当初は4.22でしたが、幾つかのVersionUpを経て現在のVersionとなります。

基本設計について

G2 StudiosはUnityEngineを用いた開発を中心に行ってきました。
今回は研究開発となります。
UnrealEngineでの基本操作、応用としてアクションゲームのプロトタイプを作る事により、チーム内で知見を得て、なるべく職種問わず、社内全体に共有する事を目的としています。
その為、ベース設計をc++で開発しています。

c++ベースで開発するのは、この3つが主な理由です。

①基本的なパラメーターはc++記述にして、共通管理を行う。
②API(関数)を独自で作成して、デザイナーの要望に応えられる様な、柔軟なAPI(関数)を提供する為。
③UnrealEngine4でBlueprintに公開していない機能があり、それらの機能使用、機能検証の為
(UnrealEngine4のVersionによっては公開されている機能もあります。)

とは言え必ずc++が必要かというと、必要ではありませんし、デザイナーが1人でアプリリリースをする事もできます。
開発において以下のサイトを参考にしました。
[UE4]C++or Blueprint?


Actor、Pawn、Characterの役割について

そもそもActor(アクタ)って何?と思われる方もいるはずなので、こちらで簡単に纏めています。

Actor
・オブジェクト(UObject)を継承したクラス
・UE4のワールド内に配置可能な全てのオブジェクトはActorクラスを親に持つ
・コンポーネントを付与する事が出来る
・Unityで言う所のGameObject
・物

Pawn
・Actorを継承したクラス
・ControllerがPawnを所有し、操作を行う
・歩行機能が備わっている
・者、人以外の動物等

Character
・Pawnを継承したクラス
・カプセルコリジョンがRootに存在し、スケルタルメッシュが子供に割り当てられている。
・歩行機能が人型に特化している
・人

ControllerはPawnを所有しますが、PlayerControllerはプレイアブルキャラクターを所有します。
UnityでいうSceneはUnealEngine4ではMapといったファイルで管理されているのですが、それらも元はActorになり、基本的にはActorを継承している設計となっています。

研究開発ではプレイアブルキャラクターは人型なので、Characterクラスを継承した、カスタムCharacterクラスを実装し、Blueprint上で継承しています。
動物などの操作があるのであれば、Pawnから独自で作る場合もあります。
誤解しやすいのは「人型なら全てCharacterクラス」とは限らないという事です。
モーションキャプチャーを使ったゲーム開発などでは、カプセルコリジョンを使わず、Meshに精密なコリジョンを設定する事で当たり判定を行う事も可能です。
それにより、リアルな物理干渉を行う事ができます。

c++での基本的な記述は以下となります。​

UCLASS(ABSTRACT)
class FLARE_API ABaseCharacter : public ACharacter, public IAISightTargetInterface, public IGenericTeamAgentInterface
{
	GENERATED_BODY()

public:
	ABaseCharacter(const FObjectInitializer& ObjectInitializer);
	virtual void OnConstruction(const FTransform& Transform) override;
	virtual void PostInitializeComponents() override;
	virtual void BeginDestroy() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
	virtual void Tick(float DeltaTime) override;
	virtual float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;

protected:
	virtual void BeginPlay() override;
	virtual void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode = 0) override;
	virtual void OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
	virtual void OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
};
ABaseCharacter::ABaseCharacter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
	// 初期化処理
}

void ABaseCharacter::OnConstruction(const FTransform& Transform)
{
	// Blueprint変数の初期化	
	Super::OnConstruction(Transform);
}

void ABaseCharacter::PostInitializeComponents()
{
	// ActorのComponentが初期化された後に呼ばれる
	Super::PostInitializeComponents();
}

void ABaseCharacter::BeginDestroy()
{
	// オブジェクトがメモリを解放して別のマルチスレッドリソースを処理する
	Super::BeginDestroy();
}

void ABaseCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	// Destroy呼び出し等による終了処理
	Super::EndPlay(EndPlayReason);
}

void ABaseCharacter::Tick(float DeltaTime)
{
	// 1フレームに1回の一定間隔で呼ばれる処理
	// UnityのUpdateに相当します。
	Super::Tick(DeltaTime);
}

float ABaseCharacter::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	// ダメージを受けた際の処理
	const float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
	return ActualDamage;
}

void ABaseCharacter::BeginPlay()
{
	// Game開始時に呼ばれる
	// UnityのStart関数に相当します。
	Super::BeginPlay();
}

void ABaseCharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode)
{
	// 行動の変化を検出します。
	// 例:ジャンプ、地面設置、泳ぐ
	Super::OnMovementModeChanged(PrevMovementMode, PreviousCustomMode);
}

void ABaseCharacter::OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust)
{
	// しゃがみを開始します。
	Super::OnStartCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);
}

void ABaseCharacter::OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust)
{
	// しゃがみを終了します。
	Super::OnEndCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);
}

また、Interfaceを自作し、それらも継承しています。
「c++記述しないとUnrealEngine4を扱うのは難しいの?」と思われがちですが、InterfaceやカスタムクラスはBlueprintでも実装できます。
c++が分からない、コーディングが苦手な方はBlueprintから作るのもアリです。
研究開発プロジェクトではプレイアブルキャラクター、敵、NPCは全てCustomCharacterを継承して開発されています。

Blueprintについて

ビジュアルスクリプティングシステムと言う仕組みを採用したツールになり、基本的にはノードとピンから成り立っています。
視覚的にプログラムを作成できます。
プロトタイピングや、修正の手軽さの面で圧倒的に使い易いです。
Unityでいう所Prefabに近いかな?と思います。
ロジックを書けるPrefabになります。

エンジニアの方は理解できると思いますが、コーディングをすると、compile(コンパイル処理)というのが走り、大規模案件になるとそれなりに時間がかかります...
Blueprintを使う事でcompile処理やビルド処理を大幅に削減できます。

ClassSettings

画像1

この画面はBlueprintのViewportWindowになります。
UnrealEngineはComponent指向で管理できるようになっています。
上画像左部分がこのプレイアブルキャラクターで使われるComponentが表示されています。
研究開発プロジェクトでは予め使われるComponent等についてはc++で生成し、Blueprint上で公開する仕組みにしています。
デザイナーはそのComponentにMeshを割り当てたり、パラメーターの調整を行ってもらう事でゲームに反映される仕組みです。

右側にParentClassとありますがc++の継承クラス元を選択しており、関連するInterfaceを継承されている事がわかります。

ClassDefaults

画像2

右側にカテゴライズされているパラメーター群があり、それらの設定が可能です。勿論特定のタイミングで~...といった操作も可能です。

EventGraph

画像3

研究開発プロジェクトではc++上でカスタムAPI(関数)を公開していますが、大まかには3種類です。

UCLASS(ABSTRACT)
class FLARE_API ABaseCharacter : public ACharacter, public IMyInterface
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
	void Sample();
	virtual void Sample_Implementation() override;

protected:
	UFUNCTION(BlueprintCallable)
	void Sample();

	UFUNCTION(BlueprintImplementableEvent, BlueprintCallable)
	void BP_Sample();
};
void ABaseCharacter::Sample()
{
   BP_Sample();
}
void ABaseCharacter::Sample_Implementation()
{
}



UFUNCTION()というマクロを記述しBlueprintCallableキーワードを指定する事でカスタム関数をBlueprint上に公開する事ができます。

BlueprintImplementableEventはc++ではロジックを記載しないで、関数実行を書く事でBlueprint上の関数ロジックが処理されます。
BlueprintNativeEventはInterfaceを継承した関数となります。
画像の処理は前後左右判定を行い、回避モーションを再生するロジックとなります。
Game上では下の様になります。

他にはc++の関数を呼び出して、Blueprint上の関数を連携させる事も可能です。
下の画像ではinterface処理を呼び出した後、タイマー処理をBlueprint上で実行しています。
こういった実装がUnrealEngineでは可能で、柔軟な開発ができます。

画像4


Skeleton Asset

UnealEngine4では、SkeletonにAnimationやSkeletalMesh、PhysicsAsset(物理アセット)が紐づけられます。

Skeleton

Skeletonは複数のMeshを所持する事が可能で、物理アセットも複数所持が可能です。
研究開発プロジェクトではUnrealEngine4上でSocketを作成し、武器を持たせています。

画像9

SkeletalMesh

MorphTargetを使用している場合は、こちらに反映されます。
またMaterialがどの部分に割り当てられているがが視覚的にわかります。

画像10

PhysicsAsset

画像11

物理Assetをします。
各SkeletonJointにCollisionを設定し当たり判定を新規作成や調整します。
またJoint間のコンストレイントを連結され、回転の影響度の調整といった事も可能です。
上の画像では髪のJointも設定しており、物理挙動が開始した場合は髪の毛や胴体のCollisionが干渉するようにしています。

AnimationBlueprintについて

Skeletonを動かす際の制御を行います。
新規作成する場合、「どのSkeletonに割り当てるか?」が選択できます。
大きくEventGraph、AnimGraphと分かれています。

EventGraph

画像6

Blueprint同様EventGraphがあり、ゲームロジックを作れます。
ClassSettings、ClassDefaultsの設定はBlueprintと同じです。

AnimGraph

画像7

モーション遷移の管理や以下の機能の設定も行えます。

・IK
・揺れ物
・LayerAnimation
・AnimationSlot

上記例は一例にしか過ぎません。
カスタムノードを作成する事も可能です。

研究開発プロジェクトでは自作のIKノードに加え、揺れ物ノードをこちらのgithubより使わせて頂いています。→KawaiiPhysics

モーション遷移の管理は以下になります。サンプルで作成してみました。
下の画像のAnimationBlueprintでは以下の遷移が行えます。

・歩行
・ジャンプ
・泳ぐ
・スキルを使う
・体力がなくなる

画像8

参考がてら遷移を確認してみましょう。(↓動画です)

直感的に動作確認できるのは良いですね!!

最後に

今回はクラス設計、Blueprint、AnimationBlueprintを中心に話しましたが、全体の20%位になってしまいました。
他にも

・ビヘイビアツリー(BehaviorTree)
・Shader、Postprocess
・Plugin設計
・VFX
・パフォーマンス最適化

等、研究開発の範囲は多岐に渡ります。

機会があればお話ししたいと思います。
最後までご覧いただきありがとうございました。


▼G2 Studiosでは一緒に働く仲間を募集しています。


X(Twitter)にて最新のお知らせを配信しております。ぜひフォローしてください!