Voltar para o Blog
Quest Log

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

Cena 3D estilizada com um personagem cápsula sobre uma plataforma, câmera e luzes de uma game engine

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 → W
  • tras → S
  • esquerda → A
  • direita → D
  • pular → 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.

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

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.