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.

Animazione dei personaggi

In questa lezione finale, useremo gli strumenti di animazione integrati in Godot per far fluttuare e svolazzare i nostri personaggi. Imparerai a progettare animazioni nell'editor e a utilizzare codice per dare vita al tuo gioco.

image0

Cominceremo con un'introduzione all'utilizzo dell'editor di animazione.

Utilizzo dell'editor di animazione

Il motore è dotato di strumenti per creare animazioni nell'editor. È poi possibile utilizzare il codice per riprodurle e controllarle in fase di esecuzione.

Apri la scena del giocatore, selezionare il nodo Player e aggiungere un nodo AnimationPlayer.

Il pannello Animazione è visualizzato nel pannello inferiore.

image1

Contiene una barra degli strumenti e il menu a discesa dell'animazione in alto, un editor di tracce al centro attualmente vuoto, nonché opzioni di filtro, scatto e zoom in basso.

Creiamo un'animazione. Clicca su Animazione -> Nuova.

image2

Denomina l'animazione "float".

image3

Una volta creata l'animazione, sarà visualizzata la linea del tempo con dei numeri che rappresentano il tempo in secondi.

image4

Vogliamo che l'animazione venga riprodotta automaticamente all'inizio del gioco. Inoltre, dovrebbe ripetersi in loop.

Per fare ciò, puoi cliccare sul pulsante di riproduzione automatica (Riproduzione automatica) nella barra degli strumenti di animazione e sulle frecce di ripetizione, rispettivamente.

image5

Puoi anche fissare l'editor di animazione cliccando sull'icona a forma di puntina in alto a destra. Ciò impedisce che si chiuda quando clicchi sulla finestra e deselezioni i nodi.

image6

Imposta la durata dell'animazione su 1.2 secondi nell'angolo in alto a destra del pannello.

Dovresti vedere il nastro grigio allargarsi leggermente. Ti mostra l'inizio e la fine dell'animazione e la linea blu verticale è il cursore del tempo.

image7

È possibile cliccare e trascinare lo slider in basso a destra per ingrandire o rimpicciolire la linea del tempo.

image8

L'animazione fluttuante

Con il nodo del AnimationPlayer, puoi animare gran parte delle proprietà su tutti i nodi che desideri. Nota l'icona a forma di chiave accanto alle proprietà nell'Ispettore. Puoi cliccare su una di esse per creare un fotogramma chiave, che è una coppia di valore e tempo per la proprietà corrispondente. Il fotogramma chiave è inserito nel punto in cui si trova il cursore del tempo nella linea del tempo.

Inseriamo le nostre prime chiavi. Qui animeremo sia la posizione sia la rotazione del nodo Character.

Seleziona Character e nell'Ispettore espandi la sezione Transform. Clicca sull'icona a forma di chiave accanto a Position e Rotation.

image9

../../_images/curves.webp

Per questo tutorial, crea semplicemente una traccia RESET, che è la scelta predefinita

Due tracce sono visualizzate nell'editor, ciascuna delle quali è rappresentata da un'icona a forma di diamante.

image10

Puoi cliccare e trascinare i diamanti per spostarli nel tempo. Sposta la chiave della posizione a 0.3 secondi e la chiave della rotazione a 0.1 secondi.

image11

Sposta il cursore del tempo su 0.5 secondi cliccando e trascinando sulla timeline grigia, o immettendoli nel campo di input.

timeline_05_click

Nell'Ispettore, imposta l'asse Y di Position a 0.65 metri e l'asse X di Rotation a 8.

Se non vedi le proprietà nel pannello Ispettore, clicca prima nuovamente sul nodo Character nel pannello Scena.

image12

Crea un fotogramma chiave per entrambe le proprietà

second_keys_both

Ora, sposta il fotogramma chiave di posizione a 0.7 secondi trascinandolo sulla linea del tempo.

image13

Nota

Una lezione sui principi dell'animazione va oltre lo scopo di questo tutorial. Nota solo che non dovresti distribuire uniformemente i tempi e gli spazi. Gli animatori giocano invece con i tempi e gli spazi, due principi fondamentali dell'animazione. È necessario compensare e contrastare i movimenti dei personaggi per farli sembrare vivi.

Sposta il cursore del tempo alla fine dell'animazione, a 1.2 secondi. Imposta la posizione Y a circa 0.35 e la rotazione X a -9 gradi. Una volta ancora, crea una chiave per entrambe le proprietà.

animation_final_keyframes

Puoi visualizzare in anteprima il risultato cliccando sul pulsante di riproduzione o premendo Maiusc + D. Clicca sul pulsante di arresto o premi S per interrompere la riproduzione.

image14

