Voltar para o Blog
Quest Log

MultiplayerSpawner no Godot: Instanciar Jogadores em Rede

Diagrama de um servidor Godot criando uma cena de jogador que aparece replicada em três telas de clientes conectados

Instanciar jogadores no Godot multiplayer com o MultiplayerSpawner: spawn_path, cenas auto-spawnáveis, spawn function e o servidor criando o player.

Ferramenta:Godot

Quando você vai instanciar jogadores no Godot multiplayer, bate uma dúvida que trava muita gente: eu crio a cena do player em qual máquina? No servidor? Em cada cliente? Nos dois? A resposta curta é "só no servidor", e a peça que faz isso dar certo é o node MultiplayerSpawner. Ele existe justamente para replicar a criação e a remoção de cenas em todos os pares conectados, sem você precisar mandar mensagem manual para cada um. Neste post a gente vai fundo nele: o papel que cumpre, a propriedade spawn_path, as cenas auto-spawnáveis, a spawn function custom para passar parâmetros, e o padrão de instanciar o jogador no sinal peer_connected quando alguém entra na partida.

Se você ainda não montou uma conexão de rede e nunca disparou uma função remota, vale ler antes o básico de multiplayer no Godot com RPC, porque aqui eu assumo que você já sabe abrir servidor, conectar cliente e o que é autoridade. Vou tratar o spawner como o próximo degrau, não como o primeiro.

O que o MultiplayerSpawner realmente faz

O MultiplayerSpawner tem uma função única e bem definida: ele vigia um node pai e replica, para todos os clientes, qualquer cena que nascer ou morrer embaixo desse pai. Nada mais. Ele não sincroniza posição, não manda input, não cuida de vida de inimigo. Ele responde a uma pergunta só: "quem existe na cena agora?".

A lógica por trás é a da rede high-level da Godot, onde o servidor é a fonte da verdade. Quando o servidor cria um node embaixo do pai vigiado, o spawner percebe, serializa qual cena era e sob qual pai ela entrou, e manda essa informação para cada cliente. Cada cliente então recria exatamente aquela cena, no mesmo lugar, com o mesmo nome de node. Quando o servidor remove o node, o spawner replica a remoção e a cena some em todas as telas.

Isso resolve o problema mais chato de instanciar jogadores em rede: a consistência. Sem o spawner, você teria que mandar uma RPC para cada cliente dizendo "cria um player com o nome X aqui", garantir que todos recebessem, tratar quem conectou atrasado, e por aí vai. O spawner empacota tudo isso. Você instancia uma vez, no servidor, e confia que vai aparecer igual em todo mundo.

Uma coisa importante de entender logo: o spawner replica a partir do servidor, na direção dos clientes. Ele não funciona como "qualquer máquina cria e os outros copiam". Quem instancia tem que ser quem tem autoridade sobre o pai vigiado, que no modelo normal é o servidor (id 1).

spawn_path e cenas auto-spawnáveis

Duas configurações fazem o spawner funcionar, e as duas ficam no painel do node no editor (ou dá para setar por código).

A primeira é o spawn_path. Essa propriedade aponta para o node pai cujos filhos serão vigiados. Tudo que nascer diretamente embaixo desse node vira candidato a ser replicado. Na prática você costuma criar um node vazio só para isso, algo como um Node chamado Players, e aponta o spawn_path do spawner para ele. Detalhe que gera bug silencioso: esse caminho precisa ser o mesmo caminho relativo em todas as máquinas. Se no servidor o pai está em /root/Game/Players e no cliente a árvore está montada diferente, a replicação não encontra o lugar certo e falha quieta.

A segunda configuração é a lista de cenas auto-spawnáveis. Você registra quais cenas o spawner tem permissão de replicar. No editor isso é o campo "Auto Spawn List"; por código você usa add_spawnable_scene() passando o caminho do arquivo da cena. Só cenas nessa lista serão replicadas automaticamente quando nascerem embaixo do spawn_path. É uma trava de segurança e de clareza: o spawner não sai replicando qualquer coisa, só o que você autorizou.

Montando por código, a configuração do lado do servidor fica assim:

extends Node

@onready var spawner: MultiplayerSpawner = $MultiplayerSpawner
@onready var container_jogadores: Node = $Players

const CENA_JOGADOR: String = "res://cenas/jogador.tscn"

func _ready() -> void:
    # registra qual cena pode ser replicada automaticamente
    spawner.add_spawnable_scene(CENA_JOGADOR)
    # aponta para o node pai que sera vigiado
    spawner.spawn_path = container_jogadores.get_path()

Com isso no ar, o contrato está firmado: qualquer instância de jogador.tscn que o servidor colocar embaixo do node Players vai aparecer, sozinha, em todos os clientes.

Instanciar jogadores quando alguém conecta

Agora o coração do assunto. O momento certo de criar a cena do jogador é quando um peer conecta, e o Godot avisa isso pelo sinal multiplayer.peer_connected. A regra é: só o servidor escuta esse sinal e só o servidor instancia.

