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.

Saltare e schiacciare mostri

In questa parte aggiungeremo l'abilità di saltare e schiacciare i mostri. Nella prossima lezione, faremo morire il giocatore quando un mostro lo colpisce a terra.

Per prima cosa, dobbiamo modificare alcune impostazioni riguardo le interazioni fisiche. Entriamo nel mondo degli strati di fisica.

Controllo delle iterazioni fisiche

I corpi fisici hanno accesso a due proprietà complementari: strati e maschere. Gli strati definiscono su quali strati fisici si trova un oggetto.

Le maschere controllano gli strati che un corpo ascolterà e rileverà. Questo influenza il rilevamento delle collisioni. Quando si desidera che due corpi interagiscano, è necessario che almeno uno dei due abbia una maschera corrispondente all'altro.

Se tutto questo ti confonde, non preoccuparti, vedremo tre esempi in un secondo.

Il punto importante è che puoi usare strati e maschere per filtrare le interazioni fisiche, controllare le prestazioni ed eliminare la necessità di condizioni aggiuntive nel tuo codice.

Per valore predefinito, tutti i corpi e le aree fisiche impostano a 1 sia lo strato sia la maschera. Ciò significa che entrano tutti in collisione tra loro.

Gli strati di fisica sono rappresentati da numeri, ma possiamo dare loro dei nomi per tenere traccia di cosa è cosa.

Impostazione dei nomi degli strati

Diamo un nome ai nostri strati di fisica. Andiamo su Progetto -> Impostazioni del progetto.

image0

Nel menu a sinistra, scorri verso il basso fino a Nomi degli strati -> Fisica 3D. Vedrai un elenco di strati con un campo accanto a ciascuno di essi sulla destra. Puoi impostarne i nomi lì. Chiama i primi tre livelli rispettivamente player, enemies e world.

image1

Ora possiamo assegnarli ai nostri nodi di fisica.

Assegnare strati e maschere

Nella scena Main, seleziona il nodo Ground. Nell'Ispettore, espandi la sezione Collisione. Lì puoi vedere gli strati e le maschere del nodo come una griglia di pulsanti.

image2

Il terreno fa parte del mondo, quindi vogliamo che faccia parte del terzo strato. Clicca sul pulsante illuminato per disattivare il primo Strato e attivare il terzo. Quindi, disattiva la Maschera cliccandoci sopra.

image3

Come menzionato in precedenza, la proprietà Mask consente a un nodo di ascoltare l'interazione con altri oggetti fisici, ma non ne abbiamo bisogno per avere collisioni. Ground non ha bisogno di ascoltare nulla; serve solo a impedire alle creature di cadere.

Nota che puoi cliccare sul pulsante "..." sul lato destro delle proprietà per visualizzare un elenco di caselle di spunta con nomi.

image4

Successivamente ci sono Player e Mob. Apri player.tscn facendo doppio clic sul file nel pannello FileSystem.

Seleziona il nodo Player e imposta la sua Collisione -> Mask sia su "enemies" sia su "world". Puoi lasciare la proprietà predefinita Layer cosi com'è, perché il primo è lo strato "player".

image5

Quindi, apri la scena Mob facendo doppio clic su mob.tscn e seleziona il nodo Mob.

Imposta Collision -> Layer su "enemies" e disattiva Collision -> Mask, lasciando la maschera vuota.

image6

Queste impostazioni significano che i mostri si muoveranno l'uno attraverso l'altro. Se vuoi che i mostri si scontrino e scivolino l'uno contro l'altro, attiva la maschera "enemies".

Nota

I mob non hanno bisogno di mascherare lo strato "world" perché si muovono solo sul piano XZ. Nessuna gravità gli viene applicata apposta.

Saltare

La meccanica del salto stessa richiede solo due righe di codice. Apriamo lo script Player. Ci serve un valore per controllare la forza del salto e aggiornare _physics_process() per programmare il salto.

Dopo la riga che definisce fall_acceleration, all'inizio dello script, aggiungi jump_impulse.

#...
# Vertical impulse applied to the character upon jumping in meters per second.
@export var jump_impulse = 20

All'interno di _physics_process(), aggiungi il seguente codice prima del blocco di codice di move_and_slide().

func _physics_process(delta):
    #...

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

    #...

Ecco tutto ciò che ti serve per saltare!

Il metodo is_on_floor() è un'utilità della classe CharacterBody3D. Restituisce true se il corpo si è scontrato con il pavimento in questo frame. Ecco perché applichiamo la gravità al Player: così che ci scontriamo con il pavimento invece di fluttuarci sopra come i mostri.

Se il personaggio è sul pavimento e il giocatore preme "jump", gli diamo immediatamente un'enorme velocità verticale. Nei giochi, si desidera che i controlli siano reattivi e un'accelerazione istantanea come questa, sebbene poco realistica, è una bella sensazione.

Nota come l'asse Y è positivo verso l'alto. A differenza del 2D, dove l'asse Y è positivo verso il basso.

