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.