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.

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.

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

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

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)

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

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

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.

Para instanciar o Player, clique com o botão direito no nó Main e selecione 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 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.

imagem3

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.

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.

image6

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

image7

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

image8

Você pode executar a cena pressionando F6 (Cmd + R no macOS) e pressionar as teclas de seta para mover 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

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.

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.