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.
Clique com o botão direito do mouse no nó Jogador e selecione Anexar Script para adicionar um novo script a ele. No popup, defina o Modelo para Vazio antes de pressionar o botão Criar.

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 KinematicBody
# 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 velocity = Vector3.ZERO
public class Player : KinematicBody
{
// 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 = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
private Vector3 _velocity = Vector3.Zero;
}
Estas são propriedades comuns para um corpo em movimento. O 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 nos 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 agora. Começamos calculando o vetor de direção de entrada utilizando o objeto global Input, em _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(float 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 += 1f;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
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 += 1f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
}
Aqui, vamos fazer todos os cálculos utilizando a função virtual _physics_process(). Como _process(), ela permite atualizar o nó a cada quadro, mas é desenvolvida especificamente para código relacionado à física, como mover um corpo cinemático ou rígido.
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.
No caso do jogador apertar, por exemplo, W e D simultaneamente, o vetor terá o comprimento de aproximadamente 1.4. Mas se ele apertar só uma tecla, ele terá a largura de 1. Nós queremos que a largura do vetor seja consistente. Para isso, nós podemos chamar o método normalize().
#func _physics_process(delta):
#...
if direction != Vector3.ZERO:
direction = direction.normalized()
$Pivot.look_at(translation + direction, Vector3.UP)
public override void _PhysicsProcess(float delta)
{
// ...
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
}
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.
Neste caso, também obtemos o nó Pivot e chamamos o seu método look_at(). Esse método pega a posição no espaço para olhar em coordenadas globais e a direção para cima. Nesse caso, nós podemos usar a constante Vector3.UP.
Nota
As coordenadas locais de um nó, como translation, são relativas ao seu pai. Já as coordenadas globais são relativas aos eixos principais do mundo que por sua vez se vê no viewport.
No 3D, a propriedade que contem a posição do nó é translation`. Adicionando a direction, obtemos uma posição para olhar que é a um metro de distância do Player.
Então, atualizamos a velocidade. Temos que calcular a velocidade do solo e a velocidade de queda separadamente. Certifique-se de voltar uma aba para que as linhas estejam 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
velocity.x = direction.x * speed
velocity.z = direction.z * speed
# Vertical velocity
velocity.y -= fall_acceleration * delta
# Moving the character
velocity = move_and_slide(velocity, Vector3.UP)
public override void _PhysicsProcess(float delta)
{
// ...
// Ground velocity
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Vertical velocity
_velocity.y -= FallAcceleration * delta;
// Moving the character
_velocity = MoveAndSlide(_velocity, Vector3.Up);
}
Para a velocidade vertical, subtraímos a aceleração da queda multiplicada pelo tempo delta a cada quadro. Observe a utilização do operador -=, que é uma abreviação para variable = variable - ....
Esta linha de código fará com que o nosso jogador caia a cada quadro. Isso pode parecer estranho se ele já está no chão. Mas nós temos que fazer isso para o personagem colidir com o chão a cada quadro.
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, nós chamaremos KinematicBody.move_and_slide(). é um método poderoso da classe ``KinematicBody``que permite que o personagem se mova sutilmente. Se ele acertar a parede no meio do movimento, o motor tentará suavizar isto para você.
A função recebe dois parâmetros: nossa velocidade e nossa direção. Isso move o jogador e devolve a velocidade restante após aplicar as colisões. Quando acerta o chão ou a parede, a função reduzirá ou reiniciará a velocidade naquela direção para você. No nosso caso, armazenar o valor retornado pela função previne o personagem de acumular impulso vertical, o que evitaria do valor ficar tão grande e fazer com que o personagem atravessasse o chão depois de um tempo.
E isso é todo o código necessário para mover o personagem no chão.
Aqui está o código completo de Player.gd como referência.
extends KinematicBody
# 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 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()
$Pivot.look_at(translation + direction, Vector3.UP)
velocity.x = direction.x * speed
velocity.z = direction.z * speed
velocity.y -= fall_acceleration * delta
velocity = move_and_slide(velocity, Vector3.UP)
public class Player : KinematicBody
{
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float 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 += 1f;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
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 += 1f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
// Ground velocity
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Vertical velocity
_velocity.y -= FallAcceleration * delta;
// Moving the character
_velocity = MoveAndSlide(_velocity, Vector3.Up);
}
}
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 Principal. Você pode clicar na aba Principal 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 do mouse sobre o nó Principal e selecione Instanciar Cena Filha.

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 o Pivô do nosso Player, vamos criar um rig básico. Clique com o botão direito do mouse no nó Principal novamente e selecione Adicionar nó filho desta vez. Crie um novo Position3D, nomeie-o como CâmeraPivô e adicione um nó Câmera como filho dele. Sua árvore de cena deve ficar assim.

Observe a caixa de seleção Pré-visualização que aparece na parte superior esquerda quando você tem a Câmera 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 Câmera e ative a visualização da câmera clicando na caixa de seleção.
Na visão de cima, mova a câmera mais ou menos 19 unidades no eixo Z (azul).

Here's where the magic happens. Select the CameraPivot and rotate it -45
degrees around the X axis (using the red circle). You'll see the camera move as
if it was attached to a crane.

Você pode executar a cena apertando :kbd:`F6`e pressione as setas do teclado para movimentar 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.

Com isso, temos tanto o movimento do jogador quanto a visão no lugar. Em seguida, trabalharemos nos monstros.