Voltar para o Blog
Quest Log

Controle de primeira pessoa no Godot 4: FPS controller 3D

Vista em primeira pessoa de uma cena 3D de game engine com a mira no centro e um corredor à frente

Controle de primeira pessoa no Godot 4 do zero: cena com CharacterBody3D, mouse capturado, olhar com yaw e pitch, WASD com Input.get_vector e pulo.

Montar um controle de primeira pessoa no Godot é um daqueles marcos que fazem o projeto parecer um jogo de verdade. De repente você está dentro da cena, olhando ao redor com o mouse e andando pelos cantos com WASD. A boa notícia é que o Godot 4 dá todas as peças prontas (CharacterBody3D, Camera3D, captura de mouse, física de movimento), e o trabalho é basicamente conectar essas peças do jeito certo. Nesse tutorial a gente monta um FPS controller do zero, passo a passo, com código tipado que roda como está.

Controle de primeira pessoa no Godot 4: FPS controller 3D

Esse tutorial assume que você já viu o básico de 3D na engine: sabe que cena é uma árvore de nodes, já criou um chão com colisão e já rodou uma câmera. Se isso ainda é novo, vale dar um passo atrás e fazer o tutorial de primeiro jogo 3D no Godot antes, porque aqui a gente foca no que muda na primeira pessoa: a câmera presa no rosto do personagem e o mouse controlando o olhar.

A ideia central de um FPS controller é simples quando você separa as responsabilidades. O corpo do personagem cuida do movimento e da colisão. A câmera cuida do olhar. O mouse alimenta as duas rotações, mas em eixos diferentes. Acertar essa divisão é o que faz tudo funcionar sem aquela sensação de câmera bêbada.

Montando a cena do player

Crie uma cena nova com um CharacterBody3D como raiz e salve como player.tscn. O CharacterBody3D é o node feito para personagens controlados por código: você dita a velocidade e ele resolve as colisões deslizando nas paredes, em vez de ser jogado pela física como um RigidBody faria.

A estrutura mínima é essa:

Player (CharacterBody3D)
├── CollisionShape3D   (CapsuleShape3D)
└── Camera3D

No CollisionShape3D, crie um CapsuleShape3D na propriedade Shape. A cápsula é o formato clássico para player porque desliza bem em quinas e degraus. Um Height de 2.0 e um Radius de 0.4 dão um personagem de tamanho humano. Como a cápsula nasce centrada na origem, suba o CollisionShape3D para que a base fique no chão: posicione ele em Y = 1.0, assim o pé do personagem encosta em Y = 0.

A parte que define a primeira pessoa é a Camera3D. Ela é filha direta do CharacterBody3D e fica na altura dos olhos. Posicione a câmera em algo como Y = 1.6, um pouco abaixo do topo da cápsula, para dar a sensação de que você está enxergando pela cabeça do personagem. Não precisa de malha visível: na primeira pessoa o jogador não vê o próprio corpo, então pode deixar sem MeshInstance3D mesmo.

Um detalhe importante: a câmera é filha do corpo de propósito. Quando o corpo girar no yaw, a câmera vai junto, automaticamente. Só o pitch a gente aplica direto na câmera, e já vamos ver por quê.

Capturando o mouse

Num jogo de primeira pessoa o cursor não pode ficar solto na tela: ele precisa sumir e o movimento do mouse vira o olhar. Isso é o mouse capturado. No Godot 4 você liga isso com uma linha:

func _ready() -> void:
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

Com MOUSE_MODE_CAPTURED, o cursor fica escondido e preso no centro da janela, e cada movimento do mouse passa a ser reportado como deslocamento. É exatamente o que precisamos para olhar ao redor.

Mas prender o mouse para sempre é um pesadelo: o jogador não consegue nem sair da janela para clicar em outra coisa. Por isso a regra de ouro é deixar o Esc liberar o cursor. A gente trata isso comparando o modo atual e alternando entre capturado e visível.

