Godot 4 per Android con C#: Movimento del Personaggio e Input Touch

Godot 4 per Android con C#: Movimento del Personaggio e Input Touch

Questa è la quarta bozza della serie su Godot 4 per Android usando esclusivamente C#. In questa guida creiamo un personaggio 2D, lo facciamo muovere con CharacterBody2D, configuriamo i controlli tramite Input e InputMap, aggiungiamo pulsanti/joystick virtuali per il touch e testiamo tutto su emulatore e dispositivo fisico Android.

Obiettivi della guida

Alla fine di questo articolo sarai in grado di:

  • Creare un semplice personaggio 2D usando CharacterBody2D.
  • Scrivere uno script C# per gestire il movimento in Godot 4.
  • Usare Input e InputMap per astrarre i comandi.
  • Configurare controlli touch adatti ad Android (pulsanti virtuali e joystick virtuale).
  • Testare e regolare il comportamento su emulatore e dispositivo fisico.
  • Applicare buone pratiche di responsività e prestazioni su mobile.

Prerequisiti

Per seguire la guida ti serve:

  • Godot 4 installato (consigliata l’ultima stable 4.x).
  • Modulo C#/.NET configurato con un IDE compatibile (Visual Studio, Rider, VS Code ecc.).
  • Un progetto Godot 4 2D già creato per Android (firma, export template e dispositivo/emulatore configurati), come visto negli articoli precedenti.
  • Conoscenze di base di C# (classi, metodi, proprietà, tipi di dato fondamentali).
  • Facoltativo ma utile: un dispositivo Android reale collegato via USB con debug abilitato.

1. Creare la scena del personaggio 2D con CharacterBody2D

In Godot 4, CharacterBody2D è il nodo consigliato per personaggi 2D che si muovono e reagiscono alla fisica in modo controllato (platformer, top-down, ecc.). Offre una logica di movimento pronta, basata su Velocity e sul metodo MoveAndSlide().

1.1. Struttura base della scena del personaggio

Creiamo una nuova scena per il personaggio:

  • Crea una nuova scena vuota.
  • Aggiungi un nodo principale di tipo CharacterBody2D e rinominalo in Player.
  • Aggiungi come figlio un Sprite2D per la grafica (puoi usare un placeholder).
  • Aggiungi come figlio un CollisionShape2D con una forma (es. Rettangolo) che circonda lo sprite.

La gerarchia dovrebbe apparire circa così:

  • Player (CharacterBody2D)
    • Sprite2D
    • CollisionShape2D

1.2. Impostare l’orientamento dello schermo

Per un gioco 2D con controlli touch è importante scegliere l’orientamento in anticipo, perché influisce sulla posizione dei pulsanti virtuali.

  • Apri Project > Project Settings > Display > Window.
  • Imposta Orientation su Landscape (o Portrait se preferisci un gioco verticale).
  • Imposta Stretch > Mode su 2d e Aspect su keep o keep_width/keep_height in base alle tue necessità.

Per questa guida useremo Landscape, tipico per action e platformer.

2. Script C# per il movimento del personaggio

Ora colleghiamo uno script C# al nodo Player per gestire il movimento usando la proprietà Velocity e gli input definiti in InputMap.

2.1. Creare lo script C#

Seleziona il nodo Player e crea uno script C# chiamato, ad esempio, Player.cs. Assicurati che il linguaggio sia impostato su C# e che lo script si trovi in una cartella chiara, come Scripts/.

using Godot;
using System;

public partial class Player : CharacterBody2D
{
    [Export]
    public float Speed { get; set; } = 200.0f; // Velocità di movimento in pixel/secondo

    public override void _PhysicsProcess(double delta)
    {
        Vector2 inputDirection = GetInputDirection();

        // Normalizziamo per evitare che in diagonale sia più veloce
        if (inputDirection.Length() > 1.0f)
            inputDirection = inputDirection.Normalized();

        Velocity = inputDirection * Speed;

        MoveAndSlide();
    }

