Abbiamo già visto come usare il PlayerPref per gestire i salvataggi del gioco ma abbiamo anche notato come la cosa potrebbe risultare un po’ macchinosa in caso di un grosso numero di variabili da dover salvare e riassegnare al caricamento.
Questo articolo tratterà le basi della serializzazione e i tre metodi più comuni di serializzazione in Unity: JSON, XML e Binary.
COSA VUOL DIRE SERIALIZZARE?
La serializzazione è il processo di conversione di un oggetto (istanza di una classe) in un modulo che può essere archiviato o trasportato facilmente.
Gli oggetti sono istanze di classi e vengono convertiti in un flusso di byte. Questo flusso può quindi essere archiviato come file, salvato su un database o inviato su Internet a un servizio web.
Il contrario di questo processo, ovvero la conversione di un flusso che può essere un file o una stringa in un oggetto, si chiama deserializzazione.
Volendo riassumere, si potrebbe dire che la serializzazione permette di salvare su file lo stato di tutte le variabili di un oggetto, ovvero l’intero stato di un’istanza della classe.
Prendiamo per esempio uno script chiamato “Nemico” che ha, ipoteticamente:
energia =10, forza= 20, resistenza 5, ecc…
Se durante il gioco energia diventa 5 e forza diventa 15, resistenza diventa 8… usando la serializzzazione, non dovremmo creare un codice tipo:
“salva lo stato della variabile energia… salva lo stato della variabile forza….salva la variabile resistenza… salva la sua posizione, salva la sua rotazione….” Eccetera eccetera, per tutte le variabili, che potrebbero essere decine o centinaia!
Usando la serializzazione dovremmo usare un codice che dice semplicemente: “salva lo stato dello script Nemico, con tutte le sue variabili”.
È più facile capire la funzione di serializzazione usando un semplice esempio.
In questo esempio, i dettagli del giocatore (PlayerData) verranno salvati e poi potranno essere ricaricati su un’istanza della classe in qualunque momento.
JSON e XML sono due formati di serializzazione standard ed entrambi sono mostrati nell’immagine seguente. Questi standard hanno i loro pro e contro e saranno discussi e messi a confronto più avanti.
Poniamo di avere una classe PlayerData così:
public class PlayerData {
public string name;
public int age;
public string gender;
}
Useremo questa classe per visionare tutti e tre i tipi di tecniche di salvataggio.
NOTA: Avrete notato che questa classe non deriva da MonoBehaviour.
Molto probabilmente, se siete alle prime armi con la serializzazione, avrete una classe Player strutturata tipo così:
using UnityEngine;
public class Player: MonoBehaviour {
public string name;
public int age;
public string gender;
void Awake(){
}
void Start () {
}
......
} //Chiusura Classe Player
Quando dovremmo salvare i dati con l’uso della serializzazione dovremmo creare una classe dove spostare tutte le variabili che vorremmo salvare e usare una sua istanza.
Non sarà necessario creare un nuovo file .cs, la classe PlayerData sarà posizionabile all’interno del vostro script Player. Le variabili sannno comunque visibili nell’ispector come sempre, in modo persino più ordinato.
using UnityEngine; public class Player: MonoBehaviour { public PlayerData
playerDataObject; //L'istanza della sotto classe PlayerData //La classe PlayerData con i dati serializzabili [System.Serializable] public class PlayerData { public string name; public int age; public string gender; } //Chiusura classe PlayerData void Awake(){ ..... } void Start () { .... } ...... } //Chiusura Classe Player
Andremo ora ad usare tutti e tre tipi di serializzazione per salvare lo stato di questa classe d’esempio, senza dover specificare variabile per variabile, ognuno dei tre sistemi salverà tutto il suo stato, con tutti i valori delle sue variabili.
Serializzazione JSON
JSON è il formato di scambio di dati più usato in Internet. I browser Web e i server utilizzano continuamente JSON per comunicare tra loro.
- JSON è l’acronimo di JavaScript Object Notation
- Sintassi per l’archiviazione e lo scambio di dati
- JSON è un testo
- Indipendente dal linguaggio
- Formato di interscambio dati leggero
- Leggibile e facile da capire
using UnityEngine;
using System.IO;
...
// Percorso di salvataggio del file (JSON text)
public string filePath;
// Istanza da serializzare
public PlayerData playerDataObject;
// Funzione per serializzare e salvare il file su disco
public void Save(){
string jsonString = JsonUtility.ToJson(playerDataObject, true); //Crea la stringa da salvare
File.WriteAllText(filePath, jsonString); //Salva la stringa sul file al percorso stabilito
}
// Funzione per deserializzare il file ed inserire i dati in playerDataObject
public void Load(){
string jsonString = File.ReadAllText(filePath); //Leggi il file
playerDataObject = JsonUtility.FromJson(jsonString); //Reinposta playerDataObject con i dati caricati
}
Serializzazione XML
XML è l’acronimo di Extensible Markup Language
utilizzato principalmente per la memorizzazione o il trasferimento di dati strutturati
- La struttura di XML è molto simile all’HTML/CSS
- Leggibile dagli umani
- Struttura ben definita e gestibile in modo mirato
using UnityEngine;
using System.IO;
using System.Xml.Serializaton; // Necessario per la serializzazione XML
...
// Percorso del file da salvare
public string filePath;
// Istanza da salvare
public PlayerData playerDataObject;
// Funzione che serializza e salva il file (XML)
public void Save(){
XmlSerializer serializer = new XmlSerializer(typeof(PlayerData));
FileStream stream = new FileStream(filePath, FileMode.Create);
serialzer.Serialize(stream, playerDataObject);
stream.Close();
}
// Funzione per deserializzare il file ed inserire i dati in playerrDataObject
public void Deserialize(){
XmlSerializer serializer = new XmlSerializer(typeof(PlayerData));
FileStream stream = new FileStream(filePath, FileMode.Open);
playerDataObject = serializer.Deserialize(stream) as PlayerData;
stream.Close();
}
JSON vs XML
- XML è considerato più dettagliato di JSON, quindi il file di dati generato è leggermente più grande. Ecco perché JSON è preferito per i servizi basati sul Web. Ma si tratta di pochi bytes di differenza.
- XML può gestire strutture e relazioni di dati complesse.
- L’operazione di serializzazione e deserializzazione è più lenta per XML rispetto a JSON.
- Altre API utilizzano JSON.
Esempio: API del grafico di Facebook, API di twitter e API di google maps - XML preferito principalmente per l’applicazione desktop, JSON preferito per le applicazioni mobili e web
Abbiamo visto i due tipi di serializzazione che rendono molto facile la consultazione dei dati salvati. In entrambi i casi l’utente può aprire il file salvato, comprenderne il contenuto e modificarne i dati con un qualsiasi editor di testo.
Questo potrebbe essere un problema se volessimo che tali dati non siano modificabili dall’utente, per esempio un giocatore potrebbe “barare” impostando l’energia del player come meglio crede.
Se vogliamo evitare che il giocatore possa modificare i dati di un savegame, dovremmo usare il terzo metodo di salvataggio, il Binary. Oppure usare un sistema di crittografia.
Serializzazione binaria
- La serializzazione binaria converte un oggetto (istanza di una classe) in un formato binario, che in parole povere è un flusso di byte.
- L’operazione di serializzazione è più veloce di tutte le altre.
- Il file di dati generato è compatto e di dimensioni inferiori rispetto a JSON o XML.
- Il file di dati non è leggibile dall’uomo.
- Funziona solo su piattaforma .Net
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary; //Necessario per questo tipo di serializzazione
...
// Percorso del file da salvare
public string filePath;
// Istanza da salvare
public PlayerData playerDataObject;
// Funzione per il salvataggio
public void Save(){
BinaryFormatter bf = new BinaryFormatter();
FileStream stream = new FileStream(filePath, FileMode.Create);
bf.Serialize(stream, playerDataObject);
stream.Close();
}
// Funzione per il caricamento e inserimento dati in playerDataObject
public void Load(){
BinaryFormatter bf = new BinaryFormatter();
FileStream stream = new FileStream(filePath, FileMode.Open);
playerDataObject = bf.Deserialize(stream) as PlayerData;
stream.Close();
}
using UnityEngine; using System.IO; public class Player: MonoBehaviour { public PlayerData
playerDataObject; //L'istanza della sotto classe PlayerData //Apertura classe PlayerData [System.Serializable] public class PlayerData { public string name; public int age; public string gender; } //Chiusura classe PlayerData void Awake(){ } void Start () { } //Percorso file da salvare/caricare public string filePath; // Funzione per serializzare e salvare il file su disco public void Save(){ string jsonString = JsonUtility.ToJson(playerDataObject, true); //Crea la stringa da salvare File.WriteAllText(filePath, jsonString); //Salva la stringa sul file al percorso stabilito } // Funzione per deserializzare il file ed inserire i dati in playerDataObject public void Load(){ string jsonString = File.ReadAllText(filePath); //Leggi il file playerDataObject = JsonUtility.FromJson(jsonString); //Reimposta playerDataObject con i dati caricati } } //Chiusura Classe Player
Potremmo ora aggiungere tutte le variabili che vogliamo all’interno della classe PlayerData, senza dover curarci di di creare codice per il salvataggio di tali variabili perché sarà l’intera classe che verrà salvata e caricata “in blocco”.
Discorso a parte per la posizione e la rotazione dei trasforms che dovremmo riassegnare “a mano” dopo il caricamento.
Ricordiamoci che, se prima potevamo accedere alle variabili di un’istanza di Player semplicemente con:
playerInstance.name
playerInstance.age
playerInstance.gender
….
ora per fare la stessa cosa dovremmo passare anche la classe playerData, tipo:
playerInstance.playerDataObject.name
playerInstance.playerDataObject.age
playerInstance.playerDataObject.gender
….