[SOLVED] Load Quest Game Data from script
-
- Posts: 118
- Joined: Thu Dec 19, 2019 7:38 am
[SOLVED] Load Quest Game Data from script
Hi,
I've been using the AutoSaveLoad for Quest Machine. I've had some issues with inconsistencies with the data being loaded: sometimes the journal would load empty (despite a save present in the save system). After making some adjustments, my player is now jumping to a different Scene then when the game was stopped (when I hit Load Game from saved menu).
Does player positional data get saved with Quest Machine? When I disable Quest Machine and Load a game, the players position and scene is correct.
In my game, I want the player to be able to start a new game or load the previous game (I don't want to use several saved game slots). I was using AutoSaveLoad with the Load on Start checked. I had trouble with using Quest Machine in the main menu, so I had it only in the game play scenes. If I try and use it in the main menu, it automatically loads the previously saved scene bypassing the main menu.
I am saving other data with my own saved game system, would it be better to load from script?
I've been using the AutoSaveLoad for Quest Machine. I've had some issues with inconsistencies with the data being loaded: sometimes the journal would load empty (despite a save present in the save system). After making some adjustments, my player is now jumping to a different Scene then when the game was stopped (when I hit Load Game from saved menu).
Does player positional data get saved with Quest Machine? When I disable Quest Machine and Load a game, the players position and scene is correct.
In my game, I want the player to be able to start a new game or load the previous game (I don't want to use several saved game slots). I was using AutoSaveLoad with the Load on Start checked. I had trouble with using Quest Machine in the main menu, so I had it only in the game play scenes. If I try and use it in the main menu, it automatically loads the previously saved scene bypassing the main menu.
I am saving other data with my own saved game system, would it be better to load from script?
Last edited by mschoenhals on Sat May 02, 2020 8:32 am, edited 3 times in total.
Re: Load Quest Game Data from script
Hi,
If you're saving other data with your own saved game system, you might just want to tie Quest Machine's save data into that. Here's how:
1. Remove the Auto Save Load component.
2. On the Save System component, untick 'Save Current Scene'. If this is ticked, the Save System will save the current scene; when loading a saved game, it will load this scene.
3. Tick 'Include in Saved Game Data' on any Quest Journal and Quest Giver components that you want to include in saved games. Tick 'Save Across Scene Changes'.
4. Add any other savers that you want to use, such as PositionSaver on the player to save its position. Tick 'Save Across Scene Changes'.
5. When saving a game, use this code to get Quest Machine's save data as a string:
Then you can save the string to your own saved game system. Your Save System GameObject should still have a data serializer (e.g., JsonDataSerializer) and a saved game data storer (PlayerPrefsSavedGameDataStorer) to prevent it from logging a warning message, but it will not use the saved game data storer.
6. When loading a game, use this code to re-apply the string from your saved game system:
---
If you decide to allow Quest Machine's save system to manage saving and loading, try this setup:
Option A (use AutoSaveLoad):
1. Put the Save System GameObject in the main menu scene. Do not put the AutoSaveLoad component on it.
2. Put the AutoSaveLoad component in the first gameplay scene.
3. If the player chooses to continue, load the first gameplay scene.
4. If the player chooses to start a new game, delete the saved game (e.g., use SaveSystemMethods.DeleteSlot), then load the first gameplay scene.
Option B (do not use AutoSaveLoad):
1. Put the Save System GameObject in the main menu scene. Do not put the AutoSaveLoad component on it.
2. If the player chooses to continue, load the saved game.
3. If the player chooses to start a new game, load the first gameplay scene.
If you're saving other data with your own saved game system, you might just want to tie Quest Machine's save data into that. Here's how:
1. Remove the Auto Save Load component.
2. On the Save System component, untick 'Save Current Scene'. If this is ticked, the Save System will save the current scene; when loading a saved game, it will load this scene.
3. Tick 'Include in Saved Game Data' on any Quest Journal and Quest Giver components that you want to include in saved games. Tick 'Save Across Scene Changes'.
4. Add any other savers that you want to use, such as PositionSaver on the player to save its position. Tick 'Save Across Scene Changes'.
5. When saving a game, use this code to get Quest Machine's save data as a string:
Code: Select all
string s = PixelCrushers.SaveSystem.Serialize(PixelCrushers.SaveSystem.RecordSavedGameData());
6. When loading a game, use this code to re-apply the string from your saved game system:
Code: Select all
PixelCrushers.SaveSystem.ApplySavedGameData(PixelCrushers.SaveSystem.Deserialize<SavedGameData>(s));
If you decide to allow Quest Machine's save system to manage saving and loading, try this setup:
Option A (use AutoSaveLoad):
1. Put the Save System GameObject in the main menu scene. Do not put the AutoSaveLoad component on it.
2. Put the AutoSaveLoad component in the first gameplay scene.
3. If the player chooses to continue, load the first gameplay scene.
4. If the player chooses to start a new game, delete the saved game (e.g., use SaveSystemMethods.DeleteSlot), then load the first gameplay scene.
Option B (do not use AutoSaveLoad):
1. Put the Save System GameObject in the main menu scene. Do not put the AutoSaveLoad component on it.
2. If the player chooses to continue, load the saved game.
3. If the player chooses to start a new game, load the first gameplay scene.
-
- Posts: 118
- Joined: Thu Dec 19, 2019 7:38 am
Re: Load Quest Game Data from script
Hi Tony,
Thanks for your reply. The string code you suggest I use, I would need to include it in my saved file, correct? I'm having some difficulty with that part. I'm using a runtime binary formatter - will that work with Quest Machine's save data?
I'm assuming I would include the codeline "string = s..." in my SaveFile Method (see code copied below). That creates the string but I will still need to save that string, correct? Not sure how that will be done here. Does it get added to the dictionary?
Looking at the other options you gave, both of them would create conflicts with my own saved game system, correct? Or can they be used along with my current system? What do you recommend is the simplest solution here? Also, I plan to purchase Pixel Crusher's Dialogue System. Will this solution work for that as well?
Thank you in advance.
Thanks for your reply. The string code you suggest I use, I would need to include it in my saved file, correct? I'm having some difficulty with that part. I'm using a runtime binary formatter - will that work with Quest Machine's save data?
I'm assuming I would include the codeline "string = s..." in my SaveFile Method (see code copied below). That creates the string but I will still need to save that string, correct? Not sure how that will be done here. Does it get added to the dictionary?
Looking at the other options you gave, both of them would create conflicts with my own saved game system, correct? Or can they be used along with my current system? What do you recommend is the simplest solution here? Also, I plan to purchase Pixel Crusher's Dialogue System. Will this solution work for that as well?
Thank you in advance.
Code: Select all
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
public IEnumerator LoadLastScene(string saveFile)
{
Dictionary<string, object> state = LoadFile(saveFile);
int buildIndex = SceneManager.GetActiveScene().buildIndex;
if (state.ContainsKey("lastSceneBuildIndex"))
{
buildIndex = (int)state["lastSceneBuildIndex"];
}
yield return SceneManager.LoadSceneAsync(buildIndex);
RestoreState(state);
}
public void Save(string saveFile)
{
Dictionary<string, object> state = LoadFile(saveFile);
CaptureState(state);
SaveFile(saveFile, state);
}
public void Load(string saveFile)
{
RestoreState(LoadFile(saveFile));
}
public void Delete(string saveFile)
{
File.Delete(GetPathFromSaveFile(saveFile));
}
private Dictionary<string, object> LoadFile(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
if (!File.Exists(path))
{
return new Dictionary<string, object>();
}
using (FileStream stream = File.Open(path, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
return (Dictionary<string, object>)formatter.Deserialize(stream);
}
}
private void SaveFile(string saveFile, object state)
{
string path = GetPathFromSaveFile(saveFile);
print("Saving to " + path);
using (FileStream stream = File.Open(path, FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, state);
//string s = PixelCrushers.SaveSystem.Serialize(PixelCrushers.SaveSystem.RecordSavedGameData());
}
}
private void CaptureState(Dictionary<string, object> state)
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
state[saveable.GetUniqueIdentifier()] = saveable.CaptureState();
}
state["lastSceneBuildIndex"] = SceneManager.GetActiveScene().buildIndex;
}
private void RestoreState(Dictionary<string, object> state)
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
string id = saveable.GetUniqueIdentifier();
if (state.ContainsKey(id))
{
saveable.RestoreState(state[id]);
}
}
}
private string GetPathFromSaveFile(string saveFile)
{
return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
}
}
}
Re: Load Quest Game Data from script
If you add these lines to CaptureState and RestoreState, it should work:
(2 lines added to the bottom of each method.)
Edit:
1. RestoreState gets state["questMachine"] as string.
2. Made keys consistently "questMachine" in both methods.
(2 lines added to the bottom of each method.)
Code: Select all
private void CaptureState(Dictionary<string, object> state)
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
state[saveable.GetUniqueIdentifier()] = saveable.CaptureState();
}
state["lastSceneBuildIndex"] = SceneManager.GetActiveScene().buildIndex;
string s = PixelCrushers.SaveSystem.Serialize(PixelCrushers.SaveSystem.RecordSavedGameData());
state["questMachine"] = s;
}
private void RestoreState(Dictionary<string, object> state)
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
string id = saveable.GetUniqueIdentifier();
if (state.ContainsKey(id))
{
saveable.RestoreState(state[id]);
}
}
string s = (string)(state["questMachine"]);
PixelCrushers.SaveSystem.ApplySavedGameData(PixelCrushers.SaveSystem.Deserialize<SavedGameData>(s));
}
1. RestoreState gets state["questMachine"] as string.
2. Made keys consistently "questMachine" in both methods.
-
- Posts: 118
- Joined: Thu Dec 19, 2019 7:38 am
Re: Load Quest Game Data from script
Hi Tony,
The capture state appeared to have no errors but I got a "cannot implicity convert type object to string. An explicit conversion exists (are you missing a cast?)" error in the restore state.
I tried using Visual Studio to help solve it and it came up with this:
However, that created 2 KeyNotFoundException errors:
KeyNotFoundException: The given key was not present in the dictionary.
System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item (TKey key) (at <df7127ba07dc446d9f5831a0ec7b1d63>:0)
RPG.Saving.SavingSystem.RestoreState (System.Collections.Generic.Dictionary`2[TKey,TValue] state) (at Assets/Scripts/Saving/SavingSystem.cs:92)
RPG.Saving.SavingSystem+<LoadLastScene>d__0.MoveNext () (at Assets/Scripts/Saving/SavingSystem.cs:24)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at C:/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)
I did have an issue with the last line of code you gave me:
But managed to solve it through a using statement:
What am I missing?
The capture state appeared to have no errors but I got a "cannot implicity convert type object to string. An explicit conversion exists (are you missing a cast?)" error in the restore state.
I tried using Visual Studio to help solve it and it came up with this:
Code: Select all
PixelCrushers.SaveSystem.ApplySavedGameData(PixelCrushers.SaveSystem.Deserialize<SavedGameData>((string)state["quest_machine"]));
KeyNotFoundException: The given key was not present in the dictionary.
System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item (TKey key) (at <df7127ba07dc446d9f5831a0ec7b1d63>:0)
RPG.Saving.SavingSystem.RestoreState (System.Collections.Generic.Dictionary`2[TKey,TValue] state) (at Assets/Scripts/Saving/SavingSystem.cs:92)
RPG.Saving.SavingSystem+<LoadLastScene>d__0.MoveNext () (at Assets/Scripts/Saving/SavingSystem.cs:24)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at C:/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)
I did have an issue with the last line of code you gave me:
Code: Select all
PixelCrushers.SaveSystem.ApplySavedGameData(PixelCrushers.SaveSystem.Deserialize<SavedGameData>(s));
Code: Select all
using PixelCrushers;
Re: Load Quest Game Data from script
Oops, I typed the key as "questMachine" in one method and "quest_machine" in the other. They should be the same. I'll change my code above to use "questMachine".
-
- Posts: 118
- Joined: Thu Dec 19, 2019 7:38 am
Re: Load Quest Game Data from script
Same message:
Cannot implicitly convert type 'object' to 'string.' An explicit conversion exists (are you missing a cast?)
It's referring to this line of code:
The other lines do not appear to be creating errors.
Cannot implicitly convert type 'object' to 'string.' An explicit conversion exists (are you missing a cast?)
It's referring to this line of code:
Code: Select all
string s = state["questMachine"];
-
- Posts: 118
- Joined: Thu Dec 19, 2019 7:38 am
Re: Load Quest Game Data from script
Ok, I got it working but if you could just check to make sure that this doesn't produce other issues for Quest Machine, I would appreciate it. I edited the line to:
Adding the "as string" at the end allowed it to compile and appears to be saving in my game.
Do you see any reason for concern on this change?
Thanks.
Code: Select all
string s = state["questMachine"] as string;
Do you see any reason for concern on this change?
Thanks.
Re: Load Quest Game Data from script
That should be fine. I did something similar in my code above:
Code: Select all
string s = (string)(state["questMachine"]);
-
- Posts: 118
- Joined: Thu Dec 19, 2019 7:38 am
Re: Load Quest Game Data from script
Ok, thanks Tony. Much appreciated.