GameProgrammar's Night

ゲームプログラム系の覚え書き

UE4 C++でイベント/イベントディスパッチャーを作る

 UE C++には、デリゲートが用意されています。デリゲートとは簡単に説明しますと、メソッド・関数などを変数に保存して、任意のタイミングで呼び出せる仕組みです。詳しくはググってください。

docs.unrealengine.com

 このデリゲートの中に、BP上でイベント/イベントディスパッチャーとして扱われる仕組みがありますので紹介します。

イベントディスパッチャー

 良く使うであろうイベントディスパッチャーの実装から先に説明します。
 動的マルチキャストデリゲートを実装するとBP上ではイベントディスパッチャーとして扱われます。

docs.unrealengine.com

実装例

// 動的マルチキャストデリゲート(イベントディスパッチャー)の宣言
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyActorOnEventDispather);

UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UMyActorComponent : public UActorComponent
{
    GENERATED_BODY()

    virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;

public:
   // 動的マルチキャストデリゲート(イベントディスパッチャー)の定義
   UPROPERTY(BlueprintAssignable, Category="MyActor")
   FMyActorOnEventDispather OnEventDispather;
};

void UMyActorComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction )
{
   OnEventDispather.Broadcast(); // イベントディスパッチャーをCallする
}

 DECLARE_DYNAMIC_MULTICAST_DELEGATEマクロを使って、イベントディスパッチャー用のクラスを作ります。()の中がクラス名となります。
 その作ったクラスのプロパティをクラス内で定義し、Assingnableに設定します。そうすることでBPで作ったイベントをバインドすることができます。
イベントディスパッチャーとして宣言した変数のBroadcastメソッドを呼ぶことが、イベントディスパッチャーをCallすることと同じことになります。

 BP上でMyActorComponentをActorに追加して、使った例は以下の通り。
f:id:katze_7514:20160522001504j:plain
 イベントディスパッチャーとして使用できています。

 また、イベントディスパッチャーで引数を取る場合は、引数ありの宣言マクロを使います。

// 1つ引数を取り、型がboolで引数名がbSuccess のイベントディスパッチャー宣言
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyActorOnEventDispather, bool, bSuccess);

 引数は8個まで取ることができます。引数の数を増やしたら、OneParamのOneの部分を引数の数の英語に変えます*1
 戻り値はBPのイベントディスパッチャー同様に扱えません。

イベント

 BPでのイベントに相当するのは、動的デリゲートです。デリゲートの中にイベントもありますが別ものです。名称がまぎらわしいですね。
docs.unrealengine.com

実装例

 実装の仕方は、動的マルチキャストとほぼ同じです。
 DECLARE_DYNAMIC_MULTICAST_DELEGATEマクロではなく、DECLARE_DYNAMIC_DELEGATEを使います。

// 動的デリゲート(イベント)の宣言
DECLARE_DYNAMIC_DELEGATE(FMyActorOnEvent);

UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UMyActorComponent : public UActorComponent
{
    GENERATED_BODY()

   // BPから動的デリゲート(イベント)を呼び出すメソッド
   UFUNCTION(BluprintCallable, Category="MyActor")
   void CallOnEvent();

public:
   // 動的デリゲート(イベント)の定義
   UPROPERTY(BlueprintReadWrite, Category="MyActor")
   FMyActorOnEvent OnEvent;
};

void UMyActorComponent::CallOnEvent()
{
   OnEvent.ExecuteIfBound();
}

 イベントディスパッチャー実装と違うのは、イベントとして宣言した変数にBlueprintAssignableが使えません。代わりに通常の変数と同じようにBluprintReadWrite・BlueprintReadOnlyなどを使います。
 また、イベントの呼び出しは、Execute・ExecuteIfBoundメソッドのどちらかを使いますが、それらのメソッドはBPから直接呼ぶことができませんので、BP上から呼べるようにメソッド(CallOnEventメソッド)を別途追加します。
 BlueprintReadWriteで宣言すると、BP上のイベントをバインドできるようになります。また、イベントディスパッチャーにバインドすることも可能です。

