GameProgrammar's Night

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

UnrealEngine4で2Dゲームを作ろう! その17 UMGでテクスチャアトラスを使う

※ 任意サイズに切り出すマテリアルが作れましたので、以下の記事も参照してみてください
katze.hatenablog.jp


 UMGにおいて画像を使いUIを作るにはimageウィジェットを使います。
 imageウィジェットは、グラフィックを表示するのにSlateBrushを利用していますので、Brushを設定することで画像を表示します。
f:id:katze_7514:20160128160708j:plain

 Brushのimage設定には、テクスチャもしくはマテリアルを割り当てることができます。
 テクスチャ指定がお手軽ではあるのですが、テクスチャ全体が表示されてしまいます。
f:id:katze_7514:20160128161302j:plain
 テクスチャ指定で作るには、imageウィジェット1つに付き1つのテクスチャを用意すれば良いのですが、最近の描画システムはテクスチャ切り替えのコストが重いので、できれば避けたい所です。

 しかし、困ったことにimageウィジェットはテクスチャアトラスに対応していないため、テクスチャから部分的に画像を切り出すのは自前でやる必要があります。
 幸いBrushはマテリアルを使うことができますので、テクスチャから必要な部分を切り出すマテリアルを画像ごとに作ります。
 マテリアルインスタンスも利用してシェーダー切り替えコストも抑えたい所ですが、どう汎用化するかはお任せしまして*1、ここでは簡単な例と考え方を紹介します。

テクスチャを切り出すマテリアル

 一番簡単な例として、切り出す画像が等間隔で並んでいる場合を考えます。
 先ほどの横2列 縦4列並んでる画像から、ひとつ切り出してみます。

f:id:katze_7514:20160128171734j:plain

 マテリアルは以下の通りです。

f:id:katze_7514:20160128192705j:plain

 最初のADDで画像内の分割された位置(横,縦)を指定します。次のDivideで横を2分の1、縦を4分の1にして1枚分に切り出します。

マテリアルの仕組み

 imageウィジェットは、1つの四角ポリゴンを使ってBrush設定に従って描画されます。その四角ポリゴンに割り当てられるUVは、左上(0,0)~右下(1,1)となっているようです。

f:id:katze_7514:20160128182332j:plain

 そこで、欲しい画像の範囲がUV(0,0)~(1,1)になるように演算をいれます。

f:id:katze_7514:20160128185855j:plain

 ADDによってUVの原点をズラし、そこから分割数で割ることで1つに切り出します。
 ADDする値はDivideされることを考慮にいれます。Divideしなければ、横に1画像ズラすには0.5足しますが、横は2分の1されますので、2倍して1を足すことで1画像分ズレることになります。縦も同様です。

Misc

 パタパタアニメを仕込むのは、FlipBookマテリアル関数を使えばできるようです。
 テクスチャアトラス対応の要望自体は上がっているみたいですので、対応を期待したい所です。

*1:描画系苦手なので他力本願! むしろ、良い感じのマテリアルが出来たらご教授願います……

UnrealEngine4で2Dゲームを作ろう! その16 CreateEventノード!

 イベントディスパッチャーってやつがあります。

 イベントディスパッチャーにイベントをBindするノードの仕様で、イベントを必ず同じEventGraph上に作らなければならず、イベントディスパッチャーを使い始めるとなかなかスパゲティなコードになってしまい困っていました。

f:id:katze_7514:20160119183938j:plain

 関数をEvent化してbindできるようになってくれればなぁ、と思い調べた所、できるようになっていました。それがCreateEventノードです。
 
f:id:katze_7514:20160119184849j:plain

 リリースノートを見るとUE4.9で「使いやすくしました」とあるので、前々からあったようです。

New:CreateEvent nodes are now more user friendly - they will use the current blueprint as the default context and no longer suggest that the user select an asset for context.

使い方

 例えば、以下のようなイベントディスパチャーがあったとします。
f:id:katze_7514:20160119185501j:plain
 integer1つと、Stringを1つ引数に取るイベントとします。

bindするイベントとまったく同じ引数を持った関数を定義する

 ブループリントクラス内で関数を定義します。引数は、イベントディスパッチャーのものと同じにします。
 f:id:katze_7514:20160119185820j:plain

 引数の名前は、わかりやすく同じにしていますが同じである必要はありません。引数の型・数・順番があっていればOKです。

