インプットイベント
インプットイベントとは何か
インプットの管理は通常複雑になります。それはPCでもゲーミングプラットフォームでも同じことが言えます。この解決のために特別な型"InputEvent"が組み込まれています。このデータ型は複数種のイベントを含むことが出来ます。インプットイベントはエンジンに伝わり、目的によって様々な場所で受け取ることが出来ます。
インプットイベントはどう動くか
インプットイベントはユーザー、つまりプレイヤーから発信されます(しかし、インプットイベントの生成すること、それらをエンジンへフィードバックすることは可能です。ジェスチャーに役に立ちます)。各プラットフォームのOSオブジェクトは、デバイスからの入力を読み取り、メインループへフィードバックすることが出来ます。シーンツリーはデフォルトのメインループの実装ですので、通常シーンツリーが受け取ることになります。Godotにはカレントのシーンツリーを得る関数があります。: get_tree()
しかし、シーンツリーはイベントの処理は出来ないので、ビューポートに渡します。ルートビューポートから渡し始めます(シーンツリーの最初のノードです)。ビューポートは受け取った入力に対して、さまざまな処理をかけます。順番に説明すると:
画像
まず第一に、スタンダードな_input関数が"input processing"が有効になっている任意のノードから呼ばれます(Node.set_process_input()で有効化し、Node._input()をオーバーライドしてください)。任意の関数で入力イベントを消費するのであれば、SceneTree.set_input_as_handled()を呼び出し、入力イベントの受け取り手の検索をそこで終了させることが出来ます。これにより、GUI以前に、全てのイベントが何に紐付けられるか明確にすることが出来ます。ゲームプレイ時の入力において、普通は_unhandled_input()を有効化します。これによってGUIがイベントを中断させることが出来るようになります。
第二に、GUIに入力を送り、任意のControlが受け取れるか確認を試みます。もしControlが受け取れるなら、仮想関数からControl._input_event()を呼び出し、"input_event"シグナルが生成されます。この関数は継承したスクリプトからもう再度実行することも可能です。Controlにイベントを"consume"させたい場合、Control.accept_event()と呼ぶことで入力イベントの受け取り手の検索をそこで終了させることが出来ます。
ここまでイベントが消費されていなければ、unhandled eventコールバックが呼ばれます( Node.set_process_unhandled_input()を有効化し、 Node._unhandled_input()をオーバーライドしてください)。イベントを関数が消費するならSceneTree.set_input_as_handled()が呼ばれ、そこでイベントは終わります。unhandled inputはフルスクリーンでゲームをするときのイベントを想定したものなので、GUIが有効な時には受け取られません。
ここまでイベントが消費されておらず、そのビューポートにカメラがアサインされていれば、物理的な世界へ視線が投げられます(クリックで方向付けられます)。この視線がオブジェクトに当たると、そのオブジェクトはその物理オブジェクトに関連付けられたCollisionObject._input_event()関数を呼び出します(デフォルトではボディーがこのコールバックを受け取ります、エリアではありません。これはAreaプロパティを用いて設定できます)。
最後までイベントが処理されていなければ、ツリーの次のビューポートに渡されるか、その入力は無視されます。
インプットイベントの解析
インプットイベントは基本的なビルドインタイプに過ぎず、それ自体が何かを表すわけではありませんし、基本的な情報しか保持しません。例えばそれはイベントID(イベントが定義される毎に大きくなる)やデバイスやインデックスなどです。
ImputEventはメンバ"type"を持っています。そのメンバを割り当てることで、違う種類の入力イベントに変化することが出来るのです。各タイプには役割に応じたプロパティがあります。
イベントタイプの変更は以下のように実装します。
# イベントの作成
var ev = InputEvent()
# タイプインデックスを設定
ev.type = InputEvent.MOUSE_BUTTON
# このタイプ(MOUSE_BUTTON)固有のbutton_indexが存在する
ev.button_index = BUTTON_LEFT
タイプの一覧は以下のようになります。
イベント | タイプインデックス | 説明 |
---|---|---|
InputEvent | NONE | 空のインプットイベント |
InputEventKey | KEY | スキャンコードとユニコードの値を修飾子と同様に格納する。 |
InputEventMouseButton | MOUSE_BUTTON | クリックに関するボタンや修飾子などの情報を格納する。 |
InputEventMouseMotion | MOUSE_MOTION | マウスの移動に関する、相対的、絶対的位置や速度などの情報を格納する。 |
InputEventJoystickMotion | JOYSTICK_MOTION | ジョイスティック、ジョイパッドのアナログな傾き情報を格納する。 |
InputEventJoystickButton | JOYSTICK_BUTTON | ジョイスティック、ジョイパッドのボタン動作に関する情報を格納する。 |
InputEventScreenTouch | SCREEN_TOUCH | マルチタッチにおけるプレス、リリース情報を格納。(モバイルデバイスのみ) |
InputEventScreenDrag | SCREEN_DRAG | マルチタッチにおけるドラッグ情報を格納。(モバイルデバイスのみ) |
InputEventAction | SCREEN_ACTION | 一般的なアクションを格納。これらのイベントはプログラマーへのフィードバックを生成することが出来ます。(詳細は以下) |
アクション
インプットイベントが定義されたアクションを指してるとは限りません。アクションはゲームをプログラミングする際、抽象化して書いた方が良い場合があります。例えば以下のような場合が考えられます。
別デバイスでは別の入力でコードを実行したい時。(PCではキーボード、ゲームコンソールではジョイパッド)
ランタイムにて変更される入力
アクションはプロジェクトセッティングメニューのアクションタブで追加出来ます。アクションエディターについての説明はInput actions setupにあります。
全てのイベントはInputEvent.is_action()、InputEvent.is_pressed()、InputEventメソッドを保持しています。
また、アクションはゲームの裏で処理されなくてはいけない場合もあります(ジェスチャーなどは良い例です)。メインループのシーンツリーにはこのためのメソッドが用意されています。MainLoop.input_eventです。これは以下のように使います。
var ev = InputEvent()
ev.type = InputEvent.ACTION
# 押された時にmove_leftを実行する。
ev.set_as_action("move_left",true)
# フィードバック
get_tree().input_event(ev)
インプットマップ
インプットをコードからカスタマイズやリマッピングしたい場合はよくあります。ワークフローがアクションに依存するのであれば、ランタイム内で別のアクションに再設定するのにはInputMap単量子を使われるのが理想です。単量子は手動でそう設定しない限り保存されず、そのステートはプロジェクト設定から実行されます(engine.cfg)。Godotにおいてはどんなダイナミックなシステムでも設定を保持する必要があるので、このやりかたが最も適当だと考えられます。