Go to Top

Motivation

Vous devriez lire cet article si:

  • Vous voulez savoir comment sauvegarder des données de manières persistantes
  • Vous devez stocker des données plus complexes que de simples données primitives

Introduction

Le stockage de données avec Unity est un peu de la science. Si vous souhaitez enregistrer des scènes entières, la position des joueurs etc alors la tâche peut être intimidante. J'ai construit le UnitySerializer qui rend ce processus beaucoup plus facile et les futurs tutoriels de cette série porteront sur une solution de sauvegarde complète. Ce tutoriel couvre quelques-unes des autres manières de stocker des données et de les faire passer entre les versions du jeu, par exemple à travers une connexion RPC ou d'un serveur.

Les bases

Ok, donc les bases. Si vous voulez un morceau de données qui doit être enregistré avec Unity, votre premier recours est probablement PlayerPrefs. PlayerPrefs vous permet de stocker des valeurs fondamentales de primitives avec une simple string. C'est souvent une bonne idée de choisir des noms  significatifs au cas où vous commencer à piétiner partout dans vos propres données plus tard!

//C# & US

PlayerPrefs.SetInt("Score", currentScore);
PlayerPrefs.SetInt("HighScore1", currentScore);
PlayerPrefs.SetString("HighScore1Name", currentPlayerName);

//Obtenir les valeurs

highScore1 = PlayerPrefs.GetInt("HighScore1");
highScore1Name = PlayerPrefs.GetString("HighScore1Name", "N/A"); //Valeur par defaut optionelle

Valeurs dans le jeu

Si vous souhaitez faire passer des données d'une scène vers une autre, la meilleure solution sera une classe statique:

//US

public static class PermanentVariables
{
    public var playing : boolean;
    public var playerName : String;
}

//C#

public static class PermanentVariables
{
    public static bool playing;
    public static string playerName = "";
}

et pour accéder:

PermanentVariables.playerName = "whydoidoit";

The downside with classes like this is that you can't set the variables using the Inspector - so if you want to use a combination of Inspector and code access you want to consider a DontDestroyOnLoadobject that is present in each scene or your starting  scene.   You could do that like this:

L'inconvénient avec les classes de ce genre est que vous ne pouvez pas définir les variables à l'aide de l'inspecteur - si vous souhaitez utiliser une combinaison d' Inspector et aussi l'accessibilité des codes, pensez à l'objet DontDestroyOnLoad  qui est présent dans chaque scène ou la scène de départ. Vous pouvez le faire comme ceci:

public class SharedBehaviour : MonoBehaviour {

	public static SharedBehaviour current;

	public Transform somePrefab;

	public int score;
	public string playerName;

	void Awake()
	{
		if(current != null && current != this)
		{
			Destroy(gameObject);
		}
		else
		{
			DontDestroyOnLoad(gameObject);
			current = this;
		}
	}

}

Ou avec US

	static var current : NameOfYourScript;

	var somePrefab : Transform;

	var score : int;
	var playerName : String;

	function Awake()
	{
		if(current != null && current != this)
		{
			Destroy(gameObject);
		}
		else
		{
			DontDestroyOnLoad(gameObject);
			current = this;
		}
	}

static assure que peu importe dans combien de scènes l'objet est utilisé (utile pour les tests), il n'y a qu' une copie en mémoire. Vous devez accéder à des variables comme ceci:

var x = SharedBehaviour.current.somePrefab;

Stocker des choses plus complexes

Ok so we had a look at a high score method using PlayerPrefs - what a total load of rubbish that was - having to know it was HighScore1Name - ugh. We'd be better off defining a high score table like this:

Ok, donc nous avons jeté un coup d’œil à une méthode high score utilisant PlayerPrefs - ce dégât quand il faut  savoir que la varible s'appelle HighScore1Name et pas autrement - mouaip. Nous ferions mieux de définir un tableau des hi-score comme ceci:

public class ScoreEntry
{
     public string name;
     public int score;
}

public List<ScoreEntry> highScores = new List<ScoreEntry>();

//Utilisation
highScores.Add(new ScoreEntry { name = currentPlayerName,score = score });

//Affichage
void OnGUI()
{
     foreach(var score in highScores)
     {
         GUILayout.Label(string.Format("{0} : {1:#,0}",
            score.name, score.score));
     }
}
class ScoreEntry
{
     var name : String;
     var score : int;
}

var highScores = new List.<ScoreEntry>();

//Utilisation
var entry = new ScoreEntry();
entry.name = currentPlayerName;
entry.score = score;
highScores.Add(entry);

//Affichage

function OnGUI()
{
     for(var score in highScores)
     {
         GUILayout.Label(String.Format("{0} : {1:#,0}",
            score.name, score.score));
     }
}

Mais comment pourrions-nous conserver cela dans PlayerPrefs - BinaryFormatter vient à la rescousse! Avec BinaryFormatter nous pouvons convertir n'importe quelle classe avec un constructeur sans paramètre dans un tableau d'octets et de là dans une string qui est adapté pour stocker dans PlayerPrefs. Il vous suffit d'inclure quelques namespaces et c'est tout:

// UnityScript
//Ces namespaces sont nécessaires
//pour BinaryFormatter
import System;
import System.Runtime.Serialization.Formatters.Binary;
import System.IO;

class ScoreEntry
{
     var name : String;
     var score : int;
}

//High score
var highScores = new List.<ScoreEntry>();

function SaveScores()
{
	//Nouveau BinaryFormatter
	var b = new BinaryFormatter();
	//Creation d'un MemoryStream
	var m = new MemoryStream();
	//Sauvegarde des scores
	b.Serialize(m, highScores);
	//Addition à PLayerPrefs
	PlayerPrefs.SetString("HighScores", Convert.ToBase64String(m.GetBuffer()));
}

