Sign in to follow this  
Kk1496

Save/Load High Scores

Recommended Posts

I've used the live training on persistent data to save an array of high scores. However, when using the script i receive this error.

NullReferenceException: Object reference not set to an instance of an object Timer.Update () (at Assets/Scripts/Timer.cs:50)

The code it is talking about looks like this:

if (gameTimer <= 0)
{
inGame.SetActive (false);
playArea.SetActive (false);
gameOver.SetActive (true);
finalScore.text = "Score: " + GameManager.score;
Manager.gameManager.Save();
}

It seems to be talking about the save function that looks like this:

public void Save ()
{
BinaryFormatter bf = new BinaryFormatter ();
FileStream file = File.Create (Application.persistentDataPath + "/HihgScores.dat");

HighScores hScore = new HighScores ();
foreach(int score in hScore.highScores)
{
if (GameManager.score >= hScore.highScores[score])
{
hScore.highScores[score] = GameManager.score;
break;
}
}

bf.Serialize (file, hScore);
file.Close();
}

The class that this method is from has a public static version of itself (like the live training says) so I'm not sure why it can't find a reference to the game object since it's persisting in all scenes.

Share this post


Link to post
Share on other sites

You're instantiating a new HighScores instance (hScore).  Unless the constructor for HighScores populates itself with data (?) there will be nothing to dereference at the following line:

 

 

if (GameManager.score >= hScore.highScores[score])

because hScore.highScores (an array or list I'm assuming) hasn't been populated with any content.  For all we know, hScore.highScores itself is still null.

Share this post


Link to post
Share on other sites
So, the class that I am serializing should handle the array as far as population and sorting? I had only one score saved (not an array) just to get things working. Now that it works (I eventually solved the error, the file name string didn't match in load ;( ) how would I turn this into a leaderboard? I u derstand how to manage the player's high scores, but how could you manage this for multiple users? I've seen this done in tiny wings where I think you can change the user name in settings and it'll change your high scores accordingly. I'd also like to integrate this with facebook eventually.

Share this post


Link to post
Share on other sites


how could you manage this for multiple users?

Off-the-cuff here, but a high level version would be if you want the scoreboard to reflect local players only, the game can just load up the external .dat file when you start the game (so the current high scores are in memory), and then save it back out on exit with any changes.  

 

If you want it integrated to facebook and reflecting multiple remote users, that .dat file will need to live somewhere all the FB instances can access it, or be converted to a database table (with interface changes in the code as well as you'd no longer be loading in from a file stream).

If you want it integrated to facebook but still only want to track the local players' high scores, you can save some kind of file off into browser cache/cookies.

There are other solutions as well, my web-stack experience is much shorter than my desktop/enterprise knowledge.

Share this post


Link to post
Share on other sites

I thought I knew what I was doing when I converted that test high score to an array but I guess not.

 

    public void Save ()
    {
        Debug.Log ("Save() was called");
        if (!File.Exists (Application.persistentDataPath + "/HihgScores.dat")) 
        {
            BinaryFormatter bf = new BinaryFormatter ();
            FileStream file = File.Create (Application.persistentDataPath + "/HihgScores.dat");
            HighScores hScore = new HighScores ();
            hScore.highScores = highScores;
            bf.Serialize (filehScore);
            file.Close ();
        } 
        else {
            Load ();
        }
    }

    public void Load ()
    {
        Debug.Log ("Load() was called");
        if (File.Exists (Application.persistentDataPath + "/HihgScores.dat")) 
        {
            //Debug.Log ("File Exists");
            BinaryFormatter bf = new BinaryFormatter ();
            FileStream file = File.Open (Application.persistentDataPath + "/HihgScores.dat"FileMode.Open);
            HighScores hScore = (HighScores)bf.Deserialize (file);
            System.Array.Copy(hScore.highScoreshighScores10);
            file.Close ();
            //Debug.Log ("High Score: " + HighScoreManager.highScore);
        }
        //Debug.Log ("Exiting Load()");
    }

 

[Serializable]
class HighScores 
{
    public int[] highScores = new int[10];
}

These changes now cause this error:

 

ArgumentException: Object type System.Int32 cannot be converted to target type: System.Int32[]

Parameter name: val
 
I'm now calling Load() n Awake() and Save() in ONApplicationQuit() on my overall game manager which persists throughout all scenes if that helps any.
 
I've tried googling this error but theI don't quite understand the explanation.

Share this post


Link to post
Share on other sites

There should be more information in the Console tab/window, to include the full stack-trace of the error/output (when you select any entry in the Console list, i.e. one left click).  Each entry in the trace should tell you which file it was in for that step, and which line the function call for that step happens at.  Resize the console tab to see more (as the stacktrace content appears at the bottom of the window), or just highlight the whole mess and paste it into a notepad or something to read.

Edited by BCullis

Share this post


Link to post
Share on other sites
Wow...
 
ArgumentException: Object type System.Int32 cannot be converted to target type: System.Int32[]
Parameter name: val
System.Reflection.MonoField.SetValue (System.Object obj, System.Object val, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Reflection/MonoField.cs:133)
System.Reflection.FieldInfo.SetValue (System.Object obj, System.Object value) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Reflection/FieldInfo.cs:150)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.SetObjectValue (System.Object parentObject, System.String fieldName, System.Reflection.MemberInfo memberInfo, System.Runtime.Serialization.SerializationInfo info, System.Object value, System.Type valueType, System.Int32[] indices) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:799)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadValue (System.IO.BinaryReader reader, System.Object parentObject, Int64 parentObjectId, System.Runtime.Serialization.SerializationInfo info, System.Type valueType, System.String fieldName, System.Reflection.MemberInfo memberInfo, System.Int32[] indices) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:721)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectContent (System.IO.BinaryReader reader, System.Runtime.Serialization.Formatters.Binary.TypeMetadata metadata, Int64 objectId, System.Object& objectInstance, System.Runtime.Serialization.SerializationInfo& info) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:306)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectInstance (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:270)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObject (BinaryElement element, System.IO.BinaryReader reader, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:195)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObject (BinaryElement element, System.IO.BinaryReader reader, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:223)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadNextObject (BinaryElement element, System.IO.BinaryReader reader) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:130)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectGraph (BinaryElement elem, System.IO.BinaryReader reader, Boolean readHeaders, System.Object& result, System.Runtime.Remoting.Messaging.Header[]& headers) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:104)
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:179)
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:136)
Manager.Load () (at Assets/Scripts/Manager.cs:58)
Manager.Awake () (at Assets/Scripts/Manager.cs:20)
 

