Coding the player¶
In this lesson, we'll add player movement, animation, and set it up to detect collisions.
To do so, we need to add some functionality that we can't get from a built-in
node, so we'll add a script. Click the Player
node and click the "Attach
Script" button:

Nella finestra di impostazioni dello script, puoi lasciare le impostazioni di default. Clicca "Crea":
Nota
Se stai creando uno script C# o in altri linguaggi, scegli il linguaggio dal menu a tendina linguaggio prima di cliccare crea.

Nota
If this is your first time encountering GDScript, please read Linguaggi di scripting before continuing.
Inizia dichiarando le variabili membro che questo oggetto avrà bisogno:
extends Area2D
export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
using Godot;
using System;
public class Player : Area2D
{
[Export]
public int Speed = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
// A `player.gdns` file has already been created for you. Attach it to the Player node.
// Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
// This code goes in `player.hpp`. We also define the methods we'll be using here.
#ifndef PLAYER_H
#define PLAYER_H
#include <AnimatedSprite.hpp>
#include <Area2D.hpp>
#include <CollisionShape2D.hpp>
#include <Godot.hpp>
#include <Input.hpp>
class Player : public godot::Area2D {
GODOT_CLASS(Player, godot::Area2D)
godot::AnimatedSprite *_animated_sprite;
godot::CollisionShape2D *_collision_shape;
godot::Input *_input;
godot::Vector2 _screen_size; // Size of the game window.
public:
real_t speed = 400; // How fast the player will move (pixels/sec).
void _init() {}
void _ready();
void _process(const double p_delta);
void start(const godot::Vector2 p_position);
void _on_Player_body_entered(godot::Node2D *_body);
static void _register_methods();
};
#endif // PLAYER_H
Usando la parola chiave export
sulla prima variabile speed
ci permette di impostare il suo valore dall'Ispettore. Questo può essere utile per valori che vorresti poter modificare come le proprietà dei nodi. Clicca sul nodo Player
e vedrai che la proprietà è ora apparsa nella sezione "Variabili di Script" dell'ispettore. Ricorda, se cambi il valore qui, si sovrascriverà al valore scritto nello script.
Avvertimento
Se stai usando C#, devi (ri)costruire l'assemblaggio del progetto ogni volta che vuoi vedere le nuove variabili esportate o i segnali. Questa costruzione può essere attivata facendo click sulla parola "Mono" in basso, nella finestra dell' editor, per rivelare il panello Mono, quindi fare click sul pulsante "Build Project".

La funzione _ready()
viene chiamata quando un nodo entra nello scene tree, è un momento ideale per andare a ricavare le dimensioni della finestra di gioco:
func _ready():
screen_size = get_viewport_rect().size
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
// This code goes in `player.cpp`.
#include "player.hpp"
void Player::_ready() {
_animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
_collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
_input = godot::Input::get_singleton();
_screen_size = get_viewport_rect().size;
}
Ora possiamo usare la funzione _process()
per definire cosa farà il player. La funzione _process()
viene chiamata ad ogni frame, quindi la useremo per aggiornare gli elementi del nostro gioco che ci aspettiamo che cambino spesso. Per il player dobbiamo fare il seguente:
Controlla per input.
Muoversi nella direzione data.
Esegui l'animazione appropriata.
First, we need to check for input - is the player pressing a key? For this game, we have 4 direction inputs to check. Input actions are defined in the Project Settings under "Input Map". Here, you can define custom events and assign different keys, mouse events, or other inputs to them. For this game, we will map the arrow keys to the four directions.
Click on Project -> Project Settings to open the project settings window and
click on the Input Map tab at the top. Type "move_right" in the top bar and
click the "Add" button to add the move_right
action.

We need to assign a key to this action. Click the "+" icon on the right, then click the "Key" option in the drop-down menu. A dialog asks you to type in the desired key. Press the right arrow on your keyboard and click "Ok".

Repeat these steps to add three more mappings:
move_left
mapped to the left arrow key.move_up
mapped to the up arrow key.And
move_down
mapped to the down arrow key.
Your input map tab should look like this:

Click the "Close" button to close the project settings.
Nota
We only mapped one key to each input action, but you can map multiple keys, joystick buttons, or mouse buttons to the same input action.
Puoi rilevare quando un pulsante è premuto usando Input.is_action_pressed()
, che ritorna true
se è premuto o false
se non lo è.
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed("move_right"):
velocity.x += 1
if Input.is_action_pressed("move_left"):
velocity.x -= 1
if Input.is_action_pressed("move_down"):
velocity.y += 1
if Input.is_action_pressed("move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite.play()
else:
$AnimatedSprite.stop()
public override void _Process(float delta)
{
var velocity = Vector2.Zero; // The player's movement vector.
if (Input.IsActionPressed("move_right"))
{
velocity.x += 1;
}
if (Input.IsActionPressed("move_left"))
{
velocity.x -= 1;
}
if (Input.IsActionPressed("move_down"))
{
velocity.y += 1;
}
if (Input.IsActionPressed("move_up"))
{
velocity.y -= 1;
}
var animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite.Play();
}
else
{
animatedSprite.Stop();
}
}
// This code goes in `player.cpp`.
void Player::_process(const double p_delta) {
godot::Vector2 velocity(0, 0);
velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
if (velocity.length() > 0) {
velocity = velocity.normalized() * speed;
_animated_sprite->play();
} else {
_animated_sprite->stop();
}
}
Iniziamo impostando la velocity
a (0, 0)
- di default il giocatore non dovrebbe muoversi. Poi controlliamo ogni input e aggiungiamo/sottraiamo da velocity
per ottenere una direzione globale. Per esempio, se tieni premuto destra
e giù
allo stesso tempo, il vettore velocity
che otterremo sarà (1, 1)
. In questo caso, dato che stiamo sommando un movimento orizzontale e uno verticale, il giocatore si muoverà più velocemente in diagonale rispetto al solo movimento orizzontale.
Possiamo prevenire ciò normalizzando la velocità, che significa impostare la sua lunghezza a 1
, dopodiché possiamo moltiplicare il vettore per la velocità desiderata. In questo modo possiamo evitare i movimenti diagonali a velocità maggiore.
Suggerimento
Se non hai mai usato la matematica vettoriale prima d'ora, oppure hai bisogno di una rinfrescata, puoi vedere una spiegazione sull'uso dei vettori in Godot a v:ref:doc_vector_math. È buona cosa da sapere, ma non sarà necessario per il resto di questo tutorial.
We also check whether the player is moving so we can call play()
or
stop()
on the AnimatedSprite.
Suggerimento
$
is shorthand for get_node()
. So in the code above,
$AnimatedSprite.play()
is the same as
get_node("AnimatedSprite").play()
.
In GDScript, $
returns the node at the relative path from the
current node, or returns null
if the node is not found. Since
AnimatedSprite is a child of the current node, we can use
$AnimatedSprite
.
Ora che abbiamo una direzione di movimento, possiamo aggiornare la posizione del player. Possiamo inoltre usare clamp()
per prevenire che il player esca dallo schermo. Fare il clamping di un valore significa restringerlo ad un certo intervallo. Aggiungi il seguente codice sul fondo della funzione _process()
(assicurati che non sia indentato sotto else):
position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)
Position += velocity * delta;
Position = new Vector2(
x: Mathf.Clamp(Position.x, 0, ScreenSize.x),
y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
);
godot::Vector2 position = get_position();
position += velocity * (real_t)p_delta;
position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
set_position(position);
Suggerimento
Il parametro delta della funzione _process() si riferisce alla lunghezza del frame - il tempo di esecuzione del frame precedente. Usare questo valore garantisce che il movimento sia proporzionale al frame precedente, in caso di variazione del frame rate.
Click "Play Scene" (F6, Cmd + R on macOS) and confirm you can move the player around the screen in all directions.
Avvertimento
Se ottieni un errore nel pannello "Debugger" che dice
Tentativo di chiamare la funzione 'play' nella base 'istanza nulla' su un'istanza nulla
this likely means you spelled the name of the AnimatedSprite node
wrong. Node names are case-sensitive and $NodeName
must match
the name you see in the scene tree.
Scegliere le animazioni¶
Now that the player can move, we need to change which animation the
AnimatedSprite is playing based on its direction. We have the "walk" animation,
which shows the player walking to the right. This animation should be flipped
horizontally using the flip_h
property for left movement. We also have the
"up" animation, which should be flipped vertically with flip_v
for downward
movement. Let's place this code at the end of the _process()
function:
if velocity.x != 0:
$AnimatedSprite.animation = "walk"
$AnimatedSprite.flip_v = false
# See the note below about boolean assignment.
$AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite.animation = "up"
$AnimatedSprite.flip_v = velocity.y > 0
if (velocity.x != 0)
{
animatedSprite.Animation = "walk";
animatedSprite.FlipV = false;
// See the note below about boolean assignment.
animatedSprite.FlipH = velocity.x < 0;
}
else if (velocity.y != 0)
{
animatedSprite.Animation = "up";
animatedSprite.FlipV = velocity.y > 0;
}
if (velocity.x != 0) {
_animated_sprite->set_animation("walk");
_animated_sprite->set_flip_v(false);
// See the note below about boolean assignment.
_animated_sprite->set_flip_h(velocity.x < 0);
} else if (velocity.y != 0) {
_animated_sprite->set_animation("up");
_animated_sprite->set_flip_v(velocity.y > 0);
}
Nota
Le assegnazioni booleane nel codice qui sopra sono comuni abbreviazioni per i programmatori. Siccome stiamo facendo una comparazione (booleano) a anche assegnando un valore booleano, possiamo fare entrambi allo stesso momento. Considera questo codice contro l'assegnamento booleano in un'unica riga mostrato sopra:
if velocity.x < 0:
$AnimatedSprite.flip_h = true
else:
$AnimatedSprite.flip_h = false
if (velocity.x < 0)
{
animatedSprite.FlipH = true;
}
else
{
animatedSprite.FlipH = false;
}
Riproduci la scena e verifica che le animazioni siano corrette in ciascuna delle direzioni.
Suggerimento
A common mistake here is to type the names of the animations wrong. The
animation names in the SpriteFrames panel must match what you type in
the code. If you named the animation "Walk"
, you must also use a
capital "W" in the code.
Quando sei sicuro che il movimento stia funzionando correttamente, aggiungi questa linea a _ready()
, così che il player sarà nascosto quando il gioco ha inizio:
hide()
Hide();
hide();
Preparare per le collisioni¶
Vogliamo che il Player
rilevi quando viene colpito da un nemico, ma non abbiamo ancora creato nemici! Non c'è problema, perché useremo la funzionalità segnali di Godot per fare in modo che funzioni.
Aggiungi quanto di seguito in cima allo script, dopo extends Area2d
:
signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void Hit();
// This code goes in `player.cpp`.
// We need to register the signal here, and while we're here, we can also
// register the other methods and register the speed property.
void Player::_register_methods() {
godot::register_method("_ready", &Player::_ready);
godot::register_method("_process", &Player::_process);
godot::register_method("start", &Player::start);
godot::register_method("_on_Player_body_entered", &Player::_on_Player_body_entered);
godot::register_property("speed", &Player::speed, (real_t)400.0);
// This below line is the signal.
godot::register_signal<Player>("hit", godot::Dictionary());
}
Questo definisce un segnale personalizzato chiamato "hit" che il giocatore emetterà quando entra a contatto con un nemico. Useremo il nodo Area2D``per rilevare la collisione. Seleziona il nodo ``Player
e fai click sull'etichetta "Node", vicino a "Inspector", per vedere la lista di segnali che il giocatore può emettere:

Come puoi notare la lista contiene anche il nostro segnale personalizzato "hit". Poiché i nemici saranno nodi RigidBody2D
, vogliamo usare il segnale body_entered(body: Node)
. Questo segnale viene emesso quando un corpo entra a contatto con il giocatore. Clicca "Connetti.." e appare la finestra "Connetti un Segnale". Non abbiamo bisogno di cambiare nessuna delle impostazioni quindi clicca "Connetti" nuovamente. Godot creerà automaticamente una funzione nello script del tuo player.

Notare l'icona verde che indica che a questa funzione è collegato un segnale. Aggiungi questo codice alla funzione:
func _on_Player_body_entered(body):
hide() # Player disappears after being hit.
emit_signal("hit")
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred("disabled", true)
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(nameof(Hit));
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}
// This code goes in `player.cpp`.
void Player::_on_Player_body_entered(godot::Node2D *_body) {
hide(); // Player disappears after being hit.
emit_signal("hit");
// Must be deferred as we can't change physics properties on a physics callback.
_collision_shape->set_deferred("disabled", true);
}
Ogni volta che un nemico colpirà il giocatore, il segnale verrà emesso. Bisogna disabilitare la collisione del giocatore per non innescare il segnale hit
più di una volta.
Nota
Disabling the area's collision shape can cause an error if it happens
in the middle of the engine's collision processing. Using
set_deferred()
tells Godot to wait to disable the shape until it's
safe to do so.
L'ultimo pezzo è aggiungere una funzione che chiamiamo per resettare il player quando inizia una nuova partita.
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
public void Start(Vector2 pos)
{
Position = pos;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
// This code goes in `player.cpp`.
void Player::start(const godot::Vector2 p_position) {
set_position(p_position);
show();
_collision_shape->set_disabled(false);
}
With the player working, we'll work on the enemy in the next lesson.