Primeiro você conecta o sinal, mas guarda a instanciação para quem é servidor:

func _ready() -> void:
    spawner.add_spawnable_scene(CENA_JOGADOR)
    spawner.spawn_path = container_jogadores.get_path()

    if multiplayer.is_server():
        multiplayer.peer_connected.connect(_ao_conectar)
        multiplayer.peer_disconnected.connect(_ao_desconectar)
        # cria tambem o jogador do proprio host (id 1)
        _ao_conectar(multiplayer.get_unique_id())

Repare que eu chamo _ao_conectar manualmente para o próprio host. O sinal peer_connected dispara para os peers que chegam depois, mas o host já está ali desde o começo, então ele não recebe o próprio sinal. Criar o jogador do id 1 na mão resolve isso.

O callback que instancia de fato:

func _ao_conectar(id: int) -> void:
    var jogador: Node = load(CENA_JOGADOR).instantiate()
    # o nome do node vira o id do peer: fica unico e previsivel
    jogador.name = str(id)
    container_jogadores.add_child(jogador)

Três linhas fazem o trabalho. O instantiate() cria a cena do jogador na memória. Definir jogador.name = str(id) dá a ele um nome único e igual em todas as máquinas, o que é essencial para o spawner casar o node certo entre servidor e clientes (e facilita muito achar o player de um peer depois). E o add_child() coloca ele embaixo do spawn_path, momento em que o MultiplayerSpawner entra em ação e replica a criação para todos os clientes.

Do lado do cliente, você não escreve nada disso. O cliente só recebe a cena pronta, criada pelo spawner. Essa é a parte que mais alivia: a lógica de instanciação vive num lugar só, no servidor.

A remoção segue a mesma filosofia. Quando alguém sai, o servidor apaga o node e o spawner replica o sumiço:

func _ao_desconectar(id: int) -> void:
    var jogador: Node = container_jogadores.get_node_or_null(str(id))
    if jogador != null:
        jogador.queue_free()

Como o nome do node é o id do peer, achar quem saiu é direto com get_node_or_null(str(id)). O queue_free() no servidor some com o jogador em todas as telas.

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

Spawn function custom: nascendo com parâmetros

O fluxo acima cria sempre a mesma cena, do mesmo jeito. Mas e quando o jogador precisa nascer já com informação, tipo uma posição de spawn diferente, o personagem que a pessoa escolheu, ou a cor do time? O auto-spawn simples não passa dados: ele só replica "esta cena nasceu aqui". Para isso existe a spawn function custom.

Você registra uma função no spawner com spawn_function e, em vez de deixar o node nascer pelo add_child direto, chama spawner.spawn(dado). O spawner envia esse dado para todos os pares e, em cada máquina, chama sua função passando o dado como argumento. A função monta o node e o retorna. O valor que você passa precisa ser serializável pela rede (tipos básicos, dicionários, arrays desses tipos), porque ele viaja até os clientes.

func _ready() -> void:
    spawner.spawn_function = _criar_jogador
    spawner.spawn_path = container_jogadores.get_path()

    if multiplayer.is_server():
        multiplayer.peer_connected.connect(_ao_conectar)
        _ao_conectar(multiplayer.get_unique_id())

func _ao_conectar(id: int) -> void:
    # monta o dado inicial e pede ao spawner para replicar
    var dado: Dictionary = {
        "id": id,
        "pos": _ponto_de_spawn_livre(),
    }
    spawner.spawn(dado)

# roda em TODAS as maquinas; recebe o dado e devolve o node pronto
func _criar_jogador(dado: Dictionary) -> Node:
    var jogador: Node = load(CENA_JOGADOR).instantiate()
    jogador.name = str(dado["id"])
    jogador.position = dado["pos"]
    # define a autoridade do node para o peer dono
    jogador.set_multiplayer_authority(dado["id"])
    return jogador

Alguns pontos que fazem diferença aqui. A _criar_jogador roda em todas as máquinas, não só no servidor, porque é ela que de fato constrói o node em cada peer. Por isso ela é o lugar natural para aplicar coisas que precisam valer em todo mundo desde o nascimento, como a posição inicial e a autoridade. O set_multiplayer_authority(dado["id"]) entrega o controle daquele jogador ao peer dono, que é o que permite, depois, ele mandar o próprio input sem que os outros clientes briguem pelo controle. E note que com spawn_function definida você não dá add_child na mão: quem adiciona o node embaixo do spawn_path é o próprio spawner, com o retorno da sua função.

A escolha entre auto-spawn simples e spawn function é pragmática. Se o jogador nasce sempre igual, o auto-spawn com add_spawnable_scene basta. Se ele precisa de parâmetros no nascimento, use a spawn function. Dá até para combinar, mas comece pelo mais simples que resolve seu caso.

O erro clássico: instanciar manualmente em cada cliente

Vale gastar um parágrafo no erro que quase todo mundo comete uma vez. A tentação é pensar assim: "cada máquina roda o jogo, então cada máquina cria os jogadores que precisa ver". Aí a pessoa coloca um instantiate() no _ready de cada cliente, ou dispara uma RPC genérica mandando todo mundo criar player.

Isso quebra de um jeito confuso. Cada cliente cria a própria cópia, sem vínculo com as cópias das outras máquinas. Elas ficam com autoridade errada, nomes que não batem, e quando você tenta sincronizar posição ou mandar um evento, as máquinas discordam sobre qual node é qual jogador. O resultado típico é jogador duplicado, player fantasma que ninguém controla, ou movimento que só aparece numa tela. E o pior: funciona na sua máquina de teste, onde host e cliente estão no mesmo PC, e só quebra quando tem rede de verdade no meio.

O modelo correto é o contrário do intuitivo: só o servidor instancia, e o MultiplayerSpawner replica. O cliente nunca cria jogador por conta própria. Ele recebe o que o servidor mandou. Guardar essa inversão na cabeça economiza horas de depuração, porque praticamente todo bug de "player aparecendo errado" vem de alguém instanciando no lugar que não devia.

Depois que o jogador existe e aparece em todas as telas, falta mantê-lo atualizado: posição, rotação, animação. Isso não é trabalho do spawner, é do node irmão, o MultiplayerSynchronizer para sincronizar jogadores. A divisão é limpa: o spawner diz quem existe, o synchronizer diz como cada um está agora. Usar os dois no lugar certo é o que deixa o multiplayer organizado.

Fechando

Para instanciar jogadores no Godot multiplayer sem dor de cabeça, a receita é enxuta. O MultiplayerSpawner replica criação e remoção de cenas a partir do servidor; você configura o spawn_path apontando para o node pai vigiado e registra a cena com add_spawnable_scene(). O servidor escuta multiplayer.peer_connected, instancia o player com instantiate(), dá a ele o nome do id do peer e adiciona embaixo do spawn_path, deixando o spawner replicar para todos. Quando precisa de parâmetros no nascimento, troca o auto-spawn pela spawn function custom com spawn_function e spawn(dado). E a regra de ouro: cliente nenhum instancia jogador por conta própria, isso é tarefa só do servidor.

Se esse texto mostrou que falta base de GDScript e organização de cenas para você acompanhar com folga, não tem problema, é um sinal útil. Um curso de criação de jogos para iniciantes firma o fundamento que torna o multiplayer bem menos assustador, porque metade da dificuldade de rede é, no fundo, organização de código. E para ver todas as peças do motor em contexto, o hub de Godot reúne o que você precisa em volta disso. Monte a conexão local com dois jogadores, faça o spawner criar os players no peer_connected, e só cresça a partir do que já funciona.

Perguntas frequentes

Para que serve o MultiplayerSpawner no Godot 4?

Ele replica a criação e a remoção de cenas em todos os clientes. Quando o servidor instancia uma cena filha do node apontado pelo spawn_path, o MultiplayerSpawner recria essa mesma cena em cada peer conectado, e quando ela é removida no servidor some em todo mundo. Assim você instancia uma vez, no servidor, e não precisa mandar RPC manual para cada cliente.

O que é o spawn_path do MultiplayerSpawner?

É a propriedade que aponta para o node pai cujos filhos serão vigiados e replicados. Todo node criado diretamente embaixo desse pai, sendo de uma cena registrada como auto-spawnável, é replicado automaticamente para os clientes. O spawn_path precisa apontar para o mesmo caminho relativo em todas as máquinas, senão a replicação falha.

Como instanciar o jogador quando alguém conecta?

No servidor, conecte o sinal multiplayer.peer_connected e, dentro do callback, instancie a cena do player com instantiate(), defina o nome do node como o id do peer e adicione como filho do node do spawn_path. O MultiplayerSpawner replica essa criação para todos os clientes sozinho. Nunca instancie o jogador manualmente em cada cliente.

O que é uma spawn function custom no MultiplayerSpawner?

É uma função que você registra com spawn_function para controlar como a cena nasce. Em vez de só carregar uma cena fixa, o spawner chama sua função passando um dado que você enviou no spawn(), e ela retorna o node já montado. Serve para passar parâmetros iniciais, como a posição de nascimento ou o personagem escolhido.

Qual a diferença entre MultiplayerSpawner e MultiplayerSynchronizer?

O MultiplayerSpawner cuida de quem existe: ele replica a criação e a remoção de nodes. O MultiplayerSynchronizer cuida do estado contínuo de quem já existe, espelhando valores como posição e rotação pela rede, frame a frame. Você usa os dois juntos: o spawner faz o jogador aparecer, o synchronizer mantém ele atualizado.

Por que não devo instanciar o jogador em cada cliente separadamente?

Porque cada máquina criaria sua própria cópia sem relação com as outras, e elas discordariam sobre quem é quem. A rede high-level da Godot espera que o servidor seja a fonte da verdade e que o MultiplayerSpawner replique a partir dele. Instanciar manualmente em cada cliente gera jogadores duplicados, autoridade errada e sincronização quebrada.