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.

Utilizzare i CharacterBody2D/3D

Introduzione

Godot offre diversi oggetti di collisione, sia per il rilevamento sia per la risposta alle collisioni. Decidere quale utilizzare per il proprio progetto può risultare complicato. Comprendere il funzionamento di ciascuno di essi, i loro vantaggi e svantaggi può evitare problemi e semplificare lo sviluppo. In questo tutorial, esamineremo il nodo CharacterBody2D e mostreremo alcuni esempi del suo utilizzo.

Nota

Sebbene questo documento utilizzi CharacterBody2D nei suoi esempi, gli stessi concetti si applicano anche in 3D.

Cos'è un corpo di personaggio?

CharacterBody2D serve per implementare corpi controllati tramite codice. I corpi di personaggio rilevano le collisioni con altri corpi in movimento, ma non sono influenzati dalle proprietà fisiche del motore, come la gravità o l'attrito. Se da un lato ciò significa che è necessario scrivere codice per crearne il comportamento, dall'altro offre un controllo più preciso su come si muovono e reagiscono.

Nonostante il nome CharacterBody2D, lo si può utilizzare anche per altri oggetti fisici che richiedono una logica di movimento manuale precisa e informazioni dettagliate sulle collisioni, come piattaforme mobili o proiettili complessi.

Nota

Questo documento presuppone che tu abbia familiarità con i vari corpi fisici di Godot. Si consiglia di leggere prima Introduzione alla fisica per una panoramica delle opzioni fisiche.

Suggerimento

Un CharacterBody2D può essere influenzato dalla gravità e da altre forze, ma è necessario calcolare il movimento tramite codice. Il motore di fisica non muoverà un CharacterBody2D.

Movimento e collisione

Quando si muove un CharacterBody2D, non bisogna impostare direttamente la sua proprietà position. È invece necessario usare i metodi move_and_collide() o move_and_slide(). Questi metodi muovono il corpo lungo un determinato vettore e rilevano le collisioni.

Avvertimento

Bisogna gestire il movimento dei corpi fisici nel callback _physics_process().

I due metodi di movimento servono a scopi diversi e, più avanti in questo tutorial, vedrai degli esempi di come funzionano.

move_and_collide

Questo metodo accetta un solo parametro obbligatorio: un Vector2 che indica il movimento relativo del corpo. In genere, è il vettore della velocità moltiplicato per il passo temporale del frame (delta). Se il motore rileva una collisione in qualsiasi punto lungo questo vettore, il corpo si fermerà immediatamente. In tal caso, il metodo restituirà un oggetto KinematicCollision2D.

KinematicCollision2D è un oggetto che contiene dati sulla collisione e sull'oggetto che entra in collisione. Tramite questi dati, è possibile calcolare una reazione alla collisione.

La funzione move_and_collide è particolarmente utile quando basta semplicemente muovere il corpo e rilevare le collisioni, senza una reazione automatica in caso di collisione. Ad esempio, se si desidera che un proiettile rimbalzi su un muro, è possibile modificare direttamente l'angolo della velocità quando si rileva una collisione. Vedi di seguito per un esempio.

move_and_slide

Il metodo move_and_slide() è concepito per semplificare la gestione delle collisioni nel caso comune in cui si desidera che un corpo scivoli lungo un altro. È particolarmente utile nei giochi piattaforme o con visuale dall'alto, ad esempio.

Quando si chiama la funzione move_and_slide(), questa utilizza una serie di proprietà del nodo per calcolare il suo comportamento di scorrimento. Queste proprietà si possono trovare nell'Ispettore o impostate tramite codice.

  • velocity - valore predefinito: Vector2( 0, 0 )

    Questa proprietà rappresenta il vettore della velocità del corpo in pixel al secondo. La funzione move_and_slide() modificherà automaticamente questo valore in caso di collisione.

  • motion_mode - valore predefinito: MOTION_MODE_GROUNDED

    Questa proprietà serve tipicamente per distinguere tra movimento a scorrimento laterale e movimento dall'alto verso il basso. Con il valore predefinito, è possibile usare i metodi is_on_floor(), is_on_wall() e is_on_ceiling() per rilevare il tipo di superficie con cui il corpo è a contatto, e il corpo interagirà con le superfici inclinate. Con MOTION_MODE_FLOATING, tutte le collisioni saranno considerate "muri".

  • up_direction - valore predefinito: Vector2( 0, -1 )

    Questa proprietà consente di definire quali superfici il motore deve considerare come pavimento. Il suo valore permette di utilizzare i metodi is_on_floor(), is_on_wall() e is_on_ceiling() per rilevare il tipo di superficie con cui il corpo è a contatto. Il valore predefinito indica che il lato superiore delle superfici orizzontali sarà considerato "terreno".

  • floor_stop_on_slope - valore predefinito: true

    Questo parametro impedisce a un corpo di scivolare lungo le pendenze quando è fermo.

  • wall_min_slide_angle - valore predefinito: 0.261799 (in radianti, equivalente a 15 gradi)

    Questo è l'angolo minimo di scivolamento consentito al corpo quando si scontra con una pendenza.

  • floor_max_angle - valore predefinito: 0.785398 (in radianti, equivalente a 45 gradi)

    Questo parametro è l'angolo massimo prima che una superficie non sia più considerata un "pavimento".