f:id:katze_7514:20160522012117j:plain

 引数を取る時も、イベントディスパッチャー実装と同じです。

DECLARE_DYNAMIC_DELEGATE_OneParam(FMyActorOnEvent, bool, bSuccess);

*1:2個ならTwoなど

UE4 UMGで入力を取得する

 UMG(UMGのBP)では、各Widgetごとにマウスイベントを取得することはできますが、通常のキーイベントを取得するこはできません。

マウスイベント キーイベント
f:id:katze_7514:20160507212145j:plain f:id:katze_7514:20160507212149j:plain

 
 そのため、UMGでメニューを作りキーで操作したい場合、いつも通りにはいきません。
 UMG上でキーを取るには、2つ方法があります。UMGのOnKey系関数をオーバーライドする方法とUMGの外から入力をもらう方法です。

UMGのOnKey系関数をオーバーライドする

f:id:katze_7514:20160507220341j:plain

 OnKeyUP/OnKeyDownをオーバーライドすると、キーアップ/キーダウンされると対応する関数が呼ばれます。

f:id:katze_7514:20160507220927j:plain

 対応するキーはInKeyEvent引数に入っています。それを実際のKey情報に変換(GetKeyノード)して、何のキーかを調べます(Equal(Key)ノード)。キーをUMGで利用した場合は、Return値にHandledノードを接続し、使わないキーだったらUnHandledノードを接続します。

 また、UMGのOnKey系関数を有効するには、UMGに入力モードがある必要があります。入力モードの切り替えは、SetInputModeUIOnlyノードです。AddToViewportノードを使って画面に表示してから呼びます。
 UMGを消す時は忘れずに入力モードをGameに戻す必要があります。Gameに戻すにはSetInputModeGameOnlyノードを呼びます。入力モードがUMGにあるままだと、UMGを消してもLevelやActorで入力が取れなくなりますので、忘れないようにしましょう。

f:id:katze_7514:20160508000710j:plain

 入力モードを持つことはフォーカスを得るという意味にもなるのですが、UE4.11からフォーカスを得られるかどうかのフラグが追加されましたので、そのフラグもオンにします。

f:id:katze_7514:20160530231433p:plain

 UMGとしては、こちらが正式な方法なのですが、一つ問題があります。OnKey系関数では、ActionMappingと連携ができません。OnKey系関数は対応するキーデータを素の状態でしか受け取れず、またキーデータからActionMappingに変換する方法もありません。

 ActionMappingと連携するためにも、UMGの外から入力を受け取るのを、私としてはお勧めします。

UMGの外から入力をもうらう

f:id:katze_7514:20160508003837j:plain

 UMGのBPに、操作用の関数/イベントを作成して、それをLevel/PlayerControllerから呼ぶ形にします。
 上図では省略していますが、UMG表示されているかのチェックなど、メニューを誤操作しないようにする判定を挟むのもお忘れずに。
 
 メニューはGamePauseをかけてから表示することが多いと思いますので、メニュー操作用のキーイベントは忘れずに「Execute when Paused」にチェックをいれておきましょう。

f:id:katze_7514:20160509155946j:plain

UE4 Navigationの方向だけを取得したい!

 Navigationとは、NavMeshに従ってPawnを指定した場所に最短距離で壁などにぶつかることなく、進ませてくるUE4の機能です。

docs.unrealengine.com

 敵のAIを作るのに重宝する機能ですが、現状ではPawnを移動させることにしか使えません。用途によっては、方向だけ欲しいということもあると思います。例えば、プレイヤーを誘導する矢印を出したり、UE4が用意しているMovementComponentとは違う移動を自分で実装したい時などです。

 ここでは、そのNavigation方向を取得する方法を紹介します。
 現在は残念ながら、Navigation情報そのものにアクセスする手段がBPに公開されていませんので、C++を使わないと取得することができません。
 MovementComponentをC++で自作するのが一番簡単です。