bindノードをおいて、CreateEventとつなぐ

f:id:katze_7514:20160119190201j:plain
 bindノードにつなぐと、CreateEventノードの下部に「関数を選択」と出ます。

関数を選択から先ほど定義した「StateEnd」を選ぶ

f:id:katze_7514:20160119190335j:plain

 これで、bind完了です。
 関数リストにbindしようとした関数が出て来ない場合は、引数が間違っているということになりますので見直して見ましょう。

UnrealEngine4で2Dゲームを作ろう! その15 BehaviorTreeのDecorator/Task作ってみた

という感じで、BehaviorTree(以下BT)で敵AIを作ってみた時にわかったことを書きます。

BT全体

f:id:katze_7514:20160103170558j:plain

BT全体は上図の通りで、移動して攻撃したらちょっと待つを繰り返します。

Decorator/IsEnableAI

 BTは、外からPauseしたりStopしたりする機能がないので、代わりに根元で実行を止めるDecoratorを付けました。
 中身は、対応するControllerの状態を調べてBTを実行する状態かをチェックしています。

Decorator実装

 Decoratorの実装は、「PerformConditionCheckAI」をオーバーライドし、boolをリターンします。
f:id:katze_7514:20160103174211j:plain
 AIがついていない「PerformConditionCheck」もあり、どっちも使えるのですが、AI付きの方はBTを実行しているController/Pawnを引数として取れるのでAI付きの方が使いやすいと思います。

Task/SimpleMoveTo,Attack

SimpleMoveTo

 NavMeshがリビルドエラーを吐き使えなかったので、MoveToを自前実装したのがSimpleMoveToです。中身は単純にPlayerPawnに近づいて一定距離になったら止まるというだけです。
 このリビルドエラーは、AnswerHubにも投稿されていますが完全な解決には至ってないようです。

 移動中は処理を継続し、PlayerPawnの一定距離内に来たらFinishExecuteを読んでTaskを終了します。
 また、処理を途中で打ち切るための仕込みもいれてあります。BlackBoardにAbortフラグが立っているかをチェックして、立っていたらFinishAbortします。この辺りについては後述します。

Attack

 攻撃アニメを再生して再生終わりを待つTaskです。アニメ再生終わりは、Controllerの方で検出してBlackBoardにフラグを書き込んでいます。Taskの中で直接再生終了をイベントをbindしても良いと思います。
 ダメージのやりとりはコリジョンイベントの方で行っています。

Task実装

 Taskは2つのイベントを実装します。

f:id:katze_7514:20160103191119j:plain
f:id:katze_7514:20160103191122j:plain

 ExecuteはActorのBeginPlayに相当するのもので、Taskが実行開始時に呼ばれます。BeginPlayと違うのは、Taskが実行開始する度に呼ばれることです。
 つまり、今回の例ですと、SimpleMoveTo→Attack→Wait と実行され、最初に戻り再びSimpleMoveToが実行される時にもまたExecuteが実行されます。

 Tickは、Task内でFinish系ノードが呼ばれるまで毎フレーム呼ばれます。

Taskの実行中断

※ DecoratorのObserveAbortsという項目を使うことで途中中断ができたみたいです。Decoratorの値が変化した時に接続されているノードを中断するかを選択することができます。

 今回、BT(というかTask)の実行を中断する必要があったのは、Playerの攻撃でノックバックしたり、HPが0になったら死亡エフェクトを表示したりする必要があるからでした。
 先にも書きました通り、BT自体に動作を止める手段が用意されてないため、実行中のTask以外の動作をさせるには一端Taskを止める必要があります。

 BTのデバッグ表示を見ると以下のようになっていたので、
f:id:katze_7514:20160103192338j:plain

 SequenceのIsEnableAIも毎フレーム実行されるのかと勝手に思っていたのですが、そんなことはなく、上図の場合はSimpleMoveToのTickだけが呼ばれている状態になります。
 Decoratorは実行"開始"条件判定を行うだけで、一度判定が通り先に進むとDecoratorは実行されなくなります。実行"継続"条件としては使用できません。
 よって、Taskの実行を中断させるには、根元にIsEnableAIを仕込むだけは足りないということになり、Task内に中断する仕組みを導入する必要があります。

 TaskのTick内で中断フラグをチェックする機構をいれておきます。