    private Vector2 GetInputDirection()
    {
        float x = 0.0f;
        float y = 0.0f;

        // Legge gli input mappati (tastiera, gamepad o touch virtuale)
        if (Input.IsActionPressed("move_right"))
            x += 1.0f;
        if (Input.IsActionPressed("move_left"))
            x -= 1.0f;
        if (Input.IsActionPressed("move_down"))
            y += 1.0f;
        if (Input.IsActionPressed("move_up"))
            y -= 1.0f;

        return new Vector2(x, y);
    }
}

Questo approccio funziona sia su desktop sia su Android, a patto che le azioni move_* siano configurate correttamente in InputMap e che i controlli touch le attivino.

3. Configurare Input e InputMap

InputMap ti permette di definire azioni astratte (es. move_left) e collegarle a tasti, mouse, gamepad o zone touch. È fondamentale per avere lo stesso codice C# che funziona su più piattaforme.

3.1. Definire le azioni di movimento

Apri Project > Project Settings > Input Map e aggiungi queste azioni:

  • move_left
  • move_right
  • move_up
  • move_down

Per ognuna, aggiungi almeno un tasto da tastiera per testare su desktop, ad esempio:

  • move_left → freccia sinistra, tasto A
  • move_right → freccia destra, tasto D
  • move_up → freccia su, tasto W
  • move_down → freccia giù, tasto S

Più avanti collegheremo a queste azioni i pulsanti virtuali touch.

3.2. Test rapido su desktop

Prima di passare ad Android, avvia il gioco su desktop e verifica che il personaggio si muova con tastiera. Questo ti permette di isolare gli eventuali problemi di logica di movimento, prima di complicare la scena con l’input touch.

4. Controlli touch per Android: pulsanti virtuali

Per Android puoi creare pulsanti virtuali che, quando toccati, attivano le stesse azioni di InputMap usate per tastiera e gamepad. Useremo i controlli GUI di Godot (nodi Control) in un CanvasLayer, così restano fissi sullo schermo indipendentemente dalla telecamera.

4.1. Creare il layout dei pulsanti

Apri la scena principale del gioco (es. Main.tscn) che contiene il mondo e istanzia il tuo Player. Poi:

  • Aggiungi un nodo CanvasLayer chiamato UI.
  • Dentro UI, aggiungi un nodo Control (o MarginContainer) per gestire il layout.
  • Imposta il Layout del nodo principale UI su Full Rect (tasto destro > Layout > Full Rect).

Per creare una croce direzionale semplice:

  • Aggiungi quattro pulsanti (TextureButton o Button) agli angoli inferiori, o una vera e propria croce in basso a sinistra.
  • Ad esempio, crea un Control chiamato VirtualDPad ancorato in basso a sinistra, e dentro aggiungi i pulsanti BtnLeft, BtnRight, BtnUp, BtnDown.

Assicurati che i nodi Control usino ancoraggi e margini appropriati, così i pulsanti restano nella posizione corretta su dispositivi con risoluzioni diverse. Puoi testare varie risoluzioni con il pannello Test Width/Height nelle impostazioni di gioco o nella barra in alto.

4.2. Collegare i pulsanti a InputMap (pulsanti → azioni)

L’idea è: quando un pulsante viene premuto, chiamiamo Input.ActionPress("move_left"), e quando viene rilasciato chiamiamo Input.ActionRelease("move_left"). In questo modo il tuo script Player non sa se l’input arriva da tastiera o touch.

Crea uno script C# sul nodo VirtualDPad, ad esempio VirtualDPad.cs:

using Godot;
using System;

public partial class VirtualDPad : Control
{
    public override void _Ready()
    {
        // Colleghiamo via codice i segnali dei pulsanti (in alternativa, usa l'editor di segnali)
        GetNode<Button>("BtnLeft").Pressed += OnLeftPressed;
        GetNode<Button>("BtnLeft").ButtonUp += OnLeftReleased;

        GetNode<Button>("BtnRight").Pressed += OnRightPressed;
        GetNode<Button>("BtnRight").ButtonUp += OnRightReleased;

        GetNode<Button>("BtnUp").Pressed += OnUpPressed;
        GetNode<Button>("BtnUp").ButtonUp += OnUpReleased;

        GetNode<Button>("BtnDown").Pressed += OnDownPressed;
        GetNode<Button>("BtnDown").ButtonUp += OnDownReleased;
    }

    private void OnLeftPressed() => Input.ActionPress("move_left");
    private void OnLeftReleased() => Input.ActionRelease("move_left");

    private void OnRightPressed() => Input.ActionPress("move_right");
    private void OnRightReleased() => Input.ActionRelease("move_right");

    private void OnUpPressed() => Input.ActionPress("move_up");
    private void OnUpReleased() => Input.ActionRelease("move_up");

    private void OnDownPressed() => Input.ActionPress("move_down");
    private void OnDownReleased() => Input.ActionRelease("move_down");
}

Su mobile i pulsanti reagiranno al tocco; il tuo player userà sempre Input.IsActionPressed(), senza conoscere il dispositivo.

5. Joystick virtuale (opzionale ma consigliato)

I pulsanti direzionali funzionano, ma un joystick virtuale può risultare più fluido per alcuni giochi. L’idea di base è avere due elementi:

  • Un background circolare (area totale del joystick).
  • Un thumb (manopola) che si sposta verso la direzione del tocco.

In C#, calcoliamo la direzione dal centro al punto di tocco e la convertiamo in input normalizzati.

5.1. Struttura del joystick virtuale

Nel tuo CanvasLayer UI, aggiungi un nuovo Control chiamato VirtualJoystick, ancorato in basso a sinistra. All’interno:

  • Bg (TextureRect o TextureButton) → sfondo tondo del joystick.
  • Thumb (TextureRect) → manopola.

Assicurati che VirtualJoystick sia abbastanza grande per contenere l’area di input, ma non troppo da coprire lo schermo. Usa gli ancoraggi per adattarlo a schermi diversi.

5.2. Script C# per il joystick virtuale

Crea VirtualJoystick.cs sul nodo VirtualJoystick:

using Godot;
using System;

public partial class VirtualJoystick : Control
{
    [Export]
    public float DeadZone { get; set; } = 0.15f; // Valore sotto cui l'input viene ignorato

    private Vector2 _direction = Vector2.Zero;
    private bool _isDragging = false;
    private Vector2 _center;
    private float _radius;

    private TextureRect _thumb;

    public override void _Ready()
    {
        _thumb = GetNode<TextureRect>("Thumb");
        _center = RectSize / 2.0f;
        _radius = Math.Min(RectSize.X, RectSize.Y) / 2.0f;
        _thumb.Position = _center - _thumb.RectSize / 2.0f;
    }

    public override void _GuiInput(InputEvent @event)
    {
        if (@event is InputEventScreenTouch touchEvent)
        {
            if (touchEvent.Pressed)
            {
                _isDragging = true;
                UpdateJoystick(touchEvent.Position);
            }
            else
            {
                _isDragging = false;
                ResetJoystick();
            }
        }
        else if (@event is InputEventScreenDrag dragEvent && _isDragging)
        {
            UpdateJoystick(dragEvent.Position);
        }
    }

    private void UpdateJoystick(Vector2 touchPos)
    {
        Vector2 offset = touchPos - _center;
        float distance = offset.Length();

        if (distance > _radius)
        {
            offset = offset.Normalized() * _radius;
            distance = _radius;
        }

        _thumb.Position = _center + offset - _thumb.RectSize / 2.0f;

        if (_radius > 0)
        {
            _direction = offset / _radius; // Valori da -1 a 1
        }
        else
        {
            _direction = Vector2.Zero;
        }

        // Applica dead zone
        if (_direction.Length() < DeadZone)
            _direction = Vector2.Zero;

        UpdateActions();
    }

    private void ResetJoystick()
    {
        _direction = Vector2.Zero;
        _thumb.Position = _center - _thumb.RectSize / 2.0f;
        UpdateActions();
    }

    private void UpdateActions()
    {
        // Rilascia tutte le azioni
        Input.ActionRelease("move_left");
        Input.ActionRelease("move_right");
        Input.ActionRelease("move_up");
        Input.ActionRelease("move_down");

        // Attiva in base alla direzione
        if (_direction.X < -DeadZone)
            Input.ActionPress("move_left");
        if (_direction.X > DeadZone)
            Input.ActionPress("move_right");
        if (_direction.Y < -DeadZone)
            Input.ActionPress("move_up");
        if (_direction.Y > DeadZone)
            Input.ActionPress("move_down");
    }
}

Questo joystick converte la direzione di trascinamento in azioni di movimento discrete. Puoi estenderlo per supportare anche la velocità analogica, leggendo direttamente _direction nello script del player (vedi sezione “Approfondimenti per esperti”).

6. Responsività dei controlli e buone pratiche UI su mobile

Su Android l’esperienza dell’utente dipende moltissimo da quanto i controlli sono comodi da usare, a prescindere dalla diagonale e dalla risoluzione dello schermo.

  • Usa ancoraggi, non posizioni assolute: posiziona i pulsanti rispetto agli angoli (bottom-left, bottom-right) e usa margini in unità logiche (es. 64 px) per mantenere le distanze.
  • Dimensioni dei pulsanti: in generale, mantieni almeno 80–100 px di lato per pulsanti principali su schermi HD. Testa su dispositivi piccoli.
  • Evita di coprire il gioco: sposta i controlli agli angoli e lascia pulito il centro per la scena di gioco.
  • Supporto per mancini: opzionale ma molto apprezzato. Prevedi un toggle nelle opzioni per scambiare joystick e pulsanti d’azione.

7. Buone pratiche per le prestazioni su Android

Il codice di input e movimento qui mostrato è leggero, ma su Android le prestazioni sono influenzate anche da altri aspetti del progetto. Alcuni suggerimenti:

  • Limita il lavoro in _PhysicsProcess: tieni solo la logica di movimento essenziale, come negli esempi, ed evita allocazioni di oggetti ad ogni frame.
  • Riduci texture e draw call: usa sprite di dimensioni ragionevoli e combina elementi statici in tilemap o sprite sheet.
  • Disattiva nodi inutilizzati: nascondi o disattiva i nodi (e i relativi Process) quando non sono visibili o rilevanti.
  • Profiler: usa il profiler integrato di Godot collegando il dispositivo Android per individuare colli di bottiglia reali.

8. Test su emulatore e dispositivo fisico

8.1. Test su emulatore Android

Per un primo controllo, l’emulatore Android (Android Studio o altro) è sufficiente:

  • Configura un AVD con la stessa orientazione (Landscape) scelta per il gioco.
  • Esporta il progetto con il preset Android configurato (come visto nelle guide precedenti).
  • Installa l’APK sull’emulatore e prova i controlli touch con il mouse.

L’emulatore può avere latenze diverse da un dispositivo reale, quindi usa questo test solo per una verifica di base del layout e del funzionamento logico dei controlli.

8.2. Test su dispositivo fisico

Il vero test è sempre sul dispositivo fisico:

  • Abilita il Debug USB nelle opzioni sviluppatore del telefono.
  • Collega il dispositivo al PC.
  • Da Godot, usa Run > Select Current Device e scegli il device Android collegato.
  • Avvia il gioco e verifica: comodità dei controlli, reattività, visibilità.

Prova il gioco sia in posizione naturale di utilizzo (due mani, pollici sui controlli) sia in condizioni meno ideali (una mano sola) per capire se serve aumentare la dimensione dei pulsanti o modificarne la posizione.

