【Godot 4】CornerCorrection (角補正) コンポーネントの作り方

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

脱・初心者!Godot 4 ゲーム開発の「2歩目」

新品価格
¥831から
(2025/12/13 21:39時点)

Godot4ローグライク入門 ~ダンジョン自動生成~

新品価格
¥831から
(2025/12/13 21:44時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

横スクロールアクションを作っていると、プレイヤーが「天井の角」に頭をぶつけて、そこにガチッと引っかかってしまう問題、よくありますよね。
Godot標準の CharacterBody2D でも、move_and_slide() を使うだけだと、こうした「角に引っかかる」挙動は自動では解決してくれません。

よくある対処としては:

  • プレイヤーのコリジョン形状を丸くする(でも足場との判定が甘くなる)
  • マップ側のタイルコリジョンを細かく調整する(レベルデザインが地獄)
  • プレイヤースクリプトに「角補正ロジック」をベタ書きする(継承ツリーが太っていく)

どれも一長一短で、「別キャラを作るたびに同じロジックをコピペ」みたいな事態になりがちです。
そこで今回は、「角補正」だけを独立したコンポーネントとして切り出し、必要なキャラにポン付けできるようにしてみましょう。

【Godot 4】天井の角に引っかからない!「CornerCorrection」コンポーネント

この CornerCorrection コンポーネントは:

  • ジャンプ中に天井の角へ頭をぶつけたとき
  • 少しだけ左右にスライドさせて、
  • 「スルッ」と通り抜けやすくする

という、プラットフォーマーではおなじみの「角補正」機能を提供します。
プレイヤー本体は CharacterBody2D のまま、移動ロジックも今まで通り。
「角補正したいキャラ」にだけ、このコンポーネントをアタッチしてあげる構成です。


フルコード:CornerCorrection.gd


extends Node2D
class_name CornerCorrection
##
## CornerCorrection (角補正) コンポーネント
##
## ・親ノードに CharacterBody2D / CharacterBody3D を想定
## ・天井に頭をぶつけたときに、左右へ微調整して「角抜け」しやすくする
## ・移動処理そのものは親ノード側で行う前提
##

@export var enabled: bool = true:
	set(value):
		enabled = value

## どれくらい横にずらすか(ピクセル)
@export_range(0.0, 64.0, 0.5, "or_greater")
var correction_distance: float = 8.0

## 角補正を試す最大回数(左右合計)
## 小さくすると安全だが、引っかかりやすくなる
@export_range(1, 10, 1)
var max_attempts: int = 2

## 角補正を行う高さ範囲(頭から何ピクセル以内の障害物を対象にするか)
@export_range(0.0, 64.0, 0.5, "or_greater")
var vertical_tolerance: float = 6.0

## 補正を行う条件:
## ・親の速度.y がこの値より小さい(上向きにジャンプしている)ときのみ実行
@export var max_vertical_speed_for_correction: float = -10.0

## RayCast2D で天井ヒットを判定するかどうか
## false の場合は、CharacterBody2D の is_on_ceiling() だけを頼りにする
@export var use_raycast_check: bool = true

## RayCast 用:天井方向に飛ばす長さ
@export_range(0.0, 64.0, 0.5, "or_greater")
var ceiling_check_distance: float = 6.0

## RayCast 用:左右にオフセットした位置からもチェックする
@export_range(0.0, 32.0, 0.5, "or_greater")
var side_offset: float = 4.0

## どのレイヤーを「天井」とみなすか(PhysicsLayers)
@export_flags_2d_physics var ceiling_collision_mask: int = 1

## デバッグ描画(エディタ上で補正方向などを可視化)
@export var debug_draw: bool = false:
	set(value):
		debug_draw = value
		queue_redraw()

var _body: CharacterBody2D = null
var _space_state: PhysicsDirectSpaceState2D

func _ready() -> void:
	# 親ノードが CharacterBody2D であることを期待
	_body = get_parent() as CharacterBody2D
	if _body == null:
		push_warning("CornerCorrection: 親ノードが CharacterBody2D ではありません。このコンポーネントは無効になります。")
		enabled = false
		return

	_space_state = get_world_2d().direct_space_state


func _physics_process(delta: float) -> void:
	if not enabled:
		return
	if _body == null:
		return

	# 上向きに動いていないなら、角補正は不要
	if _body.velocity.y > max_vertical_speed_for_correction:
		return

	# すでに天井に接触しているか、RayCast で天井を検出したときのみ補正を試みる
	if not _is_hitting_ceiling():
		return

	# 実際に左右へずらす
	_try_corner_correction()

	# デバッグ描画更新
	if debug_draw:
		queue_redraw()


func _is_hitting_ceiling() -> bool:
	# CharacterBody2D の is_on_ceiling() をまずチェック
	if _body.is_on_ceiling():
		return true

	if not use_raycast_check:
		return false

	# RayCast で頭上の障害物を判定
	var up_dir := -Vector2.UP
	var origin_global := _body.global_position

	var ray_from := origin_global
	var ray_to := origin_global + up_dir * ceiling_check_distance

	var result := _space_state.intersect_ray(
		PhysicsRayQueryParameters2D.create(ray_from, ray_to, ceiling_collision_mask, [_body])
	)
	if result:
		return true

	# 左右オフセット位置からもチェック
	var left_from := origin_global + Vector2(-side_offset, 0.0)
	var left_to := left_from + up_dir * ceiling_check_distance
	result = _space_state.intersect_ray(
		PhysicsRayQueryParameters2D.create(left_from, left_to, ceiling_collision_mask, [_body])
	)
	if result:
		return true

	var right_from := origin_global + Vector2(side_offset, 0.0)
	var right_to := right_from + up_dir * ceiling_check_distance
	result = _space_state.intersect_ray(
		PhysicsRayQueryParameters2D.create(right_from, right_to, ceiling_collision_mask, [_body])
	)
	return bool(result)


func _try_corner_correction() -> void:
	# すでに頭がめり込んでいる場合など、無理に動かさないための安全策として
	# 少しだけ上方向へオフセットして判定する
	var base_pos := _body.global_position
	var up_offset := Vector2(0, -vertical_tolerance)

	# 左右両方向について、補正を試す
	var directions := [Vector2.LEFT, Vector2.RIGHT]

	for dir in directions:
		for i in range(1, max_attempts + 1):
			var offset_amount := correction_distance * float(i) / float(max_attempts)
			var offset := dir * offset_amount

			var from := base_pos + up_offset
			var to := from + offset

			var result := _space_state.intersect_ray(
				PhysicsRayQueryParameters2D.create(from, to, ceiling_collision_mask, [_body])
			)

			# Ray が何もヒットしなければ、その方向には障害物がないとみなしてスライド
			if not result:
				_body.global_position += offset
				return
	# どの方向にも空きがなかった場合は、何もしない


func _draw() -> void:
	if not debug_draw or _body == null:
		return

	var base_pos := to_local(_body.global_position)
	var up_offset := Vector2(0, -vertical_tolerance)

	# 上方向ライン
	draw_line(base_pos, base_pos + up_offset, Color.YELLOW, 1.0)

	# 左右補正方向ライン
	var left_offset := Vector2.LEFT * correction_distance
	var right_offset := Vector2.RIGHT * correction_distance
	draw_line(base_pos + up_offset, base_pos + up_offset + left_offset, Color.AQUA, 1.0)
	draw_line(base_pos + up_offset, base_pos + up_offset + right_offset, Color.AQUA, 1.0)

	# 原点マーカー
	draw_circle(base_pos, 2.0, Color.RED)

使い方の手順

基本的には「プレイヤー(や敵)」の子ノードとして、このコンポーネントをアタッチするだけです。

手順①:スクリプトを用意する

CornerCorrection.gd をプロジェクトに追加し、上記コードをそのまま保存します。
(ファイル名と class_name CornerCorrection が一致していれば、エディタの「ノードを追加」から検索できます)

手順②:プレイヤーシーンにアタッチ

例として、2Dアクション用のプレイヤーシーン構成はこんな感じにします:

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── CornerCorrection (Node2D)
  • Player ノードに、いつも通り移動ロジック(move_and_slide() など)を書いたスクリプトを付けておきます。
  • その子として CornerCorrection ノードを追加し、CornerCorrection.gd をアタッチします。

プレイヤースクリプト側で特別な呼び出しは不要です。
CornerCorrection は自前の _physics_process() 内で、親の CharacterBody2D を監視して補正を行います。

手順③:パラメータを調整する

Inspector からコンポーネントを選択し、以下のパラメータをゲームに合わせて調整します:

  • correction_distance:どれくらい横にずらすか(8〜16px くらいから試すと良いです)
  • max_attempts:左右それぞれ何段階まで距離を増やして試すか
  • vertical_tolerance:頭のどの範囲を「角」とみなすか。大きくしすぎると不自然にスライドすることがあります。
  • max_vertical_speed_for_correction:どれくらいの上向き速度まで補正を許可するか。あまり高速ジャンプ中に補正すると、ワープ感が出るので注意。
  • ceiling_collision_mask:どのレイヤーのコリジョンを「天井」とみなすか。タイルマップのレイヤーに合わせて設定しましょう。
  • debug_draw:オンにすると、エディタ上で補正方向のラインが見えるので調整に便利です。

手順④:具体的な使用例

例1:プレイヤーキャラ

典型的な 2D プラットフォーマーのプレイヤーに適用する例です。

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 ├── Camera2D
 └── CornerCorrection (Node2D)

この構成にしておくと、マップの天井タイルの角に頭をぶつけたときに、自動的に左右へスライドしてくれるので、
「ジャンプしたのに角でガクッと止められてストレス」という状況をかなり減らせます。

例2:天井にぶら下がる敵

天井近くを移動する敵キャラ(コウモリなど)にも同じコンポーネントを使い回せます:

BatEnemy (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── CornerCorrection (Node2D)

この敵が天井付近を左右に移動しているとき、微妙なタイルの角に引っかかるのを避けられます。
プレイヤーとは違う調整がしたければ、correction_distancemax_attempts を別の値にしておけば OK です。

例3:動く床

「プレイヤーが乗れる動く床」が天井付近を通過するとき、天井との角に引っかかって止まってしまうケースにも応用できます。

MovingPlatform (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── CornerCorrection (Node2D)

この場合は、max_vertical_speed_for_correction を 0 以上にして、
「上方向に移動しているときだけ角補正をする」といった使い方もできます。


メリットと応用

この CornerCorrection コンポーネントを使う最大のメリットは、「プレイヤーの移動ロジック」と「角補正ロジック」を完全に分離できることです。

  • プレイヤーのスクリプトは「入力 → 速度計算 → move_and_slide()」に集中できる
  • 角補正の有無は、ノードを付けるかどうかで切り替えられる
  • プレイヤー以外のキャラ(敵、動く床、ギミック)にも、そのままポン付けできる
  • シーン構造は浅いまま、機能ごとにコンポーネントを足していくスタイルにできる

「継承ツリーにロジックを全部詰め込む」のではなく、「合成(Composition)で機能を組み合わせる」方向に舵を切れるのがいいですね。
Godot はノードベースなので、こうしたコンポーネント指向の設計と相性がとても良いです。

レベルデザインの観点でも:

  • マップ側のタイルコリジョンを「完璧な角」にしなくても、ある程度ラフで済む
  • プレイヤーの「引っかかり」を減らすことで、操作感がかなり良くなる
  • テスト中に「この敵だけ角補正オフにしたい」といった調整も簡単

と、かなり恩恵があります。

改造案:一方向だけに角補正を許可する

たとえば「右方向にだけスライドさせたい」「プレイヤーの向いている方向にだけ補正したい」といったケースもあります。
その場合は、次のようなメソッドを追加すると簡単にカスタマイズできます。


## プレイヤーの「向き」に応じて補正方向を制限する例
@export var use_facing_direction: bool = false
@export var facing_right: bool = true

func set_facing_right(value: bool) -> void:
	facing_right = value

func _get_correction_directions() -> Array:
	if not use_facing_direction:
		# デフォルトは左右両方を試す
		return [Vector2.LEFT, Vector2.RIGHT]

	# 向きに応じて片側だけを返す
	return [Vector2.RIGHT] if facing_right else [Vector2.LEFT]

そして、_try_corner_correction() 内の


var directions := [Vector2.LEFT, Vector2.RIGHT]


var directions := _get_correction_directions()

に差し替えれば、プレイヤーの向きに応じた「片側のみ角補正」ができるようになります。
このように、コンポーネントとして独立していると、機能追加や改造もかなりやりやすいですね。

ぜひ、自分のプロジェクト用にパラメータやロジックをちょっとずつ変えながら、「気持ちいいジャンプ」の感触を追求してみてください。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

脱・初心者!Godot 4 ゲーム開発の「2歩目」

新品価格
¥831から
(2025/12/13 21:39時点)

Godot4ローグライク入門 ~ダンジョン自動生成~

新品価格
¥831から
(2025/12/13 21:44時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

URLをコピーしました!