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.

Die Animation des Charakters

In dieser letzten Lektion, werden wir Godots integrierte Animations-Tools verwenden um unseren Charakter scheben und schlenkern zu lassen. Sie werden lernen, Animationen im Editor zu entwerfen und Code zu verwenden, um das Spiel lebendiger wirken zu lassen.

image0

Wir beginnen mit einer Einführung in die Verwendung des Animations-Editors.

Verwendung des Animations-Editors

Die Engine verfügt über Tools zur Erstellung von Animationen im Editor. Sie können dann Code verwenden, um diese während des Spiels abzuspielen und zu steuern.

Öffnen Sie die Player-Szene, wählen Sie den Player-Node und fügen Sie einen AnimationPlayer-Node hinzu.

Das Animation-Dock erscheint in dem Unteren Bedienfeld.

image1

Es enthält eine Toolbar und das Dropdown-Menü für Animationen im oberen Bereich, in der Mitte einen Track-Editor, der derzeit leer ist und darunter Filter-, Einrast- und Zoomoptionen.

Lassen Sie uns nun eine Animation erstellen. Klicken Sie auf Animation -> Neu.

image2

Nennen Sie die Animation "float".

image3

Sobald Sie die Animation erstellt haben, erscheint die Zeitleiste mit Zahlen, in der die Zeit in Sekunden dargestellt wird.

image4

Wir wollen, dass die Animation automatisch zu Beginn des Spiels startet. Außerdem soll sie in einer Schleife laufen.

Klicken Sie dazu jeweils ein Mal auf den Button mit dem "A+" Icon in der Animations-Toolbar und auf den mit den Schleifenpfeilen.

image5

Sie können den Animations-Editor auch anheften, indem Sie auf das Stecknadel-Icon in der oberen rechten Ecke klicken. Dies verhindert dass er sich zusammenklappt, wenn Sie auf den Viewport klicken und die Auswahl der Nodes aufheben.

|image6|

Legen Sie die Animationsdauer auf 1.2 Sekunden in der oberen rechten Ecke des Docks fest.

|image7|

Sie sollten sehen, dass das graue Band etwas erweitert wird. Es zeigt Ihnen den Anfang und das Ende ihrer Animation, und die vertikale blaue Linie ist Ihr Zeitcursor.

|image8|

Durch das Bewegen des Schiebereglers oben rechts kann in der Zeitachse gezoomt werden.

|image9|

Die Schwebe-Animation

Mit dem Animationsplayer-Node, können Sie fast alle Eigenschaften von beliebig vielen Nodes animieren. Beachten Sie das Schlüssel-Icon neben den Eigenschaften im Inspektor. Sie können auf ein beliebiges dieser Icons klicken, um ein Keyframe zu erstellen, ein Zeit- und Wertepaar für die entsprechende Eigenschaft. Das Keyframe wird dort eingefügt wo sich Ihr Zeitcursor in dem Zeitstrahl befindet.

Fügen wir unsere ersten Keys ein. Hier werden wir sowohl die Position als auch die Drehung des Character-Nodes animieren.

Wählen Sie die Character aus und erweitern Sie im Inspektor den Abschnitt Transformation. Klicken Sie auf das Schlüssel-Icon neben Position, und Rotation.

|image10|

../../_images/curves.webp

Für dieses Tutorial erstellen Sie einfach RESET-Track(s), was die Default-Einstellung darstellt

Im Editor erscheinen zwei Tracks mit einem Rauten-Icon für jedes Keyframe.

|image11|

Sie können die Rauten per Drag&Drop in der Zeit verschieben. Stellen Sie den Positions-Key auf 0,3 Sekunden und den Rotations-Key auf 0,1 Sekunden.

|image12|

Bewegen Sie den Zeitcursor auf 0,5 Sekunden, indem Sie auf die graue Zeitleiste klicken und sie ziehen.

../../_images/timeline_05_click.webp

Setzen Sie im Inspektor die Y-Achse der Position auf 0,65 Meter und die X-Achse der Rotation auf 8.

|image13|

Erstellen Sie einen Keyframe für beide Eigenschaften

../../_images/second_keys_both.webp

Verschieben Sie nun den Positions-Keyframe auf 0,7 Sekunden, indem Sie ihn auf der Zeitachse ziehen.

|image14|

Bemerkung

Eine Lektion über die Prinzipien der Animation sprengt leider den Rahmen dieses Tutorials. Merken Sie sich einfach, dass Sie nicht unbedingt alles gleichmäßig räumlich und zeitlich verteilen wollen. Stattdessen spielen Animatoren mit Timing und Abständen, zwei zentralen Animationsprinzipien. Sie wollen die Bewegungen Ihres Charakters versetzen und kontrastieren, damit er sich lebendig anfühlt.

Bewegen Sie den Zeitcursor an das Ende der Animation, bei 1,2 Sekunden. Setzen Sie die Y-Position auf etwa 0.35 und die X-Rotation auf -9 Grad. Erstellen Sie auch hier einen Key für beide Eigenschaften.

../../_images/animation_final_keyframes.webp

Sie können eine Vorschau des Ergebnisses anzeigen, indem Sie auf den Wiedergabe-Button klicken oder Umschalt + D drücken. Klicken Sie auf den Stopp-Button oder drücken Sie S, um die Wiedergabe zu beenden.

|image15|

Wie Sie sehen können, interpoliert die Engine zwischen den Werten der Keyframes um eine kontinuierliche Animation zu erzeugen. Im Moment wirkt die Bewegung allerdings sehr roboterhaft. Das liegt daran dass die Default-Interpolation linear ist, was zu konstanten Übergängen führt, anders als bei lebendigen Dingen in der realen Welt.

Wir können die Übergänge zwischen Keyframes mit Hilfe von Easing-Kurven steueren.

Klicken und ziehen Sie, um die ersten beiden Keyframes in dem Zeitstrahl um sie auszuwählen.

|image16|

Sie können die Eigenschaften beider Keyframes gleichzeitig im Inspektor bearbeiten, wo Sie die Eigenschaft Easing sehen können.

|image17|

Klicken und ziehen Sie die Kurve nach links. Dadurch ergibt sich ein "Ease-out", d. h. der Übergang ist anfangs schnell und wird langsamer, wenn der Zeitcursor das nächste Keyframe erreicht.

|image18|

Spielen Sie die Animation erneut ab um den Unterschied zu sehen. Die erste Hälfte er Animation sollte etwas schwungvoller sein.

Wenden Sie auf das zweite Keyframe in dem Rotations-Track einen Ease-Out an.

image19

Ziehen Sie das Keyframe für die zweite Position anders als vorher nach rechts.

image20

Ihre fertige Animation sollte ungefähr so aussehen.

image21

Bemerkung

Animationen aktualisieren die Eigenschaften der animierten Nodes bei jedem Bild und überschreiben die Anfangswerte. Wenn wir den Player-Node direkt animieren würden, könnten wir ihn im Code nicht verschieben. Hier kommt der Pivot-Node ins Spiel: Auch wenn wir den Charakter animiert haben, können wir den Pivot bewegen und drehen und Änderungen in einem Skript über die Animation legen.

Wenn Sie jetzt das Spiel spielen, wird der Spieler Charakter schweben!

Wenn die Kreatur ein wenig zu nahe am Boden ist, können Sie den Pivot nach oben bewegen, um das auszugleichen.

Steuerung der Animation im Code

Wir können Code verwenden um die Wiedergabe der Animation mit den Eingaben des Spielers zu steuern. Ändern wir die Geschwindigkeit der Animation, wenn der Charakter sich bewegt.

Öffnen Sie das Player-Skript, indem Sie auf das Skript-Icon neben dem Node klicken.

image22

Fügen Sie in _physics_process() nach der Zeile, in der wir den Vektor direction überprüfen, folgenden Code ein.

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

Dieser Code sorgt dafür, dass, die Wiedergabegeschwindigkeit mit 4 multipliziert wird, wenn der Spieler sich bewegt. Wenn er anhält, setzen wir sie auf den Standardwert zurück.

Wir haben bereits erwähnt, dass der Pivot Transformationen über die Animation legen kann. Mit der folgenden Codezeile können wir den Character beim Springen einen Bogen machen lassen. Fügen Sie sie am Ende von _physics_process() ein.

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

Die Mobs animieren

Hier ist ein weiterer netter Trick für Animationen in Godot: Solange Sie eine ähnliche Nodestruktur verwenden, können Sie dieselbe Animation in verschiedene Szenen kopieren.

Zum Beispiel haben sowohl die Mob- als auch die Player-Szene einen Pivot- und einen Character-Node, so daß wir Animationen zwischen ihnen wiederverwenden können.

Öffnen Sie die Player-Szene, wählen Sie den AnimationPlayer-Node und öffnen Sie die Animation "float". Dann klicken Sie auf Animation > Kopieren. Öffnen Sie dann mob.tscn, erstellen Sie einen AnimationPlayer-Child-Node und wählen Sie ihn aus. Klicken Sie auf Animation > Einfügen und vergewissern Sie sich, dass der Button mit dem "A+"-Icon (Beim Laden automatisch abspielen) und die Schleifenpfeile (Animationswiederholung) auch im Animations-Editor im Unteren Bedienfeld aktiviert sind. Das war's: Alle Monster spielen jetzt die Schwebeanimation ab.

Wir können die Wiedergabegeschwindigkeit basierend auf der random_speed Eigenschaft der Mobs verändern. Öffnen Sie das Skript des Mobs und fügen Sie am Ende der initialize() Funktion die folgende Zeile ein.

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

Und damit haben Sie ihr erstes komplettes 3D-Spiel fertig programmiert.

Herzlichen Glückwunsch!

Im nächsten Teil werden wir das Gelernte noch einmal kurz zusammenfassen und Ihnen einige Links geben, wenn Sie noch mehr lernen möchten. Aber erstmal sind hier nochmal die vollständigen Player.gd und Mob.gd Skripte, damit Sie Ihren Code damit vergleichen können.

Hier ist das Player-Skript.

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

Und hier das Mob-Skript.

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