Python – Salvataggio automatico

Hai visto negli articoli precedenti come salvare e caricare i dati usando alcuni Sensors Keyboard per inviare l’impulso positivo al Controller Python che esegue lo script da te creato per salvare o caricare i dati che hai deciso di usare. In questo articolo invece vedrai come è possibile lanciare la stessa procedura usando, invece dei Sensors Keyboard, degli eventi che fanno parte del gioco, come la collisione con oggetti che determinano un momento chiave del gioco, come la fine del livello oppure un momento in cui si vuole salvare prima di affrontare un’area difficile del gioco (tipo combattere con un Boss). Come ho scritto altre volte le applicazioni sono molteplici ed è difficile coprirle tutte, infatti con questi tutorial cerco di dare una base per farti comprendere al meglio le funzionalità del motore di gioco di Blender e permetterti di creare il tuo gioco senza dover impazzire, il cammino è ancora lungo e gli argomenti sono tanti per cui iniziamo. Per questo articolo ho creato un file di partenza che puoi scaricare QUA, ti consiglio di estrarre i file in una cartella, l’importante è che siano separati da altri file.

Con salvataggio automatico mi riferisco ad un tipo di salvataggio che viene impostato (sempre da te) che non richiede l’interazione del giocatore. Un’esempio che mi viene subito in mente è quando finisce un livello, sempre se il tuo gioco ha diversi livelli, in questo caso nel momento in cui il player tocca l’oggetto che fa innescare la procedura di cambio livello vengono salvati i dati. Il file che ho linkato per questo tutorial parte da questa procedura; innanzitutto mi sembra doveroso spiegare velocemente il file che hai davanti. Il player ha 4 State Mask :

  • Nel primo c’è un Always che aggiunge il secondo e il terzo.
  • Nel secondo c’è il movimento, tasto sinistro del mouse per andare avanti e il movimento per fare ruotare il cubo.
  • Nel terzo c’è la logica con la Property monete, il conteggio di esse e lo spawn del punto di fine del gioco. L’ultima riga di Mattoni imposta lo State Mask al quarto.
  • Nel quarto c’è la logica già impostata per il salvataggio, manca solo lo script, e la fine del gioco dopo 3 secondi.

In debug mode puoi vedere l’andamento della Property e degli State Mask, il gioco così com’è impostato finisce e i dati vengono persi, se dovessi avere un’altro livello la Property partirebbe da zero. Ma andiamo con ordine e iniziamo con lo script.

Crea uno script e rinominalo come vuoi con estensione .py, io l’ho chiamato saveEnd.py. All’inizio del file scrivi la solita procedura che richiama il modulo bge e il collegamento del Controller al player, se non ricordi eccoti le righe

import bge

cont = bge.logic.getCurrentController()
player = cont.owner

E’ il turno di inserire nello script il dato che vuoi salvare, in questo esempio è la Property monete, lascia un paio di spazi e inserisci il codice seguente, se non ricordi come si imposta una Property via script

propMonete = player['monete']

Se guardi il file, il Sensor che innesca lo script è un Always con il Tap premuto, quindi la procedura dice che quando lo State Mask 4 è attivo al primo frame (quindi immediatamente) viene mandato l’impulso al Controller che esegue lo script per poi passare al Delay e chiudere il gioco. Quindi bisogna aggiungere il Sensor Always che ho chiamato tap allo script, se non sai come fare puoi trovare la procedura spiegata nella pagina Blender e Python su questo sito, oppure riguardare gli articoli precedenti, in ogni caso posto la riga necessaria

sens_tap = cont.sensors['tap']

Come parte finale dello script serve un’istruzione condizionale che dica : “se il sensor tap è positivo esegui il salvataggio dei dati”, questa frase che ho evidenziato si chiama pseudo codice cioè una forma umanamente leggibile del codice finale, spesso i programmatori la usano per focalizzare i passaggi utili per poi trasformarla in codice (qualsiasi sia il linguaggio di programmazione). Se hai letto gli articoli precedenti sai come fare, in caso contrario posto il codice 

if sens_tap.positive:
    
    bge.logic.globalDict["player"] = propMonete
    bge.logic.saveGlobalDict()
    print("monete salvate", propMonete)

L’ultima riga stampa sulla console la frase tra le virgolette e il valore della variabile propMonete, per mostrare la console segui la procedura sempre nella pagina Blender e Python.

Adesso lo script è completo basta selezionarlo nel Controller collegato al Sensor tap salvare e fare partire il gioco. Apparentemente non è successo niente, il gioco si chiude esattamente come prima, ma se dai un’occhiata alla console trovi la scritta “monete salvate 5” e se apri la directory dove hai estratto i file trovi il file main.bgeconf che è il globalDict. Posto l’intero script con i commenti

import bge

    ### Variabili Globali ###
cont = bge.logic.getCurrentController()
player = cont.owner

    # property del player 
propMonete = player['monete']

    # dichiarazione Sensors
sens_tap = cont.sensors['tap']

    # condizione
if sens_tap.positive:
    # dati da salvare
    bge.logic.globalDict["player"] = propMonete
    # salvataggio
    bge.logic.saveGlobalDict()
    # stampa un messaggio con variabile
    print("monete salvate", propMonete)

