【Godot 4】PlatformDropper (床抜け) コンポーネントの作り方

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時点)

横スクロールアクションを作っていると、One Way Collision(片方向の床)から下にすり抜ける動きって欲しくなりますよね。Godot 4 だと TileMap や StaticBody2D に「One Way Collision」を設定すれば、上から乗るだけなら簡単です。

でも、

  • 「↓+ジャンプ」で床をすり抜けたい
  • プレイヤーだけ特定の床をすり抜けたい
  • 敵はすり抜けないで欲しい

といったちょっとリッチな挙動を実装しようとすると、だんだん面倒になってきます。

  • プレイヤーのスクリプトが巨大化して、「床抜け」ロジックが混ざる
  • 床側のスクリプトに「誰が抜けるか」ロジックを書き始めてしまう
  • 継承ベースで PlayerFloorDropper, EnemyFloorDropper みたいなクラスが増える

こうなると、「床抜け機能」を別キャラにも使い回したいときに、また同じようなコードを書き直すハメになります。まさに「継承地獄」「巨大 Player.gd 問題」ですね。

そこで今回は、どんなキャラにもポン付けできるコンポーネントとして、

「PlatformDropper」コンポーネント

を用意して、「↓+ジャンプで OneWayPlatform を一時的に無効化する」機能を、きれいに分離してしまいましょう。


【Godot 4】↓+ジャンプですり抜け!「PlatformDropper」コンポーネント

このコンポーネントは、CharacterBody2D / CharacterBody3D などの「動く側」にアタッチするだけで動作することを想定しています。

  • 「↓+ジャンプ」を検知
  • 今立っている One Way Platform を一時的に無効化
  • 一定時間後に自動で復帰

という、床抜けに必要な処理を全部まとめています。


フルコード(GDScript / 2D 用)


extends Node
class_name PlatformDropper
"""
PlatformDropper コンポーネント
--------------------------------
「↓+ジャンプ」で One Way Platform を一時的にすり抜けるためのコンポーネント。

想定:
- 親ノードは CharacterBody2D(または同等の「動くキャラ」)
- 親のスクリプト側で通常の移動・ジャンプ処理を行う
- このコンポーネントは「床抜けの制御」だけを担当する

使い方(概要):
1. プレイヤーシーンの直下に、この PlatformDropper ノードを追加
2. InputMap に "move_down" と "jump" を設定
3. 親の CharacterBody2D が One Way Platform の上に立っている状態で
   「↓+ジャンプ」を押すと、床をすり抜ける
"""

@export_category("Input")
## 下入力に使うアクション名(InputMap で定義しておく)
@export var down_action: StringName = "move_down"

## ジャンプ入力に使うアクション名
@export var jump_action: StringName = "jump"

@export_category("Drop Settings")
## 「↓+ジャンプ」を判定する許容時間(秒)
## 例: 0.15 なら「ほぼ同時押し」でなくてもOK
@export_range(0.0, 0.5, 0.01)
var combo_time_window: float = 0.15

## 床を無効化しておく時間(秒)
## この時間中は、One Way Platform をすり抜け続ける
@export_range(0.05, 1.0, 0.01)
var drop_duration: float = 0.25

## 対象とする「床」のコリジョンレイヤー
## One Way Platform を特定のレイヤーにまとめておくと管理しやすい
@export_flags_2d_physics var platform_collision_layer: int = 1

@export_category("Debug")
## デバッグ用にログを出すかどうか
@export var debug_log: bool = false


# 内部状態管理用
var _body: CharacterBody2D
var _is_dropping: bool = false
var _drop_timer: float = 0.0

