Godot Engineで2Dゲームを開発する際、オブジェクトに「物理的な振る舞い」を持たせるために、どのノードを選択するかは非常に重要です。特に、CharacterBody2D、RigidBody2D、StaticBody2Dの3つの主要な物理ボディノードは、それぞれ異なる設計思想と用途を持っています。これらを正しく使い分けることは、ゲームのパフォーマンス、制御のしやすさ、そしてバグの少なさに直結します。
本記事では、これら3種類の物理ボディノードの基本的な定義、特徴、そしてどのようなゲームオブジェクトに適用すべきかを、具体的なコード例を交えて解説します。
3つの物理ボディ:基本概念
これら3つのノードは、すべてCollisionObject2Dを継承し、衝突形状を持つことができますが、その「動き」の主体が全く異なります。
| ノード名 | 制御の主体 | 物理エンジンの影響 | 他のオブジェクトとの相互作用 | 主な用途 |
|---|---|---|---|---|
| StaticBody2D | なし(固定) | 受けない | 衝突をブロックする「壁」になる | 地面、壁、動かない障害物 |
| RigidBody2D | 物理エンジン | 受ける(完全にシミュレーション) | 衝突によって動かされ、また動かす | ボール、箱、物理パズル要素 |
| CharacterBody2D | 開発者のコード | 一部受ける(衝突検知) | 衝突をブロックするが、基本的には動かされない | プレイヤー、敵、移動プラットフォーム |
1. StaticBody2D:不動の基準点
StaticBody2Dは、物理演算の世界における「不動の存在」です。重力や外部からの力によって動くことは一切ありません。その役割は、他のオブジェクトが衝突するための「壁」や「床」を提供することです。
- 主なプロパティ:
constant_linear_velocity,constant_angular_velocity - ポイント: 基本的に動かすべきではありませんが、もし動かす場合は
AnimatableBody2Dの使用を検討してください。これはアニメーションと同期して動く特殊な静的ボディです。
2. RigidBody2D:物理法則の体現者
RigidBody2Dは、物理エンジンの主役です。一度ワールドに配置すれば、あとは重力、摩擦、衝突といった物理法則に従って自律的に動き回ります。開発者はpositionを直接操作するのではなく、力やインパルスを与えることで間接的に制御します。
実践コード例:RigidBody2Dでオブジェクトを投げる
extends RigidBody2D
@export var throw_force: float = 500.0
func throw(direction: Vector2):
# 既存の動きをリセット
linear_velocity = Vector2.ZERO
angular_velocity = 0.0
# 指定された方向に衝撃を与える
apply_central_impulse(direction.normalized() * throw_force)
パフォーマンスに関する注意点
RigidBody2Dは計算コストが高いノードです。数百個のRigidBody2Dが互いに衝突しあうようなシーンでは、パフォーマンスが著しく低下する可能性があります。オブジェクトが静止しているときに物理計算をスキップする スリープ(can_sleep) 設定を有効にすることが非常に重要です。
3. CharacterBody2D:コードによる精密制御
CharacterBody2Dは、開発者がキャラクターの動きを完全にコントロールするために設計された、最も特殊で最も重要なノードです。物理エンジンの衝突検知能力を利用しつつも、動きそのものはmove_and_slide()メソッドを通じてコードで決定します。
実践コード例:より実践的なプレイヤー制御
Godot 4ではvelocityが組み込みプロパティになりました。以下は、ジャンプの先行入力(Coyote Time)とジャンプバッファリングを考慮した、より実践的なコードです。
extends CharacterBody2D
@export var speed: float = 300.0
@export var jump_velocity: float = -450.0
@export var gravity: float = 980.0
# コヨーテタイムとジャンプバッファ用のタイマー
# CoyoteTimer: wait_time = 0.1, one_shot = true
# JumpBufferTimer: wait_time = 0.15, one_shot = true
@onready var coyote_timer: Timer = $CoyoteTimer
@onready var jump_buffer_timer: Timer = $JumpBufferTimer
var was_on_floor: bool = false
func _physics_process(delta: float) -> void:
# 1. 重力の適用
if not is_on_floor():
velocity.y += gravity * delta
# 2. ジャンプ入力のバッファリング
if Input.is_action_just_pressed("jump"):
jump_buffer_timer.start()
# 3. ジャンプの実行
# 地面にいるか、コヨーテタイム中、かつジャンプがバッファされていればジャンプ
if (is_on_floor() or not coyote_timer.is_stopped()) and not jump_buffer_timer.is_stopped():
velocity.y = jump_velocity
jump_buffer_timer.stop()
# 4. 左右移動
var direction: float = Input.get_axis("move_left", "move_right")
velocity.x = direction * speed
# 5. 移動と衝突検知
var was_on_floor_before = is_on_floor()
move_and_slide()
# 6. 地面から離れた瞬間にコヨーテタイマーを開始
if not is_on_floor() and was_on_floor_before:
coyote_timer.start()
このコードは、単に動くだけでなく、プラットフォームゲームで「操作感が良い」と感じさせるための重要なテクニックを実装しています。
よくある間違いとベストプラクティス
| よくある間違い | なぜ問題なのか | ベストプラクティス |
|---|---|---|
プレイヤーキャラクターにRigidBody2Dを使う | 物理演算の慣性や反発で、入力通りの機敏な動きが実現できない。 | プレイヤーや敵には必ずCharacterBody2Dを使う。 |
CharacterBody2Dのpositionを直接変更する | move_and_slide()による衝突検知が機能せず、壁を抜けたりする。 | 必ずvelocityを更新し、move_and_slide()を呼び出す。 |
物理演算を_process(delta)で行う | _physics_process(delta)は固定フレームレートで呼び出され、物理演算の安定性を保証する。 | 物理関連の処理はすべて_physics_process(delta)に記述する。 |
複雑すぎるCollisionShape2Dを使う | 多角形の頂点数が多いほど、衝突判定の計算コストが増大する。 | できるだけシンプルな形状(RectangleShape2D, CircleShape2D)を使 う。 |
適切なノード選択のためのヒント
ノード選択に迷った際は、以下の質問に答えてみてください。
- そのオブジェクトは動きますか?
- No:
StaticBody2Dを選択します。(例:壁、地面)
- No:
- そのオブジェクトの動きを、物理法則(重力、衝突、力)に完全に任せたいですか?
- Yes:
RigidBody2Dを選択します。(例:ボール、箱)
- Yes:
- そのオブジェクトの動きを、プレイヤーの入力やAIロジックで精密に制御したいですか?
- Yes:
CharacterBody2Dを選択します。(例:プレイヤー、敵)
- Yes:
まとめ
Godot Engineの2D物理ボディは、それぞれの役割が明確に分かれています。
- StaticBody2D: 動かない環境要素。
- RigidBody2D: 物理法則に完全に委ねるオブジェクト。
- CharacterBody2D: コードで精密に制御するキャラクター。
これらの違いを理解し、ゲームオブジェクトの性質に合わせて適切なノードを選択することで、よりスムーズで、意図した通りの挙動をするゲーム開発を進めることができるでしょう。