UNavMovementComponent*1::ReqeustDirectMoveでインターセプト

 もろもろNavigationに関する計算が行われて進むべき速度が、UNavMovementComponent::ReqeustDirectMoveメソッドに渡って来ます。ReqeustDirectMoveメソッドをオーバーライドしてやることでNavigation方向をインターセプトすることができます。

 MovementComponentは内部で結構いろんな処理を行っているので、自作をする場合はCharacterMovementかFloatingPawnMovementを継承して作るのが良いと思います。

コード例

UCLASS(ClassGroup=Movement, meta=(BlueprintSpawnableComponent))
class PROJECT_API UMyPawnMovement : public UFloatingPawnMovement
{
	GENERATED_BODY()

public:
       virtual void RequestDirectMove(const FVector& MoveVelocity, bool bForceMaxSpeed) override;
       {
            NavVelocity = MoveVelocity;
            Super::RequestDirectMove(MoveVelocity,bForceMaxSpeed);
       }

public:
      UFUNCTION(BlueprintCallable, Category="TopViewPawnMovement")
      FVector GetNavVelocity()const{ return NavVelocity; }

private:
       FVector NavVelocity;
};

 ちなみに、RequestDirectMoveメソッドが呼ばれるのは、PathFollowingComponent*2のTickComponentのタイミングです。

*1:FloatingPawnMovementやCharacterMovementが継承しているNavgation移動のクラス

*2:AIControllerが持つNavigationの計算をメインに行っているコンポーネント

UMG用にテクスチャアトラスマテリアルを作る

※ UE4.13から、Paper2DスプライトがUMGで使えるようになりましたので、このマテリアルは必要なくなりました

katze.hatenablog.jp

 上の記事の続きです。
 
 前回は急ぎであまり検討できなかったので固定サイズからの切り出しまででしたが、ようやく任意サイズの切り出しができるようになりました。
 以下の画像の通りです。なお、TextureSourceは指定しているテクスチャの全体像です。

f:id:katze_7514:20160412221932j:plain

 切り出したい範囲(画像上部:SpriteRect)とテクスチャサイズ(画像下部:TextureWidth/TextureHeight)を指定すれば、その範囲が表示されます。UV値ではなくピクセルサイズで指定します。
 SpriteRectはVector4Parameterにしています。RGにRectの左上位置、BAにRectのサイズ(幅・高さ)を入れます。

 行っている計算は、描画したいサイズがUVとして(0.0,0.0)~(1.0,1.0)になるように拡大してから、描画原点をズラします。最後にUV値になるようにテクスチャサイズで割っています。

 プレビューは正方形ポリゴンなので縦横比が崩れていますが問題ありません。
 マテリアルをUMGのImageWidgetに設定して、ImageWidgetのサイズを設定し「SizeToContent」にチェックを入れれば、ドットバイドットで表示されます。

f:id:katze_7514:20160412224804j:plain

 あとは、マテリアルインスタンスにして、UMGで使う画像の数だけインスタンスを用意して使います。

f:id:katze_7514:20160426145821j:plain

UnrealEngine4で2Dゲームを作ろう! 「2Dゲームを作るために必要なこと 機能編」

「UnrealEngine4で2Dゲームを作ろう!」の最終回。
 2Dゲームにするための設定などを紹介した「基本編」の続きです。
 UE4の幾多の機能が2Dゲームだと使えるのか使えないのかを紹介します。すべて検証しているわけではありませんので、自分が使った範囲での紹介となります。
 個々の機能の詳細については、ドキュメントを参照してください。

移動(Movement)

 問題なく使えます。

 移動させるには、大きく2つの方法があります。自前で座標計算する方法と、MovementComponentを使う方法です。
 レトロ目な2Dゲーム作るのであれば自前で座標計算するしかありませんが、そうでない時はMovementComponentを使うことができます。
 MovementComponentを使った方法は、加速度がどうしてもついてしまうため、加速度なしの移動が欲しい時は自前で座標計算します。

