notes on procedural worlds


#21

i think this is my biggest problem with stuff i’ve written so far (other than how slow i am at this because it’s new to me): as soon as i touch delegates and local/class-specific higher order functions human readability plummets. i’ve gotten as much mileage as i know how out of smart variable naming and even improvising syntax out of extension functions for generic collections (the funniest/stupidest one so far is public static T[] For<T> (params T[] args) => args; )

also wow i sure hope you know all the intimidate details of the differences between functions/delegates/method groups/lambda functions/inline functions and the conditions under which you can cast between them (some of which will only show up at runtime with less than helpful NullObjectReference errors). these features feel like hacks.


#22

Hmm, I don’t remember it being hard to cast between functional types although you might be trying to do more complicated things than I did since you’re coming from JS? Since C# has a long history of big updates, I suspect some of those features are sort of antiquated and should be avoided, but it’s probably not obvious which ones are which, especially if you’re new!

As for syntax, I refreshed myself a bit and I actually think your code is pretty readable (I wasn’t familiar with the expression body syntax). The readability thing gets way better as you get used to a language anyway. Once you’ve been working in C# for a while I’d bet you’ll be able to tell what all that does at a glance.

I think another thing to keep in mind (which is something I was grappling with in Kotlin) is that when you’re using higher-level languages you’re fitting way more meaning on a single line, so it makes sense that it takes a bit to read; it’s very information dense.

I wouldn’t fret about it too much. There’s always a warming up period with a language and in time you’ll get better at working with it and knowing how to avoid its pain points.


#23

does anyone have insight on whether functional programming is viable for games? not “pure” functional programming like haskell but something like F# and the Nu Game Engine? it seems less like something that’s been thoroughly tried and shown to be unworkable and more like something that’s just not part of the culture, judging by how little google searches bring up. i know it’s possible to hack F# into unity through DLL injection (i’ve been working on it instead of the many things i should be working on) but like, next to no one’s doing it. i feel like i’m either being really smart or really stupid here. if i actually get invested in this i doubt i’ll really know if it’s been a waste of time or not until, like, a year in minimum.

(obviously i wouldn’t do this with low-level engine stuff but we’re talking about swapping out one multi-paradigm garbage collected language for another here. agent+component-based models should still be performant i’d think?)


#24

Any tips on large worlds that are seamlessly tiled?

For example: Dwarf Fortress generates a huge planet. Typical fractal terrain, erosion passes, temperature passes, rainfall etc. However, when playing, you are confined to a small region that’s much more highly detailed than the overworld map. There are different zoom levels and when switching between the regions at maximum detail, geographic accidents are preserved. If you are walking along the coastline, after traveling north there will still be coast on the same side in the next region. Mountain ranges preserve their shape, and so on.

This world must be millions of tiles wide. Is it all generated at the maximum zoom level, then zoomed out for the general views? This seems like it may be pretty slow.

If not, is it some low-detail version of the world generated and then “filled in” with detail as needed, only generating the low-level geography when the player accesses a particular region? How would this be accomplished?

Also. On 4X games with procedural maps, such as Civilization, you can often choose different styles of maps that, I’m assuming, are all different generation algorithms (or at least same algorithm with different input parameters). One style would generate current Earth-like continents, or pangea supercontinents, or island worlds with many small landmasses and the rest mostly covered by ocean…

Is there a technique to seamlessly combine these different styles into one big world map?

Let’s say I want part of the adventure to take place in a continental desert, so the algorithm generates that, but then the player travels to the coast, then by sea to a nearby “province” that’s an archipelago of islands. Is there a way to combine these different generation styles and ensure they remain consistent when you zoom out to see the entire world map? (i.e. no “cheating” borders between regions nor harsh transitions, respecting the planet-wide climate model etc)


#25

Use the Diamond-Square algorithm where edge tiles get their missing fourth neighbour value from the relevant adjacent tile. Interpret the value as a variation of the region’s biome (or of its neghbour, weighted by distance to the edge).

OR

Use Wave Function Collapsing (http://oskarstalberg.com/game/wave/wave.html) to generate out from the neighbouring regions to fill the current region. Use a deterministic PRNG so that regions always collapse the same way, so visiting the regions in a different order still results in the same tiles being generated.

The first approach has issues with discontinuity issues on the borders: you could go straight from thick forest to arid desert without a transition zone, or have a lake extend out of one region and not be present in the next. Use contraints to make map edges take on attributes of the neighbouring biome & stop features extending off regions.

Dwarf Fortress generates the world at a low zoom level, so you get a low-rez grid of abstract biomes like FOREST, MOUNTAIN, HILL, CITY etc. When you visit the region, it’s then filled in with appropriate tiles (with constraints like ‘has a town’ or ‘river enters and exits here and here’).

Re: deterministic PRNG: you could sample from a noise texture with octave noise & take your values/octave settings from the region’s coordinates, but you’d have to be careful about discontinuities again. I’d use Perlin simplex noise to make a low-rez global height-map & biome map, and use the same simplex noise to determine tiles in a region when visited.

This is a dumb answer but: yes, weighting/blending.


#26

Wave function collapse looks pretty cool I’ll have to investigate that a bit more.

My initial idea was to divide de map into a grid of hex tiles (because hex tiles are cool) but, as much as I like the hex aesthetic, I’m thinking of doing a naturalistic “continuous” map now, as an overworld. Thinking of just generating it at a high resolution and just dividing the play area into zoomed-in sectors.

Yes, that’s the approach I’ve decided on as well.

I’ve been noodling around a bit with the heightmaps, applying a falloff mask to give it a woldmap-like appearance of landmass surrounded by oceans, but I think the first mask I made is too aggressive. It leaves an elliptical area in the middle that results in a pangea-like continent with some lakes or just a smaller island, depending on your sea level settings

this is the map without the oceans. I will likely change to a gentler, square mask instead of this aggresive elliptical one

And this would be the noise heightmap before applying the mask. I haven’t worked out a good way to generate the biome map, but that’ll likely be the next step, then rivers, roads and settlements.

Anyway, don’t pretend to hijack the thread, but I will post more progress if ppl are interested


#27

idk if/when i’ll use this again so please don’t let me stop you


#28

Your noise has a lot of artifacts, I see a slightly-off-horizontal bias of squished doughnuts and the frequency decreases from top to bottom. How did you generate it?


#29

It’s the p5js perlin noise function with octaves set to 8, and a falloff of 0.65. I then scaled the values by 1.5

After that, I normalized the values back to the 0.0-1.0 range, but other than making it easier for me to set the sea level, I didn’t do much with that…

The squished donuts effect is based on the way I generated the mask, which was done with ellipses of increased radius. I know it’s a v. dumb way to do it (especially since I’m increasing them by whole integers), but I wanted something quick and that’s what I thought of at the time. I intend to replace it with a proper gradient.

Looking just at the mask one can see the banding of the different ellipses…


#30

extremely pedantic note that octave noise isn’t perlin noise

The p5js noise is… not great:

var noise_tex;

function setup() {
  createCanvas(400, 400);
  noise_tex = createImage(width, height);
  for (var x=0; x < noise_tex.width; x++) {
    for (var y=0; y < noise_tex.height; y++) {
      noise_tex.set(x, y, noise(x, y) * 255);
    }
  }
  noise_tex.updatePixels()
}

function draw() {
  image(noise_tex, 0, 0);
}

p5js_noise

zooming in, I can already see the doughnuts
p5js_noise_zoom


#31

Doesn’t perlin noise also use octaves? At least I’ve seen other noise height map tutorials that mention adjusting the octaves of the noise…

Perhaps not but I should mention this is literally just for me to sketch out stuff and figure out the procedures. It will not become any sort of “final” code anywhere.


#32

Octaves aren’t specific to perlin noise. They’re not even really related to the noise aspect. Noise is the building block you organize in octaves, but you could use octaves with any kind of noise, or really any kind of texture. If it’s used with noise, it becomes fractal noise.

Perlin noise’s thing is it’s specifically designed to make pattern repetitions and the underlying grid or lattice hard to spot (and even better for its successor, simplex noise). To be fair, a number of tutorials erroneously confuse some other noises like value noise and/or fractal noise for perlin noise.