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.

Animação de personagem

Nesta lição final, usaremos as ferramentas de animação embutidas do Godot para fazer nossos personagens flutuarem e baterem asas. Você aprenderá a criar animações no editor e usar o código para dar vida ao seu jogo.

imagem0

Começaremos com uma introdução para usar o editor de animação.

Usando o editor de animação

A engine vem com ferramentas para criar animações no editor. Você pode então usar o código para executá-las e controlá-los em tempo de execução.

Abra a cena do jogador, selecione o nó Player e adicione um nó AnimationPlayer.

O painel Animação aparece no painel inferior.

imagem1

Ele apresenta uma barra de ferramentas e o menu suspenso de animação na parte superior, um editor de pistas no faixa que está atualmente vazio, e opções de filtro, snap e zoom na parte inferior.

Vamos criar uma animação. Clique em Animação -> Novo.

imagem2

Chame a animação de "float".

imagem3

Once you've created the animation, the timeline appears with numbers representing time in seconds.

imagem4

Queremos que a animação inicie a reprodução automaticamente no início do jogo. Além disso, ele deve fazer um loop.

Para fazer isso, você pode clicar no botão com um ícone "A +" na barra de ferramentas de animação e nas setas em loop, respectivamente.

imagem5

Você também pode fixar o editor de animação clicando no ícone de fixação no canto superior direito. Isso evita que ele se dobre quando você clica no visor e desmarca os nós.

|imagem 6|

Defina a duração da animação para 1.2 segundos na parte superior direita do painel.

image7

Você deve ver a faixa cinza se alargar um pouco. Ela mostra o início e o fim da animação e a linha azul vertical é o cursor de tempo.

image8

Você pode clicar e arrastar o controle deslizante no canto inferior direito para aumentar e diminuir o zoom da linha do tempo.

image9

A animação fluida

Com o nó do reprodutor de animação, você pode animar a maioria das propriedades em quantos nós precisar. Observe o ícone de chave ao lado das propriedades no Inspetor. Você pode clicar em qualquer um deles para criar um quadro-chave, um par de tempo e valor para a propriedade correspondente. O quadro-chave é inserido onde seu cursor de tempo está na linha do tempo.

Vamos inserir nossas primeiras chaves. Aqui, animaremos a translação e a rotação do nó Character.

Selecione o Character e no Inspetor expanda a seção Transform. Clique no ícone de chave ao lado de Position e Rotation.

image10

../../_images/curves.webp

Para este tutorial, basta criar uma faixa RESET que é a escolha padrão

Duas faixas aparecem no editor com um ícone de diamante representando cada quadro-chave.

imagem11

You can click and drag on the diamonds to move them in time. Move the position key to 0.3 seconds and the rotation key to 0.1 seconds.

imagem12

Mova o cursor de tempo para 0.5 segundos clicando e arrastando na linha do tempo cinza.

../../_images/timeline_05_click.webp

No Inspetor, defina o eixo Position Y para os metros 0.65 e o eixo Rotation X para 8.

imagem13

Crie um quadro-chave para ambas propriedades

../../_images/second_keys_both.webp

Agora, mova o quadro-chave de posição para 0.7 segundos arrastando-o na linha do tempo.

imagem14

Nota

Uma palestra sobre os princípios da animação está além do escopo deste tutorial. Apenas observe que você não deseja cronometrar e espaçar tudo uniformemente. Em vez disso, os animadores brincam com o tempo e o espaçamento, dois princípios básicos da animação. Você quer variar e contrastar o movimento do seu personagem para fazê-lo parecer vivo.

Mova o cursor de tempo para o final da animação, em 1.2 segundos. Defina a posição Y para cerca de 0.35 e a rotação X para -9 graus. Mais uma vez, crie uma chave para ambas as propriedades.

../../_images/animation_final_keyframes.webp

Você pode visualizar o resultado clicando no botão play ou pressionando Shift + D. Clique no botão parar ou pressione S para parar a reprodução.

image15

Você pode ver que a engine interpola entre seus quadros-chave para produzir uma animação contínua. No momento, porém, o movimento parece muito robótico. Isso ocorre porque a interpolação padrão é linear, causando transições constantes, ao contrário de como os seres vivos se movem no mundo real.

Podemos controlar a transição entre quadros-chave usando curvas de suavisação.

Clique e arraste ao redor das duas primeiras chaves na linha do tempo para usar a seleção em retângulo.

image16

Você pode editar as propriedades de ambas as chaves simultaneamente no Inspector, onde você pode ver uma propriedade de*Suavisação*.

image17

Clique e arraste sobre a curva, puxando-a para a esquerda. Isso suavizará, ou seja, a transição será rápida inicialmente e desacelerada quando o cursor de tempo atingir o próximo quadro-chave.

image18

Reproduza a animação novamente para ver a diferença. A primeira metade já deve parecer um pouco mais animada.

Aplique uma suavização ao segundo quadro-chave na faixa de rotação.

image19

Faça o oposto para o segundo quadro-chave de posição, arrastando-o para a direita.

image20

Sua animação deve ser algo parecido com isto.

image21

