Voltar para o Blog
Quest Log

Sistema de Partículas no Godot: Fogo, Fumaça e Explosão com GPUParticles2D

Efeitos de partículas de fogo e explosão emitidos em uma cena 2D do Godot

Tutorial de partículas no Godot 4 com GPUParticles2D: receitas de fogo, fumaça e explosão, ParticleProcessMaterial, emissores e dicas de performance.

Sistema de Partículas no Godot: Fogo, Fumaça e Explosão com GPUParticles2D

Partículas no Godot são o jeito mais barato de fazer seu jogo parecer vivo. Uma tocha que tremula, fumaça subindo de uma chaminé, uma explosão que enche a tela por meio segundo: nada disso precisa de sprite animado quadro a quadro. É um node, um material e meia dúzia de propriedades bem escolhidas.

O problema é que o GPUParticles2D tem umas quarenta propriedades entre o node e o ParticleProcessMaterial, e ninguém te diz quais cinco importam pra cada efeito. Aí o iniciante abre o Inspector, mexe em tudo ao mesmo tempo e desiste com uma nuvem de quadrados brancos caindo pro lado errado.

Esse tutorial faz o caminho contrário: primeiro o mínimo de teoria pra você saber o que está mexendo, depois três receitas completas (fogo, fumaça e explosão) e como configurar emissores pra cada situação. Todo código é GDScript do Godot 4.x.

GPUParticles2D ou CPUParticles2D?

O Godot tem dois nodes de partículas em 2D e a diferença é onde a simulação roda.

GPUParticles2D processa as partículas na placa de vídeo. É o padrão e o que você deve usar na maioria dos casos: aguenta milhares de partículas sem pesar na CPU, que fica livre pra física e gameplay.

CPUParticles2D simula tudo no processador. Existe por compatibilidade: hardware muito antigo e alguns cenários de export web se comportam melhor com ele. A boa notícia é que a API é quase idêntica, e o editor converte de um pro outro com um clique direito no node.

Minha regra prática: comece sempre com GPUParticles2D. Se o seu alvo for um dispositivo muito fraco e você medir problema de verdade, converta. Não otimize por medo antes de medir.

Anatomia: o node e o ParticleProcessMaterial

A configuração de um efeito vive em dois lugares, e saber qual propriedade mora onde economiza muito tempo de Inspector.

No node GPUParticles2D ficam as decisões de emissão:

  • amount: quantas partículas existem no total do ciclo.
  • lifetime: quantos segundos cada partícula vive.
  • one_shot: emite uma rajada única e para. É a base de qualquer explosão.
  • explosiveness: de 0 a 1. Em 0, as partículas nascem espalhadas ao longo do lifetime (fluxo contínuo, tipo fogo). Em 1, nascem todas no mesmo instante (estouro).
  • preprocess: simula N segundos antes do primeiro frame. Serve pra tocha já nascer acesa em vez de "ligar" na frente do jogador.
  • local_coords: se as partículas seguem o node quando ele se move ou ficam pra trás no mundo. Falo disso na parte de emissores.
  • texture: a imagem de cada partícula. Sem textura você vê quadrados; um círculo branco desfocado de 32x32 pixels resolve quase todo efeito.

No ParticleProcessMaterial fica o comportamento de cada partícula durante a vida dela: direção, velocidade, gravidade, escala, cor. Você cria ele no campo process_material do node. Existe também a opção de usar um ShaderMaterial pra controle total, mas o ParticleProcessMaterial cobre tudo que esse artigo precisa.

Com esse mapa na cabeça, as receitas ficam curtas.

Fogo de tocha: a primeira receita de partículas no Godot

Fogo é fluxo contínuo subindo, com partícula que nasce amarela, vira laranja e morre transparente, encolhendo no caminho. Dá pra montar inteiro pelo Inspector, mas em código fica mais fácil de mostrar (e de copiar):

extends GPUParticles2D

func _ready():
    amount = 24
    lifetime = 0.9
    preprocess = 0.9  # nasce já aceso, sem o "boot" do efeito

    var mat = ParticleProcessMaterial.new()
    mat.direction = Vector3(0, -1, 0)       # pra cima (no 2D, Y cresce pra baixo)
    mat.spread = 12.0                        # cone estreito
    mat.initial_velocity_min = 40.0
    mat.initial_velocity_max = 70.0
    mat.gravity = Vector3(0, -120, 0)        # acelera a subida em vez de puxar pro chão
    mat.scale_min = 0.6
    mat.scale_max = 1.0
    process_material = mat

