概要
Godot Engineでゲーム制作を始めるにあたり、誰もが最初に学ぶべき最も重要な概念が「シーン (Scene)」と「ノード (Node)」です。この2つの関係性を理解することが、Godotを使いこなすための鍵となります。
ノードとは? - ゲームを構成する「部品」
ノードは、Godotにおけるゲームの基本的な構成要素、つまり「部品」です。それぞれのノードは、特定の機能を持っています。
- Sprite2D: 画像を表示する
- AudioStreamPlayer: 音を再生する
- CollisionShape2D: 物理的な衝突範囲(当たり判定)を定義する
- Button: クリック可能なUIボタン
- Camera2D: 2Dゲーム内のカメラ
これらはごく一部であり、Godotには何百もの特殊なノードが用意されています。これらの「部品」を組み合わせることで、ゲーム内のあらゆるオブジェクトやシステムを構築していきます。
シーンと は? - 部品を組み立てた「設計図」
シーンは、これらのノードを階層的なツリー構造で組み合わせたものです。1つの「ルートノード」と、それに連なる複数の「子ノード」で構成されます。このノードの集合体が「シーン」であり、ゲーム内のキャラクター、ステージ、UI画面といった、あらゆる要素を表現します。
具体例:2Dゲームの「プレイヤー」シーン
例えば、「プレイヤー」というシーンを作る場合、以下のようなノードの組み合わせが考えられます。
CharacterBody2D (ルートノード: 物理挙動と移動を管理)
├── Sprite2D (子ノード: プレイヤーの見た目を表示)
├── CollisionShape2D (子ノード: 当たり判定の形状を定義)
└── Camera2D (子ノード: プレイヤーを追従するカメラ)
このように、複数のノード(部品)を組み合わせて一つのまとまりにしたものが「シーン」です。作成したシーンは、.tscnという拡張子のファイルとして保存されます。
シーンのインスタンス化 - シーンを「入れ子」にする
Godotの真価は、作成したシーンを、別のシーンの部品(ノード)として再利用できる**「シーンのインスタンス化」**にあります。これは、単なるコピー&ペーストではなく、元のシーン(設計図)への参照を維持する機能です。
具体例:「ステージ」シーンに「プレイヤー」を配置する
先ほど作成した「プレイヤー」シーン(player.tscn)を、「ステージ1」という別のシーンに配置してみましょう。
Node2D (ステージ1のルート)
├── TileMap (地形や背景)
├── Player (player.tscnをインスタンス化)
├── Enemy1 (enemy.tscnをインスタンス化)
└── Enemy2 (enemy.tscnをインスタンス化)
インスタンス化のメリット
- 再利用性: 一度作った「プレイヤー」シーンを、「ステージ2」「ボス戦」など、様々な場所で簡単に使い回せます。
- 保守性: プレイヤーの移動速度を変更したい場合、
player.tscnを一度編集するだけで、全てのプレイヤーインスタンスに修正が反映されます。 - カプセル化: 「ステージ1」シーンは、プレイヤーがどのようなノードで構成されているかという内部構造を意識する必要がありません。
よくある間違いとベストプラクティス
| よくある間違い | ベストプラクティス |
|---|---|
| 巨大なモノリシックシーン | 小さな専門シーンに分割する。プレイヤー、敵、弾、UI要素など、再利用可能な単位で積極的にシーン化し、それらを組み合わせて大きなシーンを構築します。 |
| 脆弱なノードパスへの依存 | シグナルとメソッド呼び出しで疎結合を保つ。get_node('../../Player/Camera2D')のような構造に依存したパスは、変更に非常に弱いです。 |
_processでの過剰なポーリング | イベント駆動を意識する。入力は_input()や_unhandled_input()で、物理的な衝突はボディのシグナルで検知するなど、必要な時にだけコードが実行されるようにします。 |
get_node()の多用 | @onreadyでノード参照をキャッシュする。_ready関数が呼ばれる前にノード参照を保持しておくことで、コードをクリーンに保ちます。 |
パフォーマンスを意識したシーン設計
プロジェクトが大規模になるにつれて、パフォーマンスは無視できない問題になります。
- ノード数: シーン内のノード、特に毎フレーム処理を行うノードの数は、CPU負荷に直結します。
get_node()の呼び出しコスト:_processのようなループ内でget_node()を呼び出すのは避けるべきです。@onreadyはこの問題を解決するための最もシンプルな解決策です。- 物理演算のコスト: 当たり判定が必要ない装飾的なオブジェクトにまで物理ボディを適用しないように注意しましょう。
実践的なコード例
1. @onreadyによるノード参照のキャッシュ
# PlayerAnimation.gd
@onready var sprite: Sprite2D = $Sprite2D
@onready var anim_player: AnimationPlayer = $AnimationPlayer
func _physics_process(delta):
var velocity = get_parent().velocity
if velocity.length() > 0:
anim_player.play("run")
else:
anim_player.play("idle")
if velocity.x != 0:
sprite.flip_h = velocity.x < 0
2. シーンの動的なインスタンス化
# Player.gd
const BULLET_SCENE = preload("res://bullet.tscn")
@onready var muzzle = $Muzzle
func _process(delta):
if Input.is_action_just_pressed("shoot"):
var bullet = BULLET_SCENE.instantiate()
# 弾をメインシーンの子として追加(プレイヤーの子にしない)
get_tree().current_scene.add_child(bullet)
bullet.global_position = muzzle.global_position
preloadでシーンファイルを読み込んでおき、必要なタイミングでinstantiate()を呼び出します。
まとめ
- ノード: ゲームを構成する最小単位の「部品」。それぞれが特定の機能を持つ
- シーン: ノードをツリー状に組み立てた「設計図」。キャラクターやステージなど、ゲームの具体的な要素を構成する
- インスタンス化: 作成したシーンを、別のシーンの「部品」として再利用すること
Godotでのゲーム開発は、「ノード」を組み合わせて「シーン」を作り、さらにその「シーン」を組み合わせてより大きな「シーン(ゲーム全体)」を構築していく、という流れになります。