前提
身内用に書いたものを、暫定的に公開しているものです。何かおこったら閉じるかもしれません。
前の記事に書いた知識(ブループリントの最低限の知識)は持っている、という前提で書いてます。特に説明のない限り、Unreal Engine 4.25 で FirstPerson テンプレートで作成したプロジェクトのレベルブループリントのイベントグラフ上で操作をする前提です。
準備
外部オブジェクト(ブループリント)の参照の例の説明のために、FirstPerson プロジェクトにブループリントを追加しておきます。
キューブのアクターをひとつ画面内に配置して、右の詳細から「ブループリント/スクリプトを追加」を押して、ブループリントを追加してください。
デフォルトで Cube_Blueprint という名前のブループリントになりますが、ここではそのままの名前で作成したものとして説明します。
配置したキューブは、可動性を「ムーバブル」にして、Simulate Physics と Enable Gravity にチェックが入った状態にしておいてください。
キーボードやマウス、ゲームパッドからの入力を拾う
AnyKey イベント
AnyKey イベントを使えば、あらゆるキー、マウスのボタン、ゲームパッドのボタンからの入力(押した、離した)を拾えます。
どのキーが押されたかは、GetKeyDisplayName を使えば判定できます。ただし、押したキーに対応する名前は、必ずしもキーボードのキーに書かれた文字や、ゲームパッドのボタンに書かれた文字などと一致しないことが多いので、あらかじめ上のコードを使って調べたほうが良いです。
AnyKey イベントノードの「詳細」にある Input Key の項目から、イベントを拾うキーやボタンを限定することもできます。というか、限定して使うことのほうが多いです。
入力を拾うキーを限定すると、イベントノードの表記も変わります。上は Enter キーに限定したところです。
マウスのイベントも拾えます。
でも入力イベントを拾うにはプロジェクトのバインディング設定を使ったほうがいい
AnyKey イベントでキー入力などは簡単に拾えるので、デバッグ時には便利です。しかし実際にゲームやアプリなどでキー入力を拾う場合は、AnyKey イベントではなく**「プロジェクト設定」のバインディングの機能**を使ったほうがよいようです。
プロジェクトの設定は、メイン画面の「設定」からメニューを出して選べます。
プロジェクトの設定のウィンドウが開いたら、エンジンという項目の中のインプットを選びます。すると上のような画面になります。「バインディング」のところに何も項目がない場合は「バインディング」の左側の三角形を押すと、上のようは表示になるはずです(FirstPersonExample でプロジェクトを作っている場合です)。
このウィンドウで、キー入力に対する新たなイベントを作成できます。「アクションマッピング」の右の+を押すと、新たな項目が追加されるので、イベント名と対応する入力キーを選びます。Shift キーや Control キーを押したときだけ反応するようにもできます。
追加したイベント(MyAction)の右側の+を押すことで、イベントに対応するキーやマウス入力などを追加できます。この状態で設定のウィンドウを閉じて、レベルブループリントを開けば、追加したイベント(MyAction)をノードとして追加できます。
このように、名前で MyAction ノードを検索できます。
MyAction ノードには Enter キーを割り当てたので、実質的に MyAction ノードは Enter キーを押したときのイベントを拾ってくれます。
このように、プロジェクトの設定で独自のイベントを追加しておくと、キーとイベントの割り当てを一か所でまとめて管理できるようになって便利です。
キー入力を拾うイベントをブループリント内で個別に書いてしまうと、割り当てるキーを変更しようとしたときに、どこに何のキーのイベントを書いたかわからなくなることが多いです。プロジェクトの設定の中で一括してキー入力とイベントとの関連付け(バインディング)を管理することで、その手の混乱が防げます。
ゲーム開始時(アクタ作成時)のイベントを拾う
BeginPlay イベントで拾えます。ゲームが開始した直後(レベルが読み込まれた直後)か、アクタが作成された直後に呼び出されるので、アクタやブループリントを初期化する目的で使えます。
呼び出される厳密なタイミングについては、下記のドキュメントを参照してください。
- アクタのライフサイクル: 公式ドキュメント
アクターのブループリントクラスを作成すると、最初から BeginPlay イベント(と OnActorBeginOverlap、EventTick イベント)が配置されています。
レベルブループリントには BeginPlay イベントは配置されていませんが、自分で追加することはできます。
ゲームの実行中に毎フレーム呼び出されるイベントを拾う
UE4 がアプリの実行中に、画面を更新しようとするたびに呼び出すイベントです。
Unity でいうところの Update() に相当するものです。アクターを移動させたり、継続的に状態を変化させたり(回転、拡大縮小、マテリアルを変更するなど)するのに使えます。
しかし、EventTick は毎フレーム処理されるため、ここに重い処理を記述すると、ゲーム自体が重くなってしまうことがあります。継続的な移動や変形をするだけなら、タイムラインやアニメーションを使ったほうが良いことが多いです。
アクターを EventTick で動かす例
画面に Cube を配置して、Blueprint クラスを追加します。Cube の「詳細」で可動性を必ずムーバブルにしておきます。
EventTick イベント以下を上のように書きます。これで、UE4 のゲーム画面が更新されるたびに Velocity の分だけ Cube が移動するようになります。Z軸(高さ)が 1000 (cm) になるまで上に移動し、1000 cm になったら下方向に移動して、今度は Z軸が 0 (cm) になったら上に移動するようにしています。
このような感じで、EventTick を使うことでアクタを移動させることができます。
このような、アクタの位置を強制的に更新する方法では重力が無視されるので、動きが人工的になります。物理シミュレーションを使って「自然に」アクタを動かすには、AddImpuct のようなアクタに力を加えるノードを使います(後述、するかなー)。
アクターをタイムラインを使って動かす
EventTick を使えば継続的にアクタを動かすことはできますが、往復運動のような比較的単純な動きをさせる場合は、タイムラインを使うか、アニメーションを使うほうが楽なことが多いです
タイムラインは、値が時間の経過に従って自動的に変わっていく変数のようなもの、と思えばたぶんだいたいあってます。イベントグラフで右クリックして Timeline と入力すると、タイムラインを追加するという項目があるので、それを選ぶことで追加できます。
タイムラインへのトラックの追加と編集
タイムラインのノードが追加されたら、ダブルクリックしてタイムラインを編集します。
最初にタイムラインのタブを開いた時点では、上のように何も中身がない状態になっています。「トラック」を追加することで、タイムラインが出力する値の種類を選べます。
ためしに左の「フロート」トラックを追加してみます。
フロートトラックを追加すると、上のように時間経過に従って値をどのように変化させるかを編集できるトラックが追加されます。トラックの上でマウスのホイールを上下させることで、時間軸方向の拡大率を変更できます。
トラック名がデフォルトで「新規トラック_0」とかなっているので、適当に変更します。ここでは Height としてみます。
「長さ」の値を変更すると、タイムラインの時間の長さを変更できます。ここでは 2 秒に設定してみました。
タイムライン内で右クリックすることで、キーフレームを追加できます。
キーフレームを3つ追加して、1秒で値が1まで増えて、そのあとさらに1秒で0まで戻るようにしてみました。
キーフレームの黄色いポチをクリックしてから、左上にある「時間」「値」の項目を直接編集することで、値を正確にいれることもできます。
右上の「オートプレイ」と「ループ」にチェックを入れておきます。
キーフレームで右クリックして、キー補間を追加することで、トラックの線を曲線的にすることもできます。
タイムラインを使ったグラフの作成
イベントグラフに戻って、上のような感じでグラフを作ります。ポイントは、BeginPlay イベントの先にタイムラインをつなぐことです。うっかり EventTick につなぎそうになりますが、タイムラインノードはそれ自体が EventTick にように、タイムラインより後ろにつないだノードを毎フレーム実行するので、それではうまくいきません。
上のようにプログラムを書くと、ゲームスタートした直後からCubeが上下に移動します。オートプレイにチェックをしていないと、自動的には動き始めません(Play From Start にスタートのトリガーを入れる必要があります)。ループにチェックを入れていないと、上下に一度移動しただけで止まります。
知っておくと便利な関数(おまけ)
画面に文字を表示する
PrintString ノードや PrintText ノードを使うと、画面上に文字を表示できます。文字は自動的にスクロールして消えます。
文字の表示は上のようになります。
一定時間の遅延を入れる(ウェイトを入れる)
Delay を使うと前のノードを実行してから、一定時間だけ待って次のノードを実行させることができます。上のようなコードを書くと、Enter を押してから 1秒後に Enter と表示されます。
Delay はちょっとした遅延を入れるのには便利です。ただし、Delay ノードの後に実行されるノードでは、そのノードに入力される値が(遅延によって)予期しない値になることがあります。
こんな感じのコードを書いてプレイし、Enter キーを連打すると、連打のタイミングによって HOGE と表示されたり DELAY と表示されたりします。プログラムの見た目上は DELAY と表示されそうな感じがしますが、必ずしもそうなりません。
これは、Enter のイベントの処理が、非同期かつ重複して行われるためです。Enter イベント以下につながっているノードの処理がすべて終わっていなくても、Enter キーが押されると Enter ノード以下の処理が(並行して)実行されることから、このようなことが起こります。
具体的には、後段にある Delay を処理している間に Enter キーが押されると、すでの処理中の Enter イベント以下の処理(これを処理Aとします)とは別に、非同期で同じ処理が実行されます(これを処理Bとします)。処理Aの後段のDelayが処理されている間に、処理BのSTRにHOGEをセットする処理が実行されてしまうと、処理Aの出力結果は HOGE になってしまう、ということです。
このような、非同期処理とDelayの組み合わせはバグの原因になることが多いです。後述の SetTimer 関連のノードでも同じようなことは起きますが、Delay では同じ実行ラインの上にノードを並べるので、つい同じ値を取ると考えがちです。Delay を使用する際には、Delay の実行中に Delay と同じライン上で参照する変数の中身が変化することを考慮する必要があります。
一定時間の遅延の後に関数を実行する
SetTimerByFunctionName を使うと、一定時間の経過後に、指定された関数を呼び出すことができます。上のようなコードをかくと、Enter を押した 5秒後に Print 関数が実行されます。
関数名は文字列で指定する必要があります。関数名が間違っていると、何も実行されません。
Looping にチェックを入れると、指定した時間間隔で関数を繰り返し実行します。Loop に指定したタイマーを止めるには、PauseTimerByFunctionName を使います。
タイマーの時間間隔の変更は UpdateTimerByFunctionName で、タイマーを完全に停止させるには(クリアするには)ClearTimerByFunctionName を使います。
なお、SetTimerByFunctionName では引数に取る関数の引数を指定する方法がないため、引数のある関数を使うことはできません。しかしビルドエラーにはならないため、予期しないバグの原因になりやすいです。
一定時間の遅延の後にイベントを実行する
SetTimerByEvent は SetTimerByFunctionName の Event 版です。javascript の SetInterval 関数によく似ています。イベントの赤い線を引く必要があるため、イベントグラフ内でしか使えません(関数内にはイベントが配置できないため)。
PauseTimerByEvent は、SetTimerByEvent の返り値を引数に取ります。上のように直接線をつながなくても、変数を経由しても問題ありません。
SetTimerByFunctionName と比べて、使い勝手の上では以下の点で異なります。
- 実行するイベントを文字列ではなく、直接線を引くことで実行するイベントを指定します。関数名のタイプミスによるバグが発生しにくいです。
- SetTimerByFunctionName は関数名でタイマーを管理するため、同じ関数を複数のタイマーで使うことができませんが、SetTimerByEvent のほうはハンドラーで管理するため、複数のタイマーで同じイベントを呼び出すことができます。
続いた
次回はアクター関連の予定です。