Maemaemae

Godot 4 入力システムの基本と活用テクニック

はじめに

ゲーム開発において、プレイヤーの入力を適切に処理することは最も基本的かつ重要な要素の一つです。Godotエンジンでは、柔軟で強力な入力システムが提供されており、キーボード、マウス、ゲームパッドなどさまざまな入力デバイスからの操作を簡単に処理できます。

この記事では、Godot 4の入力システムの基礎から応用まで、実例を交えながら解説します。この記事を読むことで、以下のことができるようになります:

基本的な入力処理の仕組み

Godotでは、入力処理の方法として主に2つのアプローチがあります:

  1. 入力イベントの直接処理 (_input_unhandled_input 関数を使用)
  2. InputMapを活用したアクション処理 (Input.is_action_pressed などの関数を使用)

まずは基本となる入力イベントの処理方法から見ていきましょう。

入力イベント処理の基本

Godotでは、_input関数をオーバーライドすることで、あらゆる入力イベントをキャッチできます。

func _input(event):
    # キー入力イベントを処理
    if event is InputEventKey and event.pressed and not event.echo:
        var keycode = event.keycode
        print("押されたキー: ", OS.get_keycode_string(keycode))

このコードでは、キーが押されたときにそのキーの文字列表現をコンソールに出力します。event.echoをチェックすることで、キーの長押しによる連続入力を無視しています。

InputMapを活用したアクション処理

実際のゲーム開発では、特定のキーコードに直接依存するよりも、InputMapを使ったアクション名でのマッピングがより柔軟です。これにより、プラットフォーム間の違いを吸収したり、プレイヤーによるキー設定のカスタマイズが容易になります。

以下は、現在押されたキーに対応するアクションを確認するコードです:

func _input(event):
    if event is InputEventKey and event.pressed and not event.echo:
        var keycode = event.keycode
        print("押されたキー: ", OS.get_keycode_string(keycode))

        # すべてのアクションを確認
        var actions = InputMap.get_actions()
        var matching_actions = []

        for action in actions:
            if Input.is_action_just_pressed(action):
                matching_actions.append(action)

        if matching_actions.size() > 0:
            print("このキーに対応するアクション: ", matching_actions)
        else:
            print("このキーに対応するアクションはありません")

このコードは、キーが押されたときに以下の情報を提供します:

  1. 押されたキーの文字列表現
  2. そのキーに対応するInputMapのアクション一覧

入力デバッグの実践テクニック

開発中のゲームで入力関連の問題をトラブルシューティングする場合、上記のコードは非常に役立ちます。ここでは、さらに実践的なデバッグテクニックを紹介します。

デバッグ用のオーバーレイ表示

開発中は、現在の入力状態を画面上に表示すると便利です。以下は簡単なオーバーレイ表示の実装例です:

extends CanvasLayer

var active_actions = []

func _process(_delta):
    # 更新のたびにアクティブなアクションをクリア
    active_actions.clear()

    # すべてのアクションを確認
    var actions = InputMap.get_actions()
    for action in actions:
        if Input.is_action_pressed(action):
            active_actions.append(action)

    # 表示更新
    $Label.text = "アクティブなアクション: " + str(active_actions)

このスクリプトをCanvasLayerノードにアタッチし、子ノードとしてLabelを追加することで、現在アクティブなアクションをリアルタイムで表示できます。

入力イベントの詳細情報取得

より詳細な入力情報が必要な場合は、イベントオブジェクトから追加情報を取得できます:

func _input(event):
    if event is InputEventKey and event.pressed:
        var info = {
            "keycode": event.keycode,
            "scancode": event.physical_keycode,
            "unicode": event.unicode,
            "echo": event.echo,
            "alt": event.alt_pressed,
            "shift": event.shift_pressed,
            "ctrl": event.ctrl_pressed
        }
        print("キー詳細情報: ", info)

これにより、修飾キー(Alt、Shift、Ctrl)の状態や、物理キーコードなどの追加情報も取得できます。

InputMapの効率的な設定と管理

Godotのプロジェクト設定からInputMapを設定することもできますが、大規模なプロジェクトでは、コードでプログラム的に設定することも有効です。

コードによるInputMapの設定

func setup_input_mapping():
    # 既存のマッピングをクリア
    InputMap.action_erase_events("move_right")

    # 新しいキーボード入力イベントを作成
    var event = InputEventKey.new()
    event.keycode = KEY_D

    # アクションに追加
    InputMap.action_add_event("move_right", event)

    # ゲームパッド入力も追加
    var pad_event = InputEventJoypadMotion.new()
    pad_event.axis = JOY_AXIS_0
    pad_event.axis_value = 1.0
    InputMap.action_add_event("move_right", pad_event)

このアプローチを使用すると、ゲーム開始時に動的にキー設定を行ったり、プレイヤーが好みに合わせてカスタマイズできる設定画面を実装したりすることが可能です。

InputMapの共通パターン

複数のプラットフォームで一貫した体験を提供するために、以下のような命名規則を採用するとよいでしょう:

# 移動系
"move_up", "move_down", "move_left", "move_right"

# アクション系
"action_primary", "action_secondary", "action_dodge"

# UI操作系
"ui_accept", "ui_cancel", "ui_menu"

このようなアクション名を使用することで、実際のキー割り当てに依存せず、意図を明確にしたコードが書けます。

マルチプラットフォーム対応のためのベストプラクティス

Godotは複数のプラットフォームに対応していますが、入力処理においてはいくつかの考慮点があります。

デバイス検出と入力切替

func _ready():
    # 初期化時にデバイスタイプを検出
    update_input_device_type()

func _input(event):
    # 入力デバイスの種類を検出
    if event is InputEventKey or event is InputEventMouse:
        if current_device_type != "keyboard_mouse":
            current_device_type = "keyboard_mouse"
            update_ui_for_device()
    elif event is InputEventJoypadButton or event is InputEventJoypadMotion:
        if current_device_type != "gamepad":
            current_device_type = "gamepad"
            update_ui_for_device()

func update_ui_for_device():
    # 現在のデバイスに基づいてUIのプロンプトを更新
    if current_device_type == "keyboard_mouse":
        $Prompts.texture = keyboard_prompts
    else:
        $Prompts.texture = gamepad_prompts

このコードは、プレイヤーが途中でキーボードからゲームパッドに切り替えた場合など、現在使用している入力デバイスを検出し、それに合わせてUIを更新します。

タッチスクリーン対応

モバイルデバイス向けにタッチ入力を処理する例:

func _input(event):
    if event is InputEventScreenTouch:
        if event.pressed:
            print("タッチ開始位置: ", event.position)
        else:
            print("タッチ終了位置: ", event.position)

    elif event is InputEventScreenDrag:
        print("ドラッグ位置: ", event.position)
        print("相対移動量: ", event.relative)

タッチ入力では、従来のキーボードやゲームパッドとは異なる設計アプローチが必要です。オンスクリーンコントロールを実装するか、スワイプやジェスチャーを活用するかを検討しましょう。

よくある問題と解決策

キー入力の競合

複数のシステムで同じキーを使用している場合、意図しない動作が発生することがあります。

# 優先順位を付けて処理する例
func _unhandled_input(event):
    if is_in_menu:
        handle_menu_input(event)
    elif is_in_dialog:
        handle_dialog_input(event)
    else:
        handle_gameplay_input(event)

_inputではなく_unhandled_inputを使用することで、UIなどの優先度の高いシステムがイベントを処理した後の残りの入力のみを処理できます。

入力の遅延やバッファリング

アクションゲームなどでは、入力の正確なタイミングが重要です。以下はシンプルな入力バッファリングの実装例です:

var input_buffer = []
var buffer_timeout = 0.2  # 秒

func _input(event):
    if event is InputEventKey and event.pressed and not event.echo:
        # 入力をバッファに追加
        input_buffer.append({
            "action": get_action_for_key(event.keycode),
            "time": Time.get_ticks_msec() / 1000.0
        })

func _process(delta):
    var current_time = Time.get_ticks_msec() / 1000.0

    # 古い入力を削除
    while input_buffer.size() > 0 and current_time - input_buffer[0].time > buffer_timeout:
        input_buffer.pop_front()

    # バッファ内の入力を処理
    process_buffered_inputs()

このテクニックを使用すると、プレイヤーの入力をより寛容に処理できます。たとえば、ジャンプ直前に走るアクションを入力した場合でも、短時間であればジャンプ中に走り始めるなどの処理が可能になります。

実践的なユースケース

プレイヤーキャラクターの移動制御

典型的なプレイヤー移動制御の例:

func _physics_process(delta):
    var direction = Vector2.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_down"):
        direction.y += 1
    if Input.is_action_pressed("move_up"):
        direction.y -= 1

    # 方向を正規化して斜め移動が速くなりすぎないようにする
    if direction.length() > 0:
        direction = direction.normalized()

    # 移動速度を適用
    velocity = direction * speed

    # 実際の移動を適用
    move_and_slide()

カスタム入力システムの実装

より複雑なゲームでは、カスタム入力システムを実装するとよいでしょう:

class_name InputManager
extends Node

signal action_pressed(action_name)
signal action_released(action_name)

var active_actions = {}
var previous_actions = {}

func _process(_delta):
    previous_actions = active_actions.duplicate()
    active_actions.clear()

    for action in InputMap.get_actions():
        if Input.is_action_pressed(action):
            active_actions[action] = true

            # 新たに押されたアクション
            if not previous_actions.has(action):
                emit_signal("action_pressed", action)
        elif previous_actions.has(action):
            # 離されたアクション
            emit_signal("action_released", action)

このようなマネージャークラスを使用すると、入力の変化をシグナルとして他のシステムに通知できるため、コードの分離と再利用性が向上します。

まとめ

Godot 4の入力システムは非常に柔軟で強力です。この記事で紹介した基本的な入力処理から、デバッグテクニック、マルチプラットフォーム対応、そして実践的なユースケースまでを理解することで、よりレスポンシブでユーザーフレンドリーなゲームを開発することができます。

ポイントをまとめると:

  1. 基本的な入力処理には_input_unhandled_input関数を活用する
  2. InputMapシステムでキーコードへの直接依存を避ける
  3. デバッグ用のツールや表示を活用して開発を効率化する
  4. マルチプラットフォーム対応を初期段階から考慮する
  5. ゲームのジャンルに合わせた適切な入力処理を実装する

これらの知識とテクニックを活用して、プレイヤーにとって直感的で快適な操作感を実現しましょう!

参考資料


Godot 4 以上を前提とした記事です。記載されているコードは、Godot 4 で動作確認済みです。

Godot入力処理ゲーム開発InputMap