All this seems to be telling me is that there is a problem where save and load are called.

Share this post


Link to post
Share on other sites

Exactly.  The dat file had previously serialized out an older version of the struct that held a single integer instead of an array.  As the dat file was external to the program context, nothing updated its internal structure when you changed the code itself.  Deserialize was reading this old data type and choking when your explicit cast said "now populate this array field with this integer".

Share this post


Link to post
Share on other sites

Where is the best place to compare high scores. Right now, I have a timer that checks that the time hasn't run out, and i compare the current score to the saved high scores when the timer runs out. However, it runs every frame, so it checks correctly the first frame, but then when  it runs again, it changes the next value to the current score. I can't think of another place to run this comparison function. Any Suggestions?

Share this post


Link to post
Share on other sites

Why can't it just run once?  It's something you only need to do once at the end of a game session.  Certainly no need for including it in every frame.

I'd lean towards sticking it in a static utility function just for ease of editing later.  Then in whichever method handles "what happens when a game is over" you just call over to the score-checking method and shuffle around high score order if necessary.

There's no "one best way" (arguably) to do it, but there are certainly easier-to-maintain ways.  

 

First, just make it work.  Then, if you want and have the time/need, make it work better :)

Share this post


Link to post
Share on other sites
My problem is that I'm trying to figure out HOW to make it run once. I already have it in a separate function and I call it where the rest of my and game logic goes. Unfortunately, that's in Updat because I have to check that a timer ran out.

I've thought of stopping the timescale and do the end game logic in fixedUpfate, but I think I'll have the same problem.

Share this post


Link to post
Share on other sites

You might want to look at implementing different game states to the overall game loop, so that you don't have to fit the logic for every possible state of the game into one monolithic Update() method.  That's not a brief topic though, (and I suggest doing some googling on "managing multiple game states" even here in the forums).  

 

As a rough explanation, when your game is playing, some variable somewhere should know that the game is in  regular "play mode".  That's a state, and update should only do stuff that's pertinent for "play mode" updates.  Things like decrementing the timer and checking for "is time up yet", for example.

 

When the timer reaches zero, state should switch to "endgame mode", and then update calls should have a completely separate set of logic that makes more sense for the game during the post-play time period.  Things like "was the high score good enough to put on the board" and "did the player press 'restart' ", for example.

 

 

However, in the interest of a quick solution for now, it sounds like "I already have it in a separate function and I call it where the rest of my [end] game logic goes" should result in only calling the score-comparison once.  What does that code look like?

Edited by BCullis

Share this post


Link to post
Share on other sites

Heres the Update that calls the function. I was trying to fix the problem, thats why stuff is commented out.

 

    void Update ()
    {
        if (gameTimer > 0)
        {
            gameTimer -= Time.deltaTime;
            minutes = (int)gameTimer / 60;
            seconds = (int)gameTimer % 60;
            timer.text = "Time: " + minutes + ":" + seconds;
            
        }
        if (gameTimer <= 0)
        {
            //Debug.Log ("Game Over");
            inGame.SetActive (false);
            playArea.SetActive (false);
            gameOver.SetActive (true);
            finalScore.text = "Score: " + GameManager.score;
            Manager.currentScore = GameManager.score;
            //Manager.CalculateHighScores()
        }
    }

 

Here is the function itself.

 

    public static void CalculateHighScores ()

    {
        Debug.Log ("Calculating High Scores");
        bool isGreater = false;
        int i = 0;
        //Debug.Log ("Recieved" + newScore);
        do {
            if (Manager.currentScore > Manager.highScores.Length)
            {
                Debug.Log("Curent Score: " + Manager.currentScore + ">" + Manager.highScores[i]);
                isGreater = true;
                Manager.highScores[i+1] = Manager.highScores[i];
                Manager.highScores[i] = Manager.currentScore;
                Debug.Log("break;");
            }
            else{
                i++;
            }
        }while(!isGreater);
        Debug.Log ("Done");
    }

Share this post


Link to post
Share on other sites

When should we to save our game data?

I Mean if we should save data before quit Game,or just save to file when game data changed?

That depends on how your game works. My game was a small mobile game so I could save every time the player game ended. However, if you had an RPG type game you may want to set points in the game world at which your player could use a save option.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this