Primeiro Jogo 3D no Godot 4: Tutorial Completo do Zero

Crie seu primeiro jogo 3D no Godot 4 do zero: cena 3D, CharacterBody3D, câmera com SpringArm3D e movimento WASD com pulo. Tutorial prático em GDScript.
Primeiro Jogo 3D no Godot 4: Tutorial Completo do Zero
Fazer o primeiro jogo 3D no Godot assusta menos do que parece. A engine resolve o pesado (renderização, física, câmera) e sobra pra você o que importa: montar a cena, escrever o movimento e sentir o personagem andando pelo mundo. Nesse tutorial a gente sai do projeto vazio e chega num protótipo jogável: um personagem que anda com WASD, pula, e uma câmera em terceira pessoa que segue ele girando com o mouse.
Não precisa de nenhum asset. Tudo aqui usa as formas primitivas da própria engine, então você pode focar 100% na lógica. Todo código é GDScript do Godot 4.x e roda como está.
Uma coisa antes de começar: se você nunca abriu o Godot, vale dez minutos num projeto 2D qualquer só pra se acostumar com a ideia de cena e node. Mas se você já sabe que cena é uma árvore de nodes, pode seguir direto.
Criando o projeto e a cena 3D
Crie um projeto novo e escolha o renderer Forward+, que é o padrão pra desktop e o que entrega mais qualidade visual. Se o alvo fosse mobile ou web, a escolha seria Mobile ou Compatibility, mas pro primeiro jogo 3D fique no Forward+.
Crie a cena principal com um node raiz Node3D e salve como main.tscn. É nela que vamos montar o mundo.
O chão
O chão precisa de duas coisas: aparência e colisão. A estrutura clássica é um StaticBody3D com uma malha e uma shape:
Chao (StaticBody3D)
├── MeshInstance3D (BoxMesh, ex: 20 x 1 x 20)
└── CollisionShape3D (BoxShape3D do mesmo tamanho)
No MeshInstance3D, crie um BoxMesh na propriedade Mesh e ajuste o Size pra algo como 20, 1, 20. No CollisionShape3D, crie um BoxShape3D com o mesmo tamanho. Se a malha e a shape ficarem com tamanhos diferentes, o personagem vai flutuar ou afundar no chão, então confira os dois.
Dica de prototipagem: os nodes CSGBox3D (e os outros CSG) fazem malha e colisão de uma vez, basta ligar a propriedade Use Collision. Pra blocar um nível de teste em cinco minutos, eles são imbatíveis. O custo de performance só vira problema em cena grande, o que não é o caso aqui.
Luz e céu
Cena 3D sem luz é um breu. Adicione dois nodes na raiz:
- DirectionalLight3D: o "sol" da cena. Rotacione ele pra apontar pra baixo em diagonal e ligue Shadow > Enabled. Sombra é o que faz o cérebro entender onde as coisas estão no espaço, ela vale o custo.
- WorldEnvironment: crie um Environment novo na propriedade, mude o Background > Mode pra Sky e crie um Sky com material ProceduralSkyMaterial. Pronto, você tem céu e iluminação ambiente decente sem baixar nada.
Esse trio (chão, luz direcional, céu procedural) é o esqueleto de praticamente toda cena 3D de protótipo. Aperte F6 pra rodar a cena: vai dar erro de câmera, e é o que vamos resolver agora, junto com o player.
O player: CharacterBody3D, colisão e câmera
Pra personagem controlado por código, o node certo é o CharacterBody3D. Ele não é simulado pela física como um RigidBody: você dita a velocidade e ele resolve as colisões deslizando nas superfícies, que é exatamente o comportamento que um player precisa.
Crie uma cena nova com essa estrutura e salve como player.tscn:
Player (CharacterBody3D)
├── MeshInstance3D (CapsuleMesh)
├── CollisionShape3D (CapsuleShape3D)
└── CameraPivot (Node3D)
└── SpringArm3D
└── Camera3D
Cada peça tem um motivo:
- CapsuleShape3D é a melhor shape pra personagem. A base arredondada sobe pequenos desníveis e desliza em quinas sem travar, coisa que uma caixa não faz.
- CameraPivot é um Node3D vazio que serve de "pescoço": girar ele no eixo Y gira a câmera ao redor do personagem.
- SpringArm3D é um braço retrátil. A câmera fica na ponta dele, e quando uma parede entra entre a câmera e o player, o braço encolhe sozinho pra câmera não atravessar a parede. Esse node resolve de graça um dos problemas mais chatos de câmera em terceira pessoa.
Posicione o CameraPivot na altura da cabeça (Y em torno de 1,5) e configure o SpringArm3D com Spring Length de 4 ou 5. Ele estica no eixo -Z local, então a câmera filha já fica atrás do pivot naturalmente.
Mapeando o WASD no Input Map
Antes do script, registre as ações de input. Em Project Settings > Input Map, crie cinco ações e associe as teclas:
frente→ Wtras→ Sesquerda→ Adireita→ Dpular→ Espaço
Usar ações em vez de teclas direto no código não é burocracia: é o que permite trocar o mapeamento depois (ou suportar controle) sem tocar uma linha de script.
Movimento WASD do seu jogo 3D no Godot
Agora o script. Anexe ao node Player:
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
const MOUSE_SENSITIVITY = 0.003
@onready var pivot = $CameraPivot
@onready var spring_arm = $CameraPivot/SpringArm3D
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
func _ready():
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
# Impede o braço da câmera de colidir com o próprio player.
spring_arm.add_excluded_object(get_rid())
func _input(event):
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
# Mouse pros lados gira o pivot inteiro no eixo Y.
pivot.rotate_y(-event.relative.x * MOUSE_SENSITIVITY)
# Mouse pra cima/baixo inclina só o braço, com trava pra não capotar.
spring_arm.rotation.x = clamp(
spring_arm.rotation.x - event.relative.y * MOUSE_SENSITIVITY,
-1.2, 0.3
)
if event.is_action_pressed("ui_cancel"):
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
func _physics_process(delta):
if not is_on_floor():
velocity.y -= gravity * delta
if Input.is_action_just_pressed("pular") and is_on_floor():
velocity.y = JUMP_VELOCITY
var input_dir = Input.get_vector("esquerda", "direita", "frente", "tras")
# Transforma o input 2D em direção 3D relativa pra onde a câmera aponta.
var direction = (pivot.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()
O script é curto, mas três pontos merecem explicação porque são onde iniciante trava.
Todo movimento vive no _physics_process. Esse callback roda em passo fixo (60 vezes por segundo por padrão), independente do FPS da máquina. Movimento no _process varia com o framerate e dessincroniza da física, gerando tremor. Regra simples: mexeu em velocity ou posição de corpo físico, é _physics_process.
A gravidade é manual. CharacterBody3D não cai sozinho, você soma a gravidade na velocidade a cada frame de física. Repare que no 3D ela é subtraída (velocity.y -= ...), porque o eixo Y aponta pra cima. É o oposto do 2D, e é o bug mais clássico de quem migra de um pro outro.
A direção é relativa à câmera. O Input.get_vector() devolve um Vector2 onde apertar W dá Y negativo, porque no 3D do Godot "frente" é o -Z. Multiplicar esse vetor pela pivot.basis rotaciona o input pro referencial da câmera: apertar W sempre move pra onde você está olhando, que é o comportamento esperado num jogo em terceira pessoa. Sem essa multiplicação, W moveria sempre pro norte do mundo, não importa pra onde a câmera aponte.
Mouse capturado é o detalhe de qualidade de vida: MOUSE_MODE_CAPTURED prende e esconde o cursor pra câmera girar livre, e o ESC (ui_cancel, que já vem mapeado de fábrica) solta o mouse pra você conseguir fechar a janela em paz.
Instancie o player.tscn dentro da main.tscn (arraste o arquivo da aba FileSystem pra viewport), suba ele um pouco no eixo Y pra não nascer dentro do chão, e rode com F5 definindo a main como cena principal. Você tem um personagem andando, pulando e uma câmera orbitando. Isso já é um jogo 3D funcionando.
Dando um objetivo: moedas com Area3D
Andar pelo mundo é legal por uns dois minutos. Jogo precisa de objetivo, e o mais barato de implementar é coletável. Pra detectar "o player encostou aqui" sem colisão física, o node certo é o Area3D.
Crie a cena moeda.tscn:
Moeda (Area3D)
├── MeshInstance3D (CylinderMesh achatado, ou TorusMesh)
└── CollisionShape3D (CylinderShape3D)
E o script:
extends Area3D
signal coletada
func _ready():
body_entered.connect(_on_body_entered)
func _process(delta):
# Gira no próprio eixo. Rotação é visual, então _process serve.
rotate_y(2.0 * delta)
func _on_body_entered(body):
if body.name == "Player":
coletada.emit()
queue_free()
O sinal body_entered dispara quando um corpo físico entra na área. A checagem por nome funciona pro protótipo; num projeto maior você usaria grupos (body.is_in_group("player")), que aguentam renomear nodes sem quebrar nada.
O sinal coletada fica emitido pra quem quiser ouvir: um script na cena principal pode conectar cada moeda a um contador e mostrar num Label. Espalhe dez moedas pela cena (duplicar com Ctrl+D é seu amigo), e de repente existe um loop de gameplay: vá até ali, pegue aquilo.
Onde esse protótipo vira jogo
Com personagem, câmera e coletável funcionando, você tem a base de um 3D platformer. O caminho natural a partir daqui, em ordem de retorno por esforço:
- Nível de verdade. Empilhe CSGBox3D criando plataformas em alturas diferentes. Pular entre plataformas testa o controle de um jeito que andar no plano não testa, e você vai sentir na hora se a gravidade e a força do pulo estão boas.
- Queda e respawn. Um Area3D gigante abaixo do mapa que devolve o player pro início quando ele cai. Três linhas de código e o nível passa a ter risco.
- Tuning do movimento. SPEED, JUMP_VELOCITY e a gravidade do projeto não têm valor "correto". Roda, sente, ajusta. Esse ciclo é o trabalho real de game feel, e os valores que funcionam pro seu jogo só aparecem testando.
- Um modelo de verdade. Quando cansar da cápsula, importe um glTF (o Godot lê nativamente) e coloque no lugar do MeshInstance3D. A lógica não muda nada.
Fechando
O primeiro jogo 3D no Godot se resume a uma cena com chão, luz e céu, um CharacterBody3D com cápsula, um SpringArm3D segurando a câmera e umas 60 linhas de GDScript. Nenhuma peça dessas é difícil sozinha, o que confunde é não saber quais peças existem e como se encaixam. Agora você sabe.
O erro que eu mais vejo nessa fase é pular pra modelagem, shader e asset bonito antes do movimento estar gostoso. Resista. Personagem de cápsula com controle bom é um jogo melhor que personagem lindo com controle ruim, e o controle você acabou de construir. Itera nele primeiro, o resto é troca de visual.


