Ganhe FPS ao usar MultiMeshInstance2D no Godot 4 sem travar
Aprenda a configurar o MultiMeshInstance2D no Godot 4 para reduzir milhares de draw calls a apenas uma e manter seu jogo 2D rodando liso em 60 FPS.
Quando um projeto 2D evolui de um simples protótipo para um jogo comercial no estilo bullet hell, jogo de estratégia com centenas de unidades ou simulação de multidões, a quantidade de elementos na tela dispara. No motor Godot 4.7.2, instanciar milhares de nós do tipo Sprite2D ou Node2D causa uma queda drástica na taxa de quadros por segundo. O problema raramente está no processamento gráfico do chip de vídeo, mas sim no excesso de interações entre a CPU e o driver gráfico. Saber como implementar o multimeshinstance2d no godot 4 é o passo divisor de águas para substituir milhares de instruções individuais por uma única chamada de desenho consolidada, mantendo o seu jogo fluido cravado em 60 FPS ou mais.
Neste artigo, você vai entender a mecânica por trás dos draw calls no ecossistema do Godot 4, aprender a configurar o nó MultiMeshInstance2D programaticamente e integrar atualizações dinâmicas de posição, rotação e dados customizados em tempo real sem afunilar o encadeamento principal do GDScript.
Por que milhares de Sprite2D destroem o desempenho no Godot 4?