9. Errori comuni e come risolverli

  • Il personaggio non si muove
    Controlla che:
    – Lo script Player.cs sia effettivamente collegato al nodo Player.
    – Le azioni in InputMap abbiano il nome esatto usato nel codice (move_left ecc.).
    – MoveAndSlide() venga chiamato in _PhysicsProcess.
  • I pulsanti touch non fanno nulla
    Verifica che:
    – Lo script VirtualDPad.cs sia assegnato al nodo giusto.
    – I nomi dei pulsanti in GetNode<Button>() corrispondano alla gerarchia della scena.
    – Il CanvasLayer UI sia incluso nella scena principale che stai eseguendo.
  • Joystick virtuale non risponde correttamente
    – Controlla che VirtualJoystick abbia Mouse Filter impostato a Stop per ricevere gli eventi.
    – Verifica che il joystick non sia coperto da altri nodi UI.
    – Stampa _direction in debug per vedere i valori effettivi.
  • Controlli spostati o tagliati su alcuni schermi
    – Usa ancoraggi corretti (bottom-left/bottom-right) invece di posizioni assolute.
    – Testa varie risoluzioni con gli strumenti di Godot.

10. FAQ

Posso usare lo stesso codice per desktop e Android?

Sì. Se usi InputMap e azioni astratte, lo script del personaggio non deve sapere da dove arrivano gli input. Su desktop userai tastiera/gamepad, su Android pulsanti o joystick virtuale, ma il codice rimane lo stesso.

Come gestisco la sensibilità del joystick virtuale?

La proprietà DeadZone ti permette di ignorare movimenti molto piccoli, che spesso sono tocchi involontari. Puoi anche usare _direction.Length() per modulare la velocità in base a quanto l’utente spinge il joystick verso il bordo.

E se volessi usare solo swipe invece di pulsanti/joystick?

È possibile: gestisci gli eventi InputEventScreenDrag globalmente (es. in un nodo Control a pieno schermo) e traduci lo swipe in direzioni e azioni Input.ActionPress()/ActionRelease(). La logica di movimento del personaggio può rimanere quasi invariata.

Serve per forza un joystick virtuale?

No. Per molti giochi bastano 2–3 pulsanti ben posizionati. Il joystick virtuale è utile per movimenti liberi 360° o per imitare il feeling dei gamepad.

11. Approfondimenti per sviluppatori esperti

  • Input analogico completo
    Invece di trasformare il joystick in azioni discrete move_left/move_right, puoi esporre una proprietà public Vector2 Direction in VirtualJoystick e leggerla direttamente nel player. In questo modo puoi modulare la velocità (Velocity = joystick.Direction * MaxSpeed) e avere movimenti più fluidi.
  • Separazione input / logica
    Per progetti complessi, considera di separare completamente il “controller” del player dalla logica del personaggio. Ad esempio: un nodo PlayerInput che produce un vettore di input, e un PlayerController che muove il CharacterBody2D. In questo modo puoi sostituire facilmente la fonte di input (IA, rete, replay).
  • Configurazioni multiple di layout
    Puoi definire scene UI diverse per telefoni e tablet (o orientamento diverso) e caricare dinamicamente quella corretta in base alla risoluzione o al rapporto d’aspetto.
  • Profilazione dell’input
    Su dispositivi di fascia bassa, troppi nodi UI possono pesare. Riduci il numero di nodi nei controlli, usa texture ottimizzate e disattiva _Process/_PhysicsProcess dove non necessario.

12. Riepilogo e prossimi passi

In questa bozza abbiamo creato un personaggio 2D con CharacterBody2D, scritto uno script C# per gestire il movimento tramite Velocity e MoveAndSlide(), configurato InputMap per astrarre i controlli e collegato pulsanti e joystick virtuali per il touch su Android. Abbiamo visto come rendere i controlli responsivi e stabili su schermi diversi, come testare il comportamento su emulatore e dispositivo fisico e quali errori evitare.

Nel prossimo articolo della serie ci concentreremo sull’interfaccia Android: creeremo una HUD con pulsanti contestuali, indicatori (vita, punteggio, energia) e menù in-game, sempre usando C# e seguendo buone pratiche di design UI per dispositivi mobili.

Commenti

Lascia un commento