Repare nos dois detalhes de eixo: a direção é (0, -1, 0) e a gravidade tem Y negativo. O ParticleProcessMaterial usa Vector3 porque é o mesmo recurso do 3D, e no 2D do Godot o Y positivo aponta pra baixo. Fogo que cai em vez de subir é quase sempre sinal trocado aqui.

Falta a parte que transforma "pontos subindo" em fogo: cor e escala ao longo da vida.

    # Gradiente de cor: amarelo -> laranja transparente.
    var grad = Gradient.new()
    grad.set_color(0, Color(1.0, 0.85, 0.3))
    grad.set_color(1, Color(1.0, 0.25, 0.0, 0.0))
    var ramp = GradientTexture1D.new()
    ramp.gradient = grad
    mat.color_ramp = ramp

    # Curva de escala: nasce no tamanho cheio, morre pequena.
    var curve = Curve.new()
    curve.add_point(Vector2(0.0, 1.0))
    curve.add_point(Vector2(1.0, 0.1))
    var curve_tex = CurveTexture.new()
    curve_tex.curve = curve
    mat.scale_curve = curve_tex

O color_ramp mapeia a vida da partícula (0 = nasceu, 1 = morreu) no gradiente. O alpha 0 no fim é o que faz a chama "dissolver" em vez de sumir de repente. A scale_curve faz a língua de fogo afinar conforme sobe.

O toque final é o blend aditivo, que faz partículas sobrepostas somarem brilho em vez de uma tampar a outra. É a diferença entre bolinhas laranjas e algo que parece emitir luz:

    var canvas_mat = CanvasItemMaterial.new()
    canvas_mat.blend_mode = CanvasItemMaterial.BLEND_MODE_ADD
    material = canvas_mat

Esse material é do próprio node (CanvasItem), não confunda com o process_material. Rode e ajuste amount, lifetime e as velocidades até a chama ter o tamanho da sua tocha. Não existe valor universal: fogo de vela e fogueira de acampamento são a mesma receita com números diferentes.

Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

Fumaça: a irmã lenta do fogo

Fumaça usa a mesma estrutura e inverte quase todas as decisões. Onde o fogo é rápido, estreito e brilhante, a fumaça é lenta, larga e opaca:

  • lifetime longo, na casa de 2 a 4 segundos.
  • spread maior, uns 25 a 40 graus, pra nuvem abrir.
  • initial_velocity baixa (15 a 30) e gravidade pra cima bem fraca, tipo (0, -20, 0).
  • Curva de escala invertida: fumaça nasce pequena e cresce. É o oposto do fogo, e é o detalhe que mais vende o efeito.
  • Cor cinza com alpha baixo já no início (uns 0.5), morrendo em alpha 0.
  • Sem blend aditivo. Fumaça bloqueia luz, não emite. Blend normal.

Duas propriedades do material que ficam ótimas aqui: angular_velocity_min e angular_velocity_max com valores tipo -30 e 30 fazem cada nuvem girar de leve numa direção aleatória, e randomness no node (uns 0.3) quebra a regularidade da emissão. Fumaça perfeitinha e simétrica parece errada na hora; a sujeira é proposital.

Pra fumaça que acompanha fogo (chaminé, fogueira), eu uso dois nodes irmãos: um GPUParticles2D de fogo embaixo e um de fumaça com position um pouco acima, onde a chama termina. Tentar fazer os dois num material só dá mais trabalho e fica pior.

Explosão: one_shot e explosiveness

Explosão é onde one_shot e explosiveness ganham o jogo. A receita: todas as partículas nascem no mesmo instante, voam pra todas as direções e morrem rápido.

extends GPUParticles2D

func _ready():
    amount = 40
    lifetime = 0.6
    one_shot = true
    explosiveness = 1.0   # tudo nasce junto
    emitting = false      # espera o comando

    var mat = ParticleProcessMaterial.new()
    mat.spread = 180.0                       # esfera completa: voa pra todo lado
    mat.initial_velocity_min = 150.0
    mat.initial_velocity_max = 320.0
    mat.gravity = Vector3.ZERO               # o sopro inicial domina, gravidade atrapalha
    mat.damping_min = 100.0                  # freia as partículas no fim do voo
    mat.damping_max = 180.0
    process_material = mat

    finished.connect(queue_free)

func detonar():
    restart()

Três pontos que valem explicação:

