venerdì 26 settembre 2025

GODOT - Elemento dietro e davanti ad un oggetto

In alcuni giochi è necessario che il personaggio passi davanti all'oggetto, oppure dietro. Ad esempio, davanti ad un albero o dietro ad esso.
E questo deve dipendere dal punto in cui l'oggetto è "attraversato".
Godot ha un sistema per generare questo effetto: l'ordinamento sull'asse Y.

In pratica, per gli oggetti che hanno lo stesso "Z index", il sistema disegna prima gli oggetti che hanno un valore di Y più "basso" (cioè verso il fondo dello schermo) e poi quelli con un valore più alto.

Per abilitare questo, si entra nel "nodo root" della scena -che dev'essere ereditato da CanvasItem (tipo Node2D), si apre il menu "ordering", si controlla che lo Z Index sia 0 e poi si abilita "Y Sort Enabled".

Facendo questo, quando l'elemento (ad esempio, il protagonista) ha un valore Y più "basso" del secondo oggetto (ad esempio, un ostacolo), il sistema lo rappresenterà davanti. Se invece è più alto, il sistema lo disegnerà prima, e quindi apparirà "dietro" all'ostacolo.
Questo "valore Y" viene considerato prendendo il centro della "croce" che appare sull'oggetto (cioè la posizione del suo "posizionamento").

lunedì 22 settembre 2025

GODOT - Uso dei timer

Mi è capitato di dover inserire un timer nel codice GDScript per gestire degli eventi temporizzati senza utilizzare un singolo elemento aggiunto ai nodi (cosa che potrebbe essere problematica se si devono temporizzare più effetti/azioni).

La definizione/inizializzazione del timer è abbastanza semplice:

self.timer = Timer.new() # creo una nuova variabile di tipo Timer
self.timer.autostart = false # indico che non deve partire in automatico
self.timer.one_shot = true # indico che deve "girare" solo una volta
self.timer.paused = false # indico che il timer non è "in pausa"
A questo punto, è tutto pronto.
Però... per poter partire, il timer dev'essere inserito in un nodo per poter funzionare!
$NodeInTree.add_child(self.timer)
A questo punto, si può collegare il signal "timeout" ad una funzione e poi far partire il timer, indicando il tempo entro cui raggiungere il timeout:
self.timer.timeout.connect(self._on_timeout)
self.timer.start(5)

func _on_timeout() -> void:
    pass

lunedì 15 settembre 2025

GODOT - Risoluzione per cellulari Android

Per avere l'applicazione a schermo intero, la risoluzione corretta è: 2400x1080 (almeno, sul mio OnePlus Nord CE 5G).

E' possibile ovviamente utilizzare il viewport per definire delle risoluzioni minori, che "scalino".

Usando la 1200x540, l'effetto è gradevole e non è "pixellato".

domenica 14 settembre 2025

GODOT - Icone per Android

Provando a realizzare un'icona adatta ad un'app per android, mi sono accorto che il sistema "taglia" l'immagine con il contenuto centrale anziché utilizzare l'intera immagine.

In pratica, producendo un'icona 432x432 (come da guida dell'Export), si ottiene un'icona più piccola. Per cui bisogna considerare visibile esclusivamente il campo 284x284 all'interno dell'icona.

Per un'analisi dettagliata su come creare le icone per Android, andare qui.

mercoledì 10 settembre 2025

LOVE2D - Formattazione di stringhe

In Love2d è possibile utilizzare la funzione string.format per stampare (o, in generale, formattare) i dati. Considerando la mancanza di una funzione di serializzazione per il salvataggio dei dati, è bene sapere come funziona questa formattazione.

In pratica, funziona come la printf in C: utilizza una combinazione di caratteri particolari per indicare che tipologia di dato si vuole stampare.

Ecco una lista delle tipologie di dato stampabili:

%s  stringa
%d  decimale o intero
%f  formattazione a virgola mobile  (ad esempio, per math.pi stampa 3.141593)
%e  formattazione scientifica  (ad esempio, per math.pi stampa 3.141593e+00)
$E  formattazione scientifica  (ad esempio, per math.pi stampa 3.141593E+00)
%g  formattazione "automatica" (ad esempio, per math.pi stampa 3.141593)
%G  formattazione "automatica" (ad esempio, per math.pi stampa 3.141593)
%o  valore in base ottale
%x  valore in base esadecimale
%c  carattere
%q  codice Lua

Da notare l'ultima tipologia, utilissima per stampare esattamente quello che ha interpretato il codice Lua!

Altre informazioni si possono trovare a questo indirizzo:

https://tui.ninja/lua/strings/formatting/

martedì 9 settembre 2025

GODOT - Animare un contatore "score"

 Per animare un contatore di score, bisogna usare uno script e sfruttare il "delta" fornito dalla funzione "process".

Questo lo script di esempio:

var finished = true
var time = 0.0
var time_to_current_score = 2.0
var a = true

var score = 1001

func _process(delta):
  if finished and a:
    time += delta
    text = str(int(time / time_to_current_score * score))
    if time >= time_to_current_score:
      a = false
      text = str(score)

GODOT - Impostare i font per la Pixel Art

Ho ricreato un giochetto sviluppato con il framework Love2d in Godot ed uno dei problemi più grossi che ho riscontrato è stato... come visualizzare correttamente il font senza antialiasing o altre "storture grafiche".

In Love2d, una volta impostato il filtro come "nearest", il gioco è fatto. Il rendering è perfetto.

In Godot non è così: il solo filtro non è sufficiente...

Dopo aver impostato la scena (per i test ho inserito uno sfondo, una label ed ho impostato una risoluzione di 384x216 in fullscreen), questi sono i passaggi da fare:

Gestione del file con il font

  • doppio click sul file .ttf
  • impostare Antialiasing a "None"
  • cliccare "Reimport"

Gestione del progetto

  • nella schermata General > Display > Window, impostare lo stretch mode a "viewport"
  • nella schermata General > Rendering > Textures, impostare su Default Texture Filter il valore "Nearest"

Se questo non basta, ci sono altre impostazioni... che al momento non ho trovato nei menu (e che avevo visto mentre impazzivo per risolvere questo problema).

GODOT - Esportare per Windows

Per prima cosa, va installato il template di progetto.

Bisogna cliccare sul link "manage" nella "guida agli errori" (in basso), poi cliccare su "Go online" e quindi scaricare il pacchetto appropriato.

Altra cosa da scaricare e configurare: il file RCEdit.

Si trova a questo indirizzo: https://github.com/electron/rcedit/releases

Per configurarlo, cliccare su Editor > Editor Settings... > Export > Windows ed inserire il collegamento al file nella casella "rcedit".

A questo punto, è possibile impostare i vari parametri e quindi cliccare su "Export Project...".

A questo punto, Godot genererà un file eseguibile. Da notare che è di... dimensioni considerevoli!

GODOT - Esportare per Android

Con Godot è possibile sviluppare dei giochi che possono essere caricati sul cellulare (nel mio caso, un device Android).
Per farlo i passaggi sono molteplici, vediamo quindi la lista dettagliata.

No Export Template

Quando compare questo errore, bisogna cliccare su "Manage Export Template" (in basso), apparirà la schermata del Manager. Per prima cosa, bisogna assicurarsi di "essere online" (c'è un'opzione sotto la gestione del download), quindi si seleziona "Download and Install".

Java SDK e Android SDK

Per la produzione di un pacchetto Android corretto, è necessario che sulla macchina sia installato sia Android Studio che l'openSDK di Java, che si può trovare a questo indirizzo:

https://adoptium.net/temurin/releases/?variant=openjdk17

Una volta installati i due software, impostare i percorsi degli SDK in:

Editor > Editor Settings... > Export > Android

Nota:
Godot produce giochi per l'SDK 34, quindi è necessario scaricare il corretto SDK da Android Studio.

Impostare il Preset

Queste sono le opzioni da impostare:

  • dopo aver aggiunto la tipologia "Android", bisogna assicurarsi che sia selezionata l'architettura "arm64"
  • bisogna indicare la versione del codice (un numero intero)
  • come "Export Format", impostare "Export APK"
  • bisogna indicare un "Unique Name" nella forma del "dominio inverso" (ad esempio: com.google.NomeProgetto)
  • il name sarà il nome dell'applicazione