f:id:katze_7514:20160103194309j:plain
 IsAbordはBlackBoardに用意したAbordフラグを見にいっています。BlackBoardはBT内からでも外からでも操作できますので、どこからでもTaskの実行を中断できます。

UE4の入力取得の仕組みとプラグインの作り方

Unreal Engine 4 (UE4) 其の弐 Advent Calendar 2015」 3日目の記事になります。

qiita.com

UE4エディタ・BPなどが一切でません。C++のみのプログラマ向けの記事になります。

ここで示すソースコードは「4.10.0-release」タグのものになります。

UE4の入力取得

 確認しておきますと、ここでいう入力とは、ゲームパッド・キーボード・マウスといった入力デバイスのことを指しています。

 UE4でこの入力状態をチェックしイベントを発生させているのは2ヶ所あります。

 まず、ゲームパッド及び独自の入力デバイスの処理が以下の場所。

LaunchEngineLoop.cpp 2414~2415行目

2414: FSlateApplication& SlateApp = FSlateApplication::Get();
2415: SlateApp.PollGameDeviceState();

 2415行目のPollGameDeviceStateメソッドが、ゲームパッドと入力プラグインなどで指しこんだ独自入力デバイスのチェックを行いイベントを発生します。全ActorのTick前に一括して処理しています。
 FSlateApplicationクラスはプラットフォームを抽象化したクラスです。プラットフォームごとのApplicationクラスを内部でもっており、プラットフォームAPI処理を行っています。*1

 2つ目は、キーボードやマウスなどウインドウメッセージによる入力処理です。メッセージ系はチェックとイベント発生の2段階にわかれて処理されています。
 1段目、メッセージの確認が以下の場所。

LaunchEngineLoop.cpp 2391行目

2391: FPlatformMisc::PumpMessages(true);

 メッセージキューにたまっているメッセージを確認して、一端、メッセージバッファに情報を格納しています。
 その後、全ActorのTickが終わった後に、メッセージイベントを発生します。もちろん、すぐに処理すべきウインドウメッセージは、メッセージチェックの段階でも処理されていますが、入力系は遅延処理されます。
 その遅延処理しているのが、以下の場所。

LaunchEngineLoop.cpp 2460行目

2460: FSlateApplication::Get().Tick();

FSlateApplicationのTickメソッドの中で、ProcessDeferredEventsメソッドが呼ばれてメッセージによるイベントを発生させます。

 ざっくりですが、UE4の入力処理は上のようになっています。

 ちなみにLaunchEngineLoopは、いわゆるメインループの実装です。メインスレッドが何をやってるのかを見たい時は、LaunchEngineLoopをのぞくと良いと思います。

独自の入力デバイプラグインを作る

 以上を踏まえて、独自の入力デバイスを使うためのプラグインの作り方を紹介します。独自といってもオリジナルハードという意味ではなく、UE4が公式対応していない程度の意味です。Oculusタッチなどが該当します。*2
 プラグイン全般の作り方は公式ドキュメントなどを参考にしていただくとして、ここでは入力プラグインに特化した内容を紹介します。

*1:プラットフォームごとのApplictionクラスの名前は、F[プラットフォーム名]Appicationという名前になっています。Windowsだと、FWindowsApplicationクラスになります。

*2:まぁ、公式でプラグインが開発中なので公式対応と言っても良い気もしなくもないですが

続きを読む

UnrealEngine4用プラグイン DirectInputPadPluginの使い方

 UE4(Windows)でDirectInputゲームパッドを使えるようにするプラグインを作りました。

github.com

 DirectInputPadPlugin Ver.0.9のものです。
 これ以降、DirectInputゲームパッドのことをDIパッド(DIPad)、XInputのゲームパッドのことをXIパッド(XIPad)、両方合わせた場合はたんにゲームパッド(GamePad)と表記します。

インストール

  1. 上のGithubからソースをチェックアウト、もしくはZIPをDOWNLOADします。
  2. それらのソースを「DirectInputPadPlugin」という名前のフォルダを作って、そこに入れます
  3. 「DirectInputPadPlugin」フォルダを、[プロジェクトフォルダ]/Plugins に移動します

あとは、

katze.hatenablog.jp

を参考にソースをビルドしてください。

DirectInputゲームパッドの接続確認

 ゲームパッドをPCに挿した状態でUE4エディタを立ち上げます。動的な抜き差しには対応していませんので、エディタやゲーム起動前には挿しておいてください。
 接続が成功していれば、アウトプットログに

DirectInputPadPlugin: DirectInputDriver initialized.
DirectInputPadPlugin: DirectInputPad detected: 1
DirectInputPadPlugin: DirectInput Joystick Create Success. : Elecom Wired Gamepad

 見つかったDIパッドの数と、その数だけ接続が確認できたDIPadの名前が表示されます。
 これでDIパッドを使用する準備が完了です。

キーイベント

 GamePadイベントをそのまま使うことができます。

f:id:katze_7514:20151127232235j:plain

など。
 基本的には、GamePadイベントを使うことを想定しています。なぜなら、GamePadイベントはXIパッドのイベントでもありますので、ユーザーがXIパッドでもDIパッドでもどちらのゲームパッドを使っていても良くなるからです。

DIPadイベント

 DIパッドとして直接イベントを取ることもできます。こちらの場合だと、プラグインが対応してる最大限のキーイベントを取得することができます。

  • 6つの軸(XYZ軸、XYZ回転軸)
  • 1つのPOV
  • 32個のボタン(XIパッドは個数にすると12個まで)

 DIGamePadで始まる名前にしてあります。

f:id:katze_7514:20151127234617j:plain

 こちらは、DIパッドのボタン配置をXIパッドに合わせるキーコンフィグや、通常のゲームパッドではないDirectInputの入力デバイスを使う時に使います。

パッドキャリブレーション

 GamePadイベントをDIパッドでも使うには、DIパッドをXIパッドの配置にする必要があります。XIパッドというのは、ようはXBOXのパッドのことです。
 デフォルトでは以下のような対応になっています。

XIパッド DIパッド
左スティックX X軸
左スティックY Y軸
右スティックX Z軸
右スティックY Z回転軸
方向パッド上 POV上
方向パッド右 POV右
方向パッド下 POV下
方向パッド左 POV左
A ボタン1
B ボタン2
X ボタン3
Y ボタン4
LB(L1) ボタン5
RB(R1) ボタン6
Lトリガー(L2) ボタン7
Rトリガー(R2) ボタン8
BACK ボタン9
START ボタン10
左スティックボタン ボタン11
右スティックボタン ボタン12

 変更に使うノードは、SetKeyMapです。

f:id:katze_7514:20160827132842p:plain

 Ver.0.9からの機能として、軸にボタン、ボタンに軸を割り当てられるなど、キャリブレーションの自由度を上げました。
 図の左から

  • 「XIパッドの左スティックX軸として、DIパッドのX軸を割り当てる」
  • 「XIパッドの右スティックY軸(下方向)として、DIパッドのボタン1を割り当てる」
    • ボタン1が押されていないなら0,ボタン1が押されていると-1 が返るようになります
    • 上方向に割り当てる時は、Negativeフラグをオフにします
  • 「XIパッドのボタンAとして、DIパッドのX回転(正方向)を割り当てる」
    • X回転の正方向の値に応じて、ボタンAが押されているという判定がされます
    • X回転の負方向の値を使うならば、Negativeをオンにします

 Ver.0.8では対応できていなかったLT(L2)/RT(R2)への軸の割り当てもできるようになっています。

 SetKeyMapは重複してキーを割り当てられるのでご注意ください。KeyMapを削除するには、DIKeyの項目を「DIGamePad END」にすることでできます。

GetDirectInputPadJoystickノード

 PlayerIndexを使ってDIパッドを取得するノードです。DIパッドは見つかった順番にPlayerIndexが割り当てられて、対応するプレイヤーの入力として使われます。
 XIパッドが同時に挿しこまれていた場合は、XIパッドを優先します。DIパッドにはXIパッドの次のPlayerIndexが割り当てられます。つまり、XIパッドが挿しこまれてないないなら0番から、XIパッドが1つ挿しこまれていたら1番から割り当てられます。
 PlayerIndexに対応するDIパッドがなかった場合は、nullが返ります。

