Jump to content
Unity Insider Forum

Die nächste Save Question! ^^


Hermetes

Recommended Posts

Ich mache meine erste Schritte im Thema Save überhaupt.

Habe Variablen in einer "Serializable Class <SaveTheme>. Diese <SaveTheme> Klasse wird bei jedem Click auf einem Button erzeugt.
Und landet in ein er Liste : saveThemeList.

Bei Ausführen von Save() bekomme ich diese Exception.

NullReferenceException: Object reference not set to an instance of an object
SaveManager.Save () (.../SaveManager.cs:154)  

Zeile 154: PlayerPrefs.SetString("theme_name" +i , saveTheme.themeName);

 

 public void Save()
   {
      
      PlayerPrefs.SetInt("Themes", saveThemeList.Count);
      
      for (int i = 0; i < saveThemeList.Count; i++)
      {
         PlayerPrefs.SetString("theme_name" +i , saveTheme[i].themeName);  
         PlayerPrefs.SetInt( "theme_index" +i, saveTheme[i].themeIndex);
      }
      
      
      PlayerPrefs.Save();  
      Debug.Log("Saved");
   }
public void SaveThemeInfo(ThemeBox box)
   {
      
      SaveTheme theme = new SaveTheme();
      
      theme.themeIndex = box.themeID;
      theme.themeName = box.themeName;
      theme.themeColor = box.themeColor;
      
      theme.themeHolder = themeBoxHolder;
      theme.themePrefab = themeboxPrefab;
      
      saveThemeList.Add(theme);
   }
public class SaveManager : MonoBehaviour
{
   
   public static SaveManager instance;
   
   public int ThemesCount;
   public GameObject themeboxPrefab;
   public Transform themeBoxHolder;
   
   private SaveTheme[] saveTheme;
   public List<SaveTheme> saveThemeList = new List<SaveTheme>();
   
   

 

savemanager_screenshot.PNG

Link zu diesem Kommentar
Auf anderen Seiten teilen

:D Ich kann nicht mal sagen das es ein Versehen war, habe das tatsächlich auch als Liste gesehen ^^.

Nachdem Load habe ich keine Werte in den Variablen. Wo steckt denn da der Wurm?

 

for (int i = 0; i < ThemesCount; i++)
            {
               
            //saveThemeList[i].themeName  = PlayerPrefs.GetString("theme_name");
            saveThemeList[i].themeIndex = PlayerPrefs.GetInt("theme_index");
            
            saveThemeList[i].themeName = PlayerPrefs.GetString("theme_name");
               
               
            }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das hatte ich auch so gemacht Sascha.Jetzt habe ich allerdings keine Einträge wenn ich Load ausführe. 
Ich kann ja keinen List.Count speichern darum habe ich das über die int ThemeCounts gemacht. Aber ich sehe hier gerade was komplett nicht.

  ThemesCount = PlayerPrefs.GetInt("Themes");
      
      
      for (int i = 0; i < ThemesCount; i++)
            {
            
            
            saveThemeList[i].themeName  = PlayerPrefs.GetString("theme_name" + i);
            saveThemeList[i].themeIndex = PlayerPrefs.GetInt("theme_index" + i);
            
            
            }
         
    
      
      Debug.Log("Loaded");
   }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich merke gerade, dass du Sachen über Index in eine Collection packst, die eventuell gar nicht so groß ist.

Wenn du in ein Array laden würdest, müsstest du das hier machen:

ThemesCount = PlayerPrefs.GetInt("Themes");

saveTheme = new SaveTheme[ThemesCount];

Da du aber deine Daten in eine Liste packst, solltest du nicht über Index hinzufügen, sondern einfach mit Add. Statt

saveThemeList[i].themeName  = PlayerPrefs.GetString("theme_name" + i);
saveThemeList[i].themeIndex = PlayerPrefs.GetInt("theme_index" + i);

also

var themeName  = PlayerPrefs.GetString("theme_name" + i);
var themeIndex = PlayerPrefs.GetInt("theme_index" + i);

saveThemeList.Add(new SaveTheme(themeName, themeIndex));

Diese Schreibweise benötigt natürlich einen Konstruktor im SaveTheme-Typ.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ein Konstruktor ist eine Methode ohne Rückgabetyp in der Signatur, die genauso heißt wie der Typ selbst:

public struct Person
{
  public string name;
  public int age;
  
  public Person(string name, int age)
  {
    this.name = name;
    this.age = age;
  }
}

Wenn du dann eine neue Person erstellst, kannst du das über diesen Konstruktor tun:

new Person("Klaus", 42)

Wie jede andere Methode auch kann der Konstruktor kann beliebige Dinge tun, er ist nicht darauf beschränkt, Feldern Parameterwerte zuzuweisen. Bei structs ist allerdings Regel, dass ein Konstruktor jedes Feld initialisieren muss.

Es gibt halt noch die andere Schreibweise, mit der du Werte von structs oder Objekten direkt initialisierst:

new Person { name = "Klaus", age = 42 }

Bemerke die geschweiften anstatt der normalen Klammern.

Ganz alternativ kannst du natürlich auch alles in einzelne Zeilen schreiben, ob das so hübsch ist, sei jedem selbst überlassen.

var person = new Person();
person.name = "Klaus";
person.age = 42;

Am Ende hast du jedenfalls immer eine Person, die du dann mit Add in die Liste hinzufügen kannst.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Und ist das für meine Collections dann eine (stabelbare) struct oder kann ich das frei wählen ? Und so ein Konstruktor ist doch eigentlich immer in der selben Klasse oder? (Weil die Variablen sich ja da befinden ,oder?) Habe das jetzt mal ohne struct gemacht......wann ist ein struct ein struct?

using UnityEngine;
using System;


[Serializable]
public class SaveTheme 
{
   
   public int  themeIndex;
   public string themeName;
   public Color themeColor;
   
   public Transform themeHolder;
   public GameObject themePrefab; 
   
   
   public SaveTheme (int index, string name)
   {
      this.themeIndex = index;
      this.themeName = name;
   }
    
}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class SaveManager : MonoBehaviour
{
   
   public static SaveManager instance;
   
   public int ThemesCount;
   public GameObject themeboxPrefab;
   public Transform themeBoxHolder;
   
  
   
   
   private SaveTheme[] saveTheme;
   public List<SaveTheme> saveThemeList = new List<SaveTheme>();
   
   
   
   
   //public List<SaveTheme> ThemeList = new SaveTheme();
   
   void Awake()
   {
      instance = this;
      
      //themeboxPrefab = (GameObject)(Resources.Load("ThemeBox"));
      themeBoxHolder = GameObject.FindGameObjectWithTag("ThemeHolder").transform;
      
      
   }
   
   void Start()
   {
     
      ThemesCount = PlayerPrefs.GetInt("Themes");
   }
   
   
   
  
   
   public void SaveThemeInfo(ThemeBox box)
   {
      
      SaveTheme theme = new SaveTheme(box.themeID, box.themeName);
      
      theme.themeIndex = box.themeID;
      theme.themeName = box.themeName;
      theme.themeColor = box.themeColor;
      
      theme.themeHolder = themeBoxHolder;
      theme.themePrefab = themeboxPrefab;
      
      saveThemeList.Add(theme);
      
    
   }
   
   
   
   public void Save()
   {
      
      PlayerPrefs.SetInt("Themes", saveThemeList.Count);
      
      for (int i = 0; i < saveThemeList.Count; i++)
      {
         PlayerPrefs.SetString("theme_name" +i , saveThemeList[i].themeName);  
         PlayerPrefs.SetInt( "theme_index" +i, saveThemeList[i].themeIndex);
      }
      
      
      PlayerPrefs.Save();  
      Debug.Log("Saved");
   }
   
   public void Load()
   {
      
      ThemesCount = PlayerPrefs.GetInt("Themes");
      
      for (int i = 0; i < ThemesCount; i++)
      {
            
         saveThemeList[i].themeName  = PlayerPrefs.GetString("theme_name" + i);
         saveThemeList[i].themeIndex = PlayerPrefs.GetInt("theme_index" + i);
            
            
      }
         
    
      
      Debug.Log("Loaded");
   }
   
   
   
   
   public void DeleteAllThemes()
   {
      
      //savesT_Title.Clear();
      //savesT_ID.Clear();
      //savesThemeBox.Clear();
      // ThemesCount = PlayerPrefs.GetInt("themeCount");
      PlayerPrefs.DeleteAll();
   }
   
   
   
   
      
   
   
   
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 57 Minuten schrieb Hermetes:

Und ist das für meine Collections dann eine (stabelbare) struct oder kann ich das frei wählen

Die Frage verstehe ich nicht so ganz, aber ich nehme an, die Antwort "du kannst alles in deine Liste reinstecken was dich glücklich macht" wird schon passen :)

vor 57 Minuten schrieb Hermetes:

Und so ein Konstruktor ist doch eigentlich immer in der selben Klasse oder?

Richtig.

vor 58 Minuten schrieb Hermetes:

Habe das jetzt mal ohne struct gemacht...

Kannste erstmal machen.

vor 58 Minuten schrieb Hermetes:

wann ist ein struct ein struct?

Ein struct hast du, wenn du statt "class" "struct" schreibst. Der Unterschied ist die fehlende Referenzsemantik. Bei Objekten einer Klasse redest du von Dingen mit einer Identität (z.B. deinen Vater). Zwei Objekte können gleich sein (z.B. sein Zwilling) und trotzdem nicht dasselbe. Und du redest von deinem Vater, aber deine Mutter von ihrem Mann - zwei reden mit unterschiedlichen Bezeichnungen von derselben Person.

structs sind dagegen für Werte. Eine 5 ist immer eine 5, und wenn zwei Leute von einer 5 reden, reden sie von demselben Wert. Einen Unterschied zwischen "das gleiche" und "dasselbe" bei Werten. Wenn du deine 5 erhöhst hast du keine 5 mehr, sondern eine 6.

Referenzsemantik spiegelt das wieder. Hast du eine Klasse "Auto", sieht das so aus:

var meinAuto = new Auto();
var dasAuto = meinAuto;

meinAuto.farbe = blau;
Debug.Log(dasAuto.farbe); // blau

In diesem Beispiel gibt es exakt ein Auto-Objekt, und zwei Variablen, die dieses Objekt referenzieren.

Hast du allerdings ein Struct, wie z.B. Vector3:

var meinePosition = new Vector3(1, 2, 3);
var diePosition = meinePosition;

diePosition.x = 3;
Debug.Log(meinePosition); // (1, 2, 3)

Hier wird bei der Zuweisung in der zweiten Zeile keine Referenz kopiert, sondern der Wert selbst. Deshalb steht in den beiden Variablen zwar derselbe Wert, aber es gibt keine Verbindung, wie ein gemeinsam referenziertes Objekt. Wenn du dann den Wert der einen Variable änderst, dann hast du eben einen neuen Wert, und änderst nicht den alten. So wie 5+1 nicht "erhöhte 5" ergibt, sondern eben 6.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Aber ich habe den Grund noch nicht so ganz verstanden. Legt man ein struct an um sicher zu stellen das alle Variablen bei einer neuen Initialisierung deklariert werden?
Oder geht es eher darum, daß  die fehlende Referenz verhindern soll das z.B. beim 3. Auto aus der Autofarbe plötzlich die Sitzpolster Farbe wird ? 

Ich glaube es sickert beim mir tatsächlich durch!

Habe es zu struct geändert. Das hat wirklich Sinn. 
Kann ich jetzt mein Save() so stehen lassen? Und wie sieht´s dann bei Load() aus?

 

using UnityEngine;
using System;


[Serializable]
public struct SaveTheme
{
   
      public int themeIndex;
      public string themeName;
      public Color themeColor;
   //public Transform themeHolder;
   // public GameObject themePrefab; 
   
   
   public SaveTheme (int index, string name, Color color)
   {
      this.themeIndex = index;
      this.themeName = name;
      this.themeColor = color;
   }
   
  
    
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 6 Stunden schrieb Hermetes:

Legt man ein struct an um sicher zu stellen das alle Variablen bei einer neuen Initialisierung deklariert werden?

Nein, das kann man auch mit einem Konstruktor sicherstellen.

vor 6 Stunden schrieb Hermetes:

Oder geht es eher darum, daß  die fehlende Referenz verhindern soll das z.B. beim 3. Auto aus der Autofarbe plötzlich die Sitzpolster Farbe wird ?

Nein, sowas gibt's bei C# nicht ...solange du es nicht drauf anlegst :)

Der Unterschied ist wirklich die Referenzsemantik. Nimm diesen Code, wie er bei Unity recht häufig vorkommt:

transform.position = target.position;

Transform.position ist ein Vector3, und das ist ein struct. Der Vektor, also die drei Zahlen, werden rüberkopiert. Wenn du nach dieser Zeile transform.position änderst, dann bewegt sich eben das Objekt, zu dem das Script gehört - das als "target" referenzierte Objekt aber natürlich nicht.

Wäre Vector3 eine Klasse, dann wäre die position von "target" ein Objekt, und "target.position" würde dieses Objekt referenzieren. Die obige Zeile bewirkt, dass diese Referenz kopiert wird. Danach zeigen sowohl "transform.position" als auch "target.position" auf dieselben drei Zahlen im Speicher. Und wenn du diese drei Zahlen änderst z.B. durch:

transform.position.x = 10;

Dann würde die neue X-Position für beide Objekte gelten, da sie beide dieselbe (geänderte) Zahl benutzen. Tatsache ist, dass diese Zeile nicht mal kompiliert, und das hängt eben genau mit dieser potentiellen Verwirrung zusammen.

Das ist eben der Unterschied zwischen Werten. Wenn ich durch ein Bonus-Item so viele Punkte erhalte, dass ich mit dir gleichauf bin, haben wir denselben Wert bei der Punktzahl. Kriege ich danach Punkte, heißt das nicht, dass du dann auch Punkte kriegst.

Wenn wir aber zum Verkehrsamt laufen und du eintragen lässt, dass dein Auto jetzt auch mein Auto ist, dann kann ich danach nicht mein Auto blau färben, ohne dass dein Auto danach auch blau ist - weil's eben dasselbe Auto ist, und Autos Objekte sind und keine Werte.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Sascha , ich hab´s beinahe! Ich taste mich von einer Variablen zur nächsten. Mit dem Namen und der ID klappts! :) Und nun weiss ich nicht wie ich bei der Color vorgehen muss.
Also, wie bringe ich das string zu Color zurück?

 

 saveThemeList.Add(new SaveTheme(themeIndex, themeName, themeColor, themeboxPrefab));
         
         themePrefab.GetComponent<ThemeBox>().themeName = saveThemeList[i].themeName;
         themePrefab.GetComponent<ThemeBox>().themeID = saveThemeList[i].themeIndex;
         themePrefab.GetComponent<ThemeBox>().themeColor = saveThemeList[i].themeColor;

Mein Struct:
 

  public SaveTheme (int index, string name, Color themecolor, GameObject prefab)
   {
      this.themeIndex = index;
      this.themeName = name;
      this.themeColor = themecolor;
      this.themePrefab = prefab;
   }

Fehlermeldung:
SaveManager.cs(99,65): error CS1503: Argument 3: cannot convert from 'string' to 'UnityEngine.Color'
SaveManager.cs(103,60): error CS0029: Cannot implicitly convert type 'string' to 'UnityEngine.Color'

Meine Umwandlung zum String
var themeColor = PlayerPrefs.GetString("theme_color" + i);


Das war ein gutes Beispiel mit der Vector 3! Irgendwie assoziere ich ein struct als ein neutraler Wert. Mag vielleicht wieder falsch sein, aber so funktioniert meine Denke.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Kleinigkeit vorweg: Du hast da recht viel sehr gleich aussehenden Code - so etwas ist immer zu vermeiden.

Statt

themePrefab.GetComponent<ThemeBox>().dies
themePrefab.GetComponent<ThemeBox>().jenes

schreibe

var themeBox = themePrefab.GetComponent<ThemeBox>();

themeBox.dies
themeBox.jenes

Aber zum Eigentlichen: Alle Typen in C# haben die ToString-Methode, also lassen sich alle Dinge als String ausgeben. Das hat schon seine Gründe - Daten mit Playerprefs speichern ist keiner davon. Entsprechend gibt es keine erzwungene Konvertierungsmöglichkeit von string zurück zu allen beliebigen Typen, da musst du selber ran.

ToString ist nicht groß optimiert, einen string zu bauen, den man dann wieder gut einlesen kann. Unter anderem deshalb empfiehlt es sich, auch das Konvertieren von BeliebigerTyp zu string noch einmal selber zu bauen. Ein Beispiel:

private static string SerializeColor(Color c)
{
  return c.r + "|" + c.g + "|" + c.b + "|" + c.a;
}

private static Color DeserializeColor(string s)
{
  var c = s.Split('|');
  try
  {
    return new Color(float.Parse(c[0]),
                     float.Parse(c[1]),
                     float.Parse(c[2]),
                     float.Parse(c[3]));
  }
  catch
  {
    Debug.LogError("Could not deserialize color.");
    return new Color(0,0,0,0);
  }
}

Ein try-catch um alles herum ist evtl. nicht die sauberste Lösung, und einfach alle Werte mit Pipes zu trennen ist nur eine von vielen Möglichkeiten, aber so würde das erstmal funktionieren.

Jedenfalls dann einfach:

PlayerPrefs.SetString(key, SerializeColor(color));
color = DeserializeColor(PlayerPrefs.GetString(key));

Natürlich kann man das noch hübsch in Methoden verpacken:

public static void SetColor(string key, Color c)
{
  PlayerPrefs.SetString(key, SerializeColor(color));
}

public static Color GetColor(string key)
{
  return DeserializeColor(PlayerPrefs.GetString(key));
}

und dann einfach GetColor genau wie GetString aufrufen, und so weiter.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Tausend Dank Dir Sascha!!!

Der Compiler meinte das man den Type zu einer Variable ersetzen muss.

Also machte ich  SerializeColor(color)) zu :

PlayerPrefs.SetString(key, SerializeColor(c));

Weiss nicht ob das richtig war :) Habe nämlich deinen Debug Error bekommen. 
Debug.LogError("Could not deserialize color.");


Und so in Save() eingeschrieben:
 

SetColor("theme_color + 1", saveThemeList[i].themeColor);


In Load()
 

var themeColor = GetColor("theme_color" + i);
theme.themeColor = saveThemeList[i].themeColor;


Playerprefs habe ich mir nicht unbedingt ausgesucht. Es erschien mir am leichtesten.😌 Zu was tendierst du denn ? Json oder XML ?

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 11 Stunden schrieb Hermetes:

Debug.LogError("Could not deserialize color.");

Das Ding wird halt ausgelöst, wenn der String, der von den PlayerPrefs kommt, nicht exakt dem Format entspricht, das die Deserialize-Funktion erwartet, also "F|F|F|F", wobei "F" stellvertretend für ein float ist.

vor 11 Stunden schrieb Hermetes:

Weiss nicht ob das richtig war

Naja, c muss halt die Variable mit deiner Farbe sein - ob die Varable jetzt c oder color oder myColor oder franz heißt, ist wurscht, solange du überall denselben Namen hinschreibst :)

vor 11 Stunden schrieb Hermetes:

var themeColor = GetColor("theme_color" + i);

theme.themeColor = saveThemeList.themeColor;

Du schreibst hier die gelandene Farbe in eine Variable namens "themeColor", liest dessen wert aber nirgendwo aus.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...