Nota

As animações atualizam as propriedades dos nós animados a cada quadro, substituindo os valores iniciais. Se animarmos diretamente o nó Player, isso nos impediria de movê-lo no código. É aqui que o nó Pivô é útil: mesmo que tenhamos animado o Personagem, ainda podemos mover e girar o Pivô e camada muda sobre a animação em um script.

Se você jogar o jogo, a criatura do jogador vai flutuar agora!

Se a criatura estiver um pouco perto demais do chão, você pode mover o Pivô para cima para deslocá-lo.

Controlando a animação por código

Podemos usar o código para controlar a reprodução da animação com base na entrada do jogador. Vamos mudar a velocidade da animação quando o personagem estiver se movendo.

Abra o script do Player clicando no ícone de script próximo a ele.

image22

Em _physics_process(), após a linha onde verificamos o vetor direction, adicione o seguinte código.

func _physics_process(delta):
    #...
    if direction != Vector3.ZERO:
        #...
        $AnimationPlayer.speed_scale = 4
    else:
        $AnimationPlayer.speed_scale = 1

Este código faz com que quando o jogador se mova, multipliquemos a velocidade de reprodução por 4. Quando eles param, nós redefinimos para o normal.

Mencionamos que o Pivot``pode transformar camadas em cima da animação. Podemos fazer o arco do personagem ao pular usando a seguinte linha de código. Adicione-o no final de ``_physics_process().

func _physics_process(delta):
    #...
    $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse

Animando os inimigos

Aqui está outro bom truque com animações no Godot: contanto que você use uma estrutura de nó semelhante, você pode copiá-los para cenas diferentes.

Por exemplo, as cenas Mob e Player têm um nó Pivot e Character, para que possamos reutilizar animações entre elas.

Abra a cena Player, selecione o nó AnimationPlayer e abra a animação "float". Em seguida, clique em Animação > Entendido. Em seguida, abra ``mob.tscn``, crie um nó filho AnimationPlayer e selecione-o. Clique **Animation > Paste e certifique-se de que o botão com um ícone "A+" (Autoplay on Load) e as setas de looping (Animation looping) também estão ligados no editor de animação no painel inferior. É isso; todos os monstros vão agora executar a animação flutuante.

Podemos alterar a velocidade de reprodução com base na random_speed da criatura. Abra o script Inimigo e no final da função initialize(), adicione a seguinte linha.

func initialize(start_position, player_position):
    #...
    $AnimationPlayer.speed_scale = random_speed / min_speed

E com isso, você terminou de programar seu primeiro jogo 3D completo.

Parabéns!

Na próxima parte, vamos recapitular rapidamente o que você aprendeu e dar alguns links para continuar aprendendo mais. Mas por enquanto, aqui estão os Jogador.gd e Mob.gd completos para que você possa comparar com seu código.

Aqui está o script do Jogador.

extends CharacterBody3D

signal hit

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration while in the air, in meters per second squared.
@export var fall_acceleration = 75
# Vertical impulse applied to the character upon jumping in meters per second.
@export var jump_impulse = 20
# Vertical impulse applied to the character upon bouncing over a mob
# in meters per second.
@export var bounce_impulse = 16

var target_velocity = Vector3.ZERO


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 = direction.x + 1
    if Input.is_action_pressed("move_left"):
        direction.x = 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 = direction.z + 1
    if Input.is_action_pressed("move_forward"):
        direction.z = direction.z - 1

    # Prevent diagonal movement being very fast
    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(position + direction,Vector3.UP)
        $AnimationPlayer.speed_scale = 4
    else:
        $AnimationPlayer.speed_scale = 1

    # 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
        target_velocity.y = target_velocity.y - (fall_acceleration * delta)

    # Jumping.
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = jump_impulse

    # Iterate through all collisions that occurred this frame
    # in C this would be for(int i = 0; i < collisions.Count; i++)
    for index in range(get_slide_collision_count()):
        # We get one of the collisions with the player
        var collision = get_slide_collision(index)

        # If the collision is with ground
        if collision.get_collider() == null:
            continue

        # If the collider is with a mob
        if collision.get_collider().is_in_group("mob"):
            var mob = collision.get_collider()
            # we check that we are hitting it from above.
            if Vector3.UP.dot(collision.get_normal()) > 0.1:
                # If so, we squash it and bounce.
                mob.squash()
                target_velocity.y = bounce_impulse
                # Prevent further duplicate calls.
                break

    # Moving the Character
    velocity = target_velocity
    move_and_slide()

    $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse

# And this function at the bottom.
func die():
    hit.emit()
    queue_free()

func _on_mob_detector_body_entered(body):
    die()

E o script do Inimigo.

extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10
# Maximum speed of the mob in meters per second.
@export var max_speed = 18

# Emitted when the player jumped on the mob
signal squashed

func _physics_process(_delta):
    move_and_slide()

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -90 and +90 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

    # We calculate a random speed (integer)
    var random_speed = randi_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

    $AnimationPlayer.speed_scale = random_speed / min_speed

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

func squash():
    squashed.emit()
    queue_free() # Destroy this node