331 lines
11 KiB
GDScript

@tool
const Util = preload("util/util.gd")
const TileUtil = preload("util/tile-util.gd")
const FieldUtil = preload("util/field-util.gd")
const PostImport = preload("post-import.gd")
enum AtlasTextureType {CompressedTexture2D, CanvasTexture}
static func build_tilesets(
definitions: Dictionary,
base_dir: String,
tileset_overrides: Dictionary
) -> Array:
Util.timer_start(Util.DebugTime.TILES)
var tilesets := {}
var tileset_sources := {}
# Reduce Layer Defs to find all unique layer grid sizes and create TileSets for each.
var layer_def_uids: Array = definitions.layers.keys()
tilesets = layer_def_uids.reduce(
func(accum: Dictionary, current: float):
var layer_def = definitions.layers[current]
var grid_size: int = layer_def.gridSize
if not accum.has(grid_size):
accum[grid_size] = get_tileset(grid_size, base_dir)
# Create TileSetSource for IntGrids
if (Util.options.integer_grid_tilesets):
if layer_def.type == "IntGrid" and layer_def.intGridValues.size() > 0:
var intgrid_uid = layer_def.uid
var intgrid_source = create_intgrid_source(layer_def)
tileset_sources[intgrid_uid] = intgrid_source
Util.add_tileset_reference(intgrid_uid, intgrid_source)
return accum
, tilesets)
# Create TileSetSources for each Tileset Def
var tileset_def_uids = definitions.tilesets.keys()
for uid in tileset_def_uids:
var tileset_def: Dictionary = definitions.tilesets[uid]
var source: TileSetSource = create_new_tileset_source(tileset_def, base_dir)
tileset_sources[uid] = source
Util.add_tileset_reference(tileset_def.uid, source)
# Add TileSetSources to TileSets
# NOTE: We also add Sources to mismatched TileSet sizes (if a layer uses that TilesetDef as an override)
for id in tilesets.keys():
var tileset: TileSet = tilesets[id]
var size: int = tileset.tile_size.x
for uid in tileset_sources.keys():
var source: TileSetAtlasSource = tileset_sources[uid]
if source == null: continue
var source_size: int = source.texture_region_size.x
# Check if override exists.
var has_override: bool = false
if (tileset_overrides.has(size)):
if (tileset_overrides[size].has(int(uid))):
has_override = true
# Include Source if size matches grid (or is an override found for this grid size)
if size == source_size or has_override:
if tileset.has_source(uid):
source = tileset.get_source(uid)
else:
source = source.duplicate()
tileset.add_source(source, uid)
if (Util.options.tileset_custom_data):
if definitions.tilesets.has(uid):
var tileset_def: Dictionary = definitions.tilesets[uid]
add_tileset_custom_data(tileset_def, tileset, source, tileset_def.__cWid)
# Post-Import
if (Util.options.tileset_post_import):
tilesets = PostImport.run_tileset_post_import(tilesets, Util.options.tileset_post_import)
# Store tilesets in Util
Util.tilesets = tilesets
Util.timer_finish("Tilesets Created", 1)
# Save tilesets
Util.timer_start(Util.DebugTime.SAVE)
var files = save_tilesets(tilesets, base_dir)
Util.timer_finish("Tilesets Saved", 1)
for key in tilesets.keys():
# reload tileset (improves performance)
var tileset = tilesets[key]
if tileset == null: continue
if not files.has(key): continue
tilesets[key] = ResourceLoader.load(files[key])
return files.values()
static func get_tileset(tile_size: int,base_dir: String) -> TileSet:
var tileset_name := "tileset_%spx" % [str(tile_size)]
var path := base_dir + "tilesets/" + tileset_name + ".res"
if not (Util.options.force_tileset_reimport):
if ResourceLoader.exists(path):
var tileset = ResourceLoader.load(path)
if tileset is TileSet:
return tileset
# Create new TileSet
var tileset := TileSet.new()
tileset.resource_name = tileset_name
tileset.tile_size = Vector2i(tile_size, tile_size)
if (Util.options.verbose_output):
Util.print("item_info", "Created new TileSet: \"%s\"" % [tileset_name], 1)
return tileset
# Create an AtlasSource using tileset definition
static func create_new_tileset_source(definition: Dictionary, base_dir: String) -> TileSetSource:
# No source texture defined
if definition.relPath == null:
Util.print("item_fail", "No texture defined for tileset '%s'" % [definition.identifier])
push_error("Tileset Definition '%s' has no source texture. Please fix this in your LDtk project file." % [definition.identifier])
return null
# Check if relPath is an absolute directory
var filepath: String
if definition.relPath.contains(":"):
push_warning("Absolute path detected for texture resource '%s'. This is not recommended. Please include this file in the Godot project." % [definition.identifier])
filepath = definition.relPath
else:
filepath = base_dir + definition.relPath
var texture := load(filepath)
# Cannot load texture
if texture == null:
push_error("Cannot access source texture: %s. Please include this file in the Godot project." % [filepath])
return null
var image: Image = texture.get_image()
# Convert texture from CompressedTexture2D to CanvasTexture
if (Util.options.atlas_texture_type == AtlasTextureType.CanvasTexture):
var canvas_texture = CanvasTexture.new()
canvas_texture.diffuse_texture = texture
texture = canvas_texture
var tile_size: int = definition.gridSize
var margin: int = definition.padding
var separation: int = definition.spacing
var grid_w: int = definition.__cWid
var grid_h: int = definition.__cHei
var source := TileSetAtlasSource.new()
# Apply TileSet properties
if source.texture == null or source.texture.get_class() != texture.get_class():
source.texture = texture
source.resource_name = definition.identifier
source.margins = Vector2i(margin, margin)
source.separation = Vector2i(separation, separation)
source.texture_region_size = Vector2(tile_size, tile_size)
source.use_texture_padding = false
# Create/remove tiles in non-empty/empty cells.
for y in range(0, grid_h):
for x in range(0, grid_w):
var coords := Vector2i(x,y)
var tile_region := TileUtil.get_tile_region(coords, tile_size, margin, separation, grid_w)
var tile_image := image.get_region(tile_region)
if not tile_image.is_invisible():
if source.get_tile_at_coords(coords) == Vector2i(-1,-1):
source.create_tile(coords)
elif not source.get_tile_at_coords(coords) == Vector2i(-1,-1):
# TODO: Make this an import flag
source.remove_tile(coords)
# Add definition UID to references
Util.add_tileset_reference(definition.uid, source)
return source
static func add_tileset_custom_data(
definition: Dictionary,
tileset: TileSet,
source: TileSetAtlasSource,
grid_w: int
) -> void:
if not definition.has("enumTags"):
return
var customData: Array = definition.customData
var custom_name: String = "LDTK Custom"
clear_custom_data(tileset, custom_name)
if not customData.is_empty():
ensure_custom_layer(tileset, custom_name)
for entry in customData:
var coords := TileUtil.tileid_to_grid(entry.tileId, grid_w)
var tile_data: TileData = source.get_tile_data(coords, 0)
if not tile_data == null:
tile_data.set_custom_data(custom_name, entry.data)
var custom_enum_name: String = "LDTK Custom Enum"
clear_custom_data(tileset, custom_enum_name)
var enumTags: Array = definition.enumTags
if not enumTags.is_empty():
ensure_custom_layer(tileset, custom_enum_name, TYPE_ARRAY)
for enumTag in enumTags:
for tileId in enumTag.tileIds:
var coords := TileUtil.tileid_to_grid(tileId, grid_w)
var tile_data: TileData = source.get_tile_data(coords, 0)
if not tile_data == null:
# Add to already existing tags
var tile_tags: Array = tile_data.get_custom_data(custom_enum_name)
tile_tags.append(enumTag.enumValueId)
tile_data.set_custom_data(custom_enum_name, tile_tags)
# Ensure custom data layer exists by name
static func ensure_custom_layer(
tileset: TileSet,
layer_name: String,
layer_type: int = TYPE_STRING
) -> void:
if tileset.get_custom_data_layer_by_name(layer_name) != -1:
return
var index_to_add = tileset.get_custom_data_layers_count()
tileset.add_custom_data_layer(index_to_add)
tileset.set_custom_data_layer_name(index_to_add, layer_name)
tileset.set_custom_data_layer_type(index_to_add, layer_type)
# Clear custom data by layer name
static func clear_custom_data(tileset: TileSet, layer_name: String) -> void:
var layer = tileset.get_custom_data_layer_by_name(layer_name)
if layer == -1:
return
tileset.remove_custom_data_layer(layer)
# Create an AtlasSource from IntGrid data
static func create_intgrid_source(definition: Dictionary) -> TileSetAtlasSource:
var values: Array = definition.intGridValues
var grid_size: int = definition.gridSize
# Create texture from IntGrid values
var width := grid_size * values.size()
var height := grid_size
var image := Image.create(width, height, false, Image.FORMAT_RGB8)
for index in range(0, values.size()):
var value: Dictionary = values[index]
var rect := Rect2i(index * grid_size, 0, grid_size, grid_size)
var color := Color.from_string(value.color, Color.MAGENTA)
image.fill_rect(rect, color)
var texture = ImageTexture.create_from_image(image)
var source := TileSetAtlasSource.new()
source.resource_name = definition.identifier + "_Tiles"
source.texture = texture
source.texture_region_size = Vector2i(grid_size, grid_size)
# Create tiles
for index in range(0, values.size()):
var coords := Vector2i(index, 0)
if not source.has_tile(coords):
source.create_tile(coords)
return source
# Save TileSets as Resources
static func save_tilesets(tilesets: Dictionary, base_dir: String) -> Dictionary:
var save_path = base_dir + "tilesets/"
var gen_files := {}
var directory = DirAccess.open(base_dir)
if not directory.dir_exists(save_path):
directory.make_dir_recursive(save_path)
var tileset_names = tilesets.values().map(func(elem): return elem.resource_name)
Util.print("item_save", "Saving Tilesets: [color=#fe8019]%s[/color]" % [tileset_names], 1)
for key in tilesets.keys():
var tileset: TileSet = tilesets.get(key)
if tileset.get_source_count() == 0:
continue
var file_name = tileset.resource_name
var file_path = "%s%s.%s" % [save_path, file_name, "res"]
var err = ResourceSaver.save(tileset, file_path)
if err == OK:
gen_files[key] = file_path
return gen_files
static func get_entity_def_tiles(definitions: Dictionary, tilesets: Dictionary) -> Dictionary:
# TODO: Loop through EntityDefs, and turn 'Tile' into an Image.
for def in definitions.entities:
var entity: Dictionary = definitions.entities[def]
if (entity.tile == null):
continue
# Find associated TileSet
var texture = FieldUtil.__parse_tile(entity.tile)
entity.tile = texture
return definitions
# Collect all layer tileset overrides. Later we'll ensure these sources are included in TileSet resources.
static func get_tileset_overrides(world_data: Dictionary) -> Dictionary:
var overrides := {}
for level in world_data.levels:
for layer in level.layerInstances:
if layer.overrideTilesetUid == null:
continue
var gridSize: int = layer.__gridSize
var overrideUid: int = layer.overrideTilesetUid
if overrideUid != null:
if not overrides.has(gridSize):
overrides[gridSize] = []
var gridsize_overrides: Array = overrides[gridSize]
if not gridsize_overrides.has(overrideUid):
gridsize_overrides.append(overrideUid)
return overrides