Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Movendo o jogador com código
É hora de programar! Nós vamos usar as ações de entradas que criamos na última parte para movimentar o jogador.
Nota
Para esse projeto, vamos seguir as convenções de nomeação do Godot.
GDScript: Classes (nós) utilizam PascalCase, variáveis e funções utilizam snake_case e constantes utilizam letras maiúsculas (veja Guia de Estilo GDScript).
C#: Classes, variáveis exportadas e métodos usam PascalCase; campos privados usam _camelCase; variáveis locais e parâmetros usam camelCase (consulte Guia de Estilo C#). Tome cuidado para digitar os nomes dos métodos exatamente ao conectar sinais.
Clique com o botão direito no nó Player e selecione Attach Script (Anexar Script) para adicionar um novo script a ele. Na janela pop-up, defina o Template (Modelo) como Empty (Vazio) antes de pressionar o botão Create (Criar). Definimos como Empty porque queremos escrever nosso próprio código para o movimento do jogador.

Vamos começar com as propriedades da classe. Nós vamos definir a velocidade do movimento, a aceleração da queda representando a gravidade e a velocidade que usaremos para mover o jogador.
extends CharacterBody3D
# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
@export var fall_acceleration = 75
var target_velocity = Vector3.ZERO
using Godot;
public partial class Player : CharacterBody3D
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
// How fast the player moves in meters per second.
[Export]
public int Speed { get; set; } = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration { get; set; } = 75;
private Vector3 _targetVelocity = Vector3.Zero;
}
Essas são propriedades comuns para um corpo em movimento. O target_velocity é um vetor 3D que combina uma velocidade com uma direção. Aqui, nós o definimos como uma propriedade porque queremos atualizar e reutilizar seu valor ao longo dos quadros.
Nota
Os valores são bem diferentes do código 2D porque as distâncias são em metros. Enquanto que no 2D, mil unidades (pixels) podem corresponder a somente metade da largura da tela, e no 3D, é um quilômetro.
Vamos programar o movimento. Começamos calculando o vetor de direção de entrada usando o objeto global Input dentro de _physics_process().
func _physics_process(delta):
# We create a local variable to store the input direction.
var direction = Vector3.ZERO
# We check for each move input and update the direction accordingly.
if Input.is_action_pressed("move_right"):
direction.x += 1
if Input.is_action_pressed("move_left"):
direction.x -= 1
if Input.is_action_pressed("move_back"):
# Notice how we are working with the vector's x and z axes.
# In 3D, the XZ plane is the ground plane.
direction.z += 1
if Input.is_action_pressed("move_forward"):
direction.z -= 1
public override void _PhysicsProcess(double delta)
{
// We create a local variable to store the input direction.
var direction = Vector3.Zero;
// We check for each move input and update the direction accordingly.
if (Input.IsActionPressed("move_right"))
{
direction.X += 1.0f;
}
if (Input.IsActionPressed("move_left"))
{
direction.X -= 1.0f;
}
if (Input.IsActionPressed("move_back"))
{
// Notice how we are working with the vector's X and Z axes.
// In 3D, the XZ plane is the ground plane.
direction.Z += 1.0f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.Z -= 1.0f;
}
}
Aqui, em vez de _process(), faremos todos os cálculos usando a função virtual _physics_process(). Ela foi projetada especificamente para código relacionado à física, como mover um corpo cinemático ou rígido. Ela atualiza o nó usando intervalos de tempo fixos.
Ver também
Para saber mais sobre a diferença entre _process() e _physics_process(), veja Processamento Ocioso e Físico.
Começamos inicializando uma variável direction para Vector3.ZERO. Em seguida, verificamos se o jogador está pressionando uma ou mais das entradas move_* e atualizamos os componentes do vetor x e z de acordo. Estes correspondem aos eixos do plano do solo.
Essas quatro condições nos dão oito possibilidades e oito direções possíveis.
Se o jogador pressionar, por exemplo, W e D simultaneamente, o vetor terá um comprimento de aproximadamente 1.4. Mas se pressionar apenas uma tecla, terá comprimento 1. Queremos que o comprimento do vetor seja consistente e que o personagem não se mova mais rápido na diagonal. Para isso, podemos chamar o método normalized().
func _physics_process(delta):
#...
if direction != Vector3.ZERO:
direction = direction.normalized()
# Setting the basis property will affect the rotation of the node.
$Pivot.basis = Basis.looking_at(direction)
public override void _PhysicsProcess(double delta)
{
// ...
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
// Setting the basis property will affect the rotation of the node.
GetNode<Node3D>("Pivot").Basis = Basis.LookingAt(direction);
}
}
Aqui, nós só normalizamos o vetor na direção que tem um comprimento maior que zero, o que significa que o jogador apertou uma tecla de direção.
Calculamos a direção para a qual o $Pivot está olhando criando um Basis que aponta na direção de direction.
Em seguida, atualizamos a velocidade. Precisamos calcular separadamente a velocidade no chão e a velocidade de queda. Certifique-se de voltar uma aba para que as linhas fiquem dentro da função _physics_process(), mas fora da condição que acabamos de escrever.
func _physics_process(delta):
#...
if direction != Vector3.ZERO:
#...
# Ground Velocity
target_velocity.x = direction.x * speed
target_velocity.z = direction.z * speed
# Vertical Velocity
if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
target_velocity.y = target_velocity.y - (fall_acceleration * delta)
# Moving the Character
velocity = target_velocity
move_and_slide()
public override void _PhysicsProcess(double delta)
{
// ...
if (direction != Vector3.Zero)
{
// ...
}
// Ground velocity
_targetVelocity.X = direction.X * Speed;
_targetVelocity.Z = direction.Z * Speed;
// Vertical velocity
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
{
_targetVelocity.Y -= FallAcceleration * (float)delta;
}
// Moving the character
Velocity = _targetVelocity;
MoveAndSlide();
}
A função CharacterBody3D.is_on_floor() retorna true se o corpo colidiu com o chão neste quadro. É por isso que aplicamos a gravidade ao Player apenas enquanto está no ar.
Para a velocidade vertical, subtraímos a aceleração da queda multiplicada pelo delta de tempo a cada quadro. Essa linha de código fará com que nosso personagem caia em todos os quadros, desde que não esteja sobre ou colidindo com o chão.
O motor de física só pode detectar interações com paredes, com chão ou com outros corpos durante um determinado quadro se o movimento e a colisão acontecerem. Nós usaremos essa propriedade mais para frente quando fizermos o código para pular.
Na última linha, chamamos de CharacterBody3D.move_and_slide() que é um método poderoso da classe CharacterBody3D que permite mover um personagem sutilmente. Se atingir uma parede a meio de um movimento, o motor vai tentar suavisar para você. Ele usa o valor velocity nativo do CharacterBody3D
E isso é todo o código necessário para mover o personagem no chão.
Aqui está o código completo de player.gd para referência.
extends CharacterBody3D
# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
@export var fall_acceleration = 75
var target_velocity = Vector3.ZERO
func _physics_process(delta):
var direction = Vector3.ZERO
if Input.is_action_pressed("move_right"):
direction.x += 1
if Input.is_action_pressed("move_left"):
direction.x -= 1
if Input.is_action_pressed("move_back"):
direction.z += 1
if Input.is_action_pressed("move_forward"):
direction.z -= 1
if direction != Vector3.ZERO:
direction = direction.normalized()
# Setting the basis property will affect the rotation of the node.
$Pivot.basis = Basis.looking_at(direction)
# Ground Velocity
target_velocity.x = direction.x * speed
target_velocity.z = direction.z * speed
# Vertical Velocity
if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
target_velocity.y = target_velocity.y - (fall_acceleration * delta)
# Moving the Character
velocity = target_velocity
move_and_slide()
using Godot;
public partial class Player : CharacterBody3D
{
// How fast the player moves in meters per second.
[Export]
public int Speed { get; set; } = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration { get; set; } = 75;
private Vector3 _targetVelocity = Vector3.Zero;
public override void _PhysicsProcess(double delta)
{
var direction = Vector3.Zero;
if (Input.IsActionPressed("move_right"))
{
direction.X += 1.0f;
}
if (Input.IsActionPressed("move_left"))
{
direction.X -= 1.0f;
}
if (Input.IsActionPressed("move_back"))
{
direction.Z += 1.0f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.Z -= 1.0f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
// Setting the basis property will affect the rotation of the node.
GetNode<Node3D>("Pivot").Basis = Basis.LookingAt(direction);
}
// Ground velocity
_targetVelocity.X = direction.X * Speed;
_targetVelocity.Z = direction.Z * Speed;
// Vertical velocity
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
{
_targetVelocity.Y -= FallAcceleration * (float)delta;
}
// Moving the character
Velocity = _targetVelocity;
MoveAndSlide();
}
}
Testando o movimento do nosso personagem
Nós vamos colocar nosso jogador na cena Main para testá-lo. Para isso, nós precisamos instanciar o jogador e depois adicionar a câmera. Diferente do 2D, no 3D você não vê nada caso a janela de exibição não tenha uma câmera apontada para algo.
Salve a cena Player e abra a cena Main. Você pode clicar na aba Main no topo do editor para fazê-lo.

