331 lines
11 KiB
GDScript
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
|