Olhando com o mouse: yaw no corpo, pitch na câmera

Aqui está o coração do FPS controller, e o conceito que mais confunde quem está começando. Existem duas rotações diferentes quando você olha em primeira pessoa:

  • Yaw: virar para os lados (esquerda e direita). É uma rotação em torno do eixo Y.
  • Pitch: olhar para cima e para baixo. É uma rotação em torno do eixo X.

A pergunta é: quem gira? E a resposta é o pulo do gato. O yaw gira o corpo inteiro (o CharacterBody3D), porque para onde o personagem olha no plano horizontal é também para onde ele deve andar. Já o pitch gira só a câmera, porque olhar para o céu é puramente visual e não deve afetar o movimento.

Se você aplicasse o pitch no corpo também, o personagem inteiro inclinaria para frente quando você olhasse para baixo. Aí o WASD passaria a empurrar você para dentro do chão ou para o céu, porque o "para frente" do corpo estaria apontando na diagonal. Separar yaw (corpo) de pitch (câmera) é o que mantém o movimento sempre rente ao chão, não importa para onde você esteja olhando.

A leitura do mouse acontece em _unhandled_input, checando se o evento é um InputEventMouseMotion. A propriedade event.relative traz quanto o mouse andou desde o último quadro, em X e Y:

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
        var motion: InputEventMouseMotion = event
        # Yaw: gira o corpo no eixo Y
        rotate_y(-motion.relative.x * mouse_sensibilidade)
        # Pitch: gira a câmera no eixo X
        camera.rotate_x(-motion.relative.y * mouse_sensibilidade)
        # Trava o pitch para não capotar
        camera.rotation.x = clamp(camera.rotation.x, -PI / 2.0, PI / 2.0)

Repare nos sinais negativos: sem eles o olhar fica invertido (você move o mouse para a direita e a visão vai para a esquerda). E note o clamp no pitch: ele prende o ângulo da câmera entre -PI/2 e PI/2 (ou seja, -90 e +90 graus em radianos). Sem esse limite, o jogador conseguiria olhar tão para cima a ponto da câmera virar de cabeça para baixo, o que destrói a sensação de primeira pessoa.

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

Sobre a sensibilidade do mouse

A mouse_sensibilidade é um multiplicador pequeno que converte pixels de movimento do mouse em radianos de rotação. Um valor como 0.003 costuma ser confortável. Quanto maior o número, mais a câmera gira para o mesmo movimento de mão. Exporte essa variável com @export para conseguir calibrar direto no Inspector, sem ficar mudando código a cada teste, porque a sensibilidade ideal é muito pessoal e muda conforme o mousepad e a DPI de cada um.

Uma coisa que vale entender: a gente multiplica pela sensibilidade, mas não multiplica por delta na rotação do olhar. Isso é de propósito. O event.relative já é a quantidade de movimento físico do mouse naquele quadro, então o olhar acompanha a mão diretamente, independente de FPS. O delta entra no movimento e na gravidade, que veremos agora, mas não no olhar.

Movimento WASD relativo ao olhar

O movimento acontece em _physics_process, que roda em passo fixo de física, o lugar certo para mexer com colisão. A entrada das teclas vem de Input.get_vector, que lê quatro ações de uma vez e já devolve um vetor 2D normalizado:

var input_dir: Vector2 = Input.get_vector("mover_esquerda", "mover_direita", "mover_frente", "mover_tras")

Para isso funcionar, você precisa cadastrar essas quatro ações no Input Map do projeto (Project > Project Settings > Input Map), mapeando A, D, W e S. Se você ainda não está confortável criando ações, dá uma olhada no guia de Input Map e controles no Godot, que cobre esse cadastro em detalhe.

O get_vector é melhor do que ler tecla por tecla porque ele já normaliza a diagonal. Sem isso, andar na diagonal (frente + lado) deixaria você mais rápido do que andar reto, um bug clássico de quem soma vetores na mão.

