Godot入門:MultiMeshInstance2Dの使い方と活用法

はじめに

Godot 4でゲームを作っていると、「草を大量に描きたい」「コインを画面いっぱいに散りばめたい」「弾幕ゲームの弾を数百発同時に動かしたい」という場面に必ずぶつかります。素直にMeshInstance2Dノードを何百個も並べると、描画のたびにGPUへ命令を送り続けるため、フレームレートが急落してしまいます。そんなときに救世主となるのがMultiMeshInstance2Dです。

このノードは、同じメッシュを何千・何万個でもたった1回のドローコールでGPUに描画させる仕組みを持っています。個々のインスタンスには位置・回転・スケール・色を個別に設定できるため、「みんな同じ形だけど、バラバラに配置したい」という用途にぴったりです。本記事では、MultiMeshInstance2Dの基本概念から実践的なGDScriptの書き方まで、丁寧に解説します。

MultiMeshInstance2Dとは?

スタンプ台のイメージ:同じ判子(メッシュ)を何千回も押すが、GPUへの命令は1回だけで済む。それぞれの押し方(位置・角度・色)は異なっていてもよい。

MultiMeshInstance2Dは、MultiMeshリソースを保持し、それをまとめて描画するノードです。MultiMeshが「何個・どこに・何色で」という情報を管理し、MultiMeshInstance2Dがそれを画面に出力する役割分担になっています。

継承ツリーは以下のとおりです。

MultiMeshInstance2D
└── Node2D
    └── CanvasItem
        └── Node
            └── Object




MultiMeshInstance2Dの概念図:1回のドローコールで多数のインスタンスを描画
MultiMeshInstance2Dは1回のドローコールで数千のインスタンスを描画できる

このノードを使うべき場面

  • 大量の草・花の描画:背景を飾る植物を数千本スポーンさせても、ドローコールは1回で済む。
  • コイン・アイテムの一括表示:ステージに散らばるコインを一度にセットアップして描画できる。
  • 弾幕シューティングの弾:数百〜数千発の弾を毎フレーム更新するような弾幕ゲームでも高パフォーマンスを維持できる。
  • 群衆・群体の表現:RPGの街人やRTSのユニット群など、同じスプライト形状を持つキャラクターを大量に表示する。

別のノードが適切な場面

インスタンスごとにアニメーション・物理演算・入力処理などの複雑なロジックが必要な場合は、通常のSprite2DCharacterBody2Dノードを使う方がコードがシンプルになります。MultiMeshInstance2Dはあくまで「見た目の大量描画」に特化したノードであり、各インスタンスに独立したノードの機能(シグナル、スクリプト等)は持たせられません。また、インスタンス数が10〜20程度の少量であれば、通常のノードで十分です。

主なプロパティと機能

プロパティ/メソッド 役割
multimesh MultiMesh 描画するMultiMeshリソースを指定する。インスタンスの位置・色情報を保持する。
texture Texture2D 各インスタンスに適用するテクスチャ。スプライト画像など。
MultiMesh.instance_count int 描画するインスタンスの総数。変更するとデータがリセットされるため、最初に設定する。
MultiMesh.mesh Mesh 各インスタンスに使用するメッシュ形状(例:QuadMesh)。
set_instance_transform_2d(index, transform) void 指定インデックスのインスタンスに2Dトランスフォーム(位置・回転・スケール)を設定する。
set_instance_color(index, color) void 指定インデックスのインスタンスに色(Color)を設定する。テクスチャに乗算適用される。
get_instance_transform_2d(index) Transform2D 指定インデックスのインスタンスのトランスフォームを取得する。

コード例1:草を1000本ランダム配置する

extends MultiMeshInstance2D

func _ready() -> void:
    # MultiMeshリソースを新規作成
    var mm := MultiMesh.new()

    # 2D用のQuadMeshを設定(草のスプライト1枚分の矩形)
    var quad := QuadMesh.new()
    quad.size = Vector2(32.0, 32.0)
    mm.mesh = quad

    # トランスフォームの種類を2Dに設定(必須)
    mm.transform_format = MultiMesh.TRANSFORM_2D

    # 色情報を有効化(インスタンスごとに色を変えたい場合)
    mm.use_colors = true

    # インスタンス数を設定(この後にトランスフォームを書き込む)
    mm.instance_count = 1000

    # このノードにMultiMeshを適用
    multimesh = mm

    # 乱数シードを固定して再現性を確保
    var rng := RandomNumberGenerator.new()
    rng.seed = 42

    for i in range(mm.instance_count):
        # ランダムな位置を生成(ビューポート内に収める)
        var pos := Vector2(
            rng.randf_range(-640.0, 640.0),
            rng.randf_range(-360.0, 360.0)
        )

        # ランダムなスケールで草の大きさを変化させる
        var scale_val := rng.randf_range(0.8, 1.4)

        # Transform2Dを構築して適用
        var xform := Transform2D()
        xform = xform.scaled(Vector2(scale_val, scale_val))
        xform.origin = pos
        mm.set_instance_transform_2d(i, xform)

        # 緑系のランダムな色を設定
        var green_shade := rng.randf_range(0.5, 1.0)
        mm.set_instance_color(i, Color(0.2, green_shade, 0.1, 1.0))

コード例2:弾幕のように弾を毎フレーム更新する

extends MultiMeshInstance2D

const BULLET_COUNT := 500
const BULLET_SPEED := 300.0

# 各弾の速度ベクトルを保持する配列
var velocities: Array[Vector2] = []

func _ready() -> void:
    var mm := MultiMesh.new()
    var quad := QuadMesh.new()
    quad.size = Vector2(8.0, 8.0)
    mm.mesh = quad
    mm.transform_format = MultiMesh.TRANSFORM_2D
    mm.use_colors = true
    mm.instance_count = BULLET_COUNT
    multimesh = mm

    var rng := RandomNumberGenerator.new()
    rng.randomize()

    velocities.resize(BULLET_COUNT)

    for i in range(BULLET_COUNT):
        # 原点から全方向へ弾を飛ばす
        var angle := rng.randf_range(0.0, TAU)
        var speed := rng.randf_range(BULLET_SPEED * 0.5, BULLET_SPEED)
        velocities[i] = Vector2(cos(angle), sin(angle)) * speed

        # 初期位置は原点(発射地点)
        var xform := Transform2D()
        xform.origin = Vector2.ZERO
        mm.set_instance_transform_2d(i, xform)

        # 弾の色を白〜黄色でランダムに設定
        mm.set_instance_color(i, Color(1.0, rng.randf_range(0.5, 1.0), 0.0, 1.0))

func _process(delta: float) -> void:
    var mm: MultiMesh = multimesh
    var screen_size := get_viewport_rect().size

    for i in range(BULLET_COUNT):
        # 現在のトランスフォームを取得して位置を更新
        var xform := mm.get_instance_transform_2d(i)
        xform.origin += velocities[i] * delta

        # 画面外に出たら反対側にワープ(トーラス状の空間)
        if xform.origin.x > screen_size.x * 0.5:
            xform.origin.x -= screen_size.x
        elif xform.origin.x < -screen_size.x * 0.5:
            xform.origin.x += screen_size.x

        if xform.origin.y > screen_size.y * 0.5:
            xform.origin.y -= screen_size.y
        elif xform.origin.y < -screen_size.y * 0.5:
            xform.origin.y += screen_size.y

        mm.set_instance_transform_2d(i, xform)




GodotエディタのInspectorでMultiMeshInstance2Dのプロパティを設定している様子
InspectorでMultiMeshリソースのinstance_countやmeshを設定する

もっと使いこなす:カスタマイズできるパラメータ

パラメータ 説明・カスタマイズのヒント
use_custom_data(MultiMesh) シェーダーに渡すカスタムデータ(Vector4)をインスタンスごとに設定できる。シェーダーアニメーションや特殊エフェクトに活用。
visible_instance_count(MultiMesh) 実際に描画するインスタンス数を動的に制限できる。instance_countを下回る値を設定すると、残りのインスタンスを非表示にできる(プールとして使いやすい)。
texture(MultiMeshInstance2D) テクスチャアトラスを使うと複数の見た目を1枚の画像に収められる。UV座標をカスタムデータやシェーダーで操作してアニメーションも可能。
インスタンスの色(use_colors テクスチャの色に乗算されるため、同じテクスチャで赤・青・緑など複数の「色違い」キャラクターを表現できる。
シェーダーとの組み合わせ CanvasItemShaderを割り当てることで、草の揺れ・水面の波紋・ネオン発光など複雑なエフェクトを全インスタンスに一括適用できる。
QuadMesh.size メッシュの基本サイズ。Texture2Dのサイズに合わせて設定する。ピクセル単位で指定(例:32x32のスプライトならVector2(32,32))。

まとめ

MultiMeshInstance2Dは、同じ形状のオブジェクトを大量に描画するときにGPUのドローコールを1回に集約し、劇的なパフォーマンス向上をもたらすノードです。

  • MultiMeshリソースを作成し、instance_countmeshtransform_formatを設定してからトランスフォームと色を書き込む手順を守る。
  • visible_instance_countを使うとインスタンスプールとして動的に表示数を制御できる。
  • シェーダーやカスタムデータと組み合わせることで、単純な大量描画を超えた表現力豊かなエフェクトを実現できる。

次回は、NavigationAgent2Dと連携して動的な障害物を表現するNavigationObstacle2Dを解説します。

シリーズ:Godot 4 ノード解説

この記事はGodot 4.xをもとに執筆しています。

コメント