function Start()
{
	//Trouver les données
	var data = PlayerPrefs.GetString("HighScores");
	//Si non-nul, charger
	if(!String.IsNullOrEmpty(data))
	{
		//Binary formatter pour le nouveau chargement
		var b = new BinaryFormatter();
		//Création d'un MemoryStrea, avec les données
	        var m = new MemoryStream(Convert.FromBase64String(data));
	        //Charger le nouveau score
	        highScores = b.Deserialize(m) as List.<ScoreEntry>;
         }
}

// C#
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Collections;
//Ces namespaces sont nécessaires 
//pour BinaryFormatter
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

public class HighScores : MonoBehaviour {

	//High score 
	public class ScoreEntry
	{
		//Nom du joueur
	     public string name;
		//Score
	     public int score;
	}

	//High score 
	public List<ScoreEntry> highScores = new List<ScoreEntry>();

	void SaveScores()
	{
		//Création d'un BinaryFormatter 
		var b = new BinaryFormatter();
		//Création d'un MemoryStream
		var m = new MemoryStream();
		//Sauvegarde des scores
		b.Serialize(m, highScores);
		//Addition à PlayerPrefs
		PlayerPrefs.SetString("HighScores", 
			Convert.ToBase64String(
				m.GetBuffer()
			)
		);
	}

	void Start()
	{
		//Obtenir les données
		var data = PlayerPrefs.GetString("HighScores");
		//Si non-nul, charger
		if(!string.IsNullOrEmpty(data))
		{
			//BinaryFormatter puis renvoyer les données
			var b = new BinaryFormatter();
			//Création d'un MemoryStream avec les données
			var m = new MemoryStream(Convert.FromBase64String(data));
			//Charger les nouveaux scores
			highScores = (List<ScoreEntry>)b.Deserialize(m);
		}
	}

}

Stocker des objets Unity

It can be tricky to store actual Unity objects so there's often a need to create a class that mirrors the data in a Unity object that you actually use for storage.  So for example if you wanted to pass a bunch of AnimationStates across an RPC call then you will need to create a class that represents the transmitted AnimationState.  The same is true for a bunch of other Unity objects, but the work is trivial.

il peut être difficile de stocker des objets Unity de sorte qu'il est souvent nécessaire de créer une classe qui reflète les données dans un objet Unity que vous utilisez réellement pour le stockage. Ainsi, par exemple, si vous vouliez passer un tas de AnimationStates à travers un appel RPC alors vous aurez besoin de créer une classe qui représente la AnimationState transmis. La même chose est vraie pour un tas d'objets Unity autres, mais le travail est trivial.

Envoyer Via RPC

Il apparaît donc clairement que le dernier code parvient à transformer une vieille classe en string, donc on peut utiliser le même principe pour envoyer n'importe quel type de paramètre via RPC!

Cela n'est pas documenté mais RPC peut également envoyer un byte [], que l'on peut obtenir directement de notre BinaryFormatter, nous permettant d'économiser beaucoup de temps de conversion vers et à partir d'une string.
Ajouter la possibilité d'envoyer nos meilleurs scores à un autre joueur est soudainement très très facile:

	[RPC]
	void ReceiveHighScores(byte[] highScores)
	{
		var b = new BinaryFormatter();
		var m = new MemoryStream(highScores);
		var otherPlayersScores = (List<ScoreEntry>)b.Deserialize(m);
	}

	void UpdateScores()
	{
		var b = new BinaryFormatter();
		var m = new MemoryStream();
		b.Serialize(highScores, m);
		networkView.RPC("ReceiveHighScores", RPCMode.Others, m.GetBuffer());
	}

Parler à la toile

L' envoi de données vers des serveurs Web à l'aide WWWForm est facile, mais il peut être plus facile de convertir les données binaires dans une string comme nous l'avons vu avec Convert.ToBase64String () même si ce n'est pas nécessaire en fonction de la configuration de votre serveur.

Saving to a file

Sauvegarder un fichier est également facile, Unity fournit Application.persistentDataPath comme un emplacement pour vos fichiers et vous venez d'utiliser l'objet FileStream en place du MemoryStream.

void SaveScores()
	{
		//Créer un BinaryFormatter
		var b = new BinaryFormatter();
		//Créer un fichier
		var f = File.Create(Application.persistentDataPath + "/highscores.dat");
		//Sauvegarder les scores
		b.Serialize(f, highScores);
                f.Close();
	}

	void Start()
	{
		//Si non-nul, charger
		if(File.Exists(Application.persistentDataPath + "/highscores.dat"))
		{
			//BinaryFormatter pour charger les nouvelles données
			var b = new BinaryFormatter();
			//Créer le fichier
			var f = File.Open(Application.persistentDataPath + "/highscores.dat", FileMode.Open);
			//Charger les scores
			highScores = (List<ScoreEntry>)b.Deserialize(f);
			f.Close();
		}
	}

Conclusion

J'espère que vous avez appris quelques nouveaux trucs pour enregistrer les données et déplacez entre les différentes instances du même jeu, que ce soit en tant que valeur stockée ou via une connexion RPC.

N'oubliez pas que si vous souhaitez enregistrer des scènes complexes et des instances de prefab vous consulter Unity Serializer.

, , , , , , ,

2 Responses to "Sauvegarde de Données – Vous vous rappelez de moi!"

  • pouit
    January 6, 2013 - 10:04 am Reply

    Un grand merci pour cet article et les autres ,Bravo.

  • Thecle
    January 11, 2013 - 10:49 am Reply

    Merci pour ces exemples très instructifs.

Leave a Reply

%d bloggers like this: