読者です 読者をやめる 読者になる 読者になる

GameProgrammar's Night

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

UE4 見下ろし2Dゲームの移動処理(TopViewMovement)を作る

UnrealEngine4 C++

 久々の2Dゲームネタです。タイトルの通り、見下ろしタイプの2Dゲームの移動処理を担うMovementComponentをつくってみます。

見下ろし移動とは?

 RPGや縦STGなどでよく見る真上からキャラクターやマップを見ていることを見下ろし視点と言います。
 2Dゲームでは上下左右の4方向、もしくはそれに斜めを追加して8方向に動かします。これは、コントローラの十字キーと一致させるためにそうなっています。
 もちろん、回転操作をいれて任意方向に進めるようにしてあるものもありますし、モデルを使っている場合は、レバーを倒した方向に移動させるものもあります(UE4 TopDownテンプレート)。
 ここでは見下ろし2Dゲームでは一番使われると思われる8方向移動させることにします。

f:id:katze_7514:20170317185818j:plain

 見下ろし視点は英語ではTopDownView/TopViewと言いますので、実装するMovementComponentのことをTopViewMovementComponentと呼ぶことにします。

TopViewMovementComponentの機能

以下の機能を実装します。

  • 8方向移動
  • 8方向移動するナビゲーション移動

 ナビゲーションが入っていますので、Pawn向けのMovementComponentとして実装します。

TopViewMovementComponentの実装方針

 TopViewMovement実装の一番の難点は、ナビゲーション移動を8方向に制限することです。ナビゲーション移動は最短距離を突き進みますので、移動中かなり自由な向きを取ります。
 以下の記事のようにすることで、移動すべき方向ベクトルを取得することができます。

katze.hatenablog.jp

 これを利用して、移動すべき方向ベクトルから8方向のどれに一番近いかを判定して、その方向に動かすように実装することで実現します。
 上記記事の通り、C++で実装しないと移動すべき方向ベクトルが取得できませんので、TopViewMovementもC++で実装することにします。
 また、MovementComponentは裏で色々と処理を行っているようなので、全部自作することはせずに、FloatingPawnMovementComponentを継承し、必要な部分だけカスタマイズすることにします。

UCLASS(ClassGroup=Movement, meta=(BlueprintSpawnableComponent))
class PROJECT_API UTopViewPawnMovement : public UFloatingPawnMovement
{ ... }

 なお、完全なコードは載せるのは長いですので、この記事では大まかな実装の紹介だけにします。 

MovementComponentでの移動処理

 まずは簡単にですが、MovementComponentでの移動の仕組みを紹介します。

TickComponentで移動する

 移動を行うタイミングはComponentのTickです。TickComponentメソッドの中で、SafeMoveUpdatedComponentメソッドを呼ぶことで移動が行われます。
 SafeMoveUpdatedComponentメソッドに、そのTickで移動する分のベクトルを渡します。

void UTopViewPawnMovement::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction)
{
  FVector DeltaVel = /* ... このTickで移動するベクトルを計算 */ ;
  const FQuat Rotation = UpdatedComponent->GetComponentQuat();
  FHitResult Hit(1.f);
  // 移動(座標の変化)が行われる
  SafeMoveUpdatedComponent(DeltaVel, Rotation, true, Hit);
}

 第一引数の移動ベクトル分、座標が変化します。
 第三引数がSweep処理を行うかのbool値です。trueにするとSweep処理が行われ、Hitがあれば第四引数のHitResultクラスに結果が入ります。

 移動メソッドは移動するための情報をため、実際の移動処理はTickComponentで行う設計となっています。

Velocity変数に移動方向がためられる

 MovementComponentは、Velocityという変数を持っています。AddInputVectorなどの移動メソッドによる移動ベクトルの計算結果がためられていきます。
 このVelocityは、次のTickでの移動方向を現します。長さは1以下で、最大速度をスケールします。Velocity変数を元にして、SafeMovementComponetメソッドの第一引数に渡す移動ベクトルを計算します。FloatingPawnMovementComponentやCharacterMovementComponentの最大速度はunit/secですので、DeltaTimeを乗算するのが基本の求め方になります。

  FVector DeltaVel = Velocity * MaxSpeed * DeltaTime;

 自作の移動メソッドを作る時は、このVelocity変数と合成するのを忘れないようにしましょう。

8方向移動

 XY平面(TopView)上の8方向とします。X方向が右に正、Y方向が下に正の座標系です。
 8方向は上方向を1として時計回りに1ずつ増やして、1~8のByteで扱うことにします。0は方向なしとして扱います。
 方向を表すenumを作っても良いのですが、それはBP側に作ることにします。代わりにByteで表すことでenumと連動しやすくしておきます。