Esistono molte altre proprietà che si possono usare per modificare il comportamento del corpo in circostanze specifiche. Consultare la documentazione di CharacterBody2D per i dettagli completi.

Rilevare le collisioni

Usando move_and_collide(), la funzione restituisce direttamente KinematicCollision2D, che è possibile usare nel proprio codice.

Usando move_and_slide() è possibile che ci siano più collisioni, mentre viene calcolata la risposta dello scivolamento. Per elaborare queste collisioni, usare get_slide_collision_count() e get_slide_collision():

# Using move_and_collide.
var collision = move_and_collide(velocity * delta)
if collision:
    print("I collided with ", collision.get_collider().name)

# Using move_and_slide.
move_and_slide()
for i in get_slide_collision_count():
    var collision = get_slide_collision(i)
    print("I collided with ", collision.get_collider().name)

Nota

get_slide_collision_count() conta solo le volte in cui il corpo è entrato in collisione e ha cambiato direzione.

Consulta KinematicCollision2D per i dettagli sui dati di collisione restituiti.

Quale metodo di movimento utilizzare?

Una domanda frequente tra i nuovi utenti di Godot è: "Come decido quale funzione di movimento usare?". Spesso, la risposta è move_and_slide() perché sembra più semplice, ma non è necessariamente così. Si può dire che move_and_slide() è un caso speciale, mentre move_and_collide() è più generale. Ad esempio, i due frammenti di codice seguenti generano la stessa risposta di collisione:

../../_images/k2d_compare.gif
# using move_and_collide
var collision = move_and_collide(velocity * delta)
if collision:
    velocity = velocity.slide(collision.get_normal())

# using move_and_slide
move_and_slide()

Tutto ciò che fai con move_and_slide() può essere fatto anche con move_and_collide(), ma potrebbe richiedere un po' più di codice. Tuttavia, come vedremo negli esempi seguenti, ci sono casi in cui move_and_slide() non fornisce la risposta desiderata.

Nell'esempio precedente, move_and_slide() altera automaticamente la variabile velocity. Questo perché quando il personaggio entra in collisione con l'ambiente, la funzione ricalcola internamente la velocità per riflettere il rallentamento.

Ad esempio, se il personaggio cade a terra, non vorresti che accumuli velocità verticale a causa dell'effetto della gravità. Vorresti invece che la sua velocità verticale sia azzerata.

move_and_slide() potrebbe anche ricalcolare la velocità del corpo cinematico più volte in un ciclo, poiché, per produrre un movimento fluido, muove il personaggio e lo fa collidere fino a cinque volte come predefinito. Al termine del processo, la nuova velocità del personaggio è disponibile all'uso nel frame successivo.

Esempi

Per osservare questi esempi in azione, scarica il progetto di esempio: character_body_2d_starter.zip

Movimento e muri

Se hai scaricato il progetto di esempio, questo esempio si trova in "basic_movement.tscn".

Per questo esempio, aggiungi un CharacterBody2D con due elementi figlio: uno Sprite2D e un CollisionShape2D. Usa il file Godot "icon.svg" come texture di Sprite2D (trascinalo dal pannello Filesystem alla proprietà Texture di Sprite2D). Nella proprietà Shape di CollisionShape2D, seleziona "Nuovo RectangleShape2D" e ridimensiona il rettangolo in modo che si adatti all'immagine dello sprite.

Nota

Consulta Panoramica del movimento 2D per esempi di come implementare uno schema di movimento in 2D.

Allega uno script al CharacterBody2D e aggiungi il codice seguente:

extends CharacterBody2D

var speed = 300

func get_input():
    var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    velocity = input_dir * speed

func _physics_process(delta):
    get_input()
    move_and_collide(velocity * delta)

Esegui questa scena e noterai che move_and_collide() funziona come previsto, muovendo il corpo lungo il vettore velocità. Ora vediamo cosa succede quando aggiungi degli ostacoli. Aggiungi un StaticBody2D con una forma di collisione rettangolare. Per la visibilità, è possibile usare uno Sprite2D, un Polygon2D o attivare "Forme di collisione visibili" dal menu "Debug".

Esegui di nuovo la scena e prova a muoverti verso l'ostacolo. Noterai che CharacterBody2D non riesce a penetrare l'ostacolo. Tuttavia, prova a muoverti verso l'ostacolo a un angolo e scoprirai che l'ostacolo agisce come colla: sembra che il corpo rimanga incastrato.