Se você fechou a cena antes, vá até o painel Sistema de Arquivos e clique duas vezes em main.tscn para reabri-la.
Para instanciar o Player, clique com o botão direito no nó Main e selecione Instantiate Child Scene.

Na pop-up, de um clique duplo em player.tscn. O personagem deve aparecer no centro da janela de exibição.
Adicionando uma câmera
Vamos adicionar a câmera a seguir. Como fizemos com nosso Player's Pivot, vamos criar um rig básico. Clique com o botão direito no nó Main novamente e selecione Adicionar Nó Filho. Crie um novo Marker3D e nomeie-o como CameraPivot. Selecione CameraPivot e adicione um nó filho Camera3D a ele. Sua árvore de cena deve ser semelhante a esta.

Observe a caixa de seleção Preview que aparece no canto superior esquerdo da visualização 3D quando a Camera está selecionada. Você pode clicar nela para visualizar a projeção da câmera dentro do jogo.

Vamos usar o Pivô para girar a câmera como se estivesse em uma grua. Vamos primeiro dividir a visualização 3D para poder navegar livremente pela cena e ver o que a câmera vê.
Na barra de ferramentas logo acima do janela de exibição, clique em Ver, depois 2 Viewports. Você também pode pressionar Ctrl + 2 (Cmd + 2 on macOS).


