mapeditor / tiled

Flexible level editor
https://www.mapeditor.org/
Other
11.21k stars 1.76k forks source link

Generate random tiles from a seed #3818

Open stephenjjbrown opened 1 year ago

stephenjjbrown commented 1 year ago

Is your feature request related to a problem? Please describe.

It would be great if randomly generated tiles didn't completely reshuffle whenever automapping rules are reapplied. For example, once I get the distribution of rocks and flower tiles more or less where I want them, I might want them to regenerate in the same locations, even as I continue to work on the level and add new rules and new rule maps.

Describe the solution you'd like

A custom property that can be added to a layer that allows the user to supply a seed to the random number generator so that it generates the same series of random values every time. If the property is omitted, current behavior is preserved.

Ideally, the same random value would be generated for a given index in the grid whether the user applies the automap rules all at once or uses the brush with automap while drawing enabled.

Describe alternatives you've considered

The Automap While Drawing feature partially solves this, as I can draw over new areas while keeping old areas in place. But there are still scenarios where I might need to reapply the entire map at once; like if I add several new complex rules or new rule maps. Additionally, if I have the automap radius property set while using the brush, I may end up accidentally reshuffling the rocks and grass in the vicinity, when I only meant to add a couple new tiles.

Another workaround is that I could output each terrain type to a different layer, and lock the layers that have random variations. But this adds a lot of complexity and forces me to use a lot of layers and/or separate tiles with random variants onto different layers from those that don't.

Edit: The gif below is just to demonstrate how the random distribution of tiles changes whenever I hit cmd+M

eishiya commented 1 year ago

I'm not so sure "seeding" would accomplish what you're looking for - seeds set the initial condition, but the output can still vary if the inputs are different or are processed in a slightly different order.

You can already accomplish this style of random generation by having rules that will randomly replace rocks with other rocks, etc, and some "initial" rules that will only generate random rocks on non-rock tiles if the input is clean (so you'd need to differentiate between Automapping output used as input and manual input - for example, you could use the untransformed solid green tile as your manual grass input, and have Automapping always output that solid green tile flipped - looks identical, but is distinct).

stephenjjbrown commented 1 year ago

Just to add a little more context—this is actually how LDtk works by default. You don't set the seed value explicitly, it's chosen for you. But you can click a "randomize" button to automatically pick a new seed and re-shuffle everything.

I know the way it handles tile generation is a little different in general, but there's certainly at least a couple different ways that random values can be made to be deterministic for a given x and y coordinate. A table could be pre-generated with random values for each coordinate, then when a random number is needed to make a decision for that tile, just index the table. Alternatively, a seed could be hashed together with the tile coordinates (maybe the layer ID too), then the resulting hash could be used as the real seed to generate a random value for that tile. Evaluation order wouldn't necessarily matter in either of these solutions.

I can probably use the tile flipping solution as workaround for now. I've also considered creating a layer with random noise and using that as another input. But to me it just seems intuitive that automapping would always yield the same output for a given input. And I think map designers might appreciate having the output feel more predictable while they're actively iterating and working on it, rather than feeling like it's a moving target unless they come up with a custom solution.

eishiya commented 1 year ago

Automapping's "tool" for deterministic output is to not randomise the output in the first place, or to randomise it within a narrow range xP Between the modulo options and specialised inputs (such as the flipped tile workaround, noise/guide layers, and any other context), I think non-random variation is already pretty easy to achieve, and in a manner much more controllable than seeding would be.

Another technique you may want to consider is to output to a separate output layer, and to only output tiles to empty cells (i.e. have an input_outputLayer checking for Empty). So, automapping a map once would fill that output layer, and Automapping again would not change anything. If you want to reroll some tiles, you'd erase the parts you want to change from the output later and Automap again.

All that said, looking at your example GIF, I'm a little confused as to how you expect seeding to work, and maybe I just don't understand what you're trying to do. Are you aiming to seed the output of a rule (in which case, the rocks would never change at all), or whether a rule runs at all (in which case, the positions in which rules run would be locked, but the output would be random each time)? Seeding the rule probability does sound pretty interesting, and it's not something that can be conveniently achieved by existing mechanisms.

stephenjjbrown commented 1 year ago

From what I can tell, right now Tiled uses a non-deterministic random number generator std::random_device in randompicker.h. I'm suggesting it use a deterministic one (like std::srand/std:rand or something similar), or at least allow that as an option.

The example gif was really just to show that the output is different every time I hit Cmd+M. In my current setup, I have one layer with different terrain prototype tiles, and one layer with all of the output tiles. I use the Probability property to output a variety of rock tiles for the rock terrain and a variety of grass tiles for the grass terrain, etc. I make changes on the prototype layer and never really touch the output layer. I could probably use terrain sets for some of these, but that's sort of beside the point.

Screenshot 2023-09-06 at 3 24 24 PM Screenshot 2023-09-06 at 3 24 34 PM

I just think it would be a much nicer user experience if automapping could produce the same random distribution every time it was run (so long as the starting seed is the same). I'm proposing it as an option through a custom property like RandomSeed on the output layer.

Sure, there are other ways of achieving this if I really want to spend the time on it. I could write a script to generate several noise layers or, heck, I could just place the tiles by hand at this point. It just seems like a small change that improves the experience, makes it behave more similarly to other level editors, and probably wouldn't break existing maps or functionality.

eishiya commented 1 year ago

Ah, I see! I misinterpreted your request, I thought the GIF was an example of what you were trying to do, not a demonstration of the existing undesirable behaviour. So, I was describing approaches for the wrong goal xP However, in this case, I'm even more confused: usually, the goal of automapping again is because the results are unsatisfactory. But with this seeding, rerolling would only change the tiles that are output by unseeded rules. If you're not satisfied with the results of the seeded rolls, you'd not be able to reroll them, you'd have to go to the rules and change the seeds, which sounds incredibly tedious. And the agreeable seeds would be different for each map. Would it not be more convenient to apply automapping only to a selected area, randomising only the parts you're unhappy with? Automap While Drawing would also let you change only the area you're modifying without rerolling the entire map. If you want a more "substantial" lock, another option is to mark the cells you want to change (or the reverse, the tiles you want to keep) with a guide layer.