3Dでも2Dでも、キャラを動かすゲームを作っていると「常に前進し続けるオートラン」を入れたくなることってありますよね。
Godot 4 だと、CharacterBody2DCharacterBody3D を継承したプレイヤースクリプトの中に「移動」「ジャンプ」「入力」「オートランフラグ」など、どんどんロジックが積みあがっていきがちです。

結果として:

  • プレイヤースクリプトが 500 行、1000 行とモノリシックになっていく
  • 敵にもオートランを入れたいときにコピペ地獄になる
  • 「オートラン機能だけ差し替えたい」のに、プレイヤー本体のコードを編集せざるを得ない

こういう「状態管理系」の機能こそ、継承ではなくコンポーネントとして分離したいところです。
そこで今回は、「特定のキーを押すと、前進入力を入れっぱなしにしてくれる」AutoRun コンポーネントを作って、プレイヤーや敵などに後付けできるようにしてみましょう。

【Godot 4】前進入力を自動ホールド!「AutoRun」コンポーネント

今回のコンポーネントのゴールはシンプルです:

  • 指定したキーもしくはアクションを押すと、オートラン ON/OFF を切り替え
  • 「前進入力」を常に 1.0(あるいは -1.0)にしてくれる
  • プレイヤー側は「オートラン中かどうか」「前進入力の値」を問い合わせるだけ

つまり、「入力の源泉」だけをコンポーネントに任せるイメージです。
移動そのもの(速度計算やスライド移動など)は、プレイヤー側のスクリプトに任せておきます。

GDScript フルコード


extends Node
class_name AutoRun
##
## AutoRun コンポーネント
## - 特定のキー/アクションで「オートランの ON/OFF」を切り替える
## - 「前進入力」の値を提供する(プレイヤー側はこれを読んで移動に使う)
##

@export_group("基本設定")
## オートランのトグルに使う入力アクション名
## - InputMap に同名のアクションを必ず定義してください
## - 例: "auto_run_toggle" を定義して、キーボードの R キーを割り当てるなど
@export var toggle_action_name: StringName = &"auto_run_toggle"

## ゲーム開始時にオートランを有効にするかどうか
@export var start_enabled: bool = false

## 「前進」の入力値(1.0 で前進、-1.0 で後退)
## - 2D 横スクロールなら 1.0 を右、-1.0 を左などに使えます
## - 3D なら「前方向」の入力として 1.0 を使うのが一般的
@export_range(-1.0, 1.0, 0.1)
@export var forward_value: float = 1.0


@export_group("入力ブレンド設定")
## プレイヤーの生入力とオートラン入力をどうブレンドするか
## - true  : オートラン中でもプレイヤー入力を上書きできる(例: 後ろに下がれる)
## - false : オートラン中は常に forward_value 固定(プレイヤー入力無視)
@export var allow_player_override: bool = true

## 入力アクション名(前進・後退)
## - プレイヤー側で Input.get_action_strength と揃えるための補助
## - 必須ではありませんが、コンポーネント単体で「最終的な入力値」を出したい場合に使います
@export var forward_action_name: StringName = &"move_forward"
@export var backward_action_name: StringName = &"move_backward"


## 現在オートランが有効かどうか
var is_auto_running: bool = false:
	set(value):
		if is_auto_running == value:
			return
		is_auto_running = value
		_auto_run_toggled.emit(is_auto_running)

## 内部用: 前フレームでトグルアクションが押されていたか
var _was_toggle_pressed: bool = false

## シグナル: オートラン状態が切り替わったときに通知
signal auto_run_toggled(enabled: bool):
	## 外部に公開するためのシグナル
	pass

# 内部実装用の別名(エディタで見せない)
@warning_ignore("unused_signal")
signal _auto_run_toggled(enabled: bool)


func _ready() -> void:
	# エディタ上でシグナル名を分けたい場合は、ここで接続しなおしてもOK
	# 今回は auto_run_toggled をそのまま使います
	is_auto_running = start_enabled


func _process(delta: float) -> void:
	# トグルアクションが押された瞬間だけを検出したいので、
	# Input.is_action_just_pressed でもよいですが、
	# カスタムな処理をしたい場合に備えて自前でエッジ検出しています。
	var pressed := Input.is_action_pressed(toggle_action_name)

	# 立ち上がりエッジ検出(前フレームは離されていて、今フレーム押された)
	if pressed and not _was_toggle_pressed:
		is_auto_running = not is_auto_running

	_was_toggle_pressed = pressed