Na visualização inferior, selecione sua Camera3D e ative a pré-visualização da câmera clicando na caixa de seleção.

Na visualização superior, certifique-se de que sua Camera3D está selecionada e mova a câmera cerca de 19 unidades no eixo Z (arraste a seta azul).

É aqui que a mágica acontece. Selecione o CameraPivot e gire-o -45 graus em torno do eixo X (usando o círculo vermelho). Você verá a câmera se mover como se estivesse presa a um guindaste.

Você pode executar a cena pressionando F6 (Cmd + R no macOS) e pressionar as teclas de seta para mover o personagem.

Podemos ver algum espaço vazio em torno do personagem devido à projeção em perspectiva. Neste jogo, vamos usar uma projeção ortográfica para melhor enquadrar a área de jogo e facilitar para o jogador ler as distâncias.
Selecione a Câmera novamente e no Inspetor, defina a Projeção para Ortogonal e o Tamanho para 19. O personagem agora deve parecer mais achatado e o chão deve preencher o fundo.
Nota
Ao usar uma câmera ortográfica no Godot 4, a qualidade das sombras direcionais depende do valor Far da câmera. Quanto maior o valor Far, mais distante a câmera poderá enxergar. No entanto, valores mais altos de Far também reduzem a qualidade das sombras, pois a renderização das sombras precisa cobrir uma distância maior.
Se as sombras direcionais parecerem muito desfocadas após mudar para uma câmera ortogonal, diminua a propriedade Far da câmera para um valor mais baixo, como 100. Não diminua muito essa propriedade Far ou os objetos distantes começarão a desaparecer.

Teste sua cena e você deve ser capaz de se mover em todas as 8 direções e não bugando através do chão!
Com isso, temos tanto o movimento do jogador quanto a visão no lugar. Em seguida, trabalharemos nos monstros.