Saving Multi Level RPG for Convention

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
n_hagialas
Posts: 36
Joined: Wed May 03, 2017 4:34 pm

Saving Multi Level RPG for Convention

Post by n_hagialas »

Hey Toni,

I've been reading through the save and load documentation but I'm a bit confused on how to get it to work with our project in particular. We have 5 scenes (and plan to have more) and the main character is created in the third scene, since the first scene is the main menu and the second is a cutscene.

So it looks something like this:
1. [Main Menu] - created a game saver here and another object that calls save on it
2. [Intro Cutscene]
3. [Scene 1] - Player is created here and starts at a particular trigger point, Lots of variables and triggers in these levels that we need to reuse.
4. [Boss Scene] -->. [Back to Scene 1] - Boss data needs to be saved so people can fight him again
5. [End Cutscene] - When the player presses quit here, we want the game to restart and return to the state the game was at the start menu

We tried this with SendMessage LoadGame and RestartGame and in each case, the players position is not reset to the default for that level. We have a levelManager component on the dialogue manager and were wondering what components we would need to add to what to make this all work. Do we need to add persistent components to EVERY object that needs to come back?

We are trying to get the game ready for a convention so we really want to make sure the reload system is as fluid as possible!

Thanks!
User avatar
Tony Li
Posts: 22062
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving Multi Level RPG for Convention

Post by Tony Li »

Hi,

Sometimes an example is worth 1000 words: ConventionSaveExample_2017-12-11.unitypackage

To play it, you must add the scenes in Dialogue System Examples / Convention Save Example / Scenes to your build settings:
  • 1. Main Menu
  • 2. Intro Cutscene
  • 3. Scene 1
  • 4. Boss Scene
  • 5. End Cutscene
This is how I set it up:

1. Main Menu
  • Added the Dialogue Manager prefab. Added a LevelManager to it. I also added a GameSaver, but I didn't use it.
  • Added a "Play" button to go to "2. Intro Cutscene" by calling LevelManager.LoadLevel. I added a LevelManager to the button so it would always have a valid reference to a LevelManager. (If the player returns from the End Cutscene, the Dialogue Manager will be carried across from the original Main Menu, so the Play button can't easily reference it directly.)
  • Added a script named ResetDialogueDatabaseOnStart to an empty GameObject. The script has one line of code that resets the Dialogue System when the player returns to the main menu.
2. Intro Cutscene
  • Added a "Proceed" button that loads "3. Scene 1" in the same manner as the "Play" button above.
3. Scene 1
  • Added a Dialogue Manager prefab just to easily playtest the scene. In real play, this will be automatically destroyed by the Main Menu's Dialogue Manager.
  • Added the Feature Demo's Player and a small area to run around in.
  • Added a Persistent WallLamp Trigger. When used, it flips a variable named "Is Light On" and activates/deactivates a child GameObject. It also has a Persistent Active Data component that activates/deactivates the child GameObject when loading a game or returning from another scene (such as the Boss Scene).
  • Added a door to the Boss Scene. This is a Usable that runs this sequence:

    Code: Select all

    LoadLevel(4. Boss Scene,Spawnpoint From Scene 1)
  • Added an empty GameObject "Spawnpoint from Boss Scene" where the player will be placed when returning from the Boss Scene.
4. Boss Scene
  • Added a Dialogue Manager prefab just to easily playtest the scene.
  • Added a door back to Scene 1, and an empty GameObject "Spawnpoint from Scene 1".
  • Added a boss NPC. He's a Usable. When used, he loads the End Cutscene.
5. End Cutscene
  • Similar to Intro Cutscene. Has a "Main Menu" button that loads the Main Menu scene. When the Main Menu scene loads, the ResetDialogueDatabaseOnStart script resets the database for the next player.
Okay, so maybe an example and 1000 words is best. If I understood you correctly, I think this is what you want to accomplish. If you have any questions, just let me know.
n_hagialas
Posts: 36
Joined: Wed May 03, 2017 4:34 pm

Re: Saving Multi Level RPG for Convention

Post by n_hagialas »

Toni,

Thanks for getting back to me with a very elaborate response! I looked through the project and compared it to my own and added some components, but I have some more questions. I want to elaborate more on a problem I seem to be having. I'm not sure if my problems lie in my design or my implementation yet...

1. In Intro Cutscene, I switched from using a script to using the OnClick() function select for LevelManager.LoadLevel(string) on a button. The LevelManager.LoadLevel string argument doesn't parse my input "Demo_Test_01,GameStartTrigger" properly like it does in your example (Boss Scene,Spawnpoint From Scene 1) and says that this scene doesn't exist in build settings. Demo_Test_01 or (3. Scene 1) is a scene in my build settings and I want GameStartTrigger to be the player start point but its not working for some reason. I had to make a script that takes 2 values or parses a string and builds a DialogueManager.Instance.PlaySequence("LoadLevel(scene,location)"), and this doesn't use the level manager correct? How can I get this OnClick() to work properly?

2. After the Intro cutscene, you are taken to the Demo_Test_01 which is equivalent to your (3. Scene 1). At the start of this, the player is supposed to start on a trigger named GameStartTrigger. This is fine the first playthrough, but when I come back the second time through it doesn't work and the player just stands still. The issue is because we use a FootCollider object (not tag) to activate the Sequence Trigger. When we load in the second time, the reference is Missing. This happens for all objects that refer to an object. I tried adding persistent active data to the Trigger point and the footcollider as well but none of that has worked. How might we go about fixing this so that the object references can also be restored?

3. What's the difference between loading via script/regular LoadLevel and using load manager's load level exactly? What does the level manager provide that the normal LoadLevel doesn't already do?

4. What is default starting level on the level manager do?

5. Also can I just add a persistent data/position component to a parent "Triggers" folder and have it apply to everything in the folder? Just trying to save time here since it looks like adding this to every component could be very tedious since we have a lot of very specific scenarios and conditions set up.

6. Whats difference between using the restart function to restart the game and using the loading function for returning to the main menu? Originally I thought it might be easiest to serialize the game state at the main menu and then keep loading that again at the start with everything in tact, but it seems like it doesn't quite work that way right?

Thanks again, looking forward to your reply!
User avatar
Tony Li
Posts: 22062
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving Multi Level RPG for Convention

Post by Tony Li »

Hi,
n_hagialas wrote: Wed Dec 13, 2017 6:28 pm1. In Intro Cutscene, I switched from using a script to using the OnClick() function select for LevelManager.LoadLevel(string) on a button. The LevelManager.LoadLevel string argument doesn't parse my input "Demo_Test_01,GameStartTrigger" properly like it does in your example (Boss Scene,Spawnpoint From Scene 1) and says that this scene doesn't exist in build settings. Demo_Test_01 or (3. Scene 1) is a scene in my build settings and I want GameStartTrigger to be the player start point but its not working for some reason. I had to make a script that takes 2 values or parses a string and builds a DialogueManager.Instance.PlaySequence("LoadLevel(scene,location)"), and this doesn't use the level manager correct? How can I get this OnClick() to work properly?
In Demo_Test_01, the player GameObject should have a Persistent Position Data component. When the Dialogue System tells Persistent Position Data to retrieve the player's position from the Dialogue System's data, this component looks for an actor field named "Spawnpoint", such as:

Code: Select all

Actor["Player"].Spawnpoint
The LoadLevel(scene,location) sequencer command does two things:

1. It sets Actor["Player"].Spawnpoint = "location".

2. Then it calls LevelManager.LoadLevel(scene).

If you want to see the code, the sequencer command script is Assets /Dialogue System / Scripts / Supplemental / Sequencer Commands / SequencerCommandLoadLevel.cs.

The LevelManager.LoadLevel() C# method does not set the spawnpoint to location. It only loads scene and tells the persistent data components in scene to apply their state from the Dialogue System's data.

Can you write a small script that does both steps above? Then add this script to the button and configure OnClick() to call LoadLevelUtility.LoadLevelWithSpawnpoint. For example:

LoadLevelUtility.cs

Code: Select all

using UnityEngine;
using PixelCrushers.DialogueSystem;

public class LoadLevelUtility : MonoBehaviour // Add to your button.
{
    public void LoadLevelWithSpawnpoint(string s) //<-- Assign to OnClick().
    { // Example: Set OnClick()'s string to: Demo_Test_01,GameStartTrigger
        string[] fields = s.Split(',');
        string levelName = fields[0];
        string playerSpawnpoint = fields[1];
        DialogueLua.SetActorField("Player", "Spawnpoint", playerSpawnpoint);
        FindObjectOfType<LevelManager>().LoadLevel(levelName);
    }
}
n_hagialas wrote: Wed Dec 13, 2017 6:28 pm2. After the Intro cutscene, you are taken to the Demo_Test_01 which is equivalent to your (3. Scene 1). At the start of this, the player is supposed to start on a trigger named GameStartTrigger. This is fine the first playthrough, but when I come back the second time through it doesn't work and the player just stands still. The issue is because we use a FootCollider object (not tag) to activate the Sequence Trigger. When we load in the second time, the reference is Missing. This happens for all objects that refer to an object. I tried adding persistent active data to the Trigger point and the footcollider as well but none of that has worked. How might we go about fixing this so that the object references can also be restored?
Is your player set to "DontDestroyOnLoad"? If so, you'll have to use tags (in the trigger's Accepted Tags list), not GameObjects (in Accepted GameObjects). Or don't make the player DontDestroyOnLoad.

The Dialogue System doesn't have anything built in to restore object references. You could write your own persistent data component to do this, though.
n_hagialas wrote: Wed Dec 13, 2017 6:28 pm3. What's the difference between loading via script/regular LoadLevel and using load manager's load level exactly? What does the level manager provide that the normal LoadLevel doesn't already do?
Before leaving the old scene, the Level Manager tells the old scene's persistent data components to record their state into the Dialogue System. For example, if NPC1 has a Persistent Position Data component, it records the NPC1's current position in the old scene.

After entering the new scene, it tells the new scene's persistent data components to retrieve their state from the Dialogue System. For example, if NPC2 had previously recorded its position in the new scene, it will move NPC2 to the recorded position.
n_hagialas wrote: Wed Dec 13, 2017 6:28 pm4. What is default starting level on the level manager do?
LevelManager has a method RestartGame() that restarts the game by resetting the Dialogue System's data and loading the Default Starting Level.

LevelManager also has a method LoadGame(string savedData) that loads a saved game. Normally the savedData string records the name of the scene in which the player saved the game. If the savedData string doesn't have this information, it loads Default Starting Level instead.
n_hagialas wrote: Wed Dec 13, 2017 6:28 pm5. Also can I just add a persistent data/position component to a parent "Triggers" folder and have it apply to everything in the folder? Just trying to save time here since it looks like adding this to every component could be very tedious since we have a lot of very specific scenarios and conditions set up.
Yes. That's a very common thing to do.
n_hagialas wrote: Wed Dec 13, 2017 6:28 pm6. Whats difference between using the restart function to restart the game and using the loading function for returning to the main menu? Originally I thought it might be easiest to serialize the game state at the main menu and then keep loading that again at the start with everything in tact, but it seems like it doesn't quite work that way right?
LevelManager.RestartGame() resets the Dialogue System's runtime database information and then loads the Default Starting Scene.
n_hagialas
Posts: 36
Joined: Wed May 03, 2017 4:34 pm

Re: Saving Multi Level RPG for Convention

Post by n_hagialas »

I've been implementing the changes you advised and I got the player to finally move on the second playthrough. But as soon as he moves from the first trigger to the second (which has a dialogue sequence trigger), I get this error:

NullReferenceException: Object reference not set to an instance of an object
PixelCrushers.DialogueSystem.UnityUIDialogueUI.Update () (at Assets/Dialogue System/Scripts/Supplemental/UI/Dialogue UI/UnityUIDialogueUI.cs:381)

It has something to do with event systems here is the line in code:

if (UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject != null)

Is there something we are perhaps missing or doing wrong?
User avatar
Tony Li
Posts: 22062
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving Multi Level RPG for Convention

Post by Tony Li »

Hi,

Add an Event System to that scene. I think that should take care of it.
n_hagialas
Posts: 36
Joined: Wed May 03, 2017 4:34 pm

Re: Saving Multi Level RPG for Convention

Post by n_hagialas »

Hey Toni,

Thanks for your previous help, we are starting to get the hang of it :), I've got 2 more questions:

1. Whenever I run Lua code that changes a variable during gameplay such as in a Dialogue System Trigger: OnStart
Run Lua Code: ----> Variable["isNatureArea"] = true;
It always changes its value back to its default value of false.

I watch it in the Watches tab of the Dialogue Panel and add the boolean as a runtime variable. I see it become true and then watch it switch back to false within a few seconds. What is the cause of this? I need this variable to stay true until I reset the dialogue database in the main menu on reset.

I am trying to get this to work so that when I come back from the boss room, I can go to 1 of 2 levels based on this variable value and it seems to always go back to being false on its own..

2. When using the DialogueLua.SetActorField("Player", "Spawnpoint", exitPoint); in a script
it quickly loads the player into the next scene, however, sometimes it shows him moving all over the map first as if he is moving to the intended position too late after the level has already loaded. It is as thought he is being teleported to the intended coordinates in the new scene but he moves in the old scene briefly to those coordinates first.

We were able to break the game this way by moving around frantically and this placed the player out of bounds because he loaded before the level did. Do you know of any ways around this?

Thanks again.
User avatar
Tony Li
Posts: 22062
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving Multi Level RPG for Convention

Post by Tony Li »

Hi,
n_hagialas wrote: Sun Dec 24, 2017 3:03 am1. Whenever I run Lua code that changes a variable during gameplay such as in a Dialogue System Trigger: OnStart
Run Lua Code: ----> Variable["isNatureArea"] = true;
It always changes its value back to its default value of false.
Variables won't change back on their own. Is it possible that another trigger is setting it back to its default value?
n_hagialas wrote: Sun Dec 24, 2017 3:03 am2. When using the DialogueLua.SetActorField("Player", "Spawnpoint", exitPoint); in a script
it quickly loads the player into the next scene, however, sometimes it shows him moving all over the map first as if he is moving to the intended position too late after the level has already loaded. It is as thought he is being teleported to the intended coordinates in the new scene but he moves in the old scene briefly to those coordinates first.
This is due to a compromise that general-purpose save systems (like the Dialogue System's save system) must make. Some third party assets require one frame to initialize, so the save system can't start manipulating them until the second frame. The order of operations is:
  • LoadScene: Loads all objects in their original positions.
  • Frame 1: Third party assets initialize themselves.
  • Frame 2: Save system applies changes (e.g., move player to saved position).
A common solution is to add a short fade from black (to hide the teleport) and disable the player character's movement controls for the first two frames.
Post Reply