spread = 180 cobre o círculo inteiro. O spread é medido a partir da direção base pros dois lados, então 180 graus vira emissão radial completa. É o formato de estouro clássico.

damping é o que dá o "punch". Sem ele, as partículas voam em velocidade constante até morrer, e fica com cara de fogos de artifício moles. Com damping alto, elas saem em disparada e freiam, que é como uma onda de choque se comporta. Junto com a variação grande entre velocidade mínima e máxima, é o ajuste que mais muda a sensação.

O sinal finished limpa a cena sozinho. Ele dispara quando todas as partículas de um ciclo one_shot morreram. Conectar no queue_free significa que você pode instanciar a cena da explosão onde quiser e esquecer dela:

# Em qualquer script que precise explodir algo:
const EXPLOSAO = preload("res://efeitos/explosao.tscn")

func criar_explosao(pos: Vector2):
    var fx = EXPLOSAO.instantiate()
    fx.position = pos
    get_tree().current_scene.add_child(fx)
    fx.detonar()

Pra explosão mais rica, empilhe camadas como fizemos com fogo e fumaça: um node de faíscas rápidas com blend aditivo, um de fumaça lenta por baixo, e o color_ramp indo de branco pra laranja pra transparente. Cada camada é simples; a soma parece cara.

Configurando emissores: forma e coordenadas

Até aqui tudo nasceu de um ponto. O emission_shape do material muda isso e abre os efeitos de área:

    # Emite de uma linha horizontal (neve, chuva, poeira caindo do teto):
    mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
    mat.emission_box_extents = Vector3(160, 1, 1)

    # Emite de um círculo (aura, buff mágico ao redor do personagem):
    mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
    mat.emission_sphere_radius = 48.0

Os extents são metade do tamanho, então o box acima tem 320 pixels de largura. Pra neve cobrindo a tela, posicione o node acima do topo da câmera, use o box na largura da viewport e gravidade positiva em Y dessa vez, porque agora queremos queda.

O outro ajuste essencial de emissor é o local_coords do node, que define o que acontece quando o emissor se move:

  • local_coords = false (padrão): a partícula nasce e fica no mundo. O emissor anda, a partícula fica pra trás. É o certo pra rastro, poeira de corrida, propulsor de nave, tocha na mão do jogador.
  • local_coords = true: as partículas acompanham o node como se fossem filhas dele. Use pra efeito que precisa ficar grudado, tipo uma aura que envolve o personagem enquanto ele corre.

Se o seu efeito de rastro parece "andar junto" de um jeito estranho, esse checkbox é o primeiro suspeito.

Performance: o que pesa de verdade

GPUParticles2D aguenta muito, mas não é de graça. As medidas na ordem do que mais importa:

Conte as partículas visíveis, não os nodes. Vinte tochas com amount = 24 são menos de 500 partículas, tranquilo. O problema é o efeito único com amount = 5000 "pra ficar bonito". Quase sempre 200 partículas com boa textura, color_ramp e blend aditivo parecem mais densas que 2000 quadradinhos.

fixed_fps controla o custo da simulação. O padrão do node é 30, e pra fumaça lenta dá até pra baixar. Só suba pra 60 em efeito rápido de gameplay (explosão, impacto) se notar serrilhado no movimento.

Textura pequena. Partícula de 32x32 ou 64x64 resolve. Textura de 512x512 em centenas de partículas sobrepostas é overdraw puro, e overdraw é o gargalo clássico de VFX 2D.

Confira o visibility_rect. O Godot usa esse retângulo pra decidir se o efeito está na tela. Se as partículas voam além dele, o efeito some nas bordas da câmera; se ele for gigante à toa, o efeito nunca é descartado. Ajuste pra cobrir o alcance real do movimento.

Fechando

O sistema de partículas do Godot parece grande, mas as receitas se repetem: fogo é fluxo contínuo subindo com blend aditivo, fumaça é a mesma coisa lenta e opaca com escala crescendo, explosão é one_shot com explosiveness no máximo e damping alto. Emissores de forma e o local_coords cobrem o resto dos casos do dia a dia.

Meu conselho é o mesmo de sempre: abra um projeto vazio e monte os três efeitos desse artigo na mão, mexendo num parâmetro por vez pra ver o que ele faz. Partícula é um daqueles assuntos em que dez minutos de tuning ensinam mais que uma hora de leitura. Depois disso, todo efeito novo vira variação do que você já sabe.