導入:なぜNode3DとMeshInstance3Dが重要なのか
Godot Engineで3Dゲーム開発を始める際、最初に理解すべき最も重要な概念が Node3D と MeshInstance3D です。これらは、3D空間に存在するすべてのオブジェクトの「場所」と「見た目」を定義する、まさに 3Dシーンの構成要素の基盤 と言えます。
2Dゲーム開発では Node2D が中心でしたが、3Dではその役割を Node3D が担います。
Godot 3D空間の基本概念
Godotの3D空間は、現実世界と同じように座標系と単位に基づいて構築されています。
3D座標系:Y-Upの右手系
Godotは、多くの3Dソフトウェアで採用されている 右手系 の座標系を使用しています。特に重要なのは、Y軸が上下方向を示す「Y-Up」 である点です。
| 軸 | 役割 | 正の方向 |
|---|---|---|
| Y軸 | 高さ (Up/Down) | 上 |
| X軸 | 幅 (Left/Right) | 右 |
| Z軸 | 奥行き (Forward/Backward) | 手前(-Zが前方) |
また、Godotの3D空間における単位は、 1ユニットが1メートル に相当します。オブジェクトのサイズや移動距離を考える際の基準となります。
Transform3D:オブジェクトの「状態」そのもの
3D空間内のすべてのオブジェクトは、 Transform3D というデータ構造によって、その空間的な状態(位置、回転、スケール)が定義されます。
Transform3Dは、主に2つの要素で構成されます。
- Basis (基底): オブジェクトの回転とスケールを定義する3x3行列です。
- Origin (原点): オブジェクトの位置を定義するVector3です。
インスペクターでよく見かける position, rotation, scale は、この複雑なTransform3Dを人間が直感的に操作できるように用意された、便利なプロパティに過ぎません。
Node3D: 空間における「存在」の基盤
Node3D は、Godotのすべての3Dノードの基底クラスです。このノードが持つ最も重要な役割は、3D空間におけるTransform3D(位置、回転、スケール)を保持することです。
Node3D自体は、目に見える形も、物理的な当たり判定も持ちません。それは純粋に、空間上の「点」あるいは「座標系」を定義するためだけの存在です。
Node3D の主な役割
- オブジェクトの配置: すべての3Dオブジェクトは、最終的に
Node3Dから継承されるため、必ずTransformを持ちます。 - グループ化と親子関係: これが
Node3Dの最も強力な使い方です。空のNode3Dを親として、複数のノードを子としてまとめることで、それらを一つのユニットとして扱えます。親を動かせば、すべての子オブジェクトが追従します。 - 相対座標の基点: 子ノードの
transformは、常に親ノードのtransformを基準とした相対的な値になります。
MeshInstance3D: オブジェクトに「見た目」を与える
MeshInstance3D は、Node3Dを継承し、そこに視覚的な形状(メッシュ)を描画する機能を追加したノードです。
Node3Dが「どこに存在するか」を決めるのに対し、MeshInstance3Dは「何が、どのような見た目で存在するか」を決定します。
| ノード | 役割 | 主なプロパティ | 概念 |
|---|---|---|---|
Node3D | 場所・姿勢 | transform (position, rotation, scale) | 存在の「どこに」「どうやって」 |
MeshInstance3D | 見た目・形状 | mesh, material_override | 存在の「なにが」「どんなふうに」 |
MeshInstance3Dが画面に何かを描画するには、最低でも mesh プロパティにMeshリソース(例: BoxMesh, SphereMesh、またはインポートした3Dモデル)を割り当てる必要があります。
よくある間違いとベストプラクティス
| よくある間違い | なぜ問題か | ベストプラクティス |
|---|---|---|
MeshInstance3Dを直接動かそうとする | 物理演算と組み合わせる際、見た目だけが移動し、衝突判定が追従しないことがある。 | CharacterBody3DやRigidBody3Dをルートとし、その子としてMeshInstance3Dを配置する。 |
positionでグローバル座標を取得しようとする | positionは親からの相対座標。シーンの階層が深くなると、意図しない座標を取得してしまう。 | グローバル座標が必要な場合は、必ずglobal_transform.originを使用する。 |
| スケールでオブジェクトを反転させる | 負のスケール(例: scale.x = -1)は、物理演算や一部のシェーダーで予期せぬ動作 を引き起こす。 | モデル自体を正しい向きでインポートするか、rotationで向きを調整する。 |
多数のMeshInstance3Dを個別に配置する | 同じメッシュが大量にある場合、描画呼び出し(Draw Call)が増加し、パフォーマンスが低下する。 | MultiMeshInstance3Dの使用を検討する。 |
実践例: 動的なオブジェクト生成と階層構造
ここでは、GDScriptを使ってより実践的なシーンを構築してみましょう。テーマは「回転する惑星とその周りを公転する衛星」です。
@tool
extends Node3D
@export var rotation_speed: float = 1.0
@export var orbit_speed: float = 2.0
@export var orbit_distance: float = 4.0
var planet: MeshInstance3D
var satellite: MeshInstance3D
func _ready():
if not has_node("PlanetPivot"):
_create_celestial_bodies()
planet = $PlanetPivot/Planet
satellite = $PlanetPivot/SatellitePivot/Satellite
func _process(delta):
if not Engine.is_editor_hint():
# 惑星の自転(Y軸を中心に毎秒rotation_speed度回転)
planet.rotate_y(deg_to_rad(rotation_speed * delta))
# 衛星の公転(親のPivotを回転させる)
var satellite_pivot = satellite.get_parent()
satellite_pivot.rotate_y(deg_to_rad(orbit_speed * delta))
func _create_celestial_bodies():
# 1. 惑星の回転軸となるNode3D (Pivot)
var planet_pivot = Node3D.new()
planet_pivot.name = "PlanetPivot"
add_child(planet_pivot)
# 2. 惑星本体 (MeshInstance3D)
planet = MeshInstance3D.new()
planet.name = "Planet"
planet.mesh = SphereMesh.new()
planet.mesh.radius = 1.5
planet.mesh.height = 3.0
var planet_material = StandardMaterial3D.new()
planet_material.albedo_color = Color.DODGER_BLUE
planet.set_surface_override_material(0, planet_material)
planet_pivot.add_child(planet)
# 3. 衛星の公転軸となるNode3D (Pivot)
var satellite_pivot = Node3D.new()
satellite_pivot.name = "SatellitePivot"
planet_pivot.add_child(satellite_pivot)
# 4. 衛星本体 (MeshInstance3D)
satellite = MeshInstance3D.new()
satellite.name = "Satellite"
satellite.mesh = SphereMesh.new()
satellite.mesh.radius = 0.5
satellite.mesh.height = 1.0
var satellite_material = StandardMaterial3D.new()
satellite_material.albedo_color = Color.LIGHT_GRAY
satellite.set_surface_override_material(0, satellite_material)
# 衛星を公転軌道上に配置(親からの相対位置)
satellite.position = Vector3(orbit_distance, 0, 0)
satellite_pivot.add_child(satellite)
コードのポイント
- Pivotノード: オブジェクト自体を直接回転させるのではなく、その親であるPivotを回転させることで、自転と公転を独立して制御できます。
- 親子関係:
satellite_pivotはplanet_pivotの子になっているため、惑星全体を移動させると衛星も追従します。
パフォーマンスに関する注意点
Node3DとMeshInstance3Dは便利ですが、使い方によってはパフォーマンスのボトルネックになる可能性があります。
- 大量のノード: シーンツリーに数千、数万の
Node3Dが存在すると、CPU負荷が増加します。 - Draw Callの増加: 異なるマテリアルを持つ
MeshInstance3Dが増えるほど、コストは増大します。
代替パターン
MultiMeshInstance3D: 同じメッシュとマテリアルを持つオブジェクトを大量に配置する場合(例:森の木々)は、これが最適です。RenderingServer: パフォーマンスを極限まで追求する場合、低レベルAPIを直接使う方法があります。
まとめ
本記事では、Godotの3D開発における最も基本的な要素であるNode3DとMeshInstance3Dについて、その役割、関係性、そして実践的な使い方を解説しました。
| 概念 | キーワード | 役 割 |
|---|---|---|
| 3D空間 | Y-Up右手系, 1ユニット=1m | オブジェクトが存在する環境 |
| Transform3D | basis, origin | 空間内でのオブジェクトの姿勢を定義 |
Node3D | 親子関係, Pivot | 場所と姿勢を定義する骨格 |
MeshInstance3D | mesh, material | 見た目を定義する肉付け役 |
この2つのノードの役割分担を理解することが、Godotの3Dシーン構築をマスターするための鍵です。Node3Dで骨格を作り、MeshInstance3Dで肉付けをする。この原則を忘れずに、ぜひゲーム開発に活かしてください。