No modelo tradicional de cena do Godot, cada nó Sprite2D adicionado à árvore de cena possui sua própria matriz de transformação, suas propriedades de visibilidade, ordenação de z-index e ciclos de vida atrelados ao processamento do nó. Sempre que o motor prepara um quadro para renderização, ele precisa percorrer a árvore de cena, calcular a matriz global de cada elemento na CPU e emitir uma instrução individual de desenho para o pipeline gráfico. Essa instrução enviada pela CPU à GPU é chamada de draw call (chamada de desenho).
Quando a sua cena possui 50 objetos, o overhead de emissão de instruções é insignificante. No entanto, ao atingir a casa dos 5.000 a 10.000 sprites individuais, a CPU passa a gastar quase todo o tempo de processamento montando comandos de desenho e transferindo pequenos blocos de dados pela ponte do sistema, antes mesmo da placa gráfica começar a rasterizar os pixels. O gargalo se instala na comunicação da CPU com o driver de vídeo.
O nó MultiMeshInstance2D resolve essa limitação estrutural aplicando uma técnica conhecida como instanced rendering (renderização por instanciamento). Em vez de enviar milhares de comandos de desenho com um único sprite por comando, o Godot envia a geometria de um único quad (dois triângulos) e um buffer de memória contendo a lista de matrizes de transformação de todas as instâncias. A GPU lê essa matriz única de dados e desenha milhares de cópias da mesma textura em uma única instrução de desenho, zerando o custo de transferência por objeto.
Como usar multimeshinstance2d no godot 4 para renderizar milhares de sprites?
Para colocar o recurso em prática de forma eficiente, a melhor abordagem é construir o nó e a sua estrutura de dados dinamicamente via código. Isso evita dependências rígidas do editor quando a quantidade de instâncias precisa mudar durante a execução do jogo.
O MultiMeshInstance2D atua como o nó visual dentro da cena, mas o trabalho pesado de armazenamento de dados é feito por um recurso interno chamado MultiMesh. Esse recurso precisa ser instanciado, configurado com uma geometria base (geralmente um QuadMesh) e associado ao número exato de instâncias que serão desenhadas.
Abaixo está um exemplo completo em GDScript que constrói um renderizador de enxame capaz de alocar e inicializar 20.000 elementos 2D em tela:
extends MultiMeshInstance2D
@export var texture_atlas: Texture2D
@export var instance_count: int = 20000
@export var sprite_size: Vector2 = Vector2(16, 16)
func _ready() -> void:
_setup_multimesh()
func _setup_multimesh() -> void:
# Create and configure the internal MultiMesh resource
var multimesh_resource := MultiMesh.new()
# Set dimensions for 2D rendering (transforms use 2D matrices)
multimesh_resource.transform_format = MultiMesh.TRANSFORM_2D
multimesh_resource.use_colors = true
multimesh_resource.use_custom_data = false
# Define the base 2D quad geometry
var quad_mesh := QuadMesh.new()
quad_mesh.size = sprite_size
multimesh_resource.mesh = quad_mesh
# Allocate memory buffer for instances
multimesh_resource.instance_count = instance_count
# Assign the MultiMesh resource to this MultiMeshInstance2D node
self.multimesh = multimesh_resource
self.texture = texture_atlas
# Populate initial positions and properties
_populate_initial_transforms()
func _populate_initial_transforms() -> void:
var mm := self.multimesh
var screen_size := get_viewport_rect().size
for i in range(mm.instance_count):
var random_pos := Vector2(
randf() * screen_size.x,
randf() * screen_size.y
)
var random_rotation := randf() * TAU
var random_scale := Vector2.ONE * randf_range(0.8, 1.2)
# Build Transform2D: origin, rotation, scale
var transform_2d := Transform2D(random_rotation, random_scale, 0.0, random_pos)
mm.set_instance_transform_2d(i, transform_2d)
mm.set_instance_color(i, Color(randf(), randf(), randf(), 1.0))
No trecho de código acima, configuramos a propriedade transform_format como TRANSFORM_2D. Essa definição indica ao sistema que a estrutura de transformação usará matrizes levemente compactadas para 2D (armazenando apenas vetores de rotação, escala e posição 2D), economizando largura de banda na GPU em comparação com matrizes 3D inteiras.
Como atualizar posições e dados de instâncias sem afunilar a CPU?
Criar instâncias estáticas é o caso mais simples. No entanto, em um jogo real, projéteis, partículas e NPCs navegam e alteram suas posições a cada quadro. Se você utilizar um loop simples no GDScript com set_instance_transform_2d para atualizar 50.000 objetos dentro da função _process(delta), notará que a taxa de FPS volta a cair — não mais por conta dos draw calls no driver de vídeo, mas pelo gargalo da interpretação de código na CPU dentro do GDScript.
Para otimizar o ciclo de atualização de posições de alta volumetria, considere as seguintes estratégias de arquitetura:
1. Atualizar apenas instâncias ativas ou em tela
Não force a atualização de transformações para elementos fora do campo de visão da câmera (frustum culling manual) ou para partículas desativadas. Mantenha um vetor de índices ativos e itere apenas sobre o intervalo necessário.
2. Manipular buffers brutos via C++ ou GDExtension para volumes extremos
Quando o número de objetos ultrapassa 50.000 unidades com física ou movimentação complexa, a manipulação direta do buffer de transformação no GDScript encontra limites de interpretação. Nesses cenários, delegar a matemática de atualização para um módulo em C++ via GDExtension ou utilizar Compute Shaders para modificar os dados de transformação diretamente no VRAM elimina qualquer travamento na CPU.
3. Utilizar o CanvasItem Shader para animações visuais
Em vez de recalcular a posição ou a cor de milhares de partículas na CPU via script, passe parâmetros globais (como o tempo ou a posição de um atrator) para um shader customizado do tipo canvas_item. Deixe a placa gráfica mover e deformar os vértices com base na ID da instância (INSTANCE_ID).
Abaixo está um exemplo de shader 2D em GLSL do Godot que anima todas as instâncias em um padrão de onda diretamente na GPU, eliminando loops no GDScript:
shader_type canvas_item;
uniform float wave_speed = 2.0;
uniform float wave_amplitude = 10.0;
void vertex() {
// Modulate y position based on instance ID and global time
float offset = sin(TIME * wave_speed + float(INSTANCE_ID) * 0.1) * wave_amplitude;
VERTEX.y += offset;
}
Abaixo, a tabela comparativa sintetiza o comportamento e o custo operacional de cada abordagem de renderização no Godot 4.7.2:
| Abordagem no Godot 4 | Draw Calls para 10.000 Objetos | Consumo de CPU | Uso do VRAM | Flexibilidade de Nós e Sinais |
|---|---|---|---|---|
| Múltiplos Sprite2D | ~10.000 calls | Extremamente Alto | Baixo | Total (Física, Árvores, Sinais) |
| Node2D com _draw() customizado | ~1 a 100 calls (com batching) | Alto (Loops no GDScript) | Baixo | Média (Lógica no nó pai) |
| MultiMeshInstance2D | 1 single call | Mínimo | Muito Baixo | Limitada (Sem nós individuais) |
Lidando com colisões e física ao usar MultiMeshInstance2D
Como o MultiMeshInstance2D gerencia as instâncias apenas como dados visuais dentro de um buffer, os elementos individuais não são nós da árvore de cena. Isso significa que eles não possuem automaticamente um CollisionShape2D ou um Area2D acoplado.
Para jogos de tiro em massa ou simulações onde projéteis instanciados precisam colidir com o jogador ou com o cenário, criar 20.000 nós de colisão anularia todo o ganho de desempenho obtido com o MultiMesh. A solução correta é desatrelar a física da árvore de nós:
- Uso do PhysicsServer2D direto: Crie formas de colisão puras diretamente na API do
PhysicsServer2Dsem anexar nós na árvore. O servidor de física 2D do Godot é escrito em C++ altamente otimizado e consegue processar colisões de milhares de corpos simples em paralelo. - Grids de Proximidade (Spatial Hashing): Para sistemas simples de projéteis contra o jogador, implemente uma grade espacial simples via código para checar a distância entre o jogador e as posições armazenadas no buffer do
MultiMeshsem envolver o motor de física tradicional.

