Muovere il giocatore tramite codice

It's time to code! We're going to use the input actions we created in the last part to move the character.

Nota

Per questo progetto, seguiremo le convenzioni di denominazione di Godot.

  • GDScript: Le classi (nodi) usano PascalCase, le variabili e le funzioni snake_case e le costanti usano ALL_CAPS (Vedi Guida di stile di GDScript).

  • C#: Le classi, le variabili di esportazione e i metodi usano PascalCase, i campi privati usano _camelCase, le variabili locali e i parametri usano camelCase (Consultare Guida di stile C#). Bisogna fare attenzione a scrivere il nome dei metodi correttamente quando si connettono i segnali.

Right-click the Player node and select Attach Script to add a new script to it. In the popup, set the Template to Empty before pressing the Create button. We set it to Empty because we want to write our own code for player movement.

image0

Let's start with the class's properties. We're going to define a movement speed, a fall acceleration representing gravity, and a velocity we'll use to move the character.

extends CharacterBody3D

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
@export var fall_acceleration = 75

var target_velocity = Vector3.ZERO

These are common properties for a moving body. The target_velocity is a 3D vector combining a speed with a direction. Here, we define it as a property because we want to update and reuse its value across frames.

Nota

The values are quite different from 2D code because distances are in meters. While in 2D, a thousand units (pixels) may only correspond to half of your screen's width, in 3D, it's a kilometer.

Let's code the movement. We start by calculating the input direction vector using the global Input object, in _physics_process().

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 += 1
    if Input.is_action_pressed("move_left"):
        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 += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

Here, instead of _process(), we're going to make all calculations using the _physics_process() virtual function. It's designed specifically for physics-related code like moving a kinematic or rigid body. It updates the node using fixed time intervals.

Vedi anche

Per saperne di più sulla differenza tra _process() e _physics_process(), vedere Processo di inattività e di fisica.

We start by initializing a direction variable to Vector3.ZERO. Then, we check if the player is pressing one or more of the move_* inputs and update the vector's x and z components accordingly. These correspond to the ground plane's axes.

These four conditions give us eight possibilities and eight possible directions.

In case the player presses, say, both W and D simultaneously, the vector will have a length of about 1.4. But if they press a single key, it will have a length of 1. We want the vector's length to be consistent, and not move faster diagonally. To do so, we can call its normalized() method.

func _physics_process(delta):
    #...

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        # Setting the basis property will affect the rotation of the node.
        $Pivot.basis = Basis.looking_at(direction)

Qui, normalizziamo il vettore solo se la direzione ha una lunghezza maggiore di zero, il che significa che il giocatore sta premendo un tasto direzionale.

Calcoliamo la direzione in cui mira $Pivot creando un Basis che guarda nella direzione direction.

Quindi, aggiorniamo la velocità. Dobbiamo calcolare la velocità al suolo e la velocità di caduta separatamente. Assicurati di tornare indietro di una tabulazione in modo che le righe siano dentro la funzione _physics_process() ma fuori alla condizione appena scritta sopra.

func _physics_process(delta):
    #...
    if direction != Vector3.ZERO:
        #...

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

    # Moving the Character
    velocity = target_velocity
    move_and_slide()

La funzione CharacterBody3D.is_on_floor() restituisce true se il corpo è entrato in collisione con il pavimento in questo frame. Ecco perché applichiamo la gravità al Player solo mentre è in aria.

Per la velocità verticale, sottraiamo l'accelerazione di caduta moltiplicata per il tempo delta a ogni frame. Questa riga di codice assicurerà che il nostro personaggio cada in ogni frame, a patto che non si trovi sul pavimento o non vi si scontri.

Il motore di fisica può rilevare interazioni con le pareti, il pavimento o altri corpi durante un determinato frame solo se si verificano movimenti e collisioni. Utilizzeremo questa proprietà in seguito per programmare il salto.

Nell'ultima riga, chiamiamo CharacterBody3D.move_and_slide(), un potente metodo della classe CharacterBody3D che permette di muovere un personaggio fluidamente. Se colpisce un muro durante il movimento, il motore cercherà di allisciarlo. Utilizza il valore velocity nativo del CharacterBody3D

E questo è tutto il codice necessario per muovere il personaggio sul pavimento.