# 「↓」が押された時間
var _last_down_press_time: float = -100.0
# 「ジャンプ」が押された時間
var _last_jump_press_time: float = -100.0


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

	# プロパティの初期値チェック
	if platform_collision_layer == 0:
		push_warning("PlatformDropper: platform_collision_layer が 0 です。One Way Platform のレイヤーを設定してください。")


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

	# 入力の監視
	_process_input_timestamps()

	# 床抜け中のタイマー処理
	if _is_dropping:
		_drop_timer -= delta
		if _drop_timer <= 0.0:
			_end_drop_through()


func _process_input_timestamps() -> void:
	# 下入力の押下タイミングを記録
	if Input.is_action_just_pressed(down_action):
		_last_down_press_time = Time.get_ticks_msec() / 1000.0

	# ジャンプ入力の押下タイミングを記録
	if Input.is_action_just_pressed(jump_action):
		_last_jump_press_time = Time.get_ticks_msec() / 1000.0
		_try_start_drop_through()


func _try_start_drop_through() -> void:
	# すでに床抜け中なら、二重に開始しない
	if _is_dropping:
		return

	# 親が床に接地していないなら、床抜けは開始しない
	# (空中で「↓+ジャンプ」しても無視)
	if not _body.is_on_floor():
		return

	# 「↓」と「ジャンプ」が combo_time_window 秒以内に押されているか判定
	var now := Time.get_ticks_msec() / 1000.0
	var down_diff := absf(now - _last_down_press_time)
	var jump_diff := absf(now - _last_jump_press_time)

	var is_combo_ok := (down_diff <= combo_time_window) and (jump_diff <= combo_time_window)
	if not is_combo_ok:
		return

	# 実際に床抜けを開始
	_start_drop_through()


func _start_drop_through() -> void:
	_is_dropping = true
	_drop_timer = drop_duration

	# コリジョンレイヤーマスクから「床レイヤー」を一時的に外す
	# これにより、One Way Platform との衝突が無効化される
	var original_mask := _body.collision_mask
	var new_mask := original_mask & ~platform_collision_layer
	_body.collision_mask = new_mask

	if debug_log:
		print("PlatformDropper: start drop-through. mask ", original_mask, " -> ", new_mask)

	# 親側のジャンプ処理と合わせるため、
	# ここで少しだけ下方向に速度を与えることも可能。
	# (必要なければコメントアウトのままでOK)
	# _body.velocity.y = max(_body.velocity.y, 50.0)


func _end_drop_through() -> void:
	_is_dropping = false

	# 床レイヤーをマスクに戻す
	# 注意:他のスクリプトが collision_mask を書き換えている場合は
	#       ここで衝突レイヤーを完全に復元したいなら、別途「元の値」を保存しておく必要があります。
	_body.collision_mask |= platform_collision_layer

	if debug_log:
		print("PlatformDropper: end drop-through. mask now ", _body.collision_mask)

このスクリプトは 単独で .gd ファイルとして保存して OK です。
`class_name PlatformDropper` を定義しているので、他のシーンからも簡単にアタッチできます。


使い方の手順

① InputMap を設定する

まず、プロジェクト設定で入力を定義しておきましょう。

  1. メニューから 「Project > Project Settings… > Input Map」 を開く
  2. 以下のアクションを追加
    • move_down …… カーソル下キー / S キー などを割り当て
    • jump …… スペース / Z キー などを割り当て

もし既に別名で使っている場合は、スクリプト側の down_action, jump_action を合わせて変更してください。


② One Way Platform 側の設定

床側は、例えばこんな構成を想定しています。

OneWayPlatform (StaticBody2D or TileMap)
 └── CollisionShape2D / TileSet Collision
  • CollisionShape2D(または TileSet の Collision)に 「One Way Collision」 を有効化
  • 床専用の物理レイヤーを 1 つ決めておく(例: Layer 2 を「Platform」とする)

そして、PlatformDropper 側の platform_collision_layer に、そのレイヤーを設定します。
(インスペクタ上で「Layer 2」にチェックを入れる)


③ プレイヤーシーンに PlatformDropper をアタッチする