Erros comuns ao configurar o MultiMeshInstance2D e como evitá-los
Ao trabalhar com o MultiMeshInstance2D, alguns contratempos frequentes podem fazer com que os elementos não sejam exibidos ou apresentem comportamento inesperado. Fique atento às seguintes armadilhas:
- Esquecer de atribuir uma Mesh e uma Textura: O nó
MultiMeshInstance2Dprecisa da propriedademeshpreenchida no recursoMultiMesh(ex.:QuadMesh) e da textura atribuída na propriedadetexturedo próprio nóMultiMeshInstance2D. - Definir o instance_count após preencher dados: Alterar a propriedade
instance_countdo recursoMultiMeshzera todo o buffer alocado. Configure o tamanho total do buffer antes de chamar métodos comoset_instance_transform_2douset_instance_color. - Não ativar as flags de recurso antes da atribuição: Se o seu projeto precisa de cores individuais por instância ou dados customizados para o shader, você deve definir
use_colors = trueouuse_custom_data = trueantes de definir oinstance_count. Alterar essas flags posteriormente causa o realloc de memória do buffer. - Ignorar o Z-Index e a ordem de desenho: O
MultiMeshInstance2Ddesenha todas as suas instâncias na ordem numérica de seus índices dentro do buffer (do índice 0 aoinstance_count - 1). Se o seu jogo exige ordenação de profundidade dinamicamente (Y-Sort), você precisará ordenar os dados das instâncias por coordenada Y no array de posições antes de atualizar o buffer de transformações.
Conclusão
Aprender a dominar o renderizador de instâncias no ecossistema do Godot é um conhecimento fundamental para quem busca criar jogos complexos, fluidos e visualmente impressionantes sem esbarrar nas limitações da CPU. Implementar o multimeshinstance2d no godot 4 garante que o pipeline de renderização execute milhares de elementos visuais com o custo de apenas um draw call, mantendo a experiência do jogador estável mesmo sob alta densidade de objetos na tela.
Ao integrar esse recurso com atualizações via shaders ou chamadas otimizadas do PhysicsServer2D, seu jogo 2D ganha margem de sobra para focar no que realmente importa: mecânicas de gameplay responsivas e visuais marcantes.