Questa semplice procedura ha milioni di sfaccettature, infatti l’esempio senza lo script sarebbe un semplice giochino, ma puoi applicarlo a diverse situazioni, ad esempio quando il player oltrepassa un punto prima di una parte difficile o lo scontro con un Boss e si vuole salvare la posizione o la quantità di energia oppure tutti i dati che servono al nostro gioco, insomma si può creare una sorta di checkpoint per poi ricaricarlo se il giocatore muore nella fase successiva o chiude il gioco per riprenderlo in seguito. Nel mio esempio il gioco si conclude, ma mettiamo che io voglia che il mio gioco continui su un’altro livello e che quando inizia il livello i dati salvati vengano caricati automaticamente in modo da continuare il gioco.

Visto che sto facendo degli esempi, evito di ricreare un livello da zero e uso la procedura di full copy per creare una nuova scene identica, modificando naturalmente un paio di cose, ad esempio i colori del piano (pavimento) e del player. Chiamo la nuova scena lev1, naturalmente essendo un’esempio puoi usare i nomi che vuoi. La prima cosa da fare per essere sicuro che il nuovo livello viene caricato è cambiare nello State Mask 4 l’Actuator Game con un’Actuator Scene, impostare l’opzione Mode su Set Scene e scegliere la scena lev1 dal menù Scene, eccoti uno screenshot, salva e prova il file.

Come pianificato quando il player tocca l’oggetto di fine il livello cambia, adesso spostati sul lev1 e inserisci un nuovo script, rinominalo come meglio credi, per questo esempio l’ho chiamato ‘loadStart.py’. Per praticità copia le prime otto righe dello script saveEnd.py e incollale su questo nuovo script. Quello che voglio ottenere è il caricamento della quantità di monete salvate appena il livello inizia. Sullo State Mask 1 (quello iniziale) inserisci un’Actuator State con l’opzione Add State e clicca il quinto State Mask, collegalo al Sensor Always, ma voglio che questo State venga eliminato quasi subito perché serve solo a caricare i dati, quindi aggiungi un Sensor Delay e imposta l’opzione Delay a 30 (mezzo secondo), aggiungi ora un’Actuator State con l’opzione Remove State e clicca sul quinto State Mask, collegali tra loro con un Controller And. Al quinto State Mask inserisci un Sensor Always, clicca sul tap, e rinominalo load_tap (o come meglio credi) questo Sensor farà parte dello script, inserisci un Controller Python e seleziona lo script per il caricamento. Adesso scrivi la dichiarazione del Sensor load_tap

# dichiarazione Sensors
load_tap = cont.sensors['load_tap']

Uso come nello script precedente un’istruzione condizionale : ” se il sensor load_tap è positivo, carica i dati”

if load_tap.positive:
    #carico il globaldict
    bge.logic.loadGlobalDict()

Assegno i dati del globalDict ad una variabile che chiamo monete.

# recupero le informazioni
monete = bge.logic.globalDict["player"]

L’ultima riga assegna l’indice della lista (array) dei dati estrapolati direttamente alla Property del player

# la Property è uguale al primo indice dei dati   
player['monete'] = monete[0]

In questo caso la lista (array) contiene un solo indice, ma è possibile avere più di un dato salvato nel globalDict, infatti quando si crea una serie di variabili da salvare bisogna tenere il conto dell’indice (che parte sempre da 0), per esempio negli articoli precedenti ho salvato la posizione del cubo con 3 variabili per poi estrapolarle e assegnarle alla posizione globale del cubo, rileggili se non ti ricordi.

A questo punto lo script è finito, eccolo per intero

import bge

    ### Variabili Globali ###
cont = bge.logic.getCurrentController()
player = cont.owner

    # property del player 
propMonete = player['monete']

# dichiarazione Sensors
load_tap = cont.sensors['load_tap']

    # condizione
if load_tap.positive:
    #carico il globaldict
    bge.logic.loadGlobalDict()
    # recupero le informazioni
    monete = bge.logic.globalDict["player"]
    # la Property è uguale al primo indice dei dati   
    player['monete'] = monete[0]

Come puoi notare non è diverso da quello che salva i dati, avrai anche notato che non ho usato l’opzione Module del Controller Python perché poteva creare confusione, si possono incorporare i due script in un’unico solo, e cambiare la modalità da Script a Module nei Controller Python che eseguono il codice. Questo se vuoi lo puoi farlo tu come esercizio.

Adesso è ora di provare se la logica implementata in questo articolo funziona, salva e fai partire il gioco dalla scena main. Quando la seconda scena (lev1) viene caricata la Property monete è automaticamente settata al valore salvato quando la scena precedente (main) è finita. Un piccolo accorgimento per completare il gioco è quello di cambiare nella scena lev1 sul Sensor Property nello State Mask 3 l’ammontare delle monete che invia l’impulso per lo spawn dell’oggetto finale da 5 a 10; cambia anche il destinatario del messaggio che viene inviato quando questo Sensor Property manda l’impulso l’oggetto è spawn_end.001.

Questa è la base per creare un salvataggio e caricamento automatico dei dati durante il gioco, quali dati salvare e quando lo decidi tu, stessa cosa per caricarli. Come sempre fai delle prove, aggiungi altre Properties e prova ad usare un file da te creato in modo da comprendere il meccanismo al meglio.