Nota:
per la pubblicazione sul Play Store, bisogna registrare una chiave ".keystore" tramite questo comando:

keytool -v -genkey -keystore NOMEAPP.keystore -alias NOMEAPP -keyalg RSA -validity 10000

Il comando chiederà alcune info, bisognerà compilare ogni elemento.

Chiave di debug

La chiave di debug dovrebbe già essere attiva e presente nel gestore (presumo sia un'impostazione di base di Godot 4.4).

Se non è così, bisogna usare questo comando:

keytool -keyalg RSA -genkeypair -alias USERNAMEDEBUG -keypass PASSWORD -keystore debug.keystore -storepass PASSWORD -dname "CN=Android Debug,O=Android,C=US" -validity 9999 -deststoretype pkcs12

A questo punto, questi comandi si caricano negli Editor Settings.

Se tutto è a posto (e se il cellulare è collegato ed ha il Debug Sviluppatore attivo), comparirà il tasto "deploy" in alto a destra.
In questo modo, è possibile debuggare il gioco direttamente sul cellulare!

Problemi nel debug

Può capitare che, tentando il deploy di un gioco, il programma restituisce un errore di installazione. Se restituisce l'errore:

INSTALL_PARSE_FAILED_NO_CERTIFICATES
Aprite la seguente cartella (che contiene l'SDK di Android):
C:\Users\[UTENTE PC]\AppData\Local\Android\Sdk\build-tools
All'interno controllate se sono presenti versioni (cartelle) precedenti alla 34.
Per un bug, il programma cerca di usare la prima versione che trova (sul mio PC usava la 29) e questo dava problemi nella "firma" del pacchetto, rendendolo "invalido" e quindi non installabile.

Eliminando le cartelle precedenti alla 34, il problema si risolve.

lunedì 8 settembre 2025

GODOT - Nascondere la visualizzazione all'esterno di margini desiderati

Sviluppando "Space Bricks", mi è capitato di dover "nascondere" parte della visualizzazione di un'animazione che "usciva dai bordi" (per la cronaca, quella dell'esplosione). Sono riuscito a farlo in Love2D, ma in Godot... ho dovuto sbatterci parecchio la testa.

Per risolvere il problema, bisogna inserire nella scena un controllo che abbia una posizione ed una dimensione.
La scelta è ricaduta sul nodo "ColorRect", che ha entrambi le voci di configurazione impostabili.

Nota: anche questo fa parte dei nodi "Control", non è un problema inserirlo come figlio di un "semplice" Node2D. L'editor mostrerà un avviso, ma il funzionamento sarà comunque corretto.

Una volta inserito il nodo ed impostato "Size" e "Position" (sotto Transform), questo nodo andrà utilizzato per l'inserimento degli elementi "da mascherare" (nel mio caso, è un inserimento dinamico).
Per abilitare la visualizzazione dei "nodi figli" in modo che utilizzi il ColorRect come maschera, bisognerà attivare (flag "On") i seguenti due parametri di configurazione:
  • Clip Content (sotto Layout)
  • Clip Children -da impostare a "Clip Only"- (sotto "Visibility")
La prima effettuerà il vero e proprio "clipping", mentre la seconda visualizzerà solo i nodi figli senza mostrare a video l'oggetto ColorRect (e cioè un pannello a tinta unita).

venerdì 5 settembre 2025

GODOT - creazione di nodi dinamicamente a runtime

Lo spawning di elementi è una costante di moltissimi giochi (si usa anche nei giochi più semplici).

Per fare questo ci serviranno le seguenti scene:

  • l'oggetto da riprodurre
  • l'oggetto che gestisce la produzione di elementi

Prendiamo ad esempio un gioco di carte. Ho l'oggetto Carta, l'oggetto Mazzo ed il Board (sono tre scene con i dettagli all'interno).

Ognuno di questi nodi avrà il suo script collegato.

Carta

Nello script, andrà creata la funzione initialize() che configurerà il contenuto, ma al momento non concentriamoci sul suo funzionamento.

Mazzo

Lo script del mazzo prevede alcuni elementi importanti (il numero di carte, l'array con le carte all'interno, etc) ed una variabile fondamentale:

@export var cardNode: PackedScene

Questa variabile contiene la scena che andrà "duplicata" per generare tutte le carte del mazzo, e con @export sarà possibile associare il nodo desiderato direttamente dall'editor della scena.

Questa è la funzione per la creazione di tutte le carte (nell'esempio, facciamo 32 carte):

func initialize() -> void:
    for i in range(self.numMaxCards):
        var c: Node2D = self.cardNode.instantiate()
        
	# al momento passo il contatore, ma si può caricare tutti i dati
        # per "generare" la carta
        c.initialize(i)

        c.visible = false

        # aggiunge la carta al mazzo
        self.cardsInDeck.append(c)

    # mescola il mazzo
    self.cardsInDeck.shuffle()

Ci sono poi altre funzioni che servono a gestire il mazzo (numero di carte rimaste / pesca una carta / etc).

Board

Per la scena Board, nella funzione _ready() (o reset, se serve), bisogna instanziare il mazzo e poi inizializzarlo.

A questo punto, il mazzo conterrà il numero di nodi Carta voluto, ognuno con il suo dettaglio.

GODOT - uso di GDscript e scene globali

Nello sviluppo di un videogame, è utile avere delle strutture (variabili, funzioni, etc) che siano accessibili da ogni pezzo di codice.
Le "variabili globali", insomma.

In Godot è possibile farlo, piazzando variabili e codice in uno script e poi aggiungendolo all'autoload.

Procedura:

  • nella sezione "FileSystem", cliccare con il tasto destro e selezionare "New Script..."
  • impostare il nome dello script (global o environment o generic_functions)
  • inserire nello script tutto ciò che il sistema deve sapere e "condividere"
  • aprire "Project" > "Project Settings..."
  • selezionare la voce di menu "Globals"
  • selezionare la sezione "Autoload"
  • inserire lo script nel campo "Path" (o cliccare sulla cartellina e selezionarlo con il mouse)
  • inserire il nome del nodo a cui si avrà accesso nel resto degli script (occhio a maiuscole e minuscole)
  • cliccare "+ Add"

E' anche possibile inserire anche delle scene nella sezione "Globals > autoload", per facilitare l'accesso ai componenti.

Potrebbe essere interessante per passare dati tra una scena e l'altra.

giovedì 4 settembre 2025

LOVE2D - Abilitare il fullscreen dal gioco

E' possibile far partire il gioco in "fullscreen" oppure si può impostarlo da codice. Vediamo come impostarlo da codice.

Partiamo dal passaggio al fullscreen.

I passaggi sono:

  • recuperare i "mode" della finestra (dimensioni e flag)
  • salvare la posizione attuale in una variabile (opzionale, serve per il ripristino)
  • impostare mode con il fullscreen

local w, h, flags = love.window.getMode()

local pos_x, pos_y, pos_index = love.window.getPosition()

Position = {
    x = pos_x ,
    y = pos_y ,
    displayindex = pos_index
}

flags.fullscreen = true

love.window.setMode(0, 0, flags)

Se invece vogliamo tornare alla finestra, i passaggi sono più o meno "inversi":

flags.fullscreen = false
flags.borderless = false
flags.resizable = false

love.window.setMode(DIM.W, DIM.H, flags)

love.window.setPosition( Position.x, Position.y, Position.displayindex )

Nota: DIM.W e DIM.H sono le dimensioni "originali" della finestra (vedere post precedente sullo "scalare" la grafica).

LOVE2D - Grafica "scalare"

 Ho creato uno o due giochetti in pixelart ed uno dei problemi che ho dovuto affrontare è stato... "scalare" la grafica per evitare di fare un "gioco per formiche".

Questo è un piccolo tutorial che spiega cosa serve fare per gestire "scaling" (più avanti ci sarà un post sul fullscreen).

Per prima cosa, è necessario scegliere una dimensione "standard" da cui partire. Per ottenere il miglior effetto di "scaling", bisogna che la grafica di arrivo (ad esempio, la risoluzione -ormai standard- 1920x1080) sia un moltiplicatore esatto della grafica di partenza.

Le due opzioni più sensate sono il x3 ed il x4, che hanno rispettivamente le seguenti dimensioni:

x3    640x360
x4    480x270
x5    384x216

Nota: si può ovviamente lavorare ad un progetto con risoluzioni iniziali "standard", ad esempio 640x480, bisogna però "ridurre" il moltiplicatore e gestire il translate.


La prima cosa da fare è inserire il filtro "nearest" per disegnare i pixel senza "blur". Questo va fatto prima di qualsiasi aggiunta di asset (creati con newImage), in questo modo tutto sarà "nitido":

love.graphics.setDefaultFilter("nearest", "nearest")

Definiamo, a questo punto, una dimensione standard (quella utilizzata per il gioco, di norma indicata come "virtuale" -VR-) e la dimensione "finale" in finestra:

DIM_VR = {
    W = 384 ,
    H = 216
}

DIM = {
    W = 384 * 2 ,
    H = 216 * 2
}

Per far partire il gioco alla dimensione corretta, impostare gli stessi valori di DIM anche nel love.conf(t):

function love.conf(t)
    t.window.width = 384
    t.window.height = 216
end

Eventualmente, si possono impostare gli stessi valori nel love.load():

function love.load()
    local w, h, flags = love.window.getMode()

    if w ~= DIM.W or h ~= DIM.H then
        love.window.setMode(DIM.W, DIM.H, flags)
    end
end

Concentriamoci sul love.draw(t), dove andremo a "sistemare" la scala della grafica.
Per disegnare correttamente il tutto, useremo un "buffer" e "scaleremo" quello.

I passaggi necessari sono questi:

  • recupero delle dimensioni della finestra
  • creazione del canvas
  • calcolo della scala come "moltiplicatore intero"
  • impostazione del canvas
  • impostazione della scala
  • eventuale traslazione per disegnare il tutto
  • disegno della schermata di gioco
  • chiusura del canvas
  • disegno a video del tutto

Il codice, quindi, è:

function love.draw(dt)
    -- dimensioni finestra
    local screenSizeX, screenSizeY = love.graphics.getDimensions()

    -- creazione canvas
    local framebuffer = love.graphics.newCanvas(screenSizeX, screenSizeY)

    -- calcolo scala
    local scale = math.floor(math.min(screenSizeX / DIM_VR.W, screenSizeY / DIM_VR.H))

    -- gestione del canvas e scala
    love.graphics.setCanvas(framebuffer)
    love.graphics.push()
    love.graphics.scale(scale, scale)

    -- gestione del translate
    trans_x = (screenSizeX - DIM_VR.W * scale) / 2
    trans_y = (screenSizeY - DIM_VR.H * scale) / 2

    love.graphics.translate(trans_x, trans_y)

    -- DISEGNO DELLA SCHERMATA DI GIOCO
    love.graphics.draw(Asset)

    -- reset
    love.graphics.setCanvas()
    love.graphics.pop()

    -- disegno del canvas
    love.graphics.draw(framebuffer, 0, 0)
end

Innestando questo codice, il sistema disegnerà la pagina della dimensione voluta in modo "scalato". Se la scala effettiva non è "intera", il disegno verrà centrato nella schermata.

Nota: a questo punto bisogna solo ricordarsi che l'intera grafica è "scalata", per cui se si utilizza il mouse come input, per ottenere la posizione corretta è necessario dividere il valore di X e Y per la scala!

martedì 2 settembre 2025

LOVE2D - Garbage collector

 Facendo un test con un videogame che sto sviluppando, mi sono accorto che il quantitativo di RAM "mangiata" dal programma aumentava a vista d'occhio, fino a rendere instabile il sistema.

A quanto pare, il garbage collector non veniva chiamato correttamente dal framework, quindi l'ho impostato manualmente nella sezione love.update() di main.lua:

function love.update(dt)
    collectgarbage("collect")
end

In questo modo il sistema pulisce costantemente ed infatti la RAM non è più stata "fagocitata".