f:id:katze_7514:20160827133534p:plain

(回転)軸の反転

 DIパッドによっては、XIパッドと軸の値が逆になっていることがあります。XIパッドの左スティックYは上がプラスで下がマイナスになりますが、私が普段使っているDIパッドのY軸は上がマイナスで下がプラスになっています。
 このまま軸を割り当てると上下が反転した状態で処理されてしまいますので、軸の値を反転させる必要があります。その時は、SetAxisReverseノードを使います。

f:id:katze_7514:20151128012340j:plain

 引数上ではボタンも選べますが、当然意味はありません。
 反転フラグの値を調べるには、IsAxisReverseノードを使います。

ChangedState系ノード

 パッドキャリブレーションをサポートするノード群です。
 呼び出したフレームで状態に変化のあったキー情報を取得するノードです。
 状態の変化とは、軸なら初期値(通常は0)以外の入力値になっている、ボタンなら押されたか離されされたか、ということになります。

IsChangedKeyStateノード

f:id:katze_7514:20160827133734p:plain

 状態が変化したキーがあるかをチェックします。変化したキーがあればtrueが返ります。

Get(All)ChangedKeyStateノード

 
f:id:katze_7514:20160827134033p:plain

 GetChangedKeyStateノードは変化のあったキー1つだけ、GetAllChangedKeyStateは変化のあったキーすべて取得します。
 変化のあったDIパッドとしてのenumと変化値が取得できます。変化値は、軸の時は軸の値、ボタンの時は正だとPressed、負だとReleasedの意味になります。

 Real引数をtrueにすると、反転フラグなどキー設定を無視したパッドの実入力値で変化値を取得できます。

 GetChangedKeyStateは、通常は軸の変化を優先しますが、Btn引数をtrueにすることでボタンの変化を優先的に取得するようになります。

 ユーザーに「Aボタンの場所を押してください」などと促し、押されたボタンを検出(GetChangeKeyState)、そのボタンをAボタンとして設定する(SetKeyMap)、という使い方を想定しています。

キャリブレーションのセーブロード

f:id:katze_7514:20160827134219p:plain

 図のノードを使うことで、現在接続されているDIパッドに設定されたキャリブレーションデータ(KeyMap)をセーブロードすることができます。引数の意味は、SaveGameToSlot/LoadGameFromSlotと同じです。

ユーティリティー

ゲームパッドの数

 挿しこまれている各ゲームパッドの数は、次のノードで取得できます。
 取得できなかったの時は、-1が返ります。

f:id:katze_7514:20151128011543j:plain

ゲームパッドの名前と識別子

 ゲームパッドの名前はGetProductNameノード、識別子はGetGUIDノードで取得できます。

f:id:katze_7514:20151202181513j:plain

 名前はまったく同じパッドの場合、同じ名前になることがあるので識別には使えませんが、ユーザーがコントロールしているパッドを示すのには使えます。
 キーコンフィグ情報を保存/復元する際など、個々のパッドを識別するためにはGUIDを使用してください。ただし、このGUIDはローカルでのみ一意性が保証されてるGUIDですのでご注意ください。

入力値のクリア

 現在保持している入力値をクリアします。

f:id:katze_7514:20151213183116j:plain

プラグインが初期化できたか

f:id:katze_7514:20160220040421p:plain

コンフィグ

BACKGROUND設定にするか

DefaultInput.ini に以下のセクションを追加してください

[DirectInputPadPlugin]
Background=true

エディタ起動時は、常にBACKGROUND動作となります。

以上になります。

使用上の注意

 タイミングよっては、最初にロードされるLevelのBeginPlayにプラグインの初期化が間に合わないことがあります。その場合は、各APIが正しい値を返せません。プラグインの初期化を待ってからAPIを呼ぶようにしてください。

質問とか

 プラグインの使い方で、質問とかありましたら、この記事のコメントか、Twitterでリプを投げてください。
 バグがあったらプルリクお待ちしてます!