Questo accade perché non c'è una risposta alla collisione. move_and_collide() arresta il movimento del corpo quando avviene una collisione. Dobbiamo programmare la risposta che vogliamo dalla collisione.

Prova a cambiare la funzione in move_and_slide() e a eseguire di nuovo.

move_and_slide() fornisce una risposta predefinita alla collisione che fa scivolare il corpo lungo l'oggetto di collisione. Questo è utile per molti tipi di giochi e potrebbe essere tutto ciò che serve per ottenere il comportamento desiderato.

Rimbalzo/riflessione

E se non vuoi una risposta scivolosa alla collisione? In questo esempio ("bounce_and_collide.tscn" nel progetto di esempio), abbiamo un personaggio che spara proiettili e vogliamo che i proiettili rimbalzino sui muri.

Questo esempio utilizza tre scene. La scena principale contiene il giocatore (Player) e i muri (Wall). Il proiettile (Bullet) e il muro (Wall) sono scene separate, in modo da poterle istanziare.

Il Player è controllato dai tasti w e s per andare avanti e indietro. La mira utilizza il puntatore del mouse. Ecco il codice per il Player, utilizzando move_and_slide():

extends CharacterBody2D

var Bullet = preload("res://bullet.tscn")
var speed = 200

func get_input():
    # Add these actions in Project Settings -> Input Map.
    var input_dir = Input.get_axis("backward", "forward")
    velocity = transform.x * input_dir * speed
    if Input.is_action_just_pressed("shoot"):
        shoot()

func shoot():
    # "Muzzle" is a Marker2D placed at the barrel of the gun.
    var b = Bullet.instantiate()
    b.start($Muzzle.global_position, rotation)
    get_tree().root.add_child(b)

func _physics_process(delta):
    get_input()
    var dir = get_global_mouse_position() - global_position
    # Don't move if too close to the mouse pointer.
    if dir.length() > 5:
        rotation = dir.angle()
        move_and_slide()

E il codice per il Bullet:

extends CharacterBody2D

var speed = 750

func start(_position, _direction):
    rotation = _direction
    position = _position
    velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    var collision = move_and_collide(velocity * delta)
    if collision:
        velocity = velocity.bounce(collision.get_normal())
        if collision.get_collider().has_method("hit"):
            collision.get_collider().hit()

func _on_VisibilityNotifier2D_screen_exited():
    # Deletes the bullet when it exits the screen.
    queue_free()

L'azione avviene in _physics_process(). Dopo aver usato move_and_collide(), se si verifica una collisione, viene restituito un oggetto KinematicCollision2D (altrimenti, il valore restituito è null).

Se viene restituita una collisione, utilizziamo la normale (normal) della collisione per riflettere la velocità (velocity) del proiettile con il metodo Vector2.bounce().

Se l'oggetto in collisione (collider) ha un metodo hit, lo richiamiamo pure. Nel progetto di esempio, abbiamo aggiunto un effetto di colore lampeggiante a Wall per dimostrarlo.

../../_images/k2d_bullet_bounce.gif

Movimento di gioco di piattaforme

Proviamo un altro esempio popolare: il piattaforme 2D. move_and_slide() è ideale per creare rapidamente un controller funzionante di un personaggio. Se hai scaricato il progetto di esempio, puoi trovarlo in "platformer.tscn".

Per questo esempio, supponiamo di avere un livello composto da uno o più oggetti StaticBody2D. Possono avere qualsiasi forma e dimensione. Nel progetto di esempio, usiamo Polygon2D per creare le forme della piattaforma.

Ecco il codice per il corpo del giocatore:

extends CharacterBody2D

var speed = 300.0
var jump_speed = -400.0

func _physics_process(delta):
    # Add the gravity.
    velocity.y += get_gravity() * delta

    # Handle Jump.
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_speed

    # Get the input direction.
    var direction = Input.get_axis("ui_left", "ui_right")
    velocity.x = direction * speed

    move_and_slide()
../../_images/k2d_platform.gif

In questo codice usiamo move_and_slide() come descritto sopra, per muovere il corpo lungo il suo vettore di velocità, scivolando lungo qualsiasi superficie di collisione come il terreno o una piattaforma. Usiamo anche is_on_floor() per verificare se è permesso saltare. Senza questo, sarebbe possibile "saltare" a mezz'aria; ottimo se stai creando Flappy Bird, ma non per un gioco piattaforme.

C'è molto altro richiesto per un personaggio piattaforme completo: accelerazione, doppi salti, coyote-time e molto altro. Il codice qui sopra è solo un punto di partenza. È possibile usarlo come base da espandere in qualsiasi comportamento di movimento ci sia bisogno per i propri progetti.