Schiacciare i mostri

Aggiungiamo ora la meccanica dello schiacciamento. Faremo rimbalzare il personaggio sui mostri e li uccideremo allo stesso tempo.

Dobbiamo rilevare le collisioni con un mostro e distinguerle dalle collisioni con il pavimento. Per farlo, possiamo usare la funzionalità dei gruppi di Godot.

Apri di nuovo la scena mob.tscn e seleziona il nodo Mob. Vai al pannello Segnali sulla destra per visualizzare un elenco di segnali. Clicca sul pulsante + per aprire la finestra di dialogo Crea un nuovo gruppo.

image7

Inserisci "mob" nel campo Nome e clicca sul pulsante Ok.

image8

Il gruppo "mob" è ora mostrato nella sezione Gruppi della scena.

image9

Un'icona appare nel pannello Scena per indicare che il nodo fa parte di almeno un gruppo.

image10

Ora possiamo utilizzare il gruppo da codice per distinguere le collisioni con i mostri da quelle con il pavimento.

Programmare la meccanica di schiacciamento

Ritorna allo script Player per programmare lo schiacciamento e il rimbalzo.

In cima allo script, ci serve un'altra proprietà, bounce_impulse. Quando schiacciamo un nemico, non vogliamo necessariamente che il personaggio vada così in alto come quando salta.

# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
@export var bounce_impulse = 16

Quindi, dopo il blocco di codice Jumping che abbiamo aggiunto sopra in _physics_process(), aggiungiamo il seguente ciclo. Con move_and_slide(), a volte, Godot fa muovere il corpo più volte di seguito per rendere più fluido il movimento del personaggio. Quindi dobbiamo ripetere il ciclo su tutte le collisioni che potrebbero essersi verificate.

A ogni iterazione del ciclo, verifichiamo se siamo atterrati su un mob. Se sì, lo uccidiamo e rimbalziamo.

Con questo codice, se non si sono verificate collisioni su un determinato frame, il ciclo non verrà eseguito.

func _physics_process(delta):
    #...

    # Iterate through all collisions that occurred this frame
    for index in range(get_slide_collision_count()):
        # We get one of the collisions with the player
        var collision = get_slide_collision(index)

        # If there are duplicate collisions with a mob in a single frame
        # the mob will be deleted after the first collision, and a second call to
        # get_collider will return null, leading to a null pointer when calling
        # collision.get_collider().is_in_group("mob").
        # This block of code prevents processing duplicate collisions.
        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

Sono davvero tante nuove funzioni. Ecco qualche informazione in più su di esse.

Le funzioni get_slide_collision_count() e get_slide_collision() provengono entrambe dalla classe CharacterBody3D e sono correlate a move_and_slide().

get_slide_collision() restituisce un oggetto KinematicCollision3D che contiene informazioni su dove e come si è verificata la collisione. Ad esempio, utilizziamo la sua proprietà get_collider per verificare se siamo entrati in collisione con un "mob" chiamando is_in_group() su di esso: collision.get_collider().is_in_group("mob").

Nota

Il metodo is_in_group() è disponibile su ogni Node.

Per verificare che stiamo atterrando sul mostro, usiamo il prodotto scalare vettoriale: Vector3.UP.dot(collision.get_normal()) > 0.1. La normale alla collisione è un vettore 3D perpendicolare al piano in cui si è verificata la collisione. Il prodotto scalare ci permette di confrontarla con la direzione su.

Con i prodotti scalari, quando il risultato è maggiore di 0, i due vettori formano un angolo inferiore a 90 gradi. Un valore superiore a 0.1 ci indica che siamo più o meno sopra il mostro.

Dopo aver gestito la logica di schiacciamento e rimbalzo, interrompiamo il ciclo in anticipo tramite l'istruzione break per impedire ulteriori chiamate duplicate a mob.squash(), che potrebbero altrimenti causare bug indesiderati, come contare il punteggio più volte per una sola uccisione.

Stiamo chiamando una funzione indefinita, mob.squash(), quindi dobbiamo aggiungerla alla classe Mob.

Apri lo script mob.gd facendo doppio clic su di esso nel pannello FileSystem. In cima allo script, vogliamo definire un nuovo segnale chiamato squashed ("schiacciato"). In fondo, puoi aggiungere la funzione squash, che emette il segnale e distrugge il mob.

# Emitted when the player jumped on the mob.
signal squashed

# ...


func squash():
    squashed.emit()
    queue_free()

Nota

Quando si utilizza C#, Godot creerà automaticamente gli eventi appropriati per tutti i segnali che terminano con EventHandler, consulta Segnali in C#.

Utilizzeremo il segnale per aggiungere punti al punteggio nella prossima lezione.

Con questo, dovresti essere in grado di uccidere i mostri saltandoci sopra. Puoi premere F5 per provare il gioco e impostare main.tscn come scena principale del tuo progetto.

Però, il giocatore non morirà ancora. Ci lavoreremo nella prossima parte.