Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Persistent world state - how to do it right?

Discussion in 'Scripting' started by charles_xl, Mar 9, 2021.

  1. charles_xl

    charles_xl

    Joined:
    Oct 4, 2016
    Posts:
    87
    Hey all,

    I've hit a wall with my exploration survival game. The world is split up into a series of tiles and as the player moves around the world, only the tile the player is in and the tiles adjacent to the player get spawned in however I'm running into the situation now where when a quest source NPC gets despawned, the current state of their available quests gets reset. Another example is I have a behavior that runs on a particular spawned object, but that behavior's logic needs to run even when it is despawned as it's ongoing (a more practical solution is probably just doing the calculation when it gets spawned based on time_last_seen or something).

    Unless I'm missing something, it seems like the solution here is to run some local DB like SQLite alongside Unity so that we can persist info and when world objects get respawned they just get their last state from the DB. The other option is an in-memory DB which I think will be difficult to maintain and gives the game memory overhead. Is this the right approach to solve something like this?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,336
    For moment-to-moment I like the GameManager singleton pattern. If you make the GameManager a MonoBehaviour you automagically get all the benefits of Unity calling your Update() loop when you want, and you can run coroutines too obviously.

    As for longer-term world state, after that it would be serialize and save to disk, just a regular old load/save step I imagine. If you want the world to "continue living" even when you're not playing, then you just check time at shutdown, time at startup, and simulate as much as you need when you resume.

    For the first, here are some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://pastebin.com/SuvBWCpJ

    Unity3D Singleton with Prefab used for predefined data:

    https://pastebin.com/cv1vtS6G

    These are pure-code solutions, do not put anything into any scene, just access it via .Instance!

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    For loading/saving, there's tons of Youtube tutorials, but here are my scribblings:

    Load/Save steps:

    https://forum.unity.com/threads/save-system-questions.930366/#post-6087384

    Don't use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
     
  3. charles_xl

    charles_xl

    Joined:
    Oct 4, 2016
    Posts:
    87
    Thanks for the reply - I'm very familiar with saving/loading to/from disk but I don't like the idea of doing this a lot as it could have performance impacts, that's why I was thinking something like SQLite that writes/reads on a separate thread.
     
  4. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    I would suggest not to rely on MonoBehaviors for large worlds. When only a tiny fraction of the world is loaded/active at a certain time you should have some underlying data structures which hold your world state for the whole world and "reconstruct" the world the player can see when he spawns and/or moves. So you could have a tile struct which determines the terrain type (enum), a bool wether the player has already explored it, movement cost/obstructed, a list of its "contents" (like loot), a reference to some monster etc.. Then you keep those structs permently in memory for your said reasons (simulation, pathfinding, ai decisions). You just spawn some tile and monster prefabs around the player as he moves around and despawn those far away. And you manipulate those underlying structs (with prefabs showing changes like particle effects etc) but you don't rely on those. The prefab are just the visual representation of your game world state. And you can simply serialize your array/list/dictionary of those structs to make it persistent too.
    That's how I would do it.

    I would consider database stuff "overkill" since you still need objects for those data to operate on. It adds a whole layer of complexity. If your game is "simple" enough I would try this struct method first.

    If you are concerned about performance/memory overhead do some calculations. Create a struct which holds all infos you need, do a sizeof and multiply it by the size of your map. Modern computers handle Gigabytes whitout breaking a sweat. And keep in mind that you have no controll over the internal memory allocations of your database solution. If all else fails you could devide the world in chunks/levels/subworlds and only keep some of them in memory at a time and keep the rest serialized on disk when far away stuff does not need simulation. So the struct method also gives you some "wiggle room" for optimization whereas the database is a black box.

    So in summary there is no need to base everything on MonoBehaviors, there are still plain C# classes and structs available in Unity. I'm not aware of a reason NOT to use them if they fit your needs.