概要
プレイヤーの操作に正確に反応するゲームを作るには、キー入力の「状態」を正しく理解することが不可欠です。「キーが押されている間ずっと」なのか、「キーが押された瞬間だけ」なのか。この違いを使い分けることで、キャラクターの動きは格段に良くなります。
「ボタンを押したらジャンプする」—単純に見えるこの操作も、実装方法を間違えるとゲームの触り心地(ゲームフィール)を大きく損ないます。例えば、ジャンプボタンを押しっぱなしにしただけでキャラクターが無限にピョンピョン跳ねてしまったり、逆に移動キーを一瞬押しただけなのに滑るように動き続けたり。こうした問題の多くは、入力の「状態」を正しく区別できていないことが原因です。
この記事では、Godotの主要な入力判定メソッドであるis_action_just_pressed, is_action_pressed, is_action_just_releasedの違いを解説します。
3つの主要な入力取得メソッド
Godotの入力処理は、主にInputシングルトン(どこからでもアクセスできるグローバルなオブジェクト)を介して行 われます。ここでは、最もよく使われる3つのポーリング方式のメソッドを見ていきましょう。これらのメソッドは、_processや_physics_processといった毎フレーム呼び出される関数内で使用し、その瞬間の入力状態をチェックします。
引数には、プロジェクト > プロジェクト設定 > 入力マップで定義したアクション名を文字列で指定します(例: "jump", "move_right")。
| メソッド | 判定タイミング | 返り値 | 主な用途 |
|---|---|---|---|
is_action_pressed() | アクションが押されている間ずっと | true | キャラクターの継続的な移動、連射、UIの高速スクロール |
is_action_just_pressed() | アクションが押された最初の1フレームだけ | true | ジャンプ、決定、メニューを開く、単発の射撃 |
is_action_just_released() | アクションが離された最初の1フレームだけ | true | チャージ攻撃の解放、ジャンプの高さ調整、ドラッグ&ドロップの終了 |
実践的なコード例
理論を理解したところで、具体的なコードを見ていきましょう。ここでは2Dプラットフォーマーゲームのキャラクターを例に、各メソッドをどのように使い分けるかを示します。
1. 基本的な移動とジャンプ(pressed & just_pressed)
これは最も基本的な組み合わせです。左右の移動には継続的な入力を、ジャンプには瞬間的な入力を使用します。
extends CharacterBody2D
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
func _physics_process(delta):
# 重力適用
if not is_on_floor():
velocity.y += gravity * delta
# ジャンプ: 押された「瞬間」に一度だけ実行
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
# 左右移動: 押されている「間」ずっと力を加える
var direction = Input.get_axis("move_left", "move_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
Input.get_axis()は内部的にis_action_pressed()と同様のチェックを行っており、キーが押されている間、-1から1の値を返します。これにより、滑らかな移動が実現できています。
2. チャージ攻撃(pressed & released)
ボタンを押している間に力を溜め、離した瞬間に攻撃を放つチャージ攻撃は、pressedとreleasedの組み合わせで実現できます。
extends Sprite2D
var is_charging = false
var charge_time = 0.0
const MAX_CHARGE_TIME = 2.0
func _process(delta):
# チャージ処理: 押されている間、チャージ時間を加算
if Input.is_action_pressed("charge_attack"):
is_charging = true
charge_time += delta
# チャージレベルに応じて見た目を変える(例: 色を変える)
modulate = Color.WHITE.lerp(Color.RED, charge_time / MAX_CHARGE_TIME)
print("Charging... %.2fs" % charge_time)
# 攻撃発動: 離された瞬間に実行
if Input.is_action_just_released("charge_attack"):
if is_charging:
var power = clamp(charge_time, 0.5, MAX_CHARGE_TIME)
print("Attack released with power: %.2f!" % power)
# ここで弾を発射するなどの処理を呼ぶ
# fire_bullet(power)
# リセット
is_charging = false
charge_time = 0.0
modulate = Color.WHITE
3. 可変ジャンプ(just_pressed & released)
ボタンを押す長さでジャンプの高さを変える、いわゆる「可変ジャンプ」は、マリオのような多くのプラットフォーマーで採用されているテクニックです。これにより、操作の幅が格段に広がります。
# (CharacterBody2Dのコードに追加)
const SHORT_JUMP_MULTIPLIER = 0.5
func _physics_process(delta):
# ... (重力と移動のコードは同じ)
# ジャンプ開始
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
# ジャンプボタンが離されたら、上昇を止める
if Input.is_action_just_released("jump") and velocity.y < 0:
velocity.y *= SHORT_JUMP_MULTIPLIER
# ... (move_and_slide())
このコードでは、ジャンプボタンが離され、かつキャラクターがまだ上昇中(velocity.y < 0)の場合に、上昇速度にブレーキをかけています。これにより、「短く押すと低いジャンプ、長く押すと高いジャンプ」が実現できます。
よくある間違いとベストプラクティス
入力処理は間違いやすいポイントがいくつかあります。以下の表を参考に、より堅牢で意図通りのコードを目指しましょう。
| カテゴリ | よくある間違い | ベストプラクティス |
|---|---|---|
| ジャンプ処理 | is_action_pressed() を 使う。ボタンを押し続けると意図せず連続ジャンプしてしまう。 | is_action_just_pressed() を使う。押した瞬間に一度だけジャンプ処理を実行する。 |
| UI操作 | is_action_pressed() を決定ボタンに使う。毎フレーム決定処理が走り、メニューが一気に進んでしまう。 | is_action_just_pressed() を使う。UI操作は単発のアクションとして扱うのが基本。 |
| 処理ループ | 物理演算に関わる入力を _process() で処理する。フレームレートの変動で物理挙動が不安定になる可能性がある。 | 物理演算に関わる入力(移動、ジャンプなど)は _physics_process() で処理し、安定した挙動を保証する。 |
| アクション定義 | スクリプト内にキーコードをハードコーディングする (Input.is_key_pressed(KEY_SPACE))。キー変更が面倒になる。 | InputMap でアクションを定義し、is_action_pressed("jump") のように名前で呼び出す。キーコンフィグが容易になる。 |
パフォーマンスと代替手法:ポーリング vs イベント駆動
これまで紹介してきたis_action_*系のメソッドは、「ポーリング」と呼ばれる方式です。これは、毎フレーム「入力はありますか?」と能動的に確認しにいく方法です。
一方で、Godotには「イベント駆動」というもう一つの主要な入力処理方法があります。これは、_input()や_unhandled_input()といった仮想関数をオーバーライドし、入力があったときにGodot側から通知を受け取る方式です。
# イベント駆動方式の例
func _unhandled_input(event: InputEvent):
if event.is_action_pressed("jump"):
# キーが押された瞬間にイベントが発生し、この関数が呼ばれる
# ※キーリピートが有効な場合は長押しで繰り返し呼ばれることがある
print("Jump action event!")
if event.is_action_released("ui_cancel"):
get_tree().quit()
どちらを使うべきか?
-
ポーリング (
is_action_*):- 長所:
_processや_physics_process内で、好きなタイミングで入力状態をチェックできる。キャラクターの移動のように「押されている状態」が重要な場合に非常に直感的。 - 短所: 毎フレームチェック処理が走る。ただし、現代のPCではこれがボトルネックになることは稀です。
- 長所:
-
イベント駆動 (
_input,_unhandled_input):- 長所: 入力があった時だけコードが実行されるため、効率が良い。マウスのクリック位置取得や、UI操作、ポーズメニューの表示など、単発のイベント処理に最適。
- 短所: 「押され続けている」状態を管理するには、自分でフラグ変数を用意する必要があり、コードが少し複雑になることがある。
キャラクターのリア ルタイムな操作にはポーリングを、UI操作や一度きりのアクションにはイベント駆動を、というように使い分けるのが効果的です。
まとめ
入力判定はゲームの触り心地(ゲームフィール)を決定づける重要な要素です。Godotが用意してくれている3つのメソッドを正しく使い分けることで、より直感的でレスポンスの良い操作性を実現できます。
- 移動のように継続的なアクションには
is_action_pressed() - ジャンプや決定のような単発のアクションには
is_action_just_pressed() - 溜め解放のような特殊なアクションには
is_action_just_released()
この基本を覚えて、ぜひあなたのゲームに活かしてみてください。