Ecco il codice completo di player.gd come riferimento.

extends CharacterBody3D

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
@export var fall_acceleration = 75

var target_velocity = Vector3.ZERO


func _physics_process(delta):
    var direction = Vector3.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

    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)

    # Moving the Character
    velocity = target_velocity
    move_and_slide()

Provare il movimento del nostro giocatore

Metteremo il nostro giocatore nella scena Main per testarlo. Per farlo, dobbiamo istanziare il giocatore e poi aggiungere una telecamera. A differenza del 2D, in 3D non vedrai nulla se la tua viewport non ha una telecamera puntata su qualcosa.

Salva la scena Player e apri la scena Main. Per farlo, clicca sulla scheda Main in alto all'editor.

image1

Se hai chiuso la scena in precedenza, vai al pannello FileSystem e fai doppio clic su main.tscn per riaprirla.

Per creare un'istanza di Player, fai clic destro sul nodo Main e seleziona Istanzia scena figlia.

image2

Nel popup, fai doppio clic su player.tscn. Il personaggio dovrebbe apparire al centro della viewport.

Aggiunta di una telecamera

Aggiungiamo ora la telecamera. Come abbiamo fatto con il Pivot del nostro Player, creeremo un rig di base. Fai di nuovo clic destro sul nodo Main e seleziona Aggiungi nodo figlio. Crea un nuovo Marker3D e dagli il nome CameraPivot. Seleziona CameraPivot e aggiungi un nodo figlio Camera3D. L'albero di scene dovrebbe apparire simile a questo.

image3

Nota la casella di spunta Anteprima che appare in alto a sinistra nella vista 3D quando hai la Camera selezionata. Puoi cliccarci sopra per visualizzare un'anteprima della proiezione della telecamera di gioco.

image4

Useremo il Pivot per ruotare la telecamera come se fosse montata su una gru. Dividiamo prima la vista 3D per poter navigare liberamente nella scena e vedere cosa vede la telecamera.

Nella barra degli strumenti giusto sopra la viewport, clicca su View, quindi su 2 Viewports. Puoi anche premere Ctrl + 2 (Cmd + 2 su macOS).

image11

image5

Nella vista in basso, seleziona la Camera3D e attiva l'anteprima della telecamera cliccando sulla casella di spunta.

image6

Nella vista dall'alto, assicurati che la tua Camera3D sia selezionata e sposta la telecamera di circa 19 unità sull'asse Z (trascina la freccia blu).

image7

È qui che avviene la magia. Seleziona CameraPivot e ruotalo di -45 gradi attorno all'asse X (utilizzando il cerchio rosso). Vedrai la telecamera muoversi come se fosse agganciata a una gru.

image8

Puoi eseguire la scena premendo F6 e premere i tasti freccia per muovere il personaggio.

image9

Possiamo vedere un po' di spazio vuoto attorno al personaggio a causa della proiezione prospettica. In questo gioco, utilizzeremo invece una proiezione ortografica per inquadrare meglio l'area di gioco e rendere le distanze più facili da giudicare per il giocatore.

Seleziona di nuovo la Telecamera e nell'Ispettore imposta Projection su Orthogonal e Size su 19. Il personaggio dovrebbe ora apparire più piatto e il terreno dovrebbe riempire lo sfondo.

Nota

Quando si utilizza una telecamera ortogonale in Godot 4, la qualità delle ombre direzionali dipende dal valore Far della telecamera. Più alto è il valore Far, più lontano sarà la telecamera in grado di vedere. Tuttavia, valori Far più alti riducono anche la qualità delle ombre, poiché il rendering delle ombre deve coprire una distanza maggiore.

Se le ombre direzionali appaiono troppo sfocate dopo essere passati a una telecamera ortogonale, riduci la proprietà Far della telecamera a un valore inferiore, ad esempio 100. Non ridurre troppo questa proprietà Far, altrimenti gli oggetti in lontananza inizieranno a scomparire.

image10

Prova la tua scena e dovresti riuscire a muoverti in tutte le 8 direzioni senza passare attraverso il pavimento!

Alla fine, abbiamo sistemato sia il movimento del giocatore sia la vista. Successivamente, lavoreremo sui mostri.