Come puoi vedere, il motore interpola tra i fotogrammi chiave per produrre un'animazione continua. Al momento, però, il movimento sembra molto robotico. Questo perché l'interpolazione predefinita è lineare, causando transizioni costanti, a differenza di come si muovono gli esseri viventi nel mondo reale.

Possiamo controllare la transizione tra i fotogrammi chiave utilizzando le curve di easing.

Clicca e trascina attorno alle prime due chiavi nella linea del tempo per selezionarle.

image15

Puoi modificare contemporaneamente le proprietà di entrambe le chiavi nell'Ispettore, dove è possibile vedere una proprietà Easing.

image16

Clicca e trascina la curva, tirandola verso sinistra. Questo applicherà un ease-out, ovvero la transizione sarà inizialmente rapida e rallenterà man mano che il cursore del tempo raggiunge il fotogramma chiave successivo.

image17

Riproduci l'animazione nuovamente per vedere la differenza. La prima metà dovrebbe già essere un po' più dinamica.

Applica un ease-out al secondo fotogramma chiave nella traccia di rotazione.

image18

Fai l'opposto per il secondo fotogramma chiave di posizione, trascinandolo verso destra.

image19

La tua animazione dovrebbe assomigliare a questa.

image20

Nota

Le animazioni aggiornano le proprietà dei nodi animati a ogni fotogramma, sovrascrivendo i valori iniziali. Se animassimo direttamente il nodo Player, non potremmo spostarlo in codice. È qui che il nodo Pivot torna utile: anche se abbiamo animato il Character, possiamo comunque spostare e ruotare il Pivot e sovrapporre all'animazione le modifiche in uno script.

Se avvii il gioco, la creatura del giocatore ora fluttuerà!

Se la creatura è un po' troppo vicina al pavimento, puoi spostare Pivot verso l'alto per compensare.

Controllo dell'animazione in codice

Possiamo usare codice per controllare la riproduzione dell'animazione secondo l'input del giocatore. Cambiamo la velocità dell'animazione quando il personaggio è in movimento.

Apri lo script Player cliccando sull'icona dello script accanto ad esso.

image21

In _physics_process(), dopo la riga in cui verifichiamo il vettore direction, aggiungiamo il seguente codice.

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

Questo codice fa in modo che quando il giocatore si muove, moltiplichiamo la velocità di riproduzione per 4. Quando si ferma, la riportiamo alla velocità normale.

Abbiamo accennato che Pivot potrebbe sovrapporre all'animazione le trasformazioni. Possiamo fare in modo che il personaggio si muova ad arco durante il salto, utilizzando la seguente riga di codice. Aggiungila alla fine di _physics_process().

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

Animazione dei mob

Ecco un altro bel trucco con le animazioni in Godot: finché si utilizza una struttura di nodi simile, è possibile copiarle in scene diverse.

Ad esempio, entrambe le scene Mob e Player hanno un nodo Pivot e un nodo Character, quindi possiamo riciclare le animazioni tra di esse.

Apri la scena Player, seleziona il nodo AnimationPlayer e clicca su Animazione > Gestisci animazioni.... Clicca sul pulsante Copia animazione negli appunti (due quadratini) accanto all'animazione float. Clicca su OK per chiudere la finestra.

Successivamente, apri mob.tscn, crea un nodo figlio AnimationPlayer e selezionalo. Clicca su Animazione > Gestisci animazioni, quindi su Aggiungi libreria. Dovrebbe apparire il messaggio "Verrà creata una libreria globale". Lascia vuoto il campo di testo e clicca su OK. Clicca sull'icona Incolla (gli appunti) e dovrebbe apparire nella finestra. Clicca su OK per chiudere la finestra.

Quindi, assicurati che il pulsante di riproduzione automatica (Riproduzione automatica) e le frecce di ripetizione (Ripetizione di animazione) siano attivati anche nell'editor di animazione nel pannello inferiore. Ecco fatto; tutti i mostri ora riprodurranno l'animazione "float".

Possiamo cambiare la velocità di riproduzione secondo la proprietà random_speed della creatura. Apriamo lo script del Mob e alla fine della funzione initialize() aggiungiamo la seguente riga.

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

E con questo, hai finito di programmare il tuo primo gioco completo in 3D.

Congratulazioni!

Nella prossima parte, riepilogheremo brevemente ciò che hai imparato e ti forniremo alcuni collegamenti per continuare a saperne di più. Per ora, ecco i file player.gd e mob.gd completi, così potrai confrontare il tuo codice con questi.

Ecco lo script di Player.

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()
        # Setting the basis property will affect the rotation of the node.
        $Pivot.basis = Basis.looking_at(direction)
        $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 lo script di Mob.

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 -45 and +45 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