Skip to main content
Rollback to Revision 1
Source Link
Philipp
  • 123k
  • 28
  • 264
  • 344

Edit

In response to @Philipp answer, I thought I'd add a little additional context here.

The reason for using the StateCache and populating through OnDestroy() came from trying to retain state cross-scene. In the project (linked above), I have Scene1 and Scene2, which both have a player that moves around the screen. When I switch to MenuScene (which has no player), I need to capture the current state of the player. The reason for this is so that when I resume the previous scene (essentially simulating somewhat of a pause/resume), the game is able to restore the player to the position it held before the scene switched.

I then decided to persist the data in the StateCache on save, as I already had a mechanism built-in to be able to read/write from/to the cache from individual objects. When saving, though, I recognised that there may be some objects that haven't been destroyed and therefore wouldn't exist in the cache. As such, I create a union set from the cache and active implementors of IPreserveState in the current scene, which I find using:

FindObjectsOfType<MonoBehaviour>()
    .OfType<IPreserveState>();

I guess there are maybe 2 additional questions to ask here:

  1. Is there a better way of preserving the state of objects (such as the player's position) between scenes without the use of the Awake() and OnDestroy() lifecycle events?
  2. Should I be detaching the persistence and pre-population of the StateCache when saving/loading?

For reference, I have explored a number of alternative approaches, such as the singleton pattern (without DI), PlayerPrefs, etc. I found this answer, which details each of the other approaches.

Edit

In response to @Philipp answer, I thought I'd add a little additional context here.

The reason for using the StateCache and populating through OnDestroy() came from trying to retain state cross-scene. In the project (linked above), I have Scene1 and Scene2, which both have a player that moves around the screen. When I switch to MenuScene (which has no player), I need to capture the current state of the player. The reason for this is so that when I resume the previous scene (essentially simulating somewhat of a pause/resume), the game is able to restore the player to the position it held before the scene switched.

I then decided to persist the data in the StateCache on save, as I already had a mechanism built-in to be able to read/write from/to the cache from individual objects. When saving, though, I recognised that there may be some objects that haven't been destroyed and therefore wouldn't exist in the cache. As such, I create a union set from the cache and active implementors of IPreserveState in the current scene, which I find using:

FindObjectsOfType<MonoBehaviour>()
    .OfType<IPreserveState>();

I guess there are maybe 2 additional questions to ask here:

  1. Is there a better way of preserving the state of objects (such as the player's position) between scenes without the use of the Awake() and OnDestroy() lifecycle events?
  2. Should I be detaching the persistence and pre-population of the StateCache when saving/loading?

For reference, I have explored a number of alternative approaches, such as the singleton pattern (without DI), PlayerPrefs, etc. I found this answer, which details each of the other approaches.

Extended question based on response from community
Source Link
Samuel Slade
  • 219
  • 1
  • 4
  • 14

Edit

In response to @Philipp answer, I thought I'd add a little additional context here.

The reason for using the StateCache and populating through OnDestroy() came from trying to retain state cross-scene. In the project (linked above), I have Scene1 and Scene2, which both have a player that moves around the screen. When I switch to MenuScene (which has no player), I need to capture the current state of the player. The reason for this is so that when I resume the previous scene (essentially simulating somewhat of a pause/resume), the game is able to restore the player to the position it held before the scene switched.

I then decided to persist the data in the StateCache on save, as I already had a mechanism built-in to be able to read/write from/to the cache from individual objects. When saving, though, I recognised that there may be some objects that haven't been destroyed and therefore wouldn't exist in the cache. As such, I create a union set from the cache and active implementors of IPreserveState in the current scene, which I find using:

FindObjectsOfType<MonoBehaviour>()
    .OfType<IPreserveState>();

I guess there are maybe 2 additional questions to ask here:

  1. Is there a better way of preserving the state of objects (such as the player's position) between scenes without the use of the Awake() and OnDestroy() lifecycle events?
  2. Should I be detaching the persistence and pre-population of the StateCache when saving/loading?

For reference, I have explored a number of alternative approaches, such as the singleton pattern (without DI), PlayerPrefs, etc. I found this answer, which details each of the other approaches.

Edit

In response to @Philipp answer, I thought I'd add a little additional context here.

The reason for using the StateCache and populating through OnDestroy() came from trying to retain state cross-scene. In the project (linked above), I have Scene1 and Scene2, which both have a player that moves around the screen. When I switch to MenuScene (which has no player), I need to capture the current state of the player. The reason for this is so that when I resume the previous scene (essentially simulating somewhat of a pause/resume), the game is able to restore the player to the position it held before the scene switched.

I then decided to persist the data in the StateCache on save, as I already had a mechanism built-in to be able to read/write from/to the cache from individual objects. When saving, though, I recognised that there may be some objects that haven't been destroyed and therefore wouldn't exist in the cache. As such, I create a union set from the cache and active implementors of IPreserveState in the current scene, which I find using:

FindObjectsOfType<MonoBehaviour>()
    .OfType<IPreserveState>();

I guess there are maybe 2 additional questions to ask here:

  1. Is there a better way of preserving the state of objects (such as the player's position) between scenes without the use of the Awake() and OnDestroy() lifecycle events?
  2. Should I be detaching the persistence and pre-population of the StateCache when saving/loading?

For reference, I have explored a number of alternative approaches, such as the singleton pattern (without DI), PlayerPrefs, etc. I found this answer, which details each of the other approaches.

Source Link
Samuel Slade
  • 219
  • 1
  • 4
  • 14

Unity Save System - is there a better way?

I've been trying to understand how best to approach carrying state over between scenes, and how to save/load game state. I've built an isolated project to focus on building out this approach.

You can view the project on GitHub.

Essentially, my approach is to use a singleton StateCache, which tracks all state objects, such as the PlayerState in my example. When the PlayerMove script (and therefore the player object) is destroyed (i.e. when a different scene is loaded), it writes its latest state to the StateCache. When clicking the Save button, all state in the cache is written to a JSON file. Finally, when loading the game data, the JSON is parsed into appropriate state types and the StateCache is prepopulated with this data.

Some caveats here, though, that make this more complex:

  1. To be able to load the state using strong typing, I need to write the actual state object type names into the JSON object.
  2. When loading, I load the next scene first and then prepopulate the cache. However, the player reads its state when the scene is loaded (before the cache is populated). If I load the cache before the scene is loaded, then in the example, the player will be destroyed and overwrite the loaded data in the cache (this could be mitigated by loading from a main menu scene where no player exists).
  3. Due to #2, I need to run an additional step to find all active instances that have a state, and apply the state from the cache (if it exists).
  4. Also, not necessarily a caveat, but adds complexity, I've brought in Zenject for dependency injection to get away from having static instance references.

To try to explain it visually, I've also created this diagram:

Save/load/cache system

This is working in my isolated project. But it feels overly complicated for what seems like a common problem. I'm relatively new to Unity, building a game as a casual hobby, so I'm keen to understand recommended approaches here. Is there a better way? Or is this complexity necessary?