【Godot】Node3DとMeshInstance3Dで学ぶGodotの3D空間基本概念

作成: 2025-12-08最終更新: 2025-12-16

Godot Engineで3Dゲーム開発を始める際に理解すべきNode3DとMeshInstance3Dの役割、3D座標系、Transform3D、親子関係の概念を、実践的なコード例とともに解説します。

導入:なぜNode3DとMeshInstance3Dが重要なのか

Godot Engineで3Dゲーム開発を始める際、最初に理解すべき最も重要な概念が Node3DMeshInstance3D です。これらは、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つの要素で構成されます。

  1. Basis (基底): オブジェクトの回転スケールを定義する3x3行列です。
  2. Origin (原点): オブジェクトの位置を定義するVector3です。

インスペクターでよく見かける position, rotation, scale は、この複雑なTransform3Dを人間が直感的に操作できるように用意された、便利なプロパティに過ぎません。


Node3D: 空間における「存在」の基盤

Node3D は、Godotのすべての3Dノードの基底クラスです。このノードが持つ最も重要な役割は、3D空間におけるTransform3D(位置、回転、スケール)を保持することです。

Node3D自体は、目に見える形も、物理的な当たり判定も持ちません。それは純粋に、空間上の「点」あるいは「座標系」を定義するためだけの存在です。

Node3Dの主な役割

  1. オブジェクトの配置: すべての3Dオブジェクトは、最終的にNode3Dから継承されるため、必ずTransformを持ちます。
  2. グループ化と親子関係: これがNode3Dの最も強力な使い方です。空のNode3Dを親として、複数のノードを子としてまとめることで、それらを一つのユニットとして扱えます。親を動かせば、すべての子オブジェクトが追従します。
  3. 相対座標の基点: 子ノードのtransformは、常に親ノードのtransformを基準とした相対的な値になります。

MeshInstance3D: オブジェクトに「見た目」を与える

MeshInstance3D は、Node3Dを継承し、そこに視覚的な形状(メッシュ)を描画する機能を追加したノードです。

Node3Dが「どこに存在するか」を決めるのに対し、MeshInstance3Dは「何が、どのような見た目で存在するか」を決定します。

ノード役割主なプロパティ概念
Node3D場所・姿勢transform (position, rotation, scale)存在の「どこに」「どうやって」
MeshInstance3D見た目・形状mesh, material_override存在の「なにが」「どんなふうに」

MeshInstance3Dが画面に何かを描画するには、最低でも mesh プロパティにMeshリソース(例: BoxMesh, SphereMesh、またはインポートした3Dモデル)を割り当てる必要があります。


よくある間違いとベストプラクティス

よくある間違いなぜ問題かベストプラクティス
MeshInstance3Dを直接動かそうとする物理演算と組み合わせる際、見た目だけが移動し、衝突判定が追従しないことがある。CharacterBody3DRigidBody3Dをルートとし、その子として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_pivotplanet_pivotの子になっているため、惑星全体を移動させると衛星も追従します。

パフォーマンスに関する注意点

Node3DMeshInstance3Dは便利ですが、使い方によってはパフォーマンスのボトルネックになる可能性があります。

  • 大量のノード: シーンツリーに数千、数万のNode3Dが存在すると、CPU負荷が増加します。
  • Draw Callの増加: 異なるマテリアルを持つMeshInstance3Dが増えるほど、コストは増大します。

代替パターン

  • MultiMeshInstance3D: 同じメッシュとマテリアルを持つオブジェクトを大量に配置する場合(例:森の木々)は、これが最適です。
  • RenderingServer: パフォーマンスを極限まで追求する場合、低レベルAPIを直接使う方法があります。

まとめ

本記事では、Godotの3D開発における最も基本的な要素であるNode3DMeshInstance3Dについて、その役割、関係性、そして実践的な使い方を解説しました。

概念キーワード役割
3D空間Y-Up右手系, 1ユニット=1mオブジェクトが存在する環境
Transform3Dbasis, origin空間内でのオブジェクトの姿勢を定義
Node3D親子関係, Pivot場所と姿勢を定義する骨格
MeshInstance3Dmesh, material見た目を定義する肉付け役

この2つのノードの役割分担を理解することが、Godotの3Dシーン構築をマスターするための鍵です。Node3Dで骨格を作り、MeshInstance3Dで肉付けをする。この原則を忘れずに、ぜひゲーム開発に活かしてください。