移動メソッド

// 指定した方向に移動する。DirectionはTOP(1)から時計回りに1ずつ増える。0は停止
UFUNCTION(BlueprintCallable, Category="TopViewPawnMovement")
void UTopViewPawnMovement::MoveToToward(uint8 Direction)
{
   FVector Vel = DirectionToVector(Direction);
   AddInputVector(Vel);
}

 方向に応じた移動ベクトルを計算して、AddInputVectorを呼びVelocityに設定しています。TickComponentでVelocityに応じた通常の移動処理を行います。

 方向からベクトルを計算する関数は以下の通りです。

// 方向を単位ベクトルに変換する
FVector DirectionToVector(uint8 Direction)
{
  float sin=0, cos=0;

  switch(Direction)
  {
  case 1:  // 上
      return FVector(0.f, 1.0f, 0.f);
   
  case 2: // 右上
      FMath::SinCos(&sin, &cos, FMath::DegreesToRadians(315.f));
      return FVector(cos, sin, 0.f);

  case 3: // 右
      return FVector(1.0f, 0.f, 0.f);

  case 4: // 右下
      FMath::SinCos(&sin, &cos, FMath::DegreesToRadians(45.f));
      return FVector(cos, sin, 0.f);

  /* ... 以下続く ... */
  }
}

8方向移動するナビゲーション

 ナビゲーション移動を開始するメソッドは、AIMoveToなどFloatingPawnMovementComponentに実装されているものをそのまま使えます。
 紹介した記事に従って、まずはナビゲーションによって計算された移動方向をインターセプトします。

void UTopViewPawnMovement::RequestDirectMove(const FVector& MoveVelocity, bool bForceMaxSpeed)
{
   NavVelocity = MoveVelocity;
}

 RequestDirectMoveメソッドをオーバーライドして、ナビゲーション移動方向であるMoveVelocityを保存しておきます。

TickComponent

 先に書きました通り、実際の移動処理は、TickComponent内で行います。
 ナビゲーション移動状態かは、以下のコードで判定します。

const AControlloer Controller = PawnOwner->GetController();
if(Controller
&& Controller->IsLocalController()
&& !(Controller->IsLocalPlayerController())
&& Controller->IsFollowingAPath())
{
   /* ... ナビゲーションの移動処理を行う ... */
}

 MovementComponentを持っているPawnのControllerが「ローカルのコントローラであり、PlayerControllerでなく、パスフォロー状態である」という判定になります。
 ナビゲーション移動処理は、以下の通りです。

 if(NavVelocity.SizeSquared2D()>0.f)
  {
     const uint8 direction = VectorToDirection(NavVelocity);
     MoveToDirection(direction);
  }

 ナビゲーション移動ベクトルが存在しているかをチェックし、ベクトルを方向に変換して、先ほど実装した8方向移動メソッドに渡します。

 ベクトルを方向に変換する関数は以下の通りです。

uint8 VectorToDirection(const FVector Vel)
{
   float angle = FMath::RagiansToDegrees(FMath::Atan2(Vel.Y, Vel.X));
   if(angle<0.f) angle += 360.f;

   if(22.5f<=angle && angle<67.5f)        return 4; // 右下
   else if(67.5f<=angle && angle<112.5f)  return 5; // 下
   else if(112.5f<=angle && angle<157.5f) return 6; // 左下
 /* ... 以下続く ... */ 
}

 360度を8分割し、どの分割範囲に入っているのかを判定しています。分割する範囲の中央には8方向がそれぞれ入るような分割にしています。

 なお、元々、任意の方向が取れる前提のナビゲーション移動を無理矢理8方向に制限していますので、通常のナビゲーション移動とは違う動きをする可能性があります。方向の切り替え角度をまたぐような移動が連続してしまいカクカクした移動になるなどです。これは仕様上どうしもないですので、目的地設定を変えるなど、上手く対応してください。

まとめ

 8方向移動をするTopViewMovementを作る指針を紹介しました。自作のMovementComponentを作るヒントになればと思います。
 TopViewMovementを完成させるには、ここで紹介する以外の所にも手を入れないといけないのですが、それは実際のコードを見てください。

 移動を伴うゲームに取って、移動処理というのは肝になる部分です。UE4が用意しているMovementComponentは、必要最小限ですので手をいれる必要がどうしてもあります。
 例えば自分の場合は、これらの処理を基本として、

  • 指定方向に指定時間だけ移動する
  • 指定方向に指定距離だけ移動する

なども実装し、簡単に移動演出を行えるようにしています。

TopViewMovementComponentのソース

 完全な実装は、以下のgithubに公開しておきますので好きに改造しておつかいください。
 実際に桃子と夢のシールで使っているものを、公開用に少し手直ししたものになります。使い方などは、READEME.mdをご覧ください。

github.com

UE4にSpineが来た

UnrealEngine4 C++

※ UE4.15の正式リリースに合わせて、UE4ランタイム実装が正式リリースとなりました。

ja.esotericsoftware.com

 Spineとは、2Dアニメーションにもボーンを取り入れて、3Dアニメーションのように、2Dアニメーションを作れるようにするツールです。誤解を恐れずに言えばSpriteStudioの親戚のようなものです。
 長らくUE4への対応は行われなかったのですが、次のバージョンアップで対応されることになりました。UE4.15から対応という予定ということで、UE4.15pre2で、動かしてみました。

 まだβ版ということもありSpine側のコードを変更する必要がありました。先行して触りたい人向けに、どこをいじれば良いかをざっと紹介したいと思います。

 Spnieが用意したUE4への導入ドキュメントはこちら

1. SpineのUE4ランタイムをDLする

 Spineのランタイムは、Githubで公開されてますのでDLします。

github.com

 UE4ランタイムが含まれているのはspine-ue4フォルダです。テスト用のUE4プロジェクトも含まれていますので、それをそのまま動かすことにします。

2. spine-ue4にspine-cのソースをコピーする

 spineランタイムの本体はspine-cフォルダのソースです。
 spine-c内のspine-cフォルダをフォルダごと、「spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public」にコピーします。
 
 コピーしたら、VSプロジェクトを再生成しておきます。

3. SpinePlugin.Build.cs にAssetToolsを追加する

 以下の2行を追加してください。

PrivateIncludePathModuleNames.AddRange(new string[] { "AssetTools" });
DynamicallyLoadedModuleNames.AddRange(new string[] { "AssetTools" });

4. コピーしたspine-c内にある「c」拡張子を「cpp」拡張子に書き換える

f:id:katze_7514:20170130202221j:plain

 Spineの製作者がcファイルをコンパイルできるようにするためのプルリクをしていて、UE4.15に含まれています。しかし、VSのプリコンパイルヘッダーの仕様でそれだけではコンパイルが通りません。そのため、cppとしてコンパイルすることにしました。