自前で座標計算する

 ActorのLocationを計算するノードはすべて使えますので、AddActorWorldOffsetなどを使ってActorを動かします。

f:id:katze_7514:20160310200227p:plain

 Sweep引数にチェックを入れることで、Collisionのヒット処理が動くようになります。
 Teleport引数は物理挙動に影響するらしいですが、自分は物理機能は使っていませんのでよくわかりません。

MovementComponentを使う

 MovementComponentとは、名前の通り移動を担うコンポーネントのことです。いくつか種類がありますが、2Dゲームで主に使うのは、FloatingPawnMovement(以下FloatingPawn)とProjectileMovement(以下Projectile)の2つになります。

FloatingPawn

 名前の通り、浮いているポーン用のMoveCompです。一番コンパクトに実装されているMoveCompで、2Dゲームには必要十分な機能を持っています。

 FloatingPawnは座標を与えて移動するのではなく、速度のようなベクトルを与えることで移動を行います。

f:id:katze_7514:20160118181427j:plain

※解説書きました
katze.hatenablog.jp

Projectile

 Projectile(投射)の名前の通り、弾などの投射物の移動計算を行います。バウンドさせることもできるので、跳弾させることも簡単にできます。
 2Dの場合、弾もFloatingPawnで問題ないといえばないのですが、Projectileは誘導機能があるので、誘導弾を作る時はProjectileを使うことができます。
 ただ、実は誘導計算がいまいちで、移動し続けるActorに対して誘導する際に、一度外れてしまうと弾の軌道がなかなか目標に近づいていきません。そのため、誘導をONにした弾は一定時間で消えるように設定しておくのをお勧めします。

その他

 3Dゲーム(FPS/TPS)のキャラクター用にCharacterMovementComponentというのもありますが2Dゲームにはオーバースペックだと思います。

 また、MovementComponentは、対応するActorの座標のフレーム間差分も考慮した速度を発生させるので、SetActorLocationなど直接座標を変更するノード併用する時は注意が必要となります。

衝突(Hit/Collision)

 問題なく使うことが出来ます。
 2D専用の仕組みは準備中ということなので、3D用の仕組みをそのまま流用して使います。Collisionや物理による移動方向をゲームとして使っている平面に制限しておくと良いと思います。

 ただ、移動しているブロックするCollision同士がぶつかった場合、片方のCollisionがもう片方のCollisionに乗り上げてしまうことがある挙動を確認しています。移動方向を制限していても発生します。乗り上げてしまうと、高さ方向が合わなくなるので、次からブロックしなくなってしまいます。
 ちゃんとした解決策はまだ見つけられていないのですが、ブロックを検出した際に乗り上げたかをチェックして元に戻す処理がいるかもしれません。

ナビゲーション(NavMesh)

 問題なく使えます。

 NavMeshに従って移動を行うMoveTo系ノードは、FloatingPawn(もしくはCharacterMovementComponent)を要求しますので、NavMeshで動作させるPawnには、それらを追加しておく必要があります。

Particle

 問題なく使えます。

 パーティクルの向きを速度に合わせて回転させるPSA_Velocity設定が、正射影カメラだと正しく動かないバグがあります。
AnswerHubではUE4.9で修正したとありますが、実際にはまだ修正されていません。

BehaviorTree

 問題なく使えます。

 Paper2D用のTaskなどは用意されていませんので、DecoratorやTaskはすべて自作する必要があります。

UMG

 問題なく使えます。

 ドットバイドット表示にも関わりますので、DPIスケーリング設定は忘れずにしておきましょう。

Audio

 問題なく使えます。

 2Dだと距離減衰などをさせると逆に違和感が生まれることもありますので、切ってしまって良いと思います。

Decal

 使うことができません。

 描画コンポーネントの対応が必要ようなようで、Paper2D/SpriteStudio共に対応していませんので、使うことができません。

Physics

 2D用のPhysicsは準備中ですので、3DのPhysicsを流用することになりますが、使うことはできます。

 まだちゃんとPhysicsを使っていませんので、どんな罠があるかはわかりません。