Portponky / better-terrain

Terrain plugin for Godot 4
The Unlicense
433 stars 21 forks source link

Ability to cleanup tiles that do not match #62

Closed Supamiu closed 6 months ago

Supamiu commented 6 months ago

In a map generation project (using cellular automata and some ruling) I'm trying to generate a map using 13 possible tiles instead of a full 64 set.

The tileset I'm using for this is free to use: template7final and I configured it with 3x3 matching and only a subset of it in the terrain, as follows: image

When generating, I'm using BetterTerrain.set_cells then BetterTerrain.update_terrain_cells in order to apply the generated terrain to my tilemap.

First of all, it really works way better than the builtin system, which was struggling to fill some basic square shapes.

The result, however, contains some unmatched cells (of course, I have 13 possibilities but the terrain has a lot of different connections) and it made me wonder if it'd be possible for thie addon to have a method that cleans up tiles that do not perfectly match.

Taking this example: image it'd remove the "lonely" tile that's on top of the uniform "blob" below it.

Cleaning things up before sending it to the terrain is quite hard because it means having to implement connection checks which is already done in your addon, and I was thinking maybe this could be done by the addon itself.

Portponky commented 6 months ago

Hi,

I'm probably not going to implement that in the plugin, but I'll provide code for it in case you want to use it. The main reason I don't want to implement it is that it'll introduce branches into the main matching algorithm, which will slow the code down for everyone. It's also pretty divergent to the plugin's main design choices, where in it always respects the user's choice of terrain tiles.

Anyhow, the main matching occurs in the function _update_tile_tiles (for matching tiles and decorations) and _update_tile_vertices for vertex matching. In _update_tile_tiles, it checks every possible match and ranks the best ones. Initially it has no match with a very negative score.

First, change the starting score to 0 and the starting set of 'best' tiles to be an empty tile. Next, adjust the scoring so that a bad match gives an incredibly negative score. Here, I've set it to -100.

    var best_score := 0
    var best := [[-1, Vector2.ZERO, -1, {}, 1.0]]
    for t in cache[type]:
        var score = 0
        for peering in t[3]:
            score += 3 if t[3][peering].has(types[tm.get_neighbor_cell(coord, peering)]) else -100

So if you paste that in starting at line 295 of BetterTerrain.gd (you should see the corresponding code), it should return an empty tile to place for any tile which fails to match against any neighbor.

Hope this helps!

Supamiu commented 6 months ago

Looks like it almost does the job, thanks a lot !

It still makes this kind of patterns possible because it's kind of valid but not really, but that's a huge step forward. image

Portponky commented 6 months ago

In general, that's why this is a problematic approach. Changing the tiles/terrain types means that neighboring tiles might also change, and this means you can have cascading changes that are complex to resolve. There isn't a good way to do this quickly (for example, in a single pass).

I'd suggest adding the two diagonal corner pieces (those missing from the examples you posted) and then making the generator only place blocks in 2x2s. That way you don't need to perform any tidy up and you won't need additional tiles.

Supamiu commented 6 months ago

Yeah, I'm already applying some changes ot the generated map to handle this kind of things, but having the terrain system handle the most obvious ones is helping a lot, so that fits my issue for sure, thanks again !