Jump to content
Unity Insider Forum

Das Laden und Speichern von Spielständen mittels miniJSON


Recommended Posts

Hallo Leute.

Ich will euch mal zeigen, wie man einen Spielstand als Textdatei mittels miniJSON speichern und laden kann.

JSON (JavaScript Object Notation) an sich, ist ein Datenübergabe Format, ähnlich wie XML.

Jedoch wird nur eine einzige Zeile, der sogenannte JSON String übergeben. Um zu unterscheiden, was zusammen gehört, werden gewisse Sonderzeichen genutzt und die Daten immer in Paaren abgelegt. Der String ist gut lesbar und gut zu parsen. Hier ist ganz gut erklärt was JSON eigentlich ist: http://www.json.org/json-de.html

 

Jetzt gibt es jede Menge DLL's die das Parsen und Decodieren sehr schnell im Hintergrund machen, jedoch gibt es gerade im Bezug auf iOS einige schwächen. So kommt es bei JSON FX ( eines dieser leicht unterschiedlichen Bibliotheken, trotzdem das beste für Unity) gerne zu Fehlermeldungen. Man ist aber am Arbeiten und ich denke sehr bald ist JSON FX komplett.

 

Zum Glück für mich, hat im Forum von Unity3d ein User einen eigenen JSON Parser geschrieben, der leichtgewichtig ist und die nötigsten Datenarten verarbeiten kann. Das ist MiniJSON!

Hier ist der Link zu dem c# Script, welches einfach in den Plugins Ordner gelegt werden muss.

https://gist.github.com/1411710

 

So, jetzt aber zum Eigentlichen. :)

 

Wir erzeugen ein neues c# Script und nennen es LoadAndSave.

MiniJSON muss mit dem using Befehl in das LoadAndSave Script eingebunden werden

Wir legen eine Variable für den Speichernamen an und deklarieren eine Dictionary zur Datenablage. Denn das ist der einfachste Weg unsere Daten dem JSON zu übergeben.

 

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using MiniJSON;

public class LoadAndSave : MonoBehaviour {
public string saveName;
Dictionary<string,object> dict = new Dictionary<string, object>();

 

Jetzt, für den Test, setzen wir die Speichervariable einfach mal per Hand in der StartRoutine.

 

 void Start () {
// für den test heisst das saveGame erstmal save1

	saveName="save1";


}

 

Außerdem nutze ich jetzt einfache Tasten um einen Spielstand zu laden und zu speichern. Bei Druck auf die Taste wird in die entsprechende Funktion gesprungen.

 

	void Update () {
	 if(Input.GetKeyDown(KeyCode.L)){ // laden
		LoadSaveGame();
	}
	if(Input.GetKeyDown(KeyCode.K)){ // speichern
		SaveSaveGame();	
	}

}

Als nächstes legen wir die Load Funktion an.

Wie ich schon geschrieben habe, eignen sich Dictionarys sehr gut um miniJSON zu bedienen. Denn JSON legt die Daten in Pärchen ab, genau wie es ein Dictionary macht.

Einen Eintrag in einem Dictionary zu ändern finde ich jedoch wiederum nicht so günstig, deswegen dient das Dictionary nur als Zwischenablage. Die gespeicherten Daten werden sofort nach dem Laden in andere Variablen geschrieben.

All die Werte die ich speichern und Laden will, liegen bei mir im Mind-Script. Ein Script rein als Sammelplatz erstellt. Alle Variablen dort sind Static und somit super einfach anzusprechen und zu beschreiben.

So sieht die Funktion aus:

	void LoadSaveGame(){
	StreamReader sr =new StreamReader(Application.dataPath +"/"+saveName+".txt");
	var jsonString=sr.ReadLine();
	sr.Close(); //damit schließen wir unseren StreamReader

	dict.Clear(); // dict löschen um evtl.alte Daten zu entfernen
	 dict = Json.Deserialize(jsonString) as Dictionary<string,object>; // daten ins dictionary schreiben
	Debug.Log("deserialized: " + dict.GetType());

// lade die Playergrunddaten	
	mind.playerName=(string) dict["name"];
	mind.playerLevel=unchecked((int)((long)dict["level"]));
	mind.playerX=unchecked((int)((long)dict["xpos"]));
	mind.playerZ=unchecked((int)((long)dict["zpos"]));
	mind.playerRichtung=unchecked((int)((long)dict["richt"]));
	mind.playerIstLoad=unchecked((int)((long)dict["isLoad"]));
	mind.playerMaxLoad=unchecked((int)((long)dict["maxLoad"]));
	mind.playerExp=unchecked((int)((long)dict["exp"]));

}

 

Ihr seht, dass ich den Typ der Daten teilweise ändere. Das ist leider so, denn wenn der JSON String ausgelesen wird, ist leider nicht ersichtlich, ob eine Variable int oder long ist, genaus kann nicht erkannt werden ob sie float oder double ist. Zurück kommen bei diesen Typen immer long und double.

Warum ich weiss, wie die Bereiche im Dictionary heissen, seht ihr gleich in der Speicherfunktion.

Ich habe sie nämlich vorher so benannt. Und diese Daten werden vom Dict nach JSON und umgekehrt geschrieben.

 

	void SaveSaveGame(){

	dict.Clear();
// speichere die Playergrunddaten ins dictionary	
	dict.Add("name",mind.playerName);
	dict.Add("level",mind.playerLevel);
	dict.Add("xpos",mind.playerX);
	dict.Add("zpos",mind.playerZ);
	dict.Add("richt",mind.playerRichtung);
	dict.Add("isLoad",mind.playerIstLoad);
	dict.Add("maxLoad",mind.playerMaxLoad);
	dict.Add("exp",mind.playerExp);

	 var str = Json.Serialize(dict); // wandle das dictionary in einen JSON string
	// schreibe den string in datei
	 StreamWriter sw = new StreamWriter(Application.dataPath +"/"+saveName+".txt");
	//zeile schrieben
	sw.WriteLine(str);
	sw.Flush();
	sw.Close();  //damit schliessen wir den StreamWriter


}

Ihr seht, dem Dictionary werden die Infos der Variablen zugefügt, aber wie der Identifyer des Dict lautet, lege ich hier selber fest.

 

Ja, das wars auch schon. Geil, oder?! ;)

 

Ich habe inzwischen Dateigrößen von rund 200kB zusammen un das alles läuft einwandfrei und schnell. MiniJSON nimmt mir die ganze Arbeit des Wandelns ab und ich brauche mich nur noch um die Daten an sich kümmern. Die Angabe des Datentyps nervt ein klein wenig, ist aber auch schnell gemacht und somit nicht schlimm.

Auch Arrays lassen sich mit MiniJSON abspeichern. Dafür eignet sich das List Item.

 

Testet es einmal, ihr werdet es mögen. :)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Schickes Tutorial!

Interessant an JSON ist auch, dass man am Ende einen String zurück bekommt, den man beliebig benutzen kann, also auch für Spielstände auf dem Server.

 

Vielleicht magst du noch erklären, warum du das hier immer machst:

unchecked((int)((long)dict["key"]))

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ach so, ja, hab ich vergessen zu erwähnen.

Ich habe meine Variablen im Spiel als int deklariert. bei der Rückgabe von miniJSON sind diese Werte im Dictionary aber vom Typ long.

Somit muss ich dieseWerte zu int umwandeln wenn ich sie meiner Variable übergebe. Unchecked brauche ich nicht wirklich, denn ich weiss ja, dass sie vorher (beim Speichern) eine int war und deswegen nicht größer sein kann.

 

In dem Link oben, wo der Code von MiniJSON drin ist, hat er im Commentar, innerhalb eines Beispiels, beschrieben, wie die Variabeln zurück kommen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 4 weeks later...

Ich grab die Leiche mal aus und habe da eine frage zu folgendem :

(Application.dataPath +"/"+saveName+".txt");

Application.dataPath ist das der Pfad von dem Ort aus an dem ich die exe starte ?

und wird die datei saveName erstellt wenn sie noch nicht existiert, wenn nein, wie kann ich diese automatisch erstellen

angemonnen SaveName = player[name]

und der Spieler ist noch nicht eingetragen wird die Datei dann angelegt?

Link zu diesem Kommentar
Auf anderen Seiten teilen

ja, das ist der pfad bzw ordner wo deine exe liegt,

 

jetzt kommt es drauf an wie du die textdatei erstellst, bei einem streamwriter wird die

automatisch erstellt

aber falls du es anders machest und du überprüfen willst ob eine datei

schon existiert oder eine erstellen oder löschen willst, dann hilft dir

System.IO.File weiter

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 1 year later...

Oh seh ich grade auch...krass^^

 

Mit dem Script lassen sich Daten ein verschlüsseln/entschlüsseln...sollte wie gesagt in 99,99% der Fälle locker ausreichen. Einfach den jsonString vor dem Speichern durch dieses Script jagen ...und natürlich auch beim Laden. ;)

 

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Encryption
{
public class RijndaelCrypt //: Singleton<RijndaelCrypt>
{
	/// <summary>
	///
	/// </summary>
	/// <param name="clearText"></param>
	/// <param name="password"></param>
	/// <returns></returns>
	public string Encrypt(string clearText, string password)
	{
		//Convert text to bytes
		byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);

		//We will derieve our Key and Vectore based on following
		//password and a random salt value, 13 bytes in size.
		var pdb = new PasswordDeriveBytes(password, new byte[]{0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d,0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

		byte[] encryptedData = Encrypt(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16));

		return Convert.ToBase64String(encryptedData);
	}

	/// <summary>
	///
	/// </summary>
	/// <param name="clearData"></param>
	/// <param name="key"></param>
	/// <param name="iv"></param>
	/// <returns></returns>
	public byte[] Encrypt(byte[] clearData, byte[] key, byte[] iv)
	{
		byte[] encryptedData;
		//Create stream for encryption
		using (var ms = new MemoryStream())
		{
			//Create Rijndael object with key and vector
			using (Rijndael alg = Rijndael.Create())
			{
				alg.Key = key;
				alg.IV = iv;
				//Forming cryptostream to link with data stream.
				using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write))
				{
					//Write all data to stream.
					cs.Write(clearData, 0, clearData.Length);
				}
				encryptedData = ms.ToArray();
			}
		}

		return encryptedData;
	}

	/// <summary>
	///
	/// </summary>
	/// <param name="cipherText"></param>
	/// <param name="password"></param>
	/// <returns></returns>
	public string Decrypt(string cipherText, string password)
	{
		//Convert base 64 text to bytes
		byte[] cipherBytes = Convert.FromBase64String(cipherText);

		//We will derieve our Key and Vectore based on following
		//password and a random salt value, 13 bytes in size.
		var pdb = new PasswordDeriveBytes(password, new byte[]{0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65,0x64, 0x76, 0x65, 0x64, 0x65, 0x76});
		byte[] decryptedData = Decrypt(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));

		//Converting unicode string from decrypted data
		return Encoding.Unicode.GetString(decryptedData);
	}

	/// <summary>
	///
	/// </summary>
	/// <param name="cipherData"></param>
	/// <param name="key"></param>
	/// <param name="iv"></param>
	/// <returns></returns>
	public byte[] Decrypt(byte[] cipherData, byte[] key, byte[] iv)
	{
		byte[] decryptedData;
		//Create stream for decryption
		using (var ms = new MemoryStream())
		{
			//Create Rijndael object with key and vector
			using (var alg = Rijndael.Create())
			{
				alg.Key = key;
				alg.IV = iv;
				//Forming cryptostream to link with data stream.
				using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write))
				{
					//Write all data to stream.
					cs.Write(cipherData, 0, cipherData.Length);
				}
				decryptedData = ms.ToArray();
			}
		}
		return decryptedData;
	}
}
}

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 4 months later...

double post incoming - was solls ;)

 

Hier mal aktuelleres:

 

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
namespace GameFramework.Encryption
{
   public class Encryption
   {
    public static string Rfc2898EncryptToString(string clearText, string password)
    {
	    //Convert text to bytes
	    byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
	    //We will derieve our Key and Vectore based on following
	    //password and a random salt value, 13 bytes in size.
	    var pdb = new Rfc2898DeriveBytes(password, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
	    byte[] encryptedData = Rfc2898EncryptToBytes(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16));
	    return Convert.ToBase64String(encryptedData);
    }
    public static byte[] Rfc2898EncryptToBytes(byte[] clearData, byte[] key, byte[] iv)
    {
	    byte[] encryptedData;
	    //Create stream for encryption
	    using (var ms = new MemoryStream())
	    {
		    //Create Rijndael object with key and vector
		    using (TripleDES alg = TripleDES.Create())
		    {
			    alg.Key = key;
			    alg.IV = iv;
			    //Forming cryptostream to link with data stream.
			    using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write))
			    {
				    //Write all data to stream.
				    cs.Write(clearData, 0, clearData.Length);
			    }
			    encryptedData = ms.ToArray();
		    }
	    }
	    return encryptedData;
    }
    public static string Rfc2898DecryptToString(string cipherText, string password)
    {
	    //Convert base 64 text to bytes
	    byte[] cipherBytes = Convert.FromBase64String(cipherText);
	    //We will derieve our Key and Vectore based on following
	    //password and a random salt value, 13 bytes in size.
	    var pdb = new Rfc2898DeriveBytes(password, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
	    byte[] decryptedData = Rfc2898DecryptToBytes(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));
	    //Converting unicode string from decrypted data
	    return Encoding.Unicode.GetString(decryptedData);
    }
    public static byte[] Rfc2898DecryptToBytes(byte[] cipherData, byte[] key, byte[] iv)
    {
	    byte[] decryptedData;
	    //Create stream for decryption
	    using (var ms = new MemoryStream())
	    {
		    //Create Rijndael object with key and vector
		    using (var alg = TripleDES.Create())
		    {
			    alg.Key = key;
			    alg.IV = iv;
			    //Forming cryptostream to link with data stream.
			    using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write))
			    {
				    //Write all data to stream.
				    cs.Write(cipherData, 0, cipherData.Length);
			    }
			    decryptedData = ms.ToArray();
		    }
	    }
	    return decryptedData;
    }
    private static byte[] AesEncryptToBytes(string plainText, byte[] key, byte[] iv)
    {
	    // Check arguments.
	    if (plainText == null || plainText.Length <= 0)
		    throw new ArgumentNullException("plainText");
	    if (key == null || key.Length <= 0)
		    throw new ArgumentNullException("key");
	    if (iv == null || iv.Length <= 0)
		    throw new ArgumentNullException("key");
	    byte[] encrypted;
	    // Create an Aes object
	    // with the specified key and IV.
	    using (Aes aesAlg = Aes.Create())
	    {
		    aesAlg.Key = key;
		    aesAlg.IV = iv;
		    // Create a decrytor to perform the stream transform.
		    ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key
			    , aesAlg.IV);
		    // Create the streams used for encryption.
		    using (MemoryStream msEncrypt = new MemoryStream())
		    {
			    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt
				    , encryptor, CryptoStreamMode.Write))
			    {
				    using (StreamWriter swEncrypt = new StreamWriter(
					    csEncrypt))
				    {
					    //Write all data to the stream.
					    swEncrypt.Write(plainText);
				    }
				    encrypted = msEncrypt.ToArray();
			    }
		    }
	    }
	    // Return the encrypted bytes from the memory stream.
	    return encrypted;
    }
    private static string AesDecryptToString(byte[] cipherText, byte[] key, byte[] iv)
    {
	    // Check arguments.
	    if (cipherText == null || cipherText.Length <= 0)
		    throw new ArgumentNullException("cipherText");
	    if (key == null || key.Length <= 0)
		    throw new ArgumentNullException("key");
	    if (iv == null || iv.Length <= 0)
		    throw new ArgumentNullException("key");
	    // Declare the string used to hold
	    // the decrypted text.
	    string plaintext = null;
	    // Create an Aes object
	    // with the specified key and IV.
	    using (Aes aesAlg = Aes.Create())
	    {
		    aesAlg.Key = key;
		    aesAlg.IV = iv;
		    // Create a decrytor to perform the stream transform.
		    ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key
			    , aesAlg.IV);
		    // Create the streams used for decryption.
		    using (MemoryStream msDecrypt = new MemoryStream(cipherText))
		    {
			    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt
				    , decryptor, CryptoStreamMode.Read))
			    {
				    using (StreamReader srDecrypt = new StreamReader(
					    csDecrypt))
				    {
					    // Read the decrypted bytes from the decrypting stream
					    // and place them in a string.
					    plaintext = srDecrypt.ReadToEnd();
				    }
			    }
		    }
	    }
	    return plaintext;
    }
    public static void TestAes()
    {
	    try
	    {
		    string original = "Here is some data to encrypt!";
		    // Create a new instance of the Aes
		    // class.  This generates a new key and initialization
		    // vector (IV).
		    using (Aes myAes = Aes.Create())
		    {
			    // Encrypt the string to an array of bytes.
			    byte[] encrypted = AesEncryptToBytes(original, myAes.Key, myAes.IV);
			    // Decrypt the bytes to a string.
			    string roundtrip = AesDecryptToString(encrypted, myAes.Key, myAes.IV);
			    //Display the original data and the decrypted data.
			    Debug.Log(String.Format("Original:   {0}", original));
			    Debug.Log(String.Format("Round Trip: {0}", roundtrip));
		    }
	    }
	    catch (Exception e)
	    {
		    Debug.Log(String.Format("Error:   {0}", e.Message));
	    }
    }
   }
}

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 8 years later...

Ich habe bei meinem Skript mehrere Input Fields, die ich in einer JSON Datei abspeichern möchte um diese dann später wieder laden zu können. Ich habe dabei viele int werte. So wie ich es verstanden habe dient das Mind-Skript hier als Zwischenspeicher. Und diesen Zwischenspeicher muss ich mit mehreren static Variablen implementieren, oder habe ich das falsch verstanden?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Och. Das alte Dingen ausgegraben.

Das mind script ist ein ganz einfachen Script, wo nur public static Variablen drin sind. Sonst nichts. Und da sie alle Static sind, kannst du von überall darauf zugreifen, ohne dass das Script auf irgendeinem Objekt in der Szene liegen muss.

Intern sieht das so aus:

 

 

public class mind : MonoBehaviour
{
    public static string playerName="Player1"; 
    public static int playerX=0; 
  	public static bool irgendwas=true;
  // usw...
}

Die Variablen müssen (wie hier im Beispiel) keine Werte haben. Ohne Werte nemen sie beim Beginn den Defaultwert an.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 6 Stunden schrieb dominikb_0802:

Und diesen Zwischenspeicher muss ich mit mehreren static Variablen implementieren, oder habe ich das falsch verstanden?

Ne, müssen tust du das nicht. Das hat malzbie gemacht, damit der Code, um den es eigentlich geht, im Tutorial nicht unnötig aufgebläht wird.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...