Indeed, you can use
RandSeed to store random seed for your world. This way if the logic of your world generation always results in the same number of calls to
Random then you can rely on that. I highlight that because while it looks trivial it actually isn’t. I’m not in the best mental shape today, but I’ll try to explain it more or less step-by-step a little bit later, let’s just then look first from the technical standpoint.
First of all, you have a great random number generator in FreePascal. But it has one big flaw - it’s only one random number generator. In other words, if you need one number generator for world and another for gameplay (e.g. chance to hit an enemy or similar) then you’ll really need to be creative with predefined seed (that defines your world) and random seed (that defines in-game behavior). What is worse, even if you separate those there is still a chance they’ll be getting in the way of each other. But this is still solvable - you just have to store somewhere the last
RandSeed you were using.
Another solution (maybe much simpler) - in Castle Engine we have a ready plugin with random number generator GitHub - castle-engine/castle-random: Implementation of quick random based on Xorshift algorithm. This solves some of the problem mentioned above: you can create multiple instances of
TCastleRandom (for example, one for world, another for gameplay) and each will have its own persistent
Seed. As of stable FPC build it was even faster than its random implementation, however, I see that in the latest unstable (TRUNK) FPC their native random can be faster than ours
Now for the hard part - generating world from the seed.
There are several approaches to procedural generation. I personally split them into two ways: Generate only and Generate and save. They can be used for different purposes.
The first one has a huge advantage that you don’t have to save the world. Whatever happens you just generate the world anew and given the static seed, it will always generate exactly the same way as it did before. Implementing such generator is relatively trivial, however, it becomes trickier as the size of the world increases (especially to semi-infinity) and it has to be chunked to work with. Another problem of this method is that you will have problems generating non-static objects this way (like pickable items) - next time you generate the world, they’ll again appear in their places, which is not always desirable. So either you have to generate them once and save (which partially defeats the benefit of this world) or you have to have it in game logic that they respawn constantly (in some ancient games stuff happened this way).
Next option is when you generate a world (or a single chunk) once, and then just save the result in the save game. It has a lot of benefits, however, obviously such method takes more storage (as in save game size) and memory (you can’t just “forget” the chunks and regenerate them on the fly, you have to implement load-unload mechanisms, or store everything generated in the RAM which also has limits). But this way you are not limited, e.g. you can even make modifiable terrain.
Generating huge worlds (in chunks) can be done either by generating all the chunks at start (will be very slow, take a lot of memory) or generating them as the Player advances (imposes some limitations on generation methods, e.g. celluar automatona-like algorithms will have problems working at borders with ungenerated chunks; but in turn technically allows semi-infinite worlds). You can also try to “force” same seed for every hypothetical chunk (e.g. using a deterministic formula to convert from “world seed” into “chunk seed”) or, maybe just don’t bother and keep on using the same (or even random) seed for generating the chunks if you save the result anyway. This way for the same seed the world can generate differently if the player goes left or right, but often it’s more than enough for a single-player games and you can skip problems with seeds for individual chunks.
I guess I’ve written the “theory” above a bit chaotically. Hopefully it could help you answer your questions and you can make sense of it.
Now for the examples, I guess the closest to what you are trying to achieve is EugeneLoza / Kryftolike · GitLab - this game generates a semi-infinite world as the Player progresses through the map. It uses the “last” version of the algorithm - there is no strict “world seed” and the same seed can produce different dungeons depending on where the Player goes.
The generation happens in code/gamemap.pas · master · EugeneLoza / Kryftolike · GitLab, or rather in code/gamemap.pas · master · EugeneLoza / Kryftolike · GitLab . In short what it does: it finds 3x3 chunks around the Player and calls
GenerateChunk for each of them. Chunk generates a maze inside of it until the passages lead outside of the chunk. When all generated passages lead outside of the chunk, next chunk gets generation queue. And so on until no unfinished passages remain inside of the chunk we were working with (all passages lead outside of the generation area of 3x3 chunks).
There is more stuff going on there - by Voronoi polygons we create areas with slightly different generation parameters which result in them looking differently on the map. E.g. this is how biomes look like on zoom-out: