简体   繁体   中英

Issue loading saved booleans in Android build of Unity game

I completed a new game where I implemented a Save Manager using serialized data, rather than using PlayerPrefs for all player info. Everything seemed to have worked great but for some reason, my Android builds are not correctly saving or loading some boolean values. In the Unity Editor, it seems to be saving and loading these boolean variables completely fine... Everything else (saved ints, strings) seems to be working fine in mobile.

Basically, what I am trying to save is whether or not the Player talked to a particular NPC and correctly answered a question.

So far I've tried a couple things hoping it might fix:

  • I have tried to save the data as List<> as well as bool. Originally I had the data for this player info stored in a List. When they correctly answered NPC, the NPC was added to a List as a String. It also didn't work so I assumed it was an issue with serializing Lists and so I tried bools.
  • Upgraded my project to the latest version of Unity. No effect on issue.
  • I've completely rebuild a handful of times with variations on my code and tested on Android with no success. It is still working in Unity Editor as I had intended.

What should happen vs. (What is happening)

  • The dialogue should be incremented so that the next conversation is different (This works, until I go to Main Menu and come back, or exit game and come back).

  • It should trigger an action, like rewarding the player (I get the reward, but then since it isn't remembering who I spoke to, it is letting me reclaim rewards when I leave game and return).

My Code: Sorry it's kind of thick, so I've tried to summarise the purpose before each chunk.

Storyteller.cs - attached to a GameObject in Main Game scene. Holds methods for rewards for when player correctly answers an NPC.

using UnityEngine;

public class Storyteller : MonoBehaviour
{
    [SerializeField] RPGTalk rpgTalk; // I am using this asset to help with dialogues

    SaveManager saveManager;

    private void Start()
    {
        saveManager = GameObject.FindGameObjectWithTag("SaveManager").GetComponent<SaveManager>();
    }

    //NPC Methods
    public void RewardFromFlowerLady()
    {
        if (saveManager.myStats.flowerLadyGiftClaimed == true)
        {
            saveManager.myStats.coins += 700;
            saveManager.Save();
        }
    }

    public void RewardFromHippie()
    {
        if (saveManager.myStats.hippieGiftClaimed == true)
        {
            ReceiveMedicinalHerbsEvent(1.1f);
            saveManager.Save();
        }
    }
    public void PetFromTrashCan()
    {
        if (saveManager.myStats.trashCanGiftClaimed == true)
        {
            saveManager.myStats.trashcat = 1;
            saveManager.Save();
        }
    }
}

DialogueProgress.cs - attached to Player object in main game scene. When a player correctly answers an NPC, it sets a bool in my save file to True and saves the game, using Save method in my save manager.

using UnityEngine;

public class DialogueProgress : MonoBehaviour
{
    SaveManager saveManager;

    private void Awake()
    {
        saveManager = GameObject.FindGameObjectWithTag("SaveManager").GetComponent<SaveManager>();
    }

    public void NPCSpokenAdder(string npcname)
    {
        switch (npcname){
            case "FlowerLady":
                saveManager.myStats.flowerLadyGiftClaimed = true;
                saveManager.Save();
                break;
            case "Hippie":
                saveManager.myStats.hippieGiftClaimed = true;
                saveManager.Save();
                break;
            case "TrashCan":
                saveManager.myStats.trashCanGiftClaimed = true;
                saveManager.Save();
                break;
        }
    }
}

QuestionAndChoiceID.cs - This script is attached to my three NPC GameObjects and on each of them, I have added an IDs for question and correct answer as ints in the inspector.

using UnityEngine;

public class QuestionAndChoiceID : MonoBehaviour
{
    public string questionID;
    public int correctchoiceID;
    RPGTalk rpgTalk;
    RPGTalkArea talkArea;
    SaveManager saveManager;
    DialogueProgress dialogueProgress;
    Storyteller storyTeller;

    void Start()
    {
        saveManager = GameObject.FindGameObjectWithTag("SaveManager").GetComponent<SaveManager>();
        rpgTalk = GameObject.FindGameObjectWithTag("RPGTalk").GetComponent<RPGTalk>();
        storyTeller = FindObjectOfType<Storyteller>();
        talkArea = this.gameObject.GetComponent<RPGTalkArea>();
        dialogueProgress = FindObjectOfType<DialogueProgress>();
        LoadCorrectResponses();
        rpgTalk.OnMadeChoice += OnMadeChoice;
    }

    private void OnDisable()
    {
        rpgTalk.OnMadeChoice -= OnMadeChoice;
    }

    private void OnMadeChoice(string question, int choice)
    {
        if (question == questionID && choice == correctchoiceID)
        {
            switch (this.gameObject.name)
            {
                case "FlowerLady":
                    dialogueProgress.NPCSpokenAdder(this.gameObject.name); 
                    IncrementDialogue(); 
                    storyTeller.RewardFromFlowerLady(); 
                    break;
                case "Hippie":
                    dialogueProgress.NPCSpokenAdder(this.gameObject.name);
                    IncrementDialogue();
                    storyTeller.RewardFromHippie();
                    break;
                case "TrashCan":
                    dialogueProgress.NPCSpokenAdder(this.gameObject.name);
                    IncrementDialogue();
                    storyTeller.PetFromTrashCan();
                    break;
            }
        }
    }

    private void IncrementDialogue()
    {
        if (saveManager.myStats.flowerLadyGiftClaimed && this.gameObject.name == "FlowerLady")
        {
            talkArea.lineToStart = this.gameObject.name + 1;
            talkArea.lineToBreak = this.gameObject.name + 1 + "_end";
            Debug.Log(this.gameObject.name + " dialogue incremented correctly;");
        }
        else if (saveManager.myStats.hippieGiftClaimed && this.gameObject.name == "Hippie")
        {
            talkArea.lineToStart = this.gameObject.name + 1;
            talkArea.lineToBreak = this.gameObject.name + 1 + "_end";
        }
        else if (saveManager.myStats.trashCanGiftClaimed && this.gameObject.name == "TrashCan")
        {
            talkArea.lineToStart = this.gameObject.name + 1;
            talkArea.lineToBreak = this.gameObject.name + 1 + "_end";
        }
    }

    private void LoadCorrectResponses()
    {
        if (saveManager.myStats.flowerLadyGiftClaimed)
        {
            if (this.gameObject.name == "FlowerLady")
            {
                talkArea.lineToStart = this.gameObject.name + 1;
                talkArea.lineToBreak = this.gameObject.name + 1 + "_end";
            }
        }
        else if (saveManager.myStats.hippieGiftClaimed)
        {
            if (this.gameObject.name == "Hippie")
            {
                talkArea.lineToStart = this.gameObject.name + 1;
                talkArea.lineToBreak = this.gameObject.name + 1 + "_end";
            }
        }
        else if (saveManager.myStats.trashCanGiftClaimed)
        {
                if (this.gameObject.name == "TrashCan")
            {
                talkArea.lineToStart = this.gameObject.name + 1;
                talkArea.lineToBreak = this.gameObject.name + 1 + "_end";
            }

        }
    }
}

Including my SaveManager.cs script here too:

using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

public class SaveManager : MonoBehaviour
{
    public Stats myStats;

    private void Awake()
    {
        if (File.Exists(Application.persistentDataPath + "/Player.dat"))
        {
            Load();
        }

        InvokeRepeating("Save", 1, 15);
    }

    private void OnApplicationQuit() 
    {
       Save();
    }

    public void Save()
    {
        Debug.Log("Saving actual game.");

        FileStream file = new FileStream(Application.persistentDataPath + "/Player.dat", FileMode.OpenOrCreate); // Create a file or open a file to save to

        try
        {
            BinaryFormatter formatter = new BinaryFormatter(); // Binary formatter -- allows us to write data to a file
            formatter.Serialize(file, myStats); 
        }
        catch (SerializationException e) 
        {
            Debug.LogError("Issue serialising this data: " + e.Message);
        }
        finally
        {
            file.Close(); 
        }
    }

    public void Load()
    {
        FileStream file = new FileStream(Application.persistentDataPath + "/Player.dat", FileMode.Open);
        try
        {
            BinaryFormatter formatter = new BinaryFormatter();
            myStats = (Stats)formatter.Deserialize(file);
        }
        catch (SerializationException e)
        {
            Debug.LogError("Error deserialising this data: " + e.Message);
        }
        finally
        {
            file.Close();
        }
    }
}

The relevant snippet from Stats.cs which holds all data I want to save:

using UnityEngine;

[System.Serializable]
public class Stats
{
    public bool flowerLadyGiftClaimed;
    public bool hippieGiftClaimed;
    public bool trashCanGiftClaimed;
}

Hoping someone might have had a similar issue and can help out. I am new to serializing data for save and load, so maybe there is something I am missing. Since my other data is saving and loading fine, then I think it's due to some incorrect logic in my above code. Just not sure why it appears to work fine in Unity Editor! It's the last step of my game which is one of the first I have completed, so really excited to get it resolved.

Thanks for reading.

After reorganising my code a bit, I have resolved the issue with saving and loading. It had nothing to do with booleans or lists. I was able to revert back to using a list instead of booleans.

I think the order of execution in scripts may have been what was messing things up. I changed what happens in Start and Awake, and also removed some redundant if checking and the order of an if/else statement that may have been affecting it.

I also removed the DialogueProgress script and added its contents to my Storyteller class instead.

Final code is like this:

Storyteller.cs

public class Storyteller : MonoBehaviour
{
    [SerializeField] RPGTalk rpgTalk;
    [SerializeField] UIManager uiMgr;

    [Header("NPCs with changing dialogues")]
    [SerializeField] GameObject flowerLady;
    [SerializeField] GameObject hippie;
    [SerializeField] GameObject trashcan;

    [SerializeField] List<string> tempNPCSpoken;

    [Header("Saved stats access")]
    SaveManager saveManager;

    [Header("Music")] 
    [SerializeField] AudioClip successSFX;

    public delegate void StorytellerDelegate(float statChange);  
    public static event StorytellerDelegate ReceiveMedicinalHerbsEvent;

    // ADDED THIS IN FROM DIALOGUE PROGRESS SCRIPT
    private void Awake()
    {
        saveManager = GameObject.FindGameObjectWithTag("SaveManager").GetComponent<SaveManager>();
        if (saveManager.myStats.NPCSpoken.Count == 0)
        {
            // There are no NPCs saved in the list.
            Debug.Log("AWAKE in DialogueProgress: NPCSpoken was empty. Making a new tempList.");
            tempNPCSpoken = new List<string>(); // Create a new list object that is empty.
        }
        else
        {   // There are NPCs that are in the saved list.
            Debug.Log("AWAKE in DialogueProgress: There are NPCs saved. NPCSpoken count is " + saveManager.myStats.NPCSpoken.Count);
            tempNPCSpoken = saveManager.myStats.NPCSpoken; // Save a local list equal to save one.
        }
    }

    // NPC ADDER
    public void NPCSpokenAdder(string npcname)
    {
        // Called from Question and ChoiceID script
        tempNPCSpoken.Add(npcname); // Add the NPC to local list.
        saveManager.myStats.NPCSpoken = tempNPCSpoken; // Set the save list equal to this list.
        saveManager.Save(); // Save the game.
        Debug.Log("NPCSpokenAdder ran: " + npcname + " was added to Saved List");
        Debug.Log("Current saved list: It contains this.gameObject now: " + saveManager.myStats.NPCSpoken.Contains(npcname));
    }


    //NPC Callbacks
    public void RewardFromFlowerLady()
    {
        saveManager.myStats.coins += 700;
        saveManager.Save();
    }

    public void RewardFromHippie()
    {
        ReceiveMedicinalHerbsEvent(1.1f);
        saveManager.Save();
    }
    public void PetFromTrashCan()
    {
        saveManager.myStats.trashcat = 1;
        saveManager.Save();
    }
  }

QuestionAndChoiceID.cs

public class QuestionAndChoiceID : MonoBehaviour
{
    public string questionID;
    public int correctchoiceID;
    RPGTalk rpgTalk;
    RPGTalkArea talkArea;
    SaveManager saveManager;
   // DialogueProgress dialogueProgress;
    Storyteller storyTeller;

    void Start()
    {
        saveManager = GameObject.FindGameObjectWithTag("SaveManager").GetComponent<SaveManager>();
        rpgTalk = GameObject.FindGameObjectWithTag("RPGTalk").GetComponent<RPGTalk>();
        talkArea = GetComponent<RPGTalkArea>();
        rpgTalk.OnMadeChoice += OnMadeChoice;
        //dialogueProgress = FindObjectOfType<DialogueProgress>();
        storyTeller = FindObjectOfType<Storyteller>();
        LoadCorrectResponses();
    }

    private void OnMadeChoice(string question, int choice)
    {
       // Debug.Log("OnMadeChoice: Player just made an RPGTalk choice.");

        if (question == questionID && choice == correctchoiceID) // If they answer NPC correctly
        {
            Debug.Log("OnMadeChoice: Player just made an RPGTalk choice that has actions attached.");
            switch (this.gameObject.name) // check which NPC it is by name
            {
                case "FlowerLady":
                    storyTeller.NPCSpokenAdder(this.gameObject.name); // NAME ADDED TO LIST, SAVED
                    IncrementDialogue(); // TALK LINES UPDATED
                    storyTeller.RewardFromFlowerLady(); // REWARD
                    break;
                case "Hippie":
                    storyTeller.NPCSpokenAdder(this.gameObject.name);
                    IncrementDialogue();
                    storyTeller.RewardFromHippie();
                    break;
                case "TrashCan":
                    storyTeller.NPCSpokenAdder(this.gameObject.name);
                    IncrementDialogue();
                    storyTeller.PetFromTrashCan();
                    break;
            }
        }
    }

    private void IncrementDialogue()
    {
        //if (saveManager.myStats.NPCSpoken.Contains(this.gameObject.name))
        //    {
                talkArea.lineToStart = this.gameObject.name + 1;
                talkArea.lineToBreak = this.gameObject.name + 1 + "_end";
            //}
    }

    private void LoadCorrectResponses()
    {
        if (SaveManager.saveExists) 

        {
            if (saveManager.myStats.NPCSpoken != null) // If NPC list is not null

            { 
                // If the NPC is the array when this is called, the dialogue on that NPC is incremented.
                if (saveManager.myStats.NPCSpoken.Contains(this.gameObject.name)) 
                {
                    talkArea.lineToStart = this.gameObject.name + 1; 
                    talkArea.lineToBreak = this.gameObject.name + 1 + "_end";
                }
                else
                {
                    Debug.Log("LoadCorrectResponses(): List does not contain NPC " + this.gameObject.name);
                }
            }
        }
    }
}

My current Stats.cs

public class Stats
{
    [Header("NPC Talk Saves")]
    public List<string> NPCSpoken = new List<string>();
}

I will close this issue off as it's resolved.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM