UnrealEngine4で2Dゲームを作ろう! その16 CreateEventノード!
イベントディスパッチャーってやつがあります。
イベントディスパッチャーにイベントをBindするノードの仕様で、イベントを必ず同じEventGraph上に作らなければならず、イベントディスパッチャーを使い始めるとなかなかスパゲティなコードになってしまい困っていました。
関数をEvent化してbindできるようになってくれればなぁ、と思い調べた所、できるようになっていました。それがCreateEventノードです。
リリースノートを見ると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.
使い方
例えば、以下のようなイベントディスパチャーがあったとします。
integer1つと、Stringを1つ引数に取るイベントとします。
bindするイベントとまったく同じ引数を持った関数を定義する
ブループリントクラス内で関数を定義します。引数は、イベントディスパッチャーのものと同じにします。
引数の名前は、わかりやすく同じにしていますが同じである必要はありません。引数の型・数・順番があっていればOKです。
bindノードをおいて、CreateEventとつなぐ
bindノードにつなぐと、CreateEventノードの下部に「関数を選択」と出ます。
関数を選択から先ほど定義した「StateEnd」を選ぶ
これで、bind完了です。
関数リストにbindしようとした関数が出て来ない場合は、引数が間違っているということになりますので見直して見ましょう。
UnrealEngine4で2Dゲームを作ろう! その15 BehaviorTreeのDecorator/Task作ってみた
やっとザコのBTが出来た…… pic.twitter.com/Yuwt7GWw8x
— katze (@katze_7514) December 26, 2015
という感じで、BehaviorTree(以下BT)で敵AIを作ってみた時にわかったことを書きます。
BT全体
BT全体は上図の通りで、移動して攻撃したらちょっと待つを繰り返します。
Decorator/IsEnableAI
BTは、外からPauseしたりStopしたりする機能がないので、代わりに根元で実行を止めるDecoratorを付けました。
中身は、対応するControllerの状態を調べてBTを実行する状態かをチェックしています。
Decorator実装
Decoratorの実装は、「PerformConditionCheckAI」をオーバーライドし、boolをリターンします。
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つのイベントを実装します。
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のデバッグ表示を見ると以下のようになっていたので、
SequenceのIsEnableAIも毎フレーム実行されるのかと勝手に思っていたのですが、そんなことはなく、上図の場合はSimpleMoveToのTickだけが呼ばれている状態になります。
Decoratorは実行"開始"条件判定を行うだけで、一度判定が通り先に進むとDecoratorは実行されなくなります。実行"継続"条件としては使用できません。
よって、Taskの実行を中断させるには、根元にIsEnableAIを仕込むだけは足りないということになり、Task内に中断する仕組みを導入する必要があります。
TaskのTick内で中断フラグをチェックする機構をいれておきます。
IsAbordはBlackBoardに用意したAbordフラグを見にいっています。BlackBoardはBT内からでも外からでも操作できますので、どこからでもTaskの実行を中断できます。
UE4の入力取得の仕組みとプラグインの作り方
「Unreal Engine 4 (UE4) 其の弐 Advent Calendar 2015」 3日目の記事になります。
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クラスになります。
UnrealEngine4用プラグイン DirectInputPadPluginの使い方
UE4(Windows)でDirectInputのゲームパッドを使えるようにするプラグインを作りました。
DirectInputPadPlugin Ver.0.9のものです。
これ以降、DirectInputのゲームパッドのことをDIパッド(DIPad)、XInputのゲームパッドのことをXIパッド(XIPad)、両方合わせた場合はたんにゲームパッド(GamePad)と表記します。
インストール
- 上のGithubからソースをチェックアウト、もしくはZIPをDOWNLOADします。
- それらのソースを「DirectInputPadPlugin」という名前のフォルダを作って、そこに入れます
- 「DirectInputPadPlugin」フォルダを、[プロジェクトフォルダ]/Plugins に移動します
あとは、
を参考にソースをビルドしてください。
DirectInputのゲームパッドの接続確認
ゲームパッドをPCに挿した状態でUE4エディタを立ち上げます。動的な抜き差しには対応していませんので、エディタやゲーム起動前には挿しておいてください。
接続が成功していれば、アウトプットログに
DirectInputPadPlugin: DirectInputDriver initialized. DirectInputPadPlugin: DirectInputPad detected: 1 DirectInputPadPlugin: DirectInput Joystick Create Success. : Elecom Wired Gamepad
見つかったDIパッドの数と、その数だけ接続が確認できたDIPadの名前が表示されます。
これでDIパッドを使用する準備が完了です。
キーイベント
GamePadイベントをそのまま使うことができます。
など。
基本的には、GamePadイベントを使うことを想定しています。なぜなら、GamePadイベントはXIパッドのイベントでもありますので、ユーザーがXIパッドでもDIパッドでもどちらのゲームパッドを使っていても良くなるからです。
パッドキャリブレーション
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です。
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が返ります。
(回転)軸の反転
DIパッドによっては、XIパッドと軸の値が逆になっていることがあります。XIパッドの左スティックYは上がプラスで下がマイナスになりますが、私が普段使っているDIパッドのY軸は上がマイナスで下がプラスになっています。
このまま軸を割り当てると上下が反転した状態で処理されてしまいますので、軸の値を反転させる必要があります。その時は、SetAxisReverseノードを使います。
引数上ではボタンも選べますが、当然意味はありません。
反転フラグの値を調べるには、IsAxisReverseノードを使います。
ChangedState系ノード
パッドキャリブレーションをサポートするノード群です。
呼び出したフレームで状態に変化のあったキー情報を取得するノードです。
状態の変化とは、軸なら初期値(通常は0)以外の入力値になっている、ボタンなら押されたか離されされたか、ということになります。
IsChangedKeyStateノード
状態が変化したキーがあるかをチェックします。変化したキーがあればtrueが返ります。
Get(All)ChangedKeyStateノード
GetChangedKeyStateノードは変化のあったキー1つだけ、GetAllChangedKeyStateは変化のあったキーすべて取得します。
変化のあったDIパッドとしてのenumと変化値が取得できます。変化値は、軸の時は軸の値、ボタンの時は正だとPressed、負だとReleasedの意味になります。
Real引数をtrueにすると、反転フラグなどキー設定を無視したパッドの実入力値で変化値を取得できます。
GetChangedKeyStateは、通常は軸の変化を優先しますが、Btn引数をtrueにすることでボタンの変化を優先的に取得するようになります。
ユーザーに「Aボタンの場所を押してください」などと促し、押されたボタンを検出(GetChangeKeyState)、そのボタンをAボタンとして設定する(SetKeyMap)、という使い方を想定しています。
ユーティリティー
ゲームパッドの名前と識別子
ゲームパッドの名前はGetProductNameノード、識別子はGetGUIDノードで取得できます。
名前はまったく同じパッドの場合、同じ名前になることがあるので識別には使えませんが、ユーザーがコントロールしているパッドを示すのには使えます。
キーコンフィグ情報を保存/復元する際など、個々のパッドを識別するためにはGUIDを使用してください。ただし、このGUIDはローカルでのみ一意性が保証されてるGUIDですのでご注意ください。
入力値のクリア
現在保持している入力値をクリアします。
プラグインが初期化できたか
コンフィグ
BACKGROUND設定にするか
DefaultInput.ini に以下のセクションを追加してください
[DirectInputPadPlugin] Background=true
エディタ起動時は、常にBACKGROUND動作となります。
以上になります。
UnrealEngine4で2Dゲームを作ろう! その14 DirectInputプラグイン作った編
前回紹介したJoystickPluginは問題が発覚したために、結局自作しました。
ひとまず、
を参考にソースからビルドしてUE4に追加してください。
通常のゲームパッドイベントとして受け取れるようにしましたので、すぐに使えると思います。
とはいえ、ボタン配置がパッドごとに違うDirectInputのゲームパッドを扱うにはもう少し手を入れる必要があるので開発は続けます。
詳しい使い方は、近いうちに記事にします。
JoystickPluginの問題
あれからソースをさらに読み込んだ所、JoystickPluginは入力の取得をActorのTickタイミングで行っていることがわかりました。
ちょっとこれは問題で、ゲームジャンルによっては致命的な問題となりかねません。
ゲームパッドなどのデバイス入力は、全Actorが動く前に取得するのが望ましいです。
UE4もその辺りの事情は当然わかっているので、独自入力デバイスをさしこんで、しかるべきタイミングで入力取得メソッドを呼んでくれる仕組み*1があります。それに沿った形で実装したプラグインとなっています。
その辺りのソースを読んでいたら、キーイベント放出あたりの事情も理解できたので、通常のゲームパッドイベントと統合できたという感じです。