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.

imagem0

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

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

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)

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)

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)

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.

imagem1

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.

imagem2

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.

imagem3

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.

imagem4

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).

imagem5

Na visualização inferior, selecione Câmera e ative a visualização da câmera clicando na caixa de seleção.

|imagem 6|

Na visão de cima, mova a câmera mais ou menos 19 unidades no eixo Z (azul).

image7

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.

image8

Você pode executar a cena apertando :kbd:`F6`e pressione as setas do teclado para movimentar o personagem.

image9

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.

image10

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