Agora o ponto crucial: esse input_dir está em coordenadas locais (frente, trás, lados em relação ao próprio personagem). A gente precisa transformar isso na direção do mundo, levando em conta para onde o corpo está apontando. É aí que entra a base do transform do personagem:

var direcao: Vector3 = (transform.basis * Vector3(input_dir.x, 0.0, input_dir.y)).normalized()

O transform.basis carrega a orientação atual do corpo. Multiplicar o vetor de input por ela rotaciona a direção desejada para alinhar com o yaw do personagem. Por isso o yaw tinha que estar no corpo e não na câmera: é o corpo que define o "para frente" do movimento. Quando você gira com o mouse e aperta W, anda exatamente para onde está olhando no plano horizontal.

Gravidade, pulo e move_and_slide()

Falta a parte vertical. O personagem precisa cair quando não está no chão e pular quando o jogador manda. A velocidade vertical fica no eixo Y do velocity, que é uma propriedade que o próprio CharacterBody3D já oferece.

A gravidade a gente acumula a cada quadro enquanto o personagem não está no chão, usando is_on_floor() para checar. O pulo é um empurrão para cima de uma só vez, dado apenas quando o personagem está com os pés no chão (senão dá para pular no ar infinitamente):

if not is_on_floor():
    velocity.y -= gravidade * delta

if Input.is_action_just_pressed("pular") and is_on_floor():
    velocity.y = forca_pulo

Repare que a gravidade é multiplicada por delta (ela é uma aceleração contínua, então depende do tempo), mas a força do pulo não (é um impulso instantâneo). Por fim, depois de definir as velocidades horizontal e vertical, chamamos move_and_slide(). Esse é o método que aplica o velocity, move o corpo e resolve as colisões deslizando nas superfícies, tudo de uma vez. Sem chamar ele, o personagem não sai do lugar.

Script completo e comentado

Juntando tudo, aqui está o player.gd para anexar ao CharacterBody3D. Está inteiro tipado e comentado, pronto para rodar no Godot 4:

extends CharacterBody3D

# Velocidade de caminhada em metros por segundo
@export var velocidade: float = 6.0
# Força do pulo (velocidade vertical inicial do salto)
@export var forca_pulo: float = 5.0
# Sensibilidade do mouse: converte pixels em radianos
@export var mouse_sensibilidade: float = 0.003
# Aceleração da gravidade
@export var gravidade: float = 18.0

# Referência à câmera filha, pega no carregamento da cena
@onready var camera: Camera3D = $Camera3D

func _ready() -> void:
    # Prende e esconde o cursor no centro da janela
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _unhandled_input(event: InputEvent) -> void:
    # Esc alterna entre cursor preso e cursor livre
    if event.is_action_pressed("ui_cancel"):
        if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
            Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
        else:
            Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

    # Só olha com o mouse quando o cursor está capturado
    if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
        var motion: InputEventMouseMotion = event
        # Yaw: gira o corpo inteiro no eixo Y (define para onde anda)
        rotate_y(-motion.relative.x * mouse_sensibilidade)
        # Pitch: gira apenas a câmera no eixo X (puramente visual)
        camera.rotate_x(-motion.relative.y * mouse_sensibilidade)
        # Trava o pitch entre -90 e +90 graus para não virar de cabeça para baixo
        camera.rotation.x = clamp(camera.rotation.x, -PI / 2.0, PI / 2.0)