5. ソースを修正する

  • 拡張子を書き換えたcppファイルすべてに「SpinePluginPrivatePCH.h」を一番最初にincludeするように書き足す
  • 「SpineAtlasImportFactory.cpp」で「IAssetTools.h」をincludeする
  • 「SkeltonJson.cpp」の_spLinkedMesh構造体宣言をコメントアウトする
  • 「Atlas.cpp」266行目274行目あたり
    • 「if(!(count = readTuple(&begin, end, tuple))」の条件式を外に出す

まとめ

 ソースのUE4対応が中途半端なので、おそらくですがgithubに上がっているのは最新ではない気がします。

 プラグインとして、競合ソフトであるSpriteStudioより優位な部分は、

  • 追加シェーダがいらない
  • マテリアルを変更することができる

 というところでしょうか。

UE4 .15 preview1 RawInputのコードをざっと読んでみた

UnrealEngine4

 RawInputとは、PCにつながっている入力デバイスの生データを取得するAPIです。
 UE4.15からサポートされることが決まり、DirectInputのみのゲームパッドもこれで動くかなと思いまして、少し該当部分のソースを読んでみました。
 速報ということで、読み取れたことを箇条書きします。

機能

  • プラグイン扱い
    • デフォルトではOFFになっているので、ONにする必要があります
  • ジョイスティック(もしくはそれに類する)デバイスのみの対応(HID Usage=0x04)
  • 8軸、12ボタンまで
  • 軸の値は、「-1.0~1.0」にClampされる
    • 元は生データですので実値は各デバイスに異なるため必要な処理です。
  • キーイベントの名称は、「GenericUSBController_***」
    • ***には、Axis1とかButton1とかが入る
  • キーイベントが全体で一つだけ
  • バイスごとの入力を設定は、[ProjectSettings]→[Plugins]→[RawInput]のConfigurationでできる
    • BPへは公開されてないので動的な変更はできない。ActionMappingと同じポリシーの様子
    • フォーラムで各入力デバイスのこの設定を募集しています
  • ControllerID(=PlayerIndex)は、0固定
    • CreatePlayerで2Pキャラクターを作ってもRawInputでの入力取得はできない
  • 振動未対応
  • キーリピート未対応

 間違いなどありましたら、ご指摘ください。随時修正します。

UE4 イベント・関数・マクロ・インターフェイスの違いと使い分け

UnrealEngine4 Blueprints

qiita.com

この記事は、UE4 AdventCalender 18日目の記事です。

 BPには、イベント・関数・マクロ・インターフェイスと手続きの実装手段が豊富にあります。
 BPはオブジェクト指向言語ではありますが、オブジェクトより手続きを重視した思想になっている現れだと個人的に思っています。
 とはいえ、手続きの実装手段が多すぎて、結局どれで実装すれば良いのかが、わかりにくいというのも確かです。そこで、各手続きの機能的な違いと使い分けについて紹介したいと思います。

主な機能一覧表

イベント 関数 マクロ インターフェイス
入力ピン 1個 0~1個 0個以上 1個
出力ピン 1個 0~1個 0個以上 1個
引数 0個以上 0個以上 0個以上 0個以上
戻り値 なし 0個以上 0個以上 0個以上
アクセス制御 public public
procted
private
private public
オーバーライド 不可
ノード制限 なし あり なし 実装方式による
EventDispatcherへのbind 不可 不可
特徴 ネットワーク越しに呼べる いわゆるメソッドに一番近い Lispのマクロのような
Functorのような
ダックタイピング

手続き解説

イベント

 イベントは、BPにおける手続き実装の基本です。
 もっとも制限のない手続きのため、戻り値がいらないならイベントを使いましょう。

 イベントの大きな特徴は、ネットワーク越しに呼べることです。
docs.unrealengine.com

 自動でRPC機能がつくので、これだけで、ネットワークゲームがある程度作ることができます。 

関数

 戻り値が必要な時は、関数を使います。
 唯一アクセス制御をコントロールできますので、アクセス制御が必要な時も関数を使います。LatentノードやTimelineノードなど一部使えないノードが存在します。

 関数はPureフラグをオンにすることで実行ピンを省略することができます。値の取得のみで済む場合は、オンにすると良いでしょう。

 他のクラスベース言語経験者で、BPのことがよくわからない内は、関数で実装して、関数ではできないことはイベントにするというやり方でも良いと思います。

マクロ

 ノードをまとめて整理したり、部分的に使い回すのに使います。インライン化機能もついており、実行時にはノードが展開されます。
 最大の特徴は、複数の入出力ピンを使えることです。

f:id:katze_7514:20161217004936j:plain
 上記のマクロは、プレイヤーのPawnとの距離に応じて、出力ピンが実行されます。
 ちなみに「ノードの折りたたむ」はマクロにすると同じです。

 マクロはprivateになりますので継承先では使えません。継承先でも使いたい時は、イベント・関数として実装しなおすか、MacroLibraryを使います。ただ、MacroLibraryに実装すると逆に継承先でしか使えませんので、イベント・関数で実装にした方が良いかもしれません。
 また、マクロ内部ではブレイクポイントをおけませんので、はじめからマクロとして作るとデバッグが大変なこともあります。

 名前から、C言語系のプリプロセッサのマクロかなと思うかもしれませんが別物です。おそらくですが、Lispマクロを参考にしているのだと思います。
 Sequenceノードと組み合わせることで継続のようなこともできますし、マクロのローカル変数は実行終了後も値を維持しますのでFunctorのような挙動もします。
 マクロだけで1冊本が書けそうなぐらい様々なことがでできます。

インターフェイス

 インターフェイスは、キャストなしに呼ぶことができます。そのため、クラス間の依存度を下げるのに使います。テンプレートやジェネリクスのないBPでダックタイピングをするための手続きです。
 わかりやすい使い所は、Overlapイベントなどエンジンが用意しているイベントです。
f:id:katze_7514:20161217012833j:plain
 エンジンが用意するイベントは、引数がActorなど汎用的な型ですので、Interfaceにしておくと楽になります。

f:id:katze_7514:20161217011007j:plain

 インターフェイスの呼び出しは、右上に手紙マークがつきます。
 右クリックメニューから選ぶ時は、Messageとついてるのを選びます。
f:id:katze_7514:20161217012109j:plain

 クラス間の依存度下げることは、コンパイルが早くなるだけでなく、エディタのクラッシュ率やバグを踏む可能性も下げますので重要です。

 インターフェイスの実装は、戻り値がない場合はイベント、戻り値がある場合は関数として実装します。
 なお、インターフェイスはデフォルト実装が直接はできませんので、場合によっては他の手続きで実装した方が楽なこともあります。

明日は

 明日は、hima_zinnさんの「UE4のショートカットをカスタマイズして開発効率を上げる」です。

UE4 FloatingPawnMovement を使う

UnrealEngine4

 FloatingPawnMovementとは、UE4が用意している移動を担うComponentの一つです。
 CharacterMovementのように状態(歩き、ジャンプ、しゃがみ)があるわけでもなく、設定したパラメータ通りに位置を変化させるだけのシンプルな実装のMovementComponentです。
 シンプルですが2Dゲームで使うには十分な機能を持っており、自分のゲームでの移動はFloatingPawnMovementをメインに使っています。
 

移動させる

f:id:katze_7514:20161026154832p:plain

 移動させる時は、AddInputVectorノードを使います。
 FloatingPawnMovementは、移動させたい方向のベクトルを与えることで移動します。

●X+方向なら(1.0, 0.0, 0.0)
f:id:katze_7514:20161026161546p:plain

●45度方向だったら(cos45, sin45, 0.0)
f:id:katze_7514:20161026161554p:plain

 方向ベクトルの長さは「0.0~1.0」です。1.0以上与えても1.0として扱われます。この長さは、次に説明する速度パラメータのスケール値として働きます。

 AddInputVectorは呼ぶ度に方向ベクトルが加算されて行きます。FloatingPawnMovementのTickが呼ばれるまでの加算結果を用いて移動処理が実行されます。
 これを利用することで、Pawnの外部からの影響を受ける特殊な移動処理を簡単に実現することもできます。

ある地点に引っ張られる

 青いエフェクトが出ている間、通常の移動処理に加えて、中央に移動する方向ベクトルをAddInputVectorすることで実現しています。

速度パラメータ

 MovementComponentでの移動は、与えたベクトル方向に加速していき、最大の速さに到達したら速さアップを止めます。ベクトルの入力が無くなったら減速します。
 移動の調整は、それらのパラメータをいじることで行います。

f:id:katze_7514:20161026150207p:plain

パラメータ名 意味(単位)
Max Speed 最大の速さ(unit/sec)
Acceleration 加速度(unit/sec)
Deceleration 減速度(unit/sec)
Turning Boost 方向転換した際の追加加速スケール

 単位は、unit/secです。2Dゲームで、PPUを1.0に設定している場合は、pixel/sec と思って大丈夫です。

 TurningBoostは、どれだけ素早く方向転換できるか、ということを示すパラメータです。TurningBoostの値が大きいほど素早く方向転換が行われ、小さいほどゆっくり方向転換します。動作としては、方向転換する時だけ、加速度がTurningBoost倍されます。

 デフォルトの値では、加速も減速も方向転換も、かなり素早く行う設定となっています。そのため、すぐに最大の速さになりますし、ピタっと静止できるし、瞬間的に方向転換します。

慣性のある移動(滑りながら移動)

 速度パラメータ設定の応用として、慣性のつく設定を紹介します。
 以下のように、最大の速さ以外のパラメータの値を大きく下げ、また加速より減速の値を低くします。

f:id:katze_7514:20161026180052p:plain

 すると次の動画のようになります。

youtu.be

移動制限パラメータ

 移動する平面を指定することができます。指定した平面以外へは動かなくなります。
 詳細パネルのPlanarMovementで設定します。
 
f:id:katze_7514:20161026154839p:plain

 図の設定だと、移動がXY平面のみとなりZ方向には動かなくなります。

パラメータ 意味
Constrain to Plane チェックを入れると平面に移動が制限される
Snap to Plane at Start Spawnした時から指定した平面に制限するか
Plane Constraint Axis Setting 移動制限したい平面の簡易設定。移動制限する平面の法線軸を指定。Customを選んだ場合は次の2項目を自分でいれる
Plane Constraint Normal 移動制限平面との法線ベクトル
Plane Constraint Origin 移動制限平面の法線ベクトルの原点

 ただし、Pawnに直接Locationを入れれば(AddWorldLocationなど)、動かない方向にも動きます。あくまで、FloatingPawnMovementによる移動が制限される設定となります。

Navigation

 FloatingPawnMovementは、Navigation移動に対応しています。AIControllerを持つPawnに持たせれば、MoveTo系ノードを使うことで、今まで紹介したパラメータに従ってNavigationします。

注意点

 FloatingPawnMovementは、AddInputVectorでベクトルが追加されなかった場合に減速する仕組み上、前フレームとの位置差分を速度ベクトルに変換する処理が入っています。
 そのため、直接Locationを入れて動かすことと併用している場合、想定してない移動になることがまれにあります。

 自分の場合は、移動制限せずに、描画順制御のためにZ方向の移動だけはSetWorldLocationで行っていた所、Z方向に速度が発生してしまいました。描画順がおかしくなったり、衝突するはずのコリジョンが当たらなくなったりしていました。