プレイヤーのノード構成例はこんな感じです:

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── PlatformDropper (Node)
  1. Player シーンを開く
  2. Player(CharacterBody2D)を選択し、右クリック → Add Child Node…
  3. 検索バーに「PlatformDropper」と打つと、クラス名からノードが出てくるので追加
  4. インスペクタから
    • down_action …… move_down
    • jump_action …… jump
    • platform_collision_layer …… One Way Platform のレイヤー
    • combo_time_window, drop_duration …… お好みで調整

プレイヤー側のスクリプトは、いつも通りのジャンプ処理を書いておけば OK です。
PlatformDropper は「↓+ジャンプ」が押されたタイミングで、衝突マスクだけをいい感じに切り替えてくれるイメージです。


④ 敵や動く床にも使い回す

このコンポーネントは「親が CharacterBody2D であれば誰でも OK」なので、例えば:

  • 敵キャラがプレイヤーを追いかけるときに、床をすり抜けてショートカットさせたい
  • 動く床(キャラ扱い)が、特定のタイミングで下に落ちるギミックを作りたい

といったケースでも、そのノードに PlatformDropper をアタッチして、
AI やスクリプト側から Input.action_press() を呼んで擬似的に「↓+ジャンプ」を押させる、という使い方もできます。

敵キャラのシーン構成例:

Enemy (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── PlatformDropper (Node)

メリットと応用

PlatformDropper をコンポーネントとして分離しておくと、こんなメリットがあります。

  • Player.gd がスリムになる
    床抜けのロジック(タイミング判定、マスク切り替え、タイマー管理など)を全部外出しできるので、
    プレイヤーのコードは「移動」「攻撃」「アニメーション」など本質的な処理に集中できます。
  • 敵・NPC・別プレイヤーにもそのまま使える
    「床抜けできるキャラ」が増えても、PlatformDropper をアタッチして Input を切り替えるだけ。
    継承ツリーを増やさずに、合成(Composition)で機能を足していけるのが気持ちいいですね。
  • シーン構造がフラットで見通しが良い
    深いノード階層に「FloorDropper」「JumpController」などを継承で積み上げる代わりに、
    1 キャラ = 1 CharacterBody2D + 複数コンポーネント というシンプルな構成にできます。
  • レベルデザイン側の自由度アップ
    「このレベルでは床抜け禁止」「この敵だけ床をすり抜ける」など、
    ノード単位で PlatformDropper を付け外しするだけで挙動をコントロールできます。

改造案:スクリプトから強制的に床抜けさせる

ゲームの演出で、「このトリガーに入ったら強制的に床をすり抜ける」みたいなギミックを作りたいときは、
PlatformDropper に 外部から呼べる API を 1 つ足しておくと便利です。


func force_drop_through(duration: float = -1.0) -> void:
	"""
	外部スクリプトから強制的に床抜けを開始させる API。
	例:
	  - トリガーゾーンに入ったときに呼び出す
	  - 敵 AI が特定条件で床をすり抜けるときに呼び出す
	"""
	if duration > 0.0:
		drop_duration = duration
	_start_drop_through()

これで、例えばトリガーゾーン側から:


func _on_body_entered(body: Node2D) -> void:
	var dropper := body.get_node_or_null("PlatformDropper") as PlatformDropper
	if dropper:
		dropper.force_drop_through(0.5)

のように呼び出せば、入力に関係なく床抜けを実行できます。
こうやってコンポーネントに小さな API を足していくと、ゲーム全体の挙動を「組み合わせ」で作れるようになっていきますね。


継承ベースで「PlayerFloorDropper」「EnemyFloorDropper」みたいなクラスを増やしていくよりも、
今回のように PlatformDropper コンポーネントとして切り出しておくと、後からの拡張・再利用が圧倒的にラクになります。
ぜひ、自分のプロジェクトでも「床抜け」機能を合成スタイルで導入してみてください。

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をコピーしました!