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.

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
using Godot;
public partial class Player : CharacterBody3D
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
// How fast the player moves in meters per second.
[Export]
public int Speed { get; set; } = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration { get; set; } = 75;
private Vector3 _targetVelocity = 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
public override void _PhysicsProcess(double 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.IsActionPressed("move_right"))
{
direction.X += 1.0f;
}
if (Input.IsActionPressed("move_left"))
{
direction.X -= 1.0f;
}
if (Input.IsActionPressed("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.0f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.Z -= 1.0f;
}
}
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)
public override void _PhysicsProcess(double delta)
{
// ...
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
// Setting the basis property will affect the rotation of the node.
GetNode<Node3D>("Pivot").Basis = Basis.LookingAt(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()
public override void _PhysicsProcess(double delta)
{
// ...
if (direction != Vector3.Zero)
{
// ...
}
// Ground velocity
_targetVelocity.X = direction.X * Speed;
_targetVelocity.Z = direction.Z * Speed;
// Vertical velocity
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
{
_targetVelocity.Y -= FallAcceleration * (float)delta;
}
// Moving the character
Velocity = _targetVelocity;
MoveAndSlide();
}
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()
using Godot;
public partial class Player : CharacterBody3D
{
// How fast the player moves in meters per second.
[Export]
public int Speed { get; set; } = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration { get; set; } = 75;
private Vector3 _targetVelocity = Vector3.Zero;
public override void _PhysicsProcess(double delta)
{
var direction = Vector3.Zero;
if (Input.IsActionPressed("move_right"))
{
direction.X += 1.0f;
}
if (Input.IsActionPressed("move_left"))
{
direction.X -= 1.0f;
}
if (Input.IsActionPressed("move_back"))
{
direction.Z += 1.0f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.Z -= 1.0f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
// Setting the basis property will affect the rotation of the node.
GetNode<Node3D>("Pivot").Basis = Basis.LookingAt(direction);
}
// Ground velocity
_targetVelocity.X = direction.X * Speed;
_targetVelocity.Z = direction.Z * Speed;
// Vertical velocity
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
{
_targetVelocity.Y -= FallAcceleration * (float)delta;
}
// Moving the character
Velocity = _targetVelocity;
MoveAndSlide();
}
}
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.

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.

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.

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.

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


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

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

È 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.

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

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.

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.