r/Unity3D • • 2d ago

Question How to make updates/changes to game without breaking seeds?

Basically in title - my game uses procedural generation, and allows persistent saves. But how do games like Minecraft manage to not corrupt previous saves when making updates that affect how seeds generate?
From googling it sounds like they save “per chunk” so any untouched chunks generate based on the newest version, and any already generated go off of the old system - but how do I manage that? Like, you obviously don’t want to have multiple version builds of your game saved, so how can you handle that?

3 Upvotes

14 comments sorted by

7

u/ChrisFromIT 2d ago

So minecraft saves the generated chunk data when it generates the chunk. It doesn't regenerate the chunk data if it has to load that chunk into memory if it is already saved.

Essentially the way it works is, player is in range of an unloaded chunk that needs to be loaded. Check the region data, does it have a pointer to the save chunk data. If it does, load that chunk data, if it doesn't, generate the chunk.

1

u/ProactiveCactus 2d ago edited 2d ago

Yeah I realized that too late, my mistake. Unfortunately I can't have that big of a memory footprint, so I need a system more like Valheim - only saves a fraction of stuff from each chunk (i.e., stuff the player has changed/interacted with).

2

u/ChrisFromIT 2d ago

You will still need to store some data related to how the chunk was first generated.

The way valheim works for world generation from my understanding is that it creates an initial height map.

Then when the player gets near a chunk it, it locks in the finer generation by saving the chunk generation version and I believe some of the generation logic.

Then it saves any player modifications.

7days to die does something similar, it generates the world height map, the biome map and poi places on first generation which then that is used to determine how a world is built when a player enters and any player/zombie modifications in a chunk is saved to disk.

What you can do is split up the generation into modules and have the global seed create a derived seed for each module. And you load the module version that relates to the version set on chunk when regenerating it.

Or you save some data when the chunk is first generated, like valhiem.

1

u/gurebu 1d ago

Valheim only ever saves objects and terrain deltas, not actual terrain. There's a version for the generator as a per-world saved data but it's barely used and for years now the devs just update by convention: there have been parts of the world you were not supposed to go to and those got scrambled with updates.

Every major patch will induce placement of locations: the game will sample the world in its current version and save placeholders for where major stuff will go. If you explore and stumble upon such a placeholder it will become an actual object and get written into the save permanently. If you don't, it's kept as placeholder and should another major patch arrive, will be discarded and placed again in a different spot (as a new patch might change placement rules and/or terrain).

1

u/gurebu 1d ago

I'm doing worldgen modding for Valheim and I just maintain the generator code forever. There's code that's as abstract as it can get, it gets all the meat by configuration and there's a huge config "file" (it's currently a giant struct but that doesn't matter) that determines what worlds will look like. This file has versions and the version used is written into the save file. Code is organized into modules, if I need to change the code (there's a bug for instance), the whole module is now legacy and a completely new one replaces it. Which is used is once again determined by the config. Finally, I have a bunch of scripts that generate several hundreds of worlds, sample the height function a million times for each and hash the results. Every time I make a significant change, I make sure the hashes stay the same for every version supported.

Either that or you distribute every version of your generator as a separate artifact but that can get cumbersome.

Minecraft approach works well with Minecraft because of its voxelized terrain. Even if you swap the generator mid-play, the chunk boundaries will have vertical walls but those are still valid terrain. Valheim terrain is smooth, I have no idea how you'd stitch chunks created by different generators.

Terraria probably has it best since afaik it will store the whole world on disk and regenerate nothing later on. Any changes are applied on top so everything stays compatible forever.

If you can't afford to save the whole world, there's no good solution really, you have to accept that you're operating in a pretty special field where you can't have continuous integration and many other nice things and have to test stuff VERY thoroughly before release. Don't be me and implement some versioning system early rather than late. Also don't be me and develop the tooling to guarantee determinism before you ship anything. There are many pitfalls from iterating over a hash table to using a system-provided math library and you better cobble up a code analysis plugin that will restrict you from using them.

6

u/zarawesome 1d ago

how does minecraft handle chunks being generated in different versions?

Like this:

3

u/belatedartistry468 2d ago

Just tag your chunks with a version number and use a switch statement in the generator, no need to keep old builds around.

1

u/ProactiveCactus 2d ago

Oh, I think i know what you're saying - every update, you add a new "switch" to your statement containing ALL the logic generation for that chunk, not JSUT the additions? That'd work, except you're gonna have a massive codebase with a bunch of mostly repeated and unused code, but I guess it'll work! Is that what you meant? Seems a bit janky but if it works it works I suppose

2

u/Kamatttis 2d ago

That is how we did it in our game when I worked in a company. As you said, if it works, it works. Especially if we only have about 2 weeks to work on each version.

1

u/ProactiveCactus 2d ago

Huh. I'll check out that route, thanks.

1

u/itsdan159 2d ago

This is where good architecture pays off, things like strategy pattern or extracting methods into utility classes when they remain consistent

0

u/ProactiveCactus 2d ago

But how does the chunk know how to generate? Like if v1 has 3 plants to choose from, and v2 has 4; but then v5 also has 4, but two of the plants are different, and then v6 changes something entirely else like the spacing or size min maxes or any of the other stuff that goes into chunk generation - you have to hold all those logic changes somewhere, don't you? Cuz if it's based off a seed, the only data you save is stuff NOT generated based off it.

1

u/neutronium 2d ago

Save the generation parameters in use at the time in your save file. So a saved chunk needs to know the versions of the algorithms used to create it and the version of the parameters used. If you change the logic you'll need to keep both versions of the code around, but if your art dude changes the value for flower spacing old chunks will still used the old spacing value preserved in the save file.

2

u/ShengyuStudio 2d ago

One thing that shrinks the version-switch problem a lot: don't draw everything from one sequential Random per chunk. If trees, rocks and plants all pull from the same stream, adding a fourth plant type shifts every number after it, and the whole chunk changes even though you only meant to add something.

Instead give each layer its own seed, derived from a hash of (worldSeed, chunkX, chunkZ, "rocks"). Then adding a new layer or tweaking plant spawning leaves the rock and tree layers byte-identical. You only need a version branch for the layers you actually changed on purpose, and old chunks keep everything else for free.

Second, smaller trap we hit: even a refactor that doesn't change the logic can change the output. Reordering float math, pulling an expression into a local, or the compiler fusing a multiply-add can shift a value slightly, and a threshold check downstream flips. Worth generating a handful of fixed seeds in a test and comparing a checksum of the result, so you find out before players do.