## 公開 API: 「前進入力の最終値」を取得する
##
## - 2D 横スクロールなら X 方向の入力
## - 3D なら「カメラ前方向」への入力スカラーとして使う想定
##
## - プレイヤー生入力(InputMap)とオートラン状態をブレンドして返します。
##
func get_forward_input() -> float:
	var player_input := _get_player_forward_input()

	if not is_auto_running:
		# オートラン無効時は、純粋にプレイヤー入力だけ返す
		return player_input

	if allow_player_override:
		# オートラン中でもプレイヤー入力を許可する場合:
		# - プレイヤーが逆方向に強く入力したら、優先してそちらを使う
		#   (例: forward_value = 1.0 でオートラン中に、後退キーを押したら後退)
		if abs(player_input) > abs(forward_value):
			return player_input
		# それ以外はオートランの値を採用
		return forward_value
	else:
		# オートラン優先: プレイヤー入力は完全に無視
		return forward_value


## 公開 API: オートランを強制的に ON/OFF する
func set_auto_run_enabled(enabled: bool) -> void:
	is_auto_running = enabled


## 公開 API: オートランをトグルする(メニューUIなどから呼びたい場合用)
func toggle_auto_run() -> void:
	is_auto_running = not is_auto_running


## 内部: InputMap からプレイヤーの前進/後退入力を取得
func _get_player_forward_input() -> float:
	var forward_strength := 0.0
	var backward_strength := 0.0

	if forward_action_name != StringName():
		forward_strength = Input.get_action_strength(forward_action_name)
	if backward_action_name != StringName():
		backward_strength = Input.get_action_strength(backward_action_name)

	# 前進を +、後退を - として合成
	return forward_strength - backward_strength

使い方の手順

ここからは、具体的にプレイヤーに組み込む手順を見ていきましょう。
例として 2D 横スクロールのプレイヤーを想定しますが、3D でも考え方は同じです。

手順①:AutoRun.gd を用意してプロジェクトに追加

  1. 上記のコードを AutoRun.gd として保存します。
  2. プロジェクト設定 > エディタ > クラス で再スキャンするか、エディタを再起動すると AutoRun がノード追加ダイアログに出てくるようになります(class_name AutoRun のおかげですね)。

手順②:InputMap にアクションを登録

プロジェクト設定 > Input Map で、以下のようなアクションを設定します:

  • move_right / move_left(2D 横スクロールの場合)
  • auto_run_toggle(オートランの ON/OFF 用)

例えば:

  • move_right : D キー / 右矢印キー
  • move_left : A キー / 左矢印キー
  • auto_run_toggle : R キー

手順③:プレイヤーシーンに AutoRun ノードを追加

プレイヤーシーンの構成例:

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── AutoRun (AutoRun)

プレイヤー本体には、最小限の移動処理だけを書きます。


# Player.gd
extends CharacterBody2D

@export var move_speed: float = 200.0

var auto_run: AutoRun


func _ready() -> void:
	# 子ノードから AutoRun コンポーネントを取得
	auto_run = $AutoRun


func _physics_process(delta: float) -> void:
	# AutoRun から「前進入力の最終値」を取得
	# 横スクロールなので X 方向の入力とみなす
	var x_input := auto_run.get_forward_input()

	# もしオートランなしで普通の左右入力も混ぜたい場合は、
	# AutoRun 側の allow_player_override を true にしておけば OK
	# (AutoRun 内部で InputMap の入力とブレンドしてくれます)

	velocity.x = x_input * move_speed

	# 簡単な重力処理(任意)
	if not is_on_floor():
		velocity.y += 800.0 * delta

	move_and_slide()

AutoRun ノード側のインスペクタ設定例:

  • Toggle Action Name : auto_run_toggle
  • Start Enabled : false(ゲーム開始時はオートラン OFF)
  • Forward Value : 1.0(右方向に自動前進)
  • Allow Player Override : true(オートラン中でも後ろに下がれる)
  • Forward Action Name : move_right
  • Backward Action Name : move_left

手順④:3D プレイヤーや動く床など、別シーンでも再利用

3D プレイヤーの例:

Player3D (CharacterBody3D)
 ├── Camera3D
 ├── CollisionShape3D
 └── AutoRun (AutoRun)

3D の場合は、get_forward_input() で得た値を「カメラの前方向ベクトル」に掛け合わせるだけです。


# Player3D.gd
extends CharacterBody3D

@export var move_speed: float = 6.0

var auto_run: AutoRun
var cam: Camera3D


func _ready() -> void:
	auto_run = $AutoRun
	cam = $Camera3D


func _physics_process(delta: float) -> void:
	var input_forward := auto_run.get_forward_input()

	# カメラの前方向(XZ 平面に投影)
	var forward_dir := -cam.global_transform.basis.z
	forward_dir.y = 0.0
	forward_dir = forward_dir.normalized()

	var desired_velocity := forward_dir * input_forward * move_speed
	velocity.x = desired_velocity.x
	velocity.z = desired_velocity.z

	# ここにジャンプや重力処理などを追加
	if not is_on_floor():
		velocity.y -= 20.0 * delta

	move_and_slide()

同じ AutoRun コンポーネントを、例えば「常に前に進む動く床」にもアタッチできます:

MovingPlatform (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── AutoRun (AutoRun)

# MovingPlatform.gd
extends CharacterBody2D

@export var move_speed: float = 80.0

var auto_run: AutoRun


func _ready() -> void:
	auto_run = $AutoRun
	# 動く床は常にオートラン ON にしておく
	auto_run.set_auto_run_enabled(true)


func _physics_process(delta: float) -> void:
	var dir := auto_run.get_forward_input()
	velocity.x = dir * move_speed
	move_and_slide()

プレイヤーと同じコンポーネントを、そのまま敵やギミックに再利用できるのがコンポーネント指向の気持ちいいところですね。


メリットと応用

この AutoRun コンポーネントを使うことで、次のようなメリットがあります。

  • プレイヤーコードがスリムになる
    「オートランの状態管理」「トグル入力の検出」といった雑務がプレイヤーから消え、
    auto_run.get_forward_input() を読むだけで済みます。
  • シーン構造がフラットで見通しが良い
    「オートラン用のプレイヤー継承クラス」を増やす必要がなく、
    AutoRun ノードをアタッチするかどうかで機能を切り替えられます。
  • 他のキャラやギミックにもそのまま使い回せる
    敵、動く床、自動で進むカメラなど、「前進入力が欲しいもの」なら何にでも付けられます。
  • テストやデバッグがしやすい
    AutoRun のみを単体でシーンに置いて、is_auto_running やシグナルを確認するなど、
    機能単位で切り分けて検証できます。

「継承で PlayerAutoRun.gd を作る」のではなく、「AutoRun コンポーネントをプレイヤーに付ける」という構成にすることで、機能の組み合わせがしやすくなります。
たとえば:

  • Player + AutoRun + DashComponent + HealthComponent
  • Enemy + AutoRun + AIComponent + HealthComponent

といった形で、レゴブロック感覚で機能を合成できますね。

改造案:UI ボタンからオートランを切り替える

最後に、ちょっとした改造案として「UI ボタンからオートランを切り替える」例を載せておきます。
メニューやモバイル UI からオートランを操作したいときに便利です。


# AutoRunToggleButton.gd
extends Button

@export var auto_run_path: NodePath

var auto_run: AutoRun


func _ready() -> void:
	auto_run = get_node_or_null(auto_run_path)
	if auto_run:
		# 初期表示を状態に合わせる
		text = auto_run.is_auto_running ? "AutoRun: ON" : "AutoRun: OFF"
		auto_run.auto_run_toggled.connect(_on_auto_run_toggled)

	pressed.connect(_on_pressed)


func _on_pressed() -> void:
	if not auto_run:
		return
	auto_run.toggle_auto_run()


func _on_auto_run_toggled(enabled: bool) -> void:
	text = enabled ? "AutoRun: ON" : "AutoRun: OFF"

このように、AutoRun の「状態」と「トグル操作」を完全にコンポーネント側に閉じ込めておけば、
プレイヤー本体は「入力値を読むだけ」、UI は「トグル API を呼ぶだけ」と役割分担がきれいに分かれます。
継承より合成、どんどん進めていきましょう。