323 lines
10 KiB
GDScript
323 lines
10 KiB
GDScript
## Functions for automatically generating terrains for an atlas.
|
|
class_name TerrainPreset
|
|
|
|
|
|
## Maps a Neighborhood to a Topology.
|
|
const NEIGHBORHOOD_TOPOLOGIES := {
|
|
TerrainDual.Neighborhood.SQUARE: Topology.SQUARE,
|
|
TerrainDual.Neighborhood.ISOMETRIC: Topology.SQUARE,
|
|
TerrainDual.Neighborhood.TRIANGLE_HORIZONTAL: Topology.TRIANGLE,
|
|
TerrainDual.Neighborhood.TRIANGLE_VERTICAL: Topology.TRIANGLE,
|
|
}
|
|
|
|
|
|
## Determines the available Terrain presets for a certain Atlas.
|
|
enum Topology {
|
|
SQUARE,
|
|
TRIANGLE,
|
|
}
|
|
|
|
|
|
## Maps a Neighborhood to a preset of the specified name.
|
|
static func neighborhood_preset(
|
|
neighborhood: TerrainDual.Neighborhood,
|
|
preset_name: String = 'Standard'
|
|
) -> Dictionary:
|
|
var topology: Topology = NEIGHBORHOOD_TOPOLOGIES[neighborhood]
|
|
# TODO: test when the preset doesn't exist
|
|
var available_presets = PRESETS[topology]
|
|
if preset_name not in available_presets:
|
|
return {'size': Vector2i.ONE, 'layers': []}
|
|
var out: Dictionary = available_presets[preset_name].duplicate(true)
|
|
# All Horizontal neighborhoods can be transposed to Vertical
|
|
if neighborhood == TerrainDual.Neighborhood.TRIANGLE_VERTICAL:
|
|
out.size = Util.transpose_vec(out.size)
|
|
out.fg = Util.transpose_vec(out.fg)
|
|
out.bg = Util.transpose_vec(out.bg)
|
|
for seq in out.layers:
|
|
for i in seq.size():
|
|
seq[i] = Util.transpose_vec(seq[i])
|
|
return out
|
|
|
|
|
|
## Contains all of the builtin Terrain presets for each topology
|
|
const PRESETS := {
|
|
Topology.SQUARE: {
|
|
'Standard': {
|
|
'size': Vector2i(4, 4),
|
|
'bg': Vector2i(0, 3),
|
|
'fg': Vector2i(2, 1),
|
|
'layers': [
|
|
[ # []
|
|
Vector2i(0, 3),
|
|
Vector2i(3, 3),
|
|
Vector2i(0, 2),
|
|
Vector2i(1, 2),
|
|
Vector2i(0, 0),
|
|
Vector2i(3, 2),
|
|
Vector2i(2, 3),
|
|
Vector2i(3, 1),
|
|
Vector2i(1, 3),
|
|
Vector2i(0, 1),
|
|
Vector2i(1, 0),
|
|
Vector2i(2, 2),
|
|
Vector2i(3, 0),
|
|
Vector2i(2, 0),
|
|
Vector2i(1, 1),
|
|
Vector2i(2, 1),
|
|
],
|
|
],
|
|
},
|
|
},
|
|
Topology.TRIANGLE: {
|
|
'Standard': {
|
|
'size': Vector2i(4, 4),
|
|
'bg': Vector2i(0, 0),
|
|
'fg': Vector2i(0, 2),
|
|
'layers': [
|
|
[ # v
|
|
Vector2i(0, 1),
|
|
Vector2i(2, 3),
|
|
Vector2i(3, 1),
|
|
Vector2i(1, 3),
|
|
Vector2i(1, 1),
|
|
Vector2i(3, 3),
|
|
Vector2i(2, 1),
|
|
Vector2i(0, 3),
|
|
],
|
|
[ # ^
|
|
Vector2i(0, 0),
|
|
Vector2i(2, 2),
|
|
Vector2i(3, 0),
|
|
Vector2i(1, 2),
|
|
Vector2i(1, 0),
|
|
Vector2i(3, 2),
|
|
Vector2i(2, 0),
|
|
Vector2i(0, 2),
|
|
],
|
|
]
|
|
},
|
|
# Old template.
|
|
# a bit inconvenient to use for Brick (Half-Off Square) tilesets.
|
|
'Winged': {
|
|
'size': Vector2i(4, 4),
|
|
'bg': Vector2i(0, 0),
|
|
'fg': Vector2i(0, 2),
|
|
'layers': [
|
|
[ # v
|
|
Vector2i(0, 1),
|
|
Vector2i(2, 1),
|
|
Vector2i(3, 1),
|
|
Vector2i(1, 3),
|
|
Vector2i(1, 1),
|
|
Vector2i(3, 3),
|
|
Vector2i(2, 3),
|
|
Vector2i(0, 3),
|
|
],
|
|
[ # ^
|
|
Vector2i(0, 0),
|
|
Vector2i(2, 0),
|
|
Vector2i(3, 0),
|
|
Vector2i(1, 2),
|
|
Vector2i(1, 0),
|
|
Vector2i(3, 2),
|
|
Vector2i(2, 2),
|
|
Vector2i(0, 2),
|
|
],
|
|
]
|
|
},
|
|
# Old template.
|
|
# The gaps between triangles made them harder to align.
|
|
'Alternating': {
|
|
'size': Vector2i(4, 4),
|
|
'bg': Vector2i(0, 0),
|
|
'fg': Vector2i(0, 2),
|
|
'layers': [
|
|
[ # v
|
|
Vector2i(0, 0),
|
|
Vector2i(2, 0),
|
|
Vector2i(3, 1),
|
|
Vector2i(1, 3),
|
|
Vector2i(1, 1),
|
|
Vector2i(3, 3),
|
|
Vector2i(2, 2),
|
|
Vector2i(0, 2),
|
|
],
|
|
[ # ^
|
|
Vector2i(0, 1),
|
|
Vector2i(2, 1),
|
|
Vector2i(3, 0),
|
|
Vector2i(1, 2),
|
|
Vector2i(1, 0),
|
|
Vector2i(3, 2),
|
|
Vector2i(2, 3),
|
|
Vector2i(0, 3),
|
|
],
|
|
],
|
|
},
|
|
},
|
|
}
|
|
|
|
##[br] Would you like to automatically create tiles in the atlas?
|
|
##[br]
|
|
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
|
##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo.
|
|
static func write_default_preset(urm: EditorUndoRedoManager, tile_set: TileSet, atlas: TileSetAtlasSource) -> void:
|
|
#print('writing default')
|
|
var neighborhood := TerrainDual.tileset_neighborhood(tile_set)
|
|
var terrain := new_terrain(
|
|
urm,
|
|
tile_set,
|
|
atlas.texture.resource_path.get_file()
|
|
)
|
|
write_preset(
|
|
urm,
|
|
atlas,
|
|
neighborhood,
|
|
terrain,
|
|
)
|
|
|
|
|
|
##[br] Creates terrain set 0 (the primary terrain set) and terrain 0 (the 'any' terrain)
|
|
##[br]
|
|
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
|
static func init_terrains(urm: EditorUndoRedoManager, tile_set: TileSet) -> void:
|
|
urm.add_do_method(TerrainPreset, "_do_init_terrains", tile_set)
|
|
urm.add_undo_method(TerrainPreset, "_undo_init_terrains", tile_set)
|
|
|
|
static func _do_init_terrains(tile_set: TileSet) -> void:
|
|
tile_set.add_terrain_set()
|
|
tile_set.set_terrain_set_mode(0, TileSet.TERRAIN_MODE_MATCH_CORNERS)
|
|
tile_set.add_terrain(0)
|
|
tile_set.set_terrain_name(0, 0, "<any>")
|
|
tile_set.set_terrain_color(0, 0, Color.VIOLET)
|
|
|
|
static func _undo_init_terrains(tile_set: TileSet) -> void:
|
|
tile_set.remove_terrain_set(0)
|
|
|
|
|
|
##[br] Adds a new terrain type to terrain set 0 for the sprites to use.
|
|
##[br]
|
|
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
|
static func new_terrain(urm: EditorUndoRedoManager, tile_set: TileSet, terrain_name: String) -> int:
|
|
var terrain: int
|
|
if tile_set.get_terrain_sets_count() == 0:
|
|
init_terrains(urm, tile_set)
|
|
terrain = 1
|
|
else:
|
|
terrain = tile_set.get_terrains_count(0)
|
|
urm.add_do_method(TerrainPreset, "_do_new_terrain", tile_set, terrain_name)
|
|
urm.add_undo_method(TerrainPreset, "_undo_new_terrain", tile_set)
|
|
return terrain
|
|
|
|
static func _do_new_terrain(tile_set: TileSet, terrain_name: String) -> void:
|
|
tile_set.add_terrain(0)
|
|
var terrain := tile_set.get_terrains_count(0) - 1
|
|
tile_set.set_terrain_name(0, terrain, "FG -%s" % terrain_name)
|
|
|
|
static func _undo_new_terrain(tile_set: TileSet) -> void:
|
|
var terrain := tile_set.get_terrains_count(0) - 1
|
|
tile_set.remove_terrain(0, terrain)
|
|
|
|
|
|
##[br] Takes a preset and writes it onto the given atlas, replacing the previous configuration.
|
|
##[br] ARGUMENTS:
|
|
##[br] - atlas: the atlas source to apply the preset to.
|
|
##[br] - filters: the neighborhood filter
|
|
##[br]
|
|
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
|
##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo.
|
|
static func write_preset(
|
|
urm: EditorUndoRedoManager,
|
|
atlas: TileSetAtlasSource,
|
|
neighborhood: TerrainDual.Neighborhood,
|
|
terrain_foreground: int,
|
|
terrain_background: int = 0,
|
|
preset: Dictionary = neighborhood_preset(neighborhood),
|
|
) -> void:
|
|
clear_and_divide_atlas(urm, atlas, preset.size)
|
|
urm.add_do_method(TerrainPreset, '_do_write_preset', atlas, neighborhood, terrain_foreground, terrain_background, preset)
|
|
|
|
static func _do_write_preset(
|
|
atlas: TileSetAtlasSource,
|
|
neighborhood: TerrainDual.Neighborhood,
|
|
terrain_foreground: int,
|
|
terrain_background: int,
|
|
preset: Dictionary,
|
|
) -> void:
|
|
var layers: Array = TerrainDual.NEIGHBORHOOD_LAYERS[neighborhood]
|
|
# Set peering bits
|
|
var sequences: Array = preset.layers
|
|
for j in layers.size():
|
|
var terrain_neighborhood = layers[j].terrain_neighborhood
|
|
var sequence: Array = sequences[j]
|
|
for i in sequence.size():
|
|
var tile: Vector2i = sequence[i]
|
|
atlas.create_tile(tile)
|
|
var data := atlas.get_tile_data(tile, 0)
|
|
data.terrain_set = 0
|
|
for neighbor in terrain_neighborhood:
|
|
data.set_terrain_peering_bit(
|
|
neighbor,
|
|
[terrain_background, terrain_foreground][i & 1]
|
|
)
|
|
i >>= 1
|
|
# Set terrains
|
|
atlas.get_tile_data(preset.bg, 0).terrain = terrain_background
|
|
atlas.get_tile_data(preset.fg, 0).terrain = terrain_foreground
|
|
|
|
##[br] Unregisters all the tiles in an atlas and changes the size of the individual sprites.
|
|
##[br]
|
|
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
|
##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo.
|
|
static func clear_and_resize_atlas(urm: EditorUndoRedoManager, atlas: TileSetAtlasSource, size: Vector2) -> void:
|
|
var atlas_data := _save_atlas_data(atlas)
|
|
urm.add_do_method(TerrainPreset, '_do_clear_and_resize_atlas', atlas, size)
|
|
urm.add_undo_method(TerrainPreset, '_undo_clear_and_resize_atlas', atlas, atlas_data)
|
|
|
|
static func _do_clear_and_resize_atlas(atlas: TileSetAtlasSource, size: Vector2) -> void:
|
|
# Clear all tiles
|
|
atlas.texture_region_size = atlas.texture.get_size() + Vector2.ONE
|
|
atlas.clear_tiles_outside_texture()
|
|
# Resize the tiles
|
|
atlas.texture_region_size = size
|
|
|
|
static func _undo_clear_and_resize_atlas(atlas: TileSetAtlasSource, atlas_data: Dictionary) -> void:
|
|
_load_atlas_data(atlas, atlas_data)
|
|
|
|
## NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else.
|
|
static func _save_atlas_data(atlas: TileSetAtlasSource) -> Dictionary:
|
|
var size_img := atlas.texture.get_size()
|
|
var size_sprite := atlas.texture_region_size
|
|
var size_dims := Vector2i(size_img) / size_sprite
|
|
var tiles := []
|
|
for y in size_dims.y:
|
|
var row := []
|
|
for x in size_dims.x:
|
|
var tile := Vector2i(x, y)
|
|
var exists := atlas.has_tile(tile)
|
|
row.push_back(exists)
|
|
tiles.push_back(row)
|
|
return {
|
|
'size_sprite': size_sprite,
|
|
'size_dims': size_dims,
|
|
'tiles': tiles,
|
|
}
|
|
|
|
static func _load_atlas_data(atlas: TileSetAtlasSource, atlas_data: Dictionary) -> void:
|
|
_do_clear_and_resize_atlas(atlas, atlas_data.size_sprite)
|
|
for y in atlas_data.size_dims.y:
|
|
for x in atlas_data.size_dims.x:
|
|
if atlas_data.tiles[y][x]:
|
|
var tile := Vector2i(x, y)
|
|
atlas.create_tile(tile)
|
|
|
|
|
|
##[br] Unregisters all the tiles in an atlas and changes the size of the
|
|
## individual sprites to accomodate a divisions.x by divisions.y grid of sprites.
|
|
##[br]
|
|
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
|
##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo.
|
|
static func clear_and_divide_atlas(urm: EditorUndoRedoManager, atlas: TileSetAtlasSource, divisions: Vector2i) -> void:
|
|
clear_and_resize_atlas(urm, atlas, atlas.texture.get_size() / Vector2(divisions))
|