Il PlayerPrefs è la classe di Unity che ci permetterà di effettuare i salvataggi permanenti, ovvero, tramite esso potremmo salvare lo stato del gioco nel momento della chiusura e ricaricare i dati alla riapertura dello stesso.
Esistono diversi modi per salvare permanentemente i dati su disco, il PlayerPrefs è sicuramente quello più immediato, anche se limitato ad una serie ristretta di tipi di dati.
Con il PlayerPrefs potremmo infatti salvare i dati di tipo float, int e string. Vedremo in seguito che esiste uno script non proprietario di Unity che permetterà di ampliare questo limite.
La classe PlayerPrefs non usa il salvataggio su file. La locazione dei salvataggi varia a seconda del dispositivo per cui si è sviluppato il gioco ma il funzionamento a livello di codice è il medesimo.
In seguito vedremo come salvare i dati con un metodo differente dal PlayerPref, più potente e veloce.
Per ora impariamo ad usare il PlayerPref che già di per se può bastare in progetti di piccola/media complessità.
Vediamo il suo funzionamento in pochi brevi passaggi.
Appena scriveremo la parola PlayerPrefs , ci verranno elencati i metodi statici disponibili su questa classe. Prendiamo in esame i tre metodi che iniziano con la parola Set e i i tre corrispettivi metodi Get.
Set sta per “setta“(salva) mentre Get sta per “prendi“(carica).
Come si può facilmente intuire, con SetFloat andremo a salvare un numero di tipo float mentre con GetFloat lo andremo a caricare.
Se peresempio volessimo salvare lo stato dell’energia del giocatore, potremmo scrivere:
PlayerPrefs.SetFloat("EnergiaPlayer", playerEnergy);
Abbiamo usato il metodo SetFloat della classe PlayerPrefs che richiede due parametri, il nome con i quale andremo a salvare il dato (di tipo string) e il dato in questione (in questo caso, di tipo float).
In questo modo avremo salvato il dato di tipo float “playerEnergy“ su disco, sotto il nome di “EnergiaPlayer“. Per convenzione, i nomi con cui salviamo le variabili su PlayerPrefs vengono chiamate keys (chiavi).
Notare che il nome scelto (la key) per il salvataggio è (e dovrà sempre essere) una stringa.
Per caricare lo stesso dato salvato su disco dovremmo usare l’istruzione GetFloat:
playerEnergy = PlayerPrefs.GetFloat("EnergiaPlayer");
In questo modo abbiamo impostato la variabile playerEnergy uguale al dato presente su disco sotto il nome EnergiaPlayer.
Potremmo creare una semplice classe per il salvataggio dei dati chiamata SaveAndLoad con questi due metodi, uno per il salvataggio e uno per il caricamento:
using UnityEngine;
public class SaveAndLoad : MonoBehaviour {
public float playerEnergy;
void SavePlayerState ()
{
PlayerPrefs.SetFloat("EnergiaPlayer", playerEnergy);
}
void LoadPlayerState()
{
playerEnergy = PlayerPrefs.GetFloat("EnergiaPlayer");
}
}
In questo modo avremo salvato solo un dato, ovvero l’energia del giocatore.
Sappiamo però che in un gioco complesso sarà necessario salvare molti più dati.
Per esempio, potremmo aggiungere il nome del giocatore e il suo punteggio attuale. Due variabili rispettivamente di tipo string e int.
using UnityEngine;
public class SaveAndLoad : MonoBehaviour {
public float playerEnergy;
public int playerPoints;
public string playerName;
void SavePlayerState ()
{
PlayerPrefs.SetFloat("EnergiaPlayer", playerEnergy);
PlayerPrefs.SetInt("PuntiPlayer", playerPoints);
PlayerPrefs.SetString("NomePlayer", playerName);
}
void LoadPlayerState()
{
playerEnergy = PlayerPrefs.GetFloat("EnergiaPlayer");
playerPoints = PlayerPrefs.GetInt("PuntiPlayer");
playerName = PlayerPrefs.GetString("NomePlayer");
}
}
In questo modo abbiamo visto tutt e tre i tipi di variabili salvabili tramite PlayerPrefs, float, int e string.
Salvare un Vector3 su PlayerPrefs
E se volessimo salvare l’attuale posizione del player nella scena?
Come abbiamo visto non ci sono i Vector3 tra le variabili salvabili. Dovremmo quinti salvare tre variabili float corrispondenti alle tre componenti del Vector3, X,Y,Z.
Per caricarle dovremo in seguito “ricomporre” il Vector3 position del player.
using UnityEngine;
public class SaveAndLoad : MonoBehaviour {
public float playerEnergy;
public int playerPoints;
public string playerName;
public Transform Player; //Il Transform del player
void SavePlayerState ()
{
PlayerPrefs.SetFloat("EnergiaPlayer", playerEnergy);
PlayerPrefs.SetInt("PuntiPlayer", playerPoints);
PlayerPrefs.SetString("NomePlayer", playerName);
//Salvo individualmente le tre componenti della position del player
PlayerPrefs.SetFloat("posizionePlayerX", Player.position.x);
PlayerPrefs.SetFloat("posizionePlayerY", Player.position.y);
PlayerPrefs.SetFloat("posizionePlayerZ", Player.position.z);
}
void LoadPlayerState()
{
playerEnergy = PlayerPrefs.GetFloat("EnergiaPlayer");
playerPoints = PlayerPrefs.GetInt("PuntiPlayer");
playerName = PlayerPrefs.GetString("NomePlayer");
//Carico i valori su tre variabili temporanee
float Xpos = PlayerPrefs.GetFloat("posizionePlayerX");
float Ypos = PlayerPrefs.GetFloat("posizionePlayerY");
float Zpos = PlayerPrefs.GetFloat("posizionePlayerZ");
//Assegno i tre valori alla position del player
Player.position = new Vector3(Xpos,Ypos,Zpos);
}
}
Abbiamo salvato in modo separato le tre componenti della posizione del giocatore, rispettivamente con i keys:
- posizionePlayerX
- posizionePlayerY
- posizionePlayerZ
Per caricarli dovremmo quindi “ricomporre” il Vector3 posizionePlayer caricandoci le tre variabili salvate su disco.
Questa operazione sembra un po’ troppo macchinosa, seppur necessaria. Considerando che sarà probabile che dovremmo salvare un sacco di posizioni di oggetti durante un gioco, dovremmo fare questa operazione per ogni Vector3.
A questo proposito, vi invito a dare un’occhiata allo script aggiuntivo presente in questo articolo. che ci permetterà di salvare i Vector3 con una sola riga di codice.
Se tentiamo di caricare qualche key che non esiste?
Prima di fare un caricamento potremmo controllare che sul PlayerPrefs esista una determinata key.
Con questa riga:
PlayerPrefs.HasKey(“nomeKey”)
che restituisce un valore booleano, vero se nomeKey esiste e falso se nomeKey non esiste.
if(PlayerPrefs.HasKey("EnergiaPlayer"))
{
//se esiste un dato salvato con il nome NomePlayer, assegnalo alla variabile playerName
playerName = PlayerPrefs.GetString("NomePlayer");
}
else
{
//Se non esiste nessun dato salvato con la key NomePlayer, assegnagli "Pippo" di default
playerName = "Pippo";
}
Senza fare un controllo, nel caso la key che si tenta di caricare non esista, non avremo un errore, ma alla variabile sarà assegnato un valore di default (stringa vuota in caso di string, e 0 in caso di float o int).
Atre tecniche di salvataggio
Come detto, usare il PlayerPref per gestire i salvataggi in un videogioco di piccole/medie dimensioni può essere la scelta più corretta.
Come abbiamo visto possiamo salvare il valore di una variabile e in seguito ricaricare il valore per reimpostarlo sulla valriabile.
Va detto però che il PlayerPref ha delle limitazioni nelle dimensioni/quantità di dati salvabili e una gestione macchinosa dei caricamenti dove bisogna lavorare singolarmente su ogni valore per riassegnare lo stato di ogni variabile. Pensate se doveste salvare degli array o una serie di dati molto grande, con molti nemici di cui salvare lo stato ecc…
Quanto sarebbe più bello poter salvare lo stato di un’intera classe, a prescindere dalle variabili che ha al suo interno, per poter poi ripristinare lo stato della classe in tutte le sue variabili, con un semplice comando?
E’ quello che andremo ad imparare usando la serializzazione per i salvataggi su files, con ben tre metodi differenti tra loro: Json, XML e Binary Serialization.