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.

Uccidere il giocatore

Possiamo uccidere i nemici saltandoci addosso, ma il giocatore non può ancora morire. Risolviamolo.

Vogliamo rilevare di essere toccati da un nemico in modo diverso dallo schiacciamento. Vogliamo che il giocatore muoia quando si muove a terra, ma non se è in aria. Potremmo usare la matematica vettoriale per distinguere i due tipi di collisione. Invece, però, useremo un nodo Area3D, che funziona bene per le hitbox.

Hitbox con il nodo Area

Ritorna alla scena player.tscn e aggiungi un nuovo nodo figlio Area3D. Dagli il nome MobDetector. Aggiungi un nodo CollisionShape3D come suo nodo figlio.

image0

Nell'Ispettore, assegnagli una forma cilindrica.

image1

Ecco un trucco che puoi usare per far avvenire le collisioni solo quando il giocatore è a terra o vicino abbastanza ad essa. Puoi ridurre l'altezza del cilindro e spostarlo in cima al personaggio. In questo modo, quando il giocatore salta, la forma sarà troppo alta per essere colpita dai nemici.

image2

Inoltre, vorresti anche che il cilindro sia più largo della sfera. In questo modo, il giocatore viene colpito prima di scontrarsi e spinto sopra il riquadro di collisione del mostro.

Più largo è il cilindro, più facilmente il giocatore verrà ucciso.

Successivamente, seleziona di nuovo il nodo MobDetector e, nell'Ispettore, disattiva la sua proprietà Monitorable. Ciò impedisce agli altri nodi di fisica di rilevare l'area. La proprietà complementare Monitoring consente di rilevare le collisioni. Quindi, rimuovi Collision -> Layer e imposta la maschera sullo strato "enemies".

image3

Quando le aree rilevano una collisione, emettono segnali. Ne collegheremo uno al nodo Player. Seleziona MobDetector e vai alla scheda Segnali, fai doppio clic sul segnale body_entered e collegalo al nodo Player

image4

MobDetector emetterà body_entered quando un nodo CharacterBody3D o un nodo RigidBody3D vi entra. Poiché maschera solo gli strati di fisica "enemies", rileverà solo i nodi Mob.

Dal livello di codice, faremo due cose: emetteremo un segnale che useremo in seguito per terminare la partita e distruggere il giocatore. Possiamo racchiudere queste operazioni in una funzione die() che ci aiuta ad assegnare un'etichetta descrittiva al codice.

# Emitted when the player was hit by a mob.
# Put this at the top of the script.
signal hit


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


func _on_mob_detector_body_entered(body):
    die()

Terminare il gioco

Possiamo usare il segnale hit di Player per terminare la partita. Tutto ciò che dobbiamo fare è collegarlo al nodo Main e fermare il MobTimer in risposta.

Apri main.tscn, seleziona il nodo Player e, nel pannello Segnali, collega il suo segnale hit al nodo Main.

image5

Ottieni il timer e fermalo nella funzione _on_player_hit().

func _on_player_hit():
    $MobTimer.stop()

Se ora provi il gioco, i mostri smetteranno di apparire quando muori e quelli rimasti abbandoneranno lo schermo.

Nota inoltre come il gioco non si blocca più, né visualizza un errore quando il giocatore muore. Poiché stiamo interrompendo il MobTimer, non viene più attivata la funzione _on_mob_timer_timeout().

Nota anche che la collisione del nemico con il giocatore e la sua morte dipendono dalle dimensioni e dalla posizione delle forme di collisione di Player e di Mob. Potrebbe essere necessario spostarle e ridimensionarle per ottenere un'esperienza di gioco migliore.

Puoi darti una pacca sulla spalla: hai realizzato un prototipo di un gioco completo in 3D, anche se è ancora un po' grezzo.

Da lì aggiungeremo un punteggio, l'opzione di riprovare la partita e vedrai come puoi rendere il gioco molto più vivo con animazioni minimaliste.

Ricapitolo del codice

Ecco gli script completi per i nodi Main, Mob e Player, come riferimento. Puoi usarli per confrontare e verificare il tuo codice.

A partire da main.gd.

extends Node

@export var mob_scene: PackedScene


func _on_mob_timer_timeout():
    # Create a new instance of the Mob scene.
    var mob = mob_scene.instantiate()

    # Choose a random location on the SpawnPath.
    # We store the reference to the SpawnLocation node.
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
    # And give it a random offset.
    mob_spawn_location.progress_ratio = randf()

    var player_position = $Player.position
    mob.initialize(mob_spawn_location.position, player_position)

    # Spawn the mob by adding it to the Main scene.
    add_child(mob)

func _on_player_hit():
    $MobTimer.stop()

Il prossimo è mob.gd.

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)

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

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

Infine, lo script più lungo, player.gd:

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 moving fast af
    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)

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

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

func _on_mob_detector_body_entered(body):
    die()

Ci vediamo alla prossima lezione per aggiungere il punteggio e l'opzione per riprovare.