Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

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.

Right-click the Player node and select Attach Script to add a new script to it. In the popup, set the Template to Empty before pressing the Create button.

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

These are common properties for a moving body. The target_velocity is a 3D vector combining a speed with a direction. Here, we define it as a property because we want to update and reuse its value across frames.

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.

Let's code the movement. We start by calculating the input direction vector using the global Input object, in _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.

In case the player presses, say, both W and D simultaneously, the vector will have a length of about 1.4. But if they press a single key, it will have a length of 1. We want the vector's length to be consistent, and not move faster diagonally. To do so, we can call its normalized() method.

#func _physics_process(delta):
    #...

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(position + 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 position, são relativas ao seu pai. Coordenadas globais, como global_position 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ó é position. Adicionando a direction a ela, obtemos uma posição para olhar que é a um metro de distância do Player.

Then, we update the velocity. We have to calculate the ground velocity and the fall speed separately. Be sure to go back one tab so the lines are inside the _physics_process() function but outside the condition we just wrote above.

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

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.

For the vertical velocity, we subtract the fall acceleration multiplied by the delta time every frame. This line of code will cause our character to fall in every frame, as long as it is not on or colliding with the floor.

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 como 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()
        $Pivot.look_at(position + direction, Vector3.UP)

    # 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()

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.

imagem1

Se você fechou a cena antes, vá até o painel Sistema de Arquivos e clique duas vezes em main.tscn para reabri-la.

To instantiate the Player, right-click on the Main node and select Instantiate Child Scene.

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ó ``Main``novamente e selecione Adicionar nó filho desta vez. Crie um novo Marker3D, nomeie-o como CâmeraPivô e adicione um nó Camera3D 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).

imagem11

imagem5

Na visualização inferior, selecione sua Camera3D e ative a pré-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.

Nota

When using an orthogonal camera in Godot 4, directional shadow quality is dependent on the camera's Far value. The higher the Far value, the further away the camera will be able to see. However, higher Far values also decrease shadow quality as the shadow rendering has to cover a greater distance.

Se sombras direcionais parecerem muito borradas depois de mudar para uma câmera ortogonal, diminua a propriedade Far da câmera para um valor mais baixo, como ``100`. Não diminua esta propriedade Far demais, ou objetos à distância começarão a desaparecer.

image10

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.