func _physics_process(delta: float) -> void:
    # Aplica gravidade enquanto estiver no ar
    if not is_on_floor():
        velocity.y -= gravidade * delta

    # Pulo: só com os pés no chão
    if Input.is_action_just_pressed("pular") and is_on_floor():
        velocity.y = forca_pulo

    # Lê WASD como um vetor 2D já normalizado
    var input_dir: Vector2 = Input.get_vector("mover_esquerda", "mover_direita", "mover_frente", "mover_tras")
    # Transforma a direção local na direção do mundo, alinhada com o yaw do corpo
    var direcao: Vector3 = (transform.basis * Vector3(input_dir.x, 0.0, input_dir.y)).normalized()

    if direcao != Vector3.ZERO:
        # Tem input: move na direção que olha
        velocity.x = direcao.x * velocidade
        velocity.z = direcao.z * velocidade
    else:
        # Sem input: para na horizontal (sem afetar a queda no Y)
        velocity.x = move_toward(velocity.x, 0.0, velocidade)
        velocity.z = move_toward(velocity.z, 0.0, velocidade)

    # Aplica o velocity, move o corpo e resolve as colisões
    move_and_slide()

Anexe esse script ao CharacterBody3D, cadastre as ações mover_esquerda, mover_direita, mover_frente, mover_tras e pular no Input Map, jogue o player num chão com colisão e rode. Você deve conseguir olhar ao redor, andar para onde olha, pular e liberar o mouse com Esc.

Próximos passos

Com a base no lugar, dá para evoluir sem reescrever nada: agachar (mudando a altura da cápsula e a posição da câmera), correr (multiplicando a velocidade enquanto uma tecla está pressionada), head bob (balanço sutil da câmera ao andar) e interação com objetos via raycast saindo da câmera. Cada um desses é uma camada por cima do que já temos, e nenhum deles muda a estrutura de yaw no corpo, pitch na câmera, movimento relativo à base e move_and_slide().

A parte difícil de aprender Godot sozinho não é escrever um script desses, é saber a ordem das coisas e por que cada decisão foi tomada, em vez de copiar trechos soltos que não se encaixam. Se você quer sair desse modo de juntar pedaços e seguir um caminho estruturado, do movimento básico até um jogo completo, vale conhecer o melhor curso de Godot, onde cada peça vem na sequência certa e com o motivo por trás. O FPS controller deixa de ser um truque isolado e vira fundamento de tudo que você construir depois.

Perguntas frequentes

Qual node usar para um personagem de primeira pessoa no Godot 4?

Use um CharacterBody3D como corpo do player, com um CollisionShape3D de cápsula para a colisão e um Camera3D como filho, posicionado na altura dos olhos. O CharacterBody3D não é simulado pela física como um RigidBody: você define a velocidade e chama move_and_slide() para resolver as colisões. É o node certo para qualquer movimento controlado por código.

Por que separar a rotação do corpo (yaw) da rotação da câmera (pitch)?

Porque cada eixo tem um papel diferente. O yaw (girar em torno do eixo Y) define para onde o personagem aponta e deve mover, então ele gira o corpo inteiro. O pitch (olhar para cima e para baixo) é só visual e deve girar apenas a câmera. Se você girasse o corpo no pitch, o personagem inclinaria e o movimento WASD ficaria errado, andando para o chão ou para o céu.

Como capturar e liberar o mouse no Godot 4?

Para prender o cursor no centro da janela e usar o movimento do mouse para olhar, defina Input.mouse_mode = Input.MOUSE_MODE_CAPTURED. Para liberar o cursor de volta (por exemplo ao apertar Esc), use Input.MOUSE_MODE_VISIBLE. É comum capturar no _ready() e alternar com uma tecla durante o jogo.

Como ler o movimento do mouse para olhar em primeira pessoa?

Leia o evento dentro de _unhandled_input(event) e verifique se ele é um InputEventMouseMotion. A propriedade event.relative traz quanto o mouse moveu em X e Y desde o último quadro. Use o X para o yaw (girar o corpo) e o Y para o pitch (girar a câmera), multiplicando pela sensibilidade.

Por que aplicar clamp no pitch da câmera?

Sem limite, o jogador conseguiria olhar tão para cima ou para baixo que a câmera viraria de cabeça para baixo, o que quebra a sensação de primeira pessoa. O clamp prende o ângulo do pitch entre cerca de -90 e +90 graus (em radianos), travando a visão no chão e no teto sem deixar a câmera capotar.