【Godot】Godotのゲームパッド入門:スティックで歩き、被弾で震え、メニューをパッドだけで操作する

作成: 2026-09-22最終更新: 2026-10-10

ゲームパッドのスティックで歩き、被弾で震え、ポーズ画面をパッドだけで操作する方法を、デッドゾーンとフォーカスを中心に解説します。

キーボードで動くゲームにゲームパッドを足すときは、スティックで歩く、被弾で震える、ポーズ画面をパッドだけで操作する、の3つがそろうと遊べる形になります。Godot では、アクションのデッドゾーン、振動の関数、ボタンのフォーカスで、これを作ります。

何も考えずにパッドを挿すと、スティックから指を離してもキャラがじわじわ歩き続けたり、ポーズを開いてもどのボタンも選べなかったりします。

この記事では、スティックの倒した量を速さに使う書き方、被弾で短く震わせる書き方、ポーズ画面をパッドだけで操作する方法を解説します。

スティックの倒した量を、歩きでは速さに使い、ポーズ画面では読まずに枠を1つずつ送るイメージ

この記事でわかること

  • スティックから指を離しても動いてしまう理由と、その直し方
  • デッドゾーンを設定する場所と、歩き用とメニュー用の値の違い
  • ポーズ画面をパッドだけで操作するために、自分で書く1行
  • 振動を1回だけ起こす書き方と、ポーズやパッドを抜いたときの後始末

動作確認環境: Godot 4.7(Windows)

Sponsored

スティックの入力とデッドゾーン

キーボードのキーは、押したか離したかの2択です。スティックは違います。どれだけ倒したかという量が、毎フレーム送られ続けます。

キーボードは0と1の2段だけを行き来し、スティックは0から1のあいだの量を送り、触らなくても小さく揺れる

歩きでは、この量をそのまま速さに使います。浅く倒せばゆっくり、深く倒せば速く。キーボードでは作れない動きで、ゲームパッドを挿す価値の半分はここにあります。

問題は 0 のあたりです。使い込んだパッドは、指を離していても 0.05 から 0.15 くらいの値を出し続けることがあります。そのまま速さにすると、誰も触っていないのにキャラが歩きます。

そこで、ある大きさより小さい量は「倒していない」として捨てます。この捨てる範囲が デッドゾーン です。

スティックの可動範囲の円のうち、中心の0.2より内側は0として捨て、その外側から速さになる

設定する場所は1か所です。プロジェクト設定の Input Map で、アクション(move_left のように、キーやスティックの向きにまとめて付けた名前)の行ごとに Deadzone が付いています。登録したイベントの側には、この設定はありません。

新しく作ったアクションは、最初から 0.2 になっています。歩きにはこの値が合います。0.1 まで下げると揺れが通り、0.5 まで上げると、かなり倒すまで歩き出しません。

移動の読み取りは1行です。

var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")

get_vector() が返すベクトルは、長さが最大で 1 です。斜めに倒しても、真横に倒したときと同じ速さで止まります。左右と上下を別々に読んで足すと、斜めだけ約1.4倍になります。

この関数はデッドゾーンも一緒に見てくれます。引数を省略すると、4つのアクションに設定された値の平均が使われます。

捨てたぶんは、残りを 0 から 1 へ割り直して返します。0.2 を捨てる設定でスティックを半分(0.5)倒すと、返ってくるのは 0.375 です。

Sponsored

ポーズ画面では、同じスティックの扱いが逆になります。倒した量は自分で読みません。Godot が「次の項目へ1回」に変換したものを受け取ります。

いま枠が付いていて、決定を受け取る相手のことを フォーカス と呼びます。画面のどこに注目しているかの印で、パッドやキーボードで操作するときは、これが動きます。

ui_upとui_downには十字キーと左スティックが入っているが、ui_acceptにはパッドのボタンを自分で足す

ui_up や ui_down には、十字キーと左スティックが最初から入っています。これらの Deadzone は 0.5 で、歩き用の 0.2 より鈍くしてあります。指を乗せただけで項目が送られないようにするためです。

決定に使われるのは ui_accept です。こちらには、最初はキーボードのキー(Enter や Space)しか入っていません。プロジェクト設定の Input Map で Show Built-in Actions をオンにすると、ui_accept の行が見えます。行の + から「Joypad Buttons」を開き、「Joypad Button 0 (Bottom Action, Sony Cross, Xbox A, Nintendo B)」を足します。パッドの下のボタンです。ui_cancel に「Joypad Button 1」(右のボタン)を足すと、右のボタンで閉じられるようになります。

自分で書くのは1行だけです。

grab_focusを呼ばないとボタンに枠が付かずスティックが効かず、呼ぶと最初のボタンから枠が動く

ポーズ画面を表示した直後に、最初のボタンへ grab_focus() を呼びます。これが無いと、どのボタンもフォーカスを持っていないので、スティックを倒しても枠が出ません。

focus_neighbor という項目もありますが、縦に3つ並べただけなら触らなくてかまいません。空のままにしておくと、Godot が位置から隣を探します。

キーボードと違って、スティックを倒し続けても、項目は1つしか動きません。1つ進むたびにスティックをいったん中央へ戻します。

振動の出し方

被弾したときの手応えは、1行で足ります。

Input.start_joy_vibration(device, 0.3, 0.7, 0.25)

第2引数は細かいモーター、第3引数は重いモーターの強さで、どちらも 0 から 1 です。第4引数は秒数です。被弾なら弱い側を 0.3、強い側を 0.7、長さを 0.25 秒くらいから試します。

長さ0.25を渡すと0.25秒で止まり、0を渡すと約1分震え続ける。途中で止めるならstop_joy_vibration

長さに 0 を渡すと、すぐには止まりません。Windows では、約1分震え続けてから止まります。シーンを切り替えても残るので、止めたいところで Input.stop_joy_vibration(device) を呼びます。ポーズを開くときに止めておくと、メニューを選んでいるあいだ震え続けることがなくなります。

呼ぶ回数にも注意が要ります。start_joy_vibration() は呼ぶたびに、その時点から指定した長さの振動をやり直します。重なっている間ずっと呼ぶと、いつまでも終わりません。ダメージを1回適用した、その瞬間だけ呼びます。

送り先のデバイス番号(つながったパッドに 0 から振られる番号)は、Input.get_connected_joypads() で今つながっている番号を取って、全部へ送ります。番号は抜き差しで変わるので、0 と決め打ちすると、つながっているのに震えないことがあります。

振動の無いパッドもあります。震えないパッドへ start_joy_vibration() を呼んでも、エラーにはなりません。震えなくても遊べるように作っておけば足ります。

パッドの抜き差し

プレイ中にパッドを抜かれたとき、何を書く必要があるかは、想像より少なめです。

パッドを抜くと��、ボタンと軸を離した扱いにするのはGodot、表示と振動の後始末は自分で書く

Godot 4.7 は、切断を見つけた時点で、そのデバイスのボタンと軸(スティックやトリガーの量)を離した扱いに戻します。 毎フレーム入力から速度を作り直す書き方なら、キャラはその場で止まります。 速度を 0 に戻すコードを自分で書く必要はありません。

自分で書くのは2つです。ひとつは画面の表示で、Input.joy_connection_changed というシグナルを受けて書き換えます。

func _ready() -> void:
	Input.joy_connection_changed.connect(_on_joy_changed)
	_update_pad_label()          # 起動時はシグナルを待たずに、今の一覧を見る

起動した時点ですでに刺さっているパッドは、このシグナルにつなぐより前に通知が済んでいることがあります。最初の表示だけは Input.get_connected_joypads() を直接見て作ります。

もうひとつは振動です。抜かれた番号へ Input.stop_joy_vibration(device) を呼んでおきます。

Sponsored

実践:歩いて、震えて、ポーズを開く

見下ろしのアクション、パズル、ミニゲームの選択画面——パッド対応で最初に必要になるのは、だいたいこの3つです。キャラを1体とトゲを1つ、ポーズ画面を1枚だけ置きます。

Mainの下にPlayer・Spike・Hud・PauseMenu。PauseMenuはWhen Pausedで、pause_menu.gdを持つ

① 置くもの

新しいシーンを1つ作ります。絵はプロジェクトに最初から入っている icon.svg だけです。

まずプロジェクト設定の Input Map です。パッドのボタンは、+ から開く一覧の「Joypad Buttons」に、番号と各社の印字を並べた名前で出ています。

  • move_left / move_right / move_up / move_down を作る。それぞれに矢印キーと、左スティックの対応する方向を登録する
  • 4つとも Deadzone が 0.2(作ったときの値)になっていることを確かめる
  • pause を作る。Esc と「Joypad Button 6 (Start, Xbox Menu, Nintendo +)」を登録する
  • Show Built-in Actions をオンにして、ui_accept の行に「Joypad Button 0 (Bottom Action, Sony Cross, Xbox A, Nintendo B)」を足す
  • 同じ手順で、ui_cancel に「Joypad Button 1 (Right Action, Sony Circle, Xbox B, Nintendo A)」を足す

次にシーンです。ルートに Node2D を置いて Main、main.gd を付けます。Process Mode(ポーズ中に動くかどうかの設定)は、既定の Inherit のままにします。

  • Main の子に CharacterBody2D を置いて Player。位置 (300, 400)、Motion Mode は Floating(見下ろしなので、床を探さない)
  • Player の子に CollisionShape2D(CircleShape2D・半径 32)
  • Player の子に Sprite2D。icon.svg を入れて Scale を (0.5, 0.5)
  • Main の子に Area2D を置いて Spike。位置 (700, 400)
  • Spike の子に CollisionShape2D(CircleShape2D・半径 48)と Sprite2D(icon.svg・Scale (0.75, 0.75))
  • Main の子に、カメラが動いても画面に貼り付いたまま描く層の CanvasLayer を置いて Hud。子に Label を2つ、SpeedLabel(16, 16)と PadLabel(16, 48)
  • Main の子にもう1つ CanvasLayer を置いて PauseMenu。Process Mode を When Paused(ポーズ中だけ動く)にして、pause_menu.gd を付ける
  • PauseMenu の子に Panel。位置 (416, 214)、Size を (320, 220)
  • Panel の子に、子を縦に並べる VBoxContainer。Anchors Preset を Full Rect(親いっぱいに広げる)にする
  • VBoxContainer の子に Button を3つ。上から ResumeButton(「続ける」)、RestartButton(「はじめから」)、QuitButton(「終わる」)
  • Player・Spike・SpeedLabel・PadLabel・PauseMenu・Panel・ResumeButton・RestartButton・QuitButton は、固有名でアクセスする項目(英語UIでは「Access as Unique Name」)をオンにする。コードからは %Player のように % を付けて取る

Player の半径 32 と Spike の半径 48 の円が重なった瞬間、つまり中心どうしが 80 まで近づいた瞬間に震えます。

② 2本のスクリプト

Main には、歩き・振動・パッドの表示と、ポーズを開く処理を書きます。

# main.gd(Main に付ける。Process Mode は Inherit のまま)
extends Node2D

const SPEED := 200.0

@onready var player: CharacterBody2D = %Player
@onready var speed_label: Label = %SpeedLabel
@onready var pad_label: Label = %PadLabel
@onready var pause_menu: CanvasLayer = %PauseMenu

func _ready() -> void:
	Input.joy_connection_changed.connect(_on_joy_changed)
	%Spike.body_entered.connect(_on_spike_entered)
	_update_pad_label()

func _physics_process(_delta: float) -> void:
	var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
	player.velocity = direction * SPEED
	player.move_and_slide()
	speed_label.text = "速さ %d / 200" % player.velocity.length()

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("pause"):          # ポーズ中は、この関数ごと止まる
		pause_menu.open()
		get_viewport().set_input_as_handled()

func _on_spike_entered(body: Node2D) -> void:
	if body != player:
		return
	for device in Input.get_connected_joypads():
		Input.start_joy_vibration(device, 0.3, 0.7, 0.25)

func _on_joy_changed(device: int, connected: bool) -> void:
	if not connected:
		Input.stop_joy_vibration(device)
	_update_pad_label()

func _update_pad_label() -> void:
	var pads := Input.get_connected_joypads()
	pad_label.text = "パッド: %s" % ("なし" if pads.is_empty() else Input.get_joy_name(pads[0]))

PauseMenu には、開いたときの準備と、閉じる処理を書きます。

# pause_menu.gd(PauseMenu に付ける。Process Mode は When Paused)
extends CanvasLayer

@onready var panel: Panel = %Panel
@onready var resume_button: Button = %ResumeButton

func _ready() -> void:
	panel.hide()
	resume_button.pressed.connect(close)
	%RestartButton.pressed.connect(_on_restart)
	%QuitButton.pressed.connect(get_tree().quit)

func open() -> void:
	for device in Input.get_connected_joypads():
		Input.stop_joy_vibration(device)          # 震えたままメニューへ入らない
	get_tree().paused = true      # ゲーム全体を一時停止する
	panel.show()
	resume_button.grab_focus()                    # この1行が無いと枠が付かない

func close() -> void:
	panel.hide()
	get_tree().paused = false

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("pause") or event.is_action_pressed("ui_cancel"):
		close()                                   # ポーズ中だけ呼ばれる
		get_viewport().set_input_as_handled()

func _on_restart() -> void:
	get_tree().paused = false
	get_tree().reload_current_scene()

開く側と閉じる側を分けたのは、Always(いつでも動く)のノードを作らないためです。Main を Always にすると、Inherit のままの子の Player や、あとから足す敵やタイマーまで、ポーズ中に動き続けます(ポーズとProcess Mode)。ここでは Main はポーズで止まり、ポーズ中に _unhandled_input()(UIが使わなかった入力が届く関数)が呼ばれるのは PauseMenu だけです。

ui_up を読む処理も、フォーカスを動かす処理も、どこにもありません。メニューの操作は、登録したアクションと grab_focus() の1行だけで成り立っています。

③ 触って確かめる

パッドを挿して再生します。持っていない場合は、矢印キーと Enter で同じ画面を確認できます。見るのは3つです。

半分倒すと速さ75、いっぱいで200、トゲに触れると0.25秒震え、ポーズでは最初のボタンに枠が付く4コマ

1つ目は歩きです。スティックを半分ほど倒すと、左上の表示は「速さ 75 / 200」前後になります。軸の値がちょうど 0.5 なら、0.2 を捨てて割り直した 0.375 に 200 を掛けた 75 です。手で倒した量は少しずれるので、数字が前後しても設定は直しません。斜めいっぱいに倒しても表示は 200 までで、指を離すと 0 に戻ります。矢印キーだけだと 0 か 200 のどちらかにしかならないので、途中の値が出ていればスティックの量が届いています。

2つ目はトゲです。(700, 400) のトゲに触れると、0.25 秒だけ震えます。いったん (800, 400) あたりまで離れてから戻ると、もう一度震えます。触れているあいだ震え続けないのは、呼んでいるのが触れた瞬間の1回だけだからです。

3つ目はポーズです。Esc か Start で開くと、キャラはその場で止まり、「続ける」に枠が付きます。スティックを下へ倒すと枠が「はじめから」へ移り、倒し続けてもそれ以上は進みません。上へ倒して「続ける」へ戻し、パッドの下のボタンで押すと閉じます。右のボタンか Esc でも閉じます。 マウスに一度も触らずに、開いて、選んで、閉じられます。

最後に、1台だけ挿しているパッドを抜いてみてください。「パッド:」の表示が「なし」に変わり、スティックを倒していたキャラはその場で止まります。

ポイントは2つです。

  • 歩きは量をそのまま使い、メニューは量を読まない: Input.get_vector() の戻り値を速さに掛けるだけで歩きは完成します。メニュー側はアクションに任せて、スティックの値をコードから触りません
  • 自分で書くのは、フォーカスと振動の後始末だけ: 枠を最初の項目へ乗せる1行と、ポーズや切断で振動を止める処理です。項目を送る仕組みは Godot がすでに持っています

うまくいかないときは

症状原因直し方
かなり倒さないと歩き出さない4.3 以前に作ったアクションで、Deadzone が 0.5 のまま保存されている0.2 へ下げる。4.4 から、新しく作るアクションの既定が 0.2 になった
Deadzone を上げても、指を離すと少し動くInput.get_joy_axis() でスティックを直接読んでいるget_joy_axis() はデッドゾーンを通らない。Input.get_vector() で読む
上に倒すと下へ歩くmove_up に左スティックの下向きを登録している登録し直す。左スティックの縦は、下がプラス
枠は動くのに、Enter でも決定できないメニューのボタンがポーズで止まっている(Process Mode の Inherit を親までたどると、ポーズで止まる Pausable)メニューの Process Mode を When Paused にする。枠の移動は止まっていても動くので気づきにくい

おまけ:先に知っておくと良いこと

  • ボタンの印字は当てにしない: 決定に使うのは「パッドの下のボタン」です。Xbox では A、PlayStation では ×、Nintendo のパッドでは印字が B のボタンがそこに来ます。番号 0 で登録しておけば、どのパッドでも同じ位置のボタンになります
  • トリガーは軸: L2 や R2 はボタンではなく、0 から 1 の量を送ります。使うなら、歩きと同じようにアクションへ登録して、デッドゾーンを通してから読みます
  • 複数のパッドを別々のプレイヤーに割り当てるとき: この記事のコードは、つながっている全部のパッドへ同じ振動を送ります。2人プレイでは、当たったほうのデバイス番号だけへ送ります。割り当ての持ち方は画面分割入門で扱っています
Sponsored

まとめ

  • スティックは量を送る: キーボードは2択、スティックは倒した量。歩きは Input.get_vector() をそのまま速さに使えば、斜めでも速くならない
  • デッドゾーンはアクションに1か所: 歩きは作ったときの 0.2、組み込みの ui_* は 0.5 のまま
  • メニューはアクションに任せる: 書くのは最初の項目への grab_focus() と、ui_accept にパッドのボタンを足すことだけ
  • ポーズは開く側と閉じる側に分ける: 開く処理をポーズで止まる側、閉じる処理を When Paused の側に置く
  • 振動は1回だけ呼ぶ: 長さ 0 はすぐ止まらないので、ポーズや切断では stop_joy_vibration() を呼ぶ

マウスを触らずに先へ進めない画面は、grab_focus() を足す場所です。いま作っているゲームは、マウスを触らずに最初から最後まで遊べますか。

次に読む学習パスの次の記事
入力処理

Godotのタッチ入力入門:指2本で拡大し、ノッチを避け、縦横どちらでも崩さない

この記事を読む
Godot このセクションのノート104