diff --git a/docs/src/workstreams.md b/docs/src/workstreams.md index 13fc80b..f25a265 100644 --- a/docs/src/workstreams.md +++ b/docs/src/workstreams.md @@ -14,12 +14,13 @@ This section is a high level overview of what is being or can be worked on. ## Vehicle Physics Model +- [ ] Kinematics low-speed model. - [ ] Dynamics (force based) vehicle model. - [ ] Implement rear-wheel drive. - [ ] Implement four-wheel drive. - [ ] Implement no-grip state (wheels have lost static grip on the road). - [ ] Implement handbrake. -- [ ] Kinematics low-speed model. + - [ ] Weight shifting - [ ] Export parameters to be configurable in Godot editor. ## Audio @@ -38,3 +39,7 @@ This section is a high level overview of what is being or can be worked on. ## Multiplayer - [ ] ? + +## CI + +- [ ] Build and host book diff --git a/godot/addons/ldtk-importer/README.md b/godot/addons/ldtk-importer/README.md new file mode 100644 index 0000000..238e2b2 --- /dev/null +++ b/godot/addons/ldtk-importer/README.md @@ -0,0 +1,5 @@ +# ldtk-importer / godot-ldtk + +[LDtk](https://ldtk.io/) importer for [Godot 4](https://godotengine.org/) + +[Github](https://github.com/heygleeson/godot-ldtk) diff --git a/godot/addons/ldtk-importer/ldtk-importer.gd b/godot/addons/ldtk-importer/ldtk-importer.gd new file mode 100644 index 0000000..9682c43 --- /dev/null +++ b/godot/addons/ldtk-importer/ldtk-importer.gd @@ -0,0 +1,311 @@ +@tool +extends EditorImportPlugin + +const LDTK_LATEST_VERSION = "1.5.3" + +enum Presets {DEFAULT} + +const Util = preload("src/util/util.gd") +const World = preload("src/world.gd") +const Level = preload("src/level.gd") +const Tileset = preload("src/tileset.gd") +const DefinitionUtil = preload("src/util/definition_util.gd") + +#region EditorImportPlugin Overrides + +#region Simple +func _get_importer_name(): + return "ldtk.import" + +func _get_visible_name(): + return "LDTK Scene" + +func _get_priority(): + return 1.0 + +func _get_import_order(): + return IMPORT_ORDER_SCENE + +func _get_resource_type(): + return "PackedScene" + +func _get_recognized_extensions(): + return ["ldtk"] + +func _get_save_extension(): + return "scn" + +func _get_preset_count(): + return Presets.size() + +func _get_preset_name(index): + match index: + Presets.DEFAULT: + return "Default" + _: + return "Unknown" + +func _get_option_visibility(path, option_name, options): + match option_name: + _: + return true + return true + +func _can_import_threaded() -> bool: + return false + +#endregion + +func _get_import_options(path, index): + return [ + # --- World --- # + {"name": "World", "default_value":"", "usage": PROPERTY_USAGE_GROUP}, + { + # Group LDTKLevels in 'LDTKWorldLayer' nodes if using LDTK's WorldDepth. + "name": "group_world_layers", + "default_value": false, + }, + # --- Levels --- # + {"name": "Level", "default_value":"", "usage": PROPERTY_USAGE_GROUP}, + { + # Save LDTKLevels as PackedScenes. + "name": "pack_levels", + "default_value": true, + }, + # --- Layers --- # + {"name": "Layer", "default_value":"", "usage": PROPERTY_USAGE_GROUP}, + { + # Save LDTKLevels as PackedScenes. + "name": "layers_always_visible", + "default_value": false, + }, + # --- Tileset --- # + {"name": "Tileset", "default_value":"", "usage": PROPERTY_USAGE_GROUP}, + { + # Add LDTK Custom Data to Tilesets + "name": "tileset_custom_data", + "default_value": false, + }, + { + # Create TileAtlasSources & TileMapLayers for IntGrid Layers + "name": "integer_grid_tilesets", + "default_value": false, + }, + { + # Define default texture type for TilesetAtlasSource (e.g. to apply normal maps to tilesets after import) + "name": "atlas_texture_type", + "default_value": 0, + "property_hint": PROPERTY_HINT_ENUM, + "hint_string": "CompressedTexture2D,CanvasTexture", + }, + # --- Entities --- # + {"name": "Entity", "default_value":"", "usage": PROPERTY_USAGE_GROUP}, + { + # + "name": "resolve_entityrefs", + "default_value": true, + }, + { + # Create LDTKEntityPlaceholder nodes to help debug importing. + "name": "use_entity_placeholders", + "default_value": false, + }, + # --- Post Import --- # + {"name": "Post Import", "default_value":"", "usage": PROPERTY_USAGE_GROUP}, + { + # Define a post-import script to apply on imported Tilesets. + "name": "tileset_post_import", + "default_value": "", + "property_hint": PROPERTY_HINT_FILE, + "hint_string": "*.gd;GDScript" + }, + { + # Define a post-import script to apply on imported Entities. + "name": "entities_post_import", + "default_value": "", + "property_hint": PROPERTY_HINT_FILE, + "hint_string": "*.gd;GDScript" + }, + { + # Define a post-import script to apply on imported Levels. + "name": "level_post_import", + "default_value": "", + "property_hint": PROPERTY_HINT_FILE, + "hint_string": "*.gd;GDScript" + }, + { + # Define a post-import script to apply on imported Worlds. + "name": "world_post_import", + "default_value": "", + "property_hint": PROPERTY_HINT_FILE, + "hint_string": "*.gd;GDScript" + }, + # --- Debug --- # + {"name": "Debug", "default_value":"", "usage": PROPERTY_USAGE_GROUP}, + { + # Force Tilesets to be recreated, resetting modifications (if experiencing import issues) + "name": "force_tileset_reimport", + "default_value": false, + }, + { + # Debug: Enable Verbose Output (used by the importer) + "name": "verbose_output", "default_value": false + } + ] + +func _import( + source_file: String, + save_path: String, + options: Dictionary, + platform_variants: Array[String], + gen_files: Array[String] +) -> Error: + + Util.timer_reset() + Util.timer_start(Util.DebugTime.TOTAL) + Util.print("import_start", source_file) + + # Add options to static var in "Util", accessible from any script. + Util.options = options + + # Parse source_file + var base_dir := source_file.get_base_dir() + "/" + var file_name := source_file.get_file() + var world_name := file_name.split(".")[0] + + Util.timer_start(Util.DebugTime.LOAD) + var world_data := Util.parse_file(source_file) + Util.timer_finish("File parsed") + + # Check version + if Util.check_version(world_data.jsonVersion, LDTK_LATEST_VERSION): + Util.print("item_ok", "LDTK VERSION (%s) OK" % [world_data.jsonVersion]) + else: + return ERR_PARSE_ERROR + + Util.timer_start(Util.DebugTime.GENERAL) + var definitions := DefinitionUtil.build_definitions(world_data) + var tileset_overrides := Tileset.get_tileset_overrides(world_data) + Util.timer_finish("Definitions Created") + + # Build Tilesets and save as Resources + if Util.options.verbose_output: Util.print("block", "Tilesets") + var tileset_paths := Tileset.build_tilesets(definitions, base_dir, tileset_overrides) + gen_files.append_array(tileset_paths) + + # Fetch EntityDef Tile textures + Tileset.get_entity_def_tiles(definitions, Util.tilesets) + + # Detect Multi-Worlds + var external_levels: bool = world_data.externalLevels + var world_iid: String = world_data.iid + + var world: LDTKWorld + if world_data.worldLayout == null: + var world_nodes: Array[LDTKWorld] = [] + var world_instances: Array = world_data.worlds + # Build each world instance + for world_instance in world_instances: + var world_instance_name: String = world_instance.identifier + var world_instance_iid: String = world_instance.iid + var levels := Level.build_levels(world_instance, definitions, base_dir, external_levels) + var world_node := World.create_world(world_instance_name, world_instance_iid, levels, base_dir) + world_nodes.append(world_node) + + world = World.create_multi_world(world_name, world_iid, world_nodes) + else: + if Util.options.verbose_output: Util.print("block", "Levels") + var levels := Level.build_levels(world_data, definitions, base_dir, external_levels) + + # Save Levels (after Level Post-Import) + if (Util.options.pack_levels): + var levels_path := base_dir + 'levels/' + var directory = DirAccess.open(base_dir) + if not directory.dir_exists(levels_path): + directory.make_dir(levels_path) + + # Resolve Refs + Cleanup Resolvers. We don't want to save 'NodePathResolver' in the Level scene. + #if (Util.options.verbose_output): Util.print("block", "References") + if (Util.options.verbose_output): Util.print("block", "Save Levels") + Util.handle_references() + var packed_levels = save_levels(levels, levels_path, gen_files) + + if (Util.options.verbose_output): Util.print("block", "Save World") + world = World.create_world(world_name, world_iid, packed_levels, base_dir) + else: + if (Util.options.verbose_output): Util.print("block", "Save World") + world = World.create_world(world_name, world_iid, levels, base_dir) + + Util.handle_references() + + # Save World as PackedScene + Util.timer_start(Util.DebugTime.SAVE) + var err = save_world(world, save_path, gen_files) + Util.timer_finish("World Saved", 1) + + if Util.options.verbose_output: Util.print("block", "Results") + + Util.timer_finish("Completed.") + + var total_time: int = Util.DebugTime.get_total_time() + var result_message: String = Util.DebugTime.get_result() + + if Util.options.verbose_output: Util.print("item_info", result_message) + Util.print("import_finish", str(total_time)) + + return err + +#endregion + +func save_world( + world: LDTKWorld, + save_path: String, + gen_files: Array[String] +) -> Error: + var packed_world = PackedScene.new() + packed_world.pack(world) + + Util.print("item_save", "Saving World [color=#fe8019][i]'%s'[/i][/color]" % [save_path], 1) + + var world_path = "%s.%s" % [save_path, _get_save_extension()] + var err = ResourceSaver.save(packed_world, world_path) + if err == OK: + gen_files.append(world_path) + return err + +func save_levels( + levels: Array[LDTKLevel], + save_path: String, + gen_files: Array[String] +) -> Array[LDTKLevel]: + Util.timer_start(Util.DebugTime.SAVE) + var packed_levels: Array[LDTKLevel] = [] + + + var level_names := levels.map(func(elem): return elem.name) + Util.print("item_save", "Saving Levels: [color=#fe8019]%s[/color]" % [level_names], 1) + + for level in levels: + for child in level.get_children(): + Util.recursive_set_owner(child, level) + var level_path = save_level(level, save_path, gen_files) + var packed_level = load(level_path).instantiate() + packed_levels.append(packed_level) + + Util.timer_finish("%s Levels Saved" % [levels.size()], 1) + return packed_levels + +func save_level( + level: LDTKLevel, + save_path: String, + gen_files: Array[String] +) -> String: + var packed_level = PackedScene.new() + packed_level.pack(level) + var level_path = "%s%s.%s" % [save_path, level.name, _get_save_extension()] + + var err = ResourceSaver.save(packed_level, level_path) + if err == OK: + gen_files.append(level_path) + + return level_path diff --git a/godot/addons/ldtk-importer/ldtk-importer.gd.uid b/godot/addons/ldtk-importer/ldtk-importer.gd.uid new file mode 100644 index 0000000..8ae16f1 --- /dev/null +++ b/godot/addons/ldtk-importer/ldtk-importer.gd.uid @@ -0,0 +1 @@ +uid://lnqlil4dt4ca diff --git a/godot/addons/ldtk-importer/plugin.cfg b/godot/addons/ldtk-importer/plugin.cfg new file mode 100644 index 0000000..53c609d --- /dev/null +++ b/godot/addons/ldtk-importer/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="LDTK" +description="LDtk Import plugin for Godot 4!" +author="Andy Gleeson (gleeson.dev)" +version="2.0.1" +script="plugin.gd" diff --git a/godot/addons/ldtk-importer/plugin.gd b/godot/addons/ldtk-importer/plugin.gd new file mode 100644 index 0000000..3681af8 --- /dev/null +++ b/godot/addons/ldtk-importer/plugin.gd @@ -0,0 +1,19 @@ +@tool +extends EditorPlugin + +var ldtk_plugin +var config = ConfigFile.new() + +func _enter_tree() -> void: + ldtk_plugin = preload("ldtk-importer.gd").new() + add_import_plugin(ldtk_plugin) + + var config = ConfigFile.new() + var err = config.load("res://addons/ldtk-importer/plugin.cfg") + var version = config.get_value("plugin", "version", "0.0") + + print_rich("[color=#ffcc00]█ Godot-LDtk-Importer █[/color] %s | [url=https://gleeson.dev]@gleeson.dev[/url] | [url=https://github.com/heygleeson/godot-ldtk-importer]View on Github[/url]" % [version]) + +func _exit_tree() -> void: + remove_import_plugin(ldtk_plugin) + ldtk_plugin = null diff --git a/godot/addons/ldtk-importer/plugin.gd.uid b/godot/addons/ldtk-importer/plugin.gd.uid new file mode 100644 index 0000000..3cf8dd5 --- /dev/null +++ b/godot/addons/ldtk-importer/plugin.gd.uid @@ -0,0 +1 @@ +uid://bqk4o57wsdal2 diff --git a/godot/addons/ldtk-importer/post-import/entity-template.gd b/godot/addons/ldtk-importer/post-import/entity-template.gd new file mode 100644 index 0000000..b95b323 --- /dev/null +++ b/godot/addons/ldtk-importer/post-import/entity-template.gd @@ -0,0 +1,15 @@ +@tool + +# Entity Post-Import Template for LDTK-Importer. + +func post_import(entity_layer: LDTKEntityLayer) -> LDTKEntityLayer: + var definition: Dictionary = entity_layer.definition + var entities: Array = entity_layer.entities + + #print("EntityLayer: ", entity_layer.name, " | Count: ", entities.size()) + + for entity in entities: + # Perform operations here + pass + + return entity_layer diff --git a/godot/addons/ldtk-importer/post-import/entity-template.gd.uid b/godot/addons/ldtk-importer/post-import/entity-template.gd.uid new file mode 100644 index 0000000..b36c9bf --- /dev/null +++ b/godot/addons/ldtk-importer/post-import/entity-template.gd.uid @@ -0,0 +1 @@ +uid://c56fiui27y4ww diff --git a/godot/addons/ldtk-importer/post-import/level-template.gd b/godot/addons/ldtk-importer/post-import/level-template.gd new file mode 100644 index 0000000..5dc18df --- /dev/null +++ b/godot/addons/ldtk-importer/post-import/level-template.gd @@ -0,0 +1,8 @@ +@tool + +# Level Post-Import Template for LDTK-Importer. + +func post_import(level: LDTKLevel) -> LDTKLevel: + # Behaviour goes here + #print("Level: ", level) + return level diff --git a/godot/addons/ldtk-importer/post-import/level-template.gd.uid b/godot/addons/ldtk-importer/post-import/level-template.gd.uid new file mode 100644 index 0000000..f76267e --- /dev/null +++ b/godot/addons/ldtk-importer/post-import/level-template.gd.uid @@ -0,0 +1 @@ +uid://05p0rgy0kklx diff --git a/godot/addons/ldtk-importer/post-import/tileset-template.gd b/godot/addons/ldtk-importer/post-import/tileset-template.gd new file mode 100644 index 0000000..87b4b3a --- /dev/null +++ b/godot/addons/ldtk-importer/post-import/tileset-template.gd @@ -0,0 +1,10 @@ +@tool + +# Tileset Post-Import Template for LDTK-Importer. + +func post_import(tilesets: Dictionary) -> Dictionary: + # Behaviour goes here + for tileset: TileSet in tilesets.values(): + #print("Tileset: ", tileset, tileset.tile_size) + pass + return tilesets diff --git a/godot/addons/ldtk-importer/post-import/tileset-template.gd.uid b/godot/addons/ldtk-importer/post-import/tileset-template.gd.uid new file mode 100644 index 0000000..60305d6 --- /dev/null +++ b/godot/addons/ldtk-importer/post-import/tileset-template.gd.uid @@ -0,0 +1 @@ +uid://bwex3re84r6o2 diff --git a/godot/addons/ldtk-importer/post-import/world-template.gd b/godot/addons/ldtk-importer/post-import/world-template.gd new file mode 100644 index 0000000..94e6337 --- /dev/null +++ b/godot/addons/ldtk-importer/post-import/world-template.gd @@ -0,0 +1,8 @@ +@tool + +# World Post-Import Template for LDTK-Importer. + +func post_import(world: LDTKWorld) -> LDTKWorld: + # Behaviour goes here + #print("World: ", world) + return world diff --git a/godot/addons/ldtk-importer/post-import/world-template.gd.uid b/godot/addons/ldtk-importer/post-import/world-template.gd.uid new file mode 100644 index 0000000..6db12ef --- /dev/null +++ b/godot/addons/ldtk-importer/post-import/world-template.gd.uid @@ -0,0 +1 @@ +uid://bko7ojwy2jnl diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.gd b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.gd new file mode 100644 index 0000000..ec48f9a --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.gd @@ -0,0 +1,8 @@ +@tool +@icon("ldtk-entity-layer.svg") +class_name LDTKEntityLayer +extends Node2D + +@export var iid: String +@export var definition: Dictionary +@export var entities: Array diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.gd.uid b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.gd.uid new file mode 100644 index 0000000..cf37932 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.gd.uid @@ -0,0 +1 @@ +uid://c6umqvu1pttjk diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.svg b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.svg new file mode 100644 index 0000000..3d2f9da --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.svg.import b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.svg.import new file mode 100644 index 0000000..b60e310 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cjhytibfjjb2y" +path="res://.godot/imported/ldtk-entity-layer.svg-4429574458c1abbb5905ae9e6174fef3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/ldtk-importer/src/components/ldtk-entity-layer.svg" +dest_files=["res://.godot/imported/ldtk-entity-layer.svg-4429574458c1abbb5905ae9e6174fef3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.tscn b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.tscn new file mode 100644 index 0000000..a658482 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity-layer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://dndtl8sgpdo6m"] + +[ext_resource type="Script" path="res://addons/ldtk-importer/src/components/ldtk-entity-layer.gd" id="1_kdfje"] + +[node name="LDTK Entity Layer" type="Node2D"] +script = ExtResource("1_kdfje") diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity.gd b/godot/addons/ldtk-importer/src/components/ldtk-entity.gd new file mode 100644 index 0000000..b95f575 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity.gd @@ -0,0 +1,96 @@ +@icon("ldtk-entity.svg") +@tool +class_name LDTKEntity +extends Node2D + +## Placeholder Node for importing LDTK maps. +## Used to demonstrate import functionality - please write an Entity Post-Import script to spawn +## your own instances when using this in your project. + +@export var iid: String +@export var identifier := "EntityPlaceholder" +@export var fields := {} +@export var pivot := Vector2.ZERO +@export var size := Vector2.ZERO +@export var smart_color := Color.hex(0xffcc0088) +@export var definition := {} + +@onready var sprite: Sprite2D = $Sprite + +var _refs := [] +var _points := [] +var _drawPaths := false + +func _ready() -> void: + _points.append(Vector2.ZERO) + for key in fields: + if fields[key] is NodePath: + _refs.append(fields[key]) + elif fields[key] is Vector2i: + _points.append(fields[key]) + elif fields[key] is Array: + for value in fields[key]: + if value is NodePath: + _refs.append(value) + elif value is Vector2i: + _points.append(value) + else: + break + _drawPaths = _refs.size() > 0 or _points.size() > 0 + _points = _parse_points(_points) + queue_redraw() + +func _draw() -> void: + if definition.is_empty(): + return + + match definition.renderMode: + "Ellipse": + if definition.hollow: + draw_arc((size * 0.5) + size * -pivot, size.x * 0.5, 0, TAU, 24, smart_color, 1.0) + else: + draw_circle((size * 0.5) + size * -pivot, size.x * 0.5, smart_color) + "Rectangle": + if definition.hollow: + draw_rect(Rect2(size * -pivot, size), smart_color, false, 1.0) + else: + draw_rect(Rect2(size * -pivot, size), smart_color, true) + "Cross": + draw_line(Vector2.ZERO, size, smart_color, 3.0) + draw_line(Vector2(0, size.y), Vector2(size.x, 0), smart_color, 3.0) + "Tile": + if definition.tile == null: + pass + if definition.tile is Texture2D: + sprite.texture = definition.tile + + if _drawPaths: + for path in _refs: + if path is not NodePath: + continue + elif path.is_empty(): + continue + var node = get_node(path) + if node != null: + draw_dashed_line(Vector2.ZERO, node.global_position - global_position, smart_color) + + var previousPoint = _points[0] + for point in _points: + if point == previousPoint: + continue + draw_dashed_line(Vector2(previousPoint), Vector2(point), smart_color, 1.0, 4.0) + draw_arc(point, 4.0, 0, TAU, 5, smart_color, 1.0) + previousPoint = point + +func _parse_points(points: Array) -> Array: + if points.size() == 0: + return points + if get_parent() is SubViewport: + return points + var origin = get_parent().global_position - global_position + var gridSize = get_parent().definition.gridSize + var cellOffset = gridSize * Vector2(0.5, 0.5) + for index in range(1, points.size()): + var pixelCoord = points[index] * gridSize + points[index] = origin + pixelCoord + cellOffset + return points diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity.gd.uid b/godot/addons/ldtk-importer/src/components/ldtk-entity.gd.uid new file mode 100644 index 0000000..951d5d6 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity.gd.uid @@ -0,0 +1 @@ +uid://ciskd8jyty7gq diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity.svg b/godot/addons/ldtk-importer/src/components/ldtk-entity.svg new file mode 100644 index 0000000..3396243 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity.svg.import b/godot/addons/ldtk-importer/src/components/ldtk-entity.svg.import new file mode 100644 index 0000000..91f015e --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8jokgcqymy8i" +path="res://.godot/imported/ldtk-entity.svg-0e9e158ef8dc55d8602ffe476bdddfa3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/ldtk-importer/src/components/ldtk-entity.svg" +dest_files=["res://.godot/imported/ldtk-entity.svg-0e9e158ef8dc55d8602ffe476bdddfa3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/ldtk-importer/src/components/ldtk-entity.tscn b/godot/addons/ldtk-importer/src/components/ldtk-entity.tscn new file mode 100644 index 0000000..b48b4e6 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-entity.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=3 uid="uid://c36l6fpttus66"] + +[ext_resource type="Script" path="res://addons/ldtk-importer/src/components/ldtk-entity.gd" id="1_hclg8"] + +[node name="LDTK Entity" type="Node2D"] +script = ExtResource("1_hclg8") +smart_color = Color(1, 0.8, 0, 0.533333) + +[node name="Sprite" type="Sprite2D" parent="."] diff --git a/godot/addons/ldtk-importer/src/components/ldtk-level.gd b/godot/addons/ldtk-importer/src/components/ldtk-level.gd new file mode 100644 index 0000000..a1a98e7 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-level.gd @@ -0,0 +1,18 @@ +@icon("ldtk-level.svg") +@tool +class_name LDTKLevel +extends Node2D + +@export var iid: String +@export var world_position: Vector2 +@export var size: Vector2i +@export var fields: Dictionary +@export var neighbours: Array +@export var bg_color: Color + +func _ready() -> void: + queue_redraw() + +func _draw() -> void: + if Engine.is_editor_hint(): + draw_rect(Rect2(Vector2.ZERO, size), bg_color, false, 2.0) diff --git a/godot/addons/ldtk-importer/src/components/ldtk-level.gd.uid b/godot/addons/ldtk-importer/src/components/ldtk-level.gd.uid new file mode 100644 index 0000000..53ad847 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-level.gd.uid @@ -0,0 +1 @@ +uid://devv1ueptyklo diff --git a/godot/addons/ldtk-importer/src/components/ldtk-level.svg b/godot/addons/ldtk-importer/src/components/ldtk-level.svg new file mode 100644 index 0000000..00a76fa --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-level.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/ldtk-importer/src/components/ldtk-level.svg.import b/godot/addons/ldtk-importer/src/components/ldtk-level.svg.import new file mode 100644 index 0000000..d4a98b6 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-level.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bn8846b01v84" +path="res://.godot/imported/ldtk-level.svg-7c4c13bcdc12c18fc0e3338deb18be11.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/ldtk-importer/src/components/ldtk-level.svg" +dest_files=["res://.godot/imported/ldtk-level.svg-7c4c13bcdc12c18fc0e3338deb18be11.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/ldtk-importer/src/components/ldtk-level.tscn b/godot/addons/ldtk-importer/src/components/ldtk-level.tscn new file mode 100644 index 0000000..465fbfe --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-level.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://5jp4st4vnouy"] + +[ext_resource type="Script" path="res://addons/ldtk-importer/src/components/ldtk-level.gd" id="1_4fbyi"] + +[node name="LDTK Level" type="Node2D"] +script = ExtResource("1_4fbyi") diff --git a/godot/addons/ldtk-importer/src/components/ldtk-world-layer.gd b/godot/addons/ldtk-importer/src/components/ldtk-world-layer.gd new file mode 100644 index 0000000..b04db2c --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-world-layer.gd @@ -0,0 +1,9 @@ +@tool +@icon("ldtk-entity-layer.svg") +class_name LDTKWorldLayer +extends Node2D + +@export var depth: int: + set(d): + depth = d + z_index = depth diff --git a/godot/addons/ldtk-importer/src/components/ldtk-world-layer.gd.uid b/godot/addons/ldtk-importer/src/components/ldtk-world-layer.gd.uid new file mode 100644 index 0000000..645d9b4 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-world-layer.gd.uid @@ -0,0 +1 @@ +uid://cvvkp8akfp51o diff --git a/godot/addons/ldtk-importer/src/components/ldtk-world-layer.tscn b/godot/addons/ldtk-importer/src/components/ldtk-world-layer.tscn new file mode 100644 index 0000000..ed54260 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-world-layer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://tiadjdf7bmjd"] + +[ext_resource type="Script" path="res://addons/ldtk-importer/src/components/ldtk-world-layer.gd" id="1_tla3i"] + +[node name="World Layer" type="Node2D"] +script = ExtResource("1_tla3i") diff --git a/godot/addons/ldtk-importer/src/components/ldtk-world.gd b/godot/addons/ldtk-importer/src/components/ldtk-world.gd new file mode 100644 index 0000000..73335b1 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-world.gd @@ -0,0 +1,22 @@ +@tool +@icon("ldtk-world.svg") +class_name LDTKWorld +extends Node2D + +@export var iid: String +@export var rect: Rect2i +@export var levels: Array[LDTKLevel] + +func _init() -> void: + child_order_changed.connect(_find_level_children) + +func _find_level_children() -> void: + for child in get_children(): + if child is LDTKLevel: + if not levels.has(child): + levels.append(child) + else: + for grandchild in child.get_children(): + if grandchild is LDTKLevel: + if not levels.has(grandchild): + levels.append(grandchild) diff --git a/godot/addons/ldtk-importer/src/components/ldtk-world.gd.uid b/godot/addons/ldtk-importer/src/components/ldtk-world.gd.uid new file mode 100644 index 0000000..84f87c4 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-world.gd.uid @@ -0,0 +1 @@ +uid://boyf0tvvercyb diff --git a/godot/addons/ldtk-importer/src/components/ldtk-world.svg b/godot/addons/ldtk-importer/src/components/ldtk-world.svg new file mode 100644 index 0000000..975c0a7 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-world.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/ldtk-importer/src/components/ldtk-world.svg.import b/godot/addons/ldtk-importer/src/components/ldtk-world.svg.import new file mode 100644 index 0000000..6c924be --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-world.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhwcp2d8b7ofe" +path="res://.godot/imported/ldtk-world.svg-afb7a8fc57a903b71679a614011495e8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/ldtk-importer/src/components/ldtk-world.svg" +dest_files=["res://.godot/imported/ldtk-world.svg-afb7a8fc57a903b71679a614011495e8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/ldtk-importer/src/components/ldtk-world.tscn b/godot/addons/ldtk-importer/src/components/ldtk-world.tscn new file mode 100644 index 0000000..7e65778 --- /dev/null +++ b/godot/addons/ldtk-importer/src/components/ldtk-world.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://b5smuv4vq5lsp"] + +[ext_resource type="Script" path="res://addons/ldtk-importer/src/components/ldtk-world.gd" id="1_k4v0p"] + +[node name="LDTK World" type="Node2D"] +script = ExtResource("1_k4v0p") diff --git a/godot/addons/ldtk-importer/src/layer.gd b/godot/addons/ldtk-importer/src/layer.gd new file mode 100644 index 0000000..e42bb92 --- /dev/null +++ b/godot/addons/ldtk-importer/src/layer.gd @@ -0,0 +1,291 @@ +@tool + +const Util = preload("util/util.gd") +const LayerUtil = preload("util/layer-util.gd") +const FieldUtil = preload("util/field-util.gd") +const TileUtil = preload("util/tile-util.gd") + +# Used to display helpful error messages (which level we are in) +static var current_level: String = "N/A" + +static func create_layers( + level_data: Dictionary, + layer_instances: Array, + definitions: Dictionary +) -> Array: + + current_level = level_data.identifier + + var layer_nodes := [] + var layer_index: int = 0 + + for layer_instance in layer_instances: + var layer_def: Dictionary = definitions.layers[layer_instance.layerDefUid] + var layer_type: String = layer_instance.__type + + match layer_type: + "Entities": + var layer = create_entity_layer(layer_instance, layer_def, definitions.entities) + layer_nodes.push_front(layer) + + "IntGrid": + # Determines if this has an 'AutoLayer' assigned + var has_tileset := layer_instance.__tilesetDefUid != null + + if has_tileset: + var layer = create_tile_layer(layer_instance, layer_def) + layer_nodes.push_front(layer) + if (not has_tileset or Util.options.integer_grid_tilesets): + var layer = create_intgrid_layer(layer_instance, layer_def) + layer_nodes.push_front(layer) + + "Tiles", "AutoLayer": + var layer = create_tile_layer(layer_instance, layer_def) + layer_nodes.push_front(layer) + + _: + push_warning("[LDtk] Tried importing an unsupported layer type: ", layer_type) + + if (Util.options.verbose_output): + var layer_names = layer_nodes.map(func(elem): return elem.name) + Util.print("item_info", "Created Layers: [color=slategray]%s[/color]" % [layer_names], 2) + return layer_nodes + +#region Layer Types + +static func create_entity_layer( + layer_data: Dictionary, + layer_def: Dictionary, + entity_defs: Dictionary +) -> LDTKEntityLayer: + + var layer = LDTKEntityLayer.new() + layer.name = layer_data.__identifier + layer.iid = layer_data.iid + layer.position = layer_def.offset + + # Create a dummy child node so EntityRef fields get a correct NodePath + # I need to find a better way to do this, but there are lots of funny behaviours to deal with. + var pathResolver = Node2D.new() + pathResolver.name = "NodePathResolver" + layer.add_child(pathResolver) + Util.path_resolvers.append(pathResolver) + + var entities: Array = LayerUtil.parse_entity_instances( + layer_data.entityInstances, + entity_defs, + pathResolver + ) + + # Add instance references + layer.definition = layer_def + layer.entities = entities + + if (Util.options.use_entity_placeholders): + LayerUtil.placeholder_counts.clear() + for entity in entities: + var placeholder: LDTKEntity = LayerUtil.create_entity_placeholder(layer, entity) + Util.update_instance_reference(placeholder.iid, placeholder) + #try_push_placeholder_ref(placeholder, pathResolver) + try_push_placeholder_ref(placeholder, placeholder) + + return layer + +static func create_intgrid_layer( + layer_data: Dictionary, + layer_def: Dictionary +) -> TileMapLayer: + + # Create TileMapLayer + var tilemap: TileMapLayer = LayerUtil.create_layer_tilemap(layer_data) + tilemap.position = layer_def.offset + + # Retrieve IntGrid values - these do not always match their array index + var values: Array = layer_def.intGridValues.map( + func(item): return item.value + ) + + # Set layer properties on the Tilemap + var layer_name := str(layer_data.__identifier) + "-values" + tilemap.set_name(layer_name) + tilemap.set_modulate(Color(1, 1, 1, layer_data.__opacity)) + + # Get tile data + var tiles: Array = layer_data.intGridCsv + var tile_source_id: int = layer_data.layerDefUid + var columns: int = layer_data.__cWid + + # Place IntGrid value tiles + for index in range(0, tiles.size()): + var value = tiles[index] + var value_index: int = values.find(value) + if value_index != -1: + var cell_coords := TileUtil.index_to_grid(index, columns) + var tile_coords := Vector2i(value_index, 0) + tilemap.set_cell(cell_coords, tile_source_id, tile_coords) + + return tilemap + +static func create_tile_layer( + layer_data: Dictionary, + layer_def: Dictionary +) -> TileMapLayer: + + # Create Tilemap + var tilemap: TileMapLayer = LayerUtil.create_layer_tilemap(layer_data) + tilemap.position = layer_def.offset + + # Set layer properties on the Tilemap + var layer_name := str(layer_data.__identifier) + tilemap.set_name(layer_name) + tilemap.set_modulate(Color(1, 1, 1, layer_data.__opacity)) + + if not Util.options.layers_always_visible: + tilemap.set_enabled(layer_data.visible) + + # Get tile data + var tiles: Array + if (layer_data.__type == "Tiles"): + tiles = layer_data.gridTiles + else: + tiles = layer_data.autoLayerTiles + + var tile_source_id = layer_data.__tilesetDefUid + var grid_size := Vector2(layer_data.__gridSize, layer_data.__gridSize) + + if (tile_source_id == null): + Util.print("item_fail", "Null Tilemap on layerInstance: '%s'" % [layer_name], 2) + push_warning("Detected null tilemap on a level '%s' layer instance '%s'. Please fix this in your LDTK project file." % [current_level, layer_name]) + return tilemap + + __place_tiles(tilemap, tiles, tile_source_id, grid_size) + + return tilemap + +#endregion + +static func try_push_placeholder_ref( + placeholder: LDTKEntity, + entity: Node +) -> void: + if not Util.options.resolve_entityrefs: return + if not placeholder.fields: return + + placeholder.fields = str_to_var(var_to_str(placeholder.fields)) + var field_defs = {} + + for key in placeholder.definition.field_defs: + var def = placeholder.definition.field_defs[key] + field_defs[def.identifier] = def + + for key in placeholder.fields: + var def = field_defs[key] + if not def.type.contains("EntityRef"): continue + + var field = placeholder.fields[key] + if not field: continue + + if field is Array: + for index in range(field.size()): + Util.add_unresolved_reference(field, index, entity) + else: + Util.add_unresolved_reference(placeholder.fields, key, entity) + +static func __place_tiles( + tilemap: TileMapLayer, + tiles: Array, + tile_source_id: int, + grid_size: Vector2, + layer_index: int = 0 +) -> void: + + var tile_source: TileSetAtlasSource + if tilemap.tile_set.has_source(tile_source_id): + tile_source = tilemap.tile_set.get_source(tile_source_id) + else: + push_error("TileSetAtlasSource missing") + return + + var tile_size := Vector2(tile_source.texture_region_size) + + # Place tiles + for tile in tiles: + var cell_px := Vector2(tile.px[0], tile.px[1]) + var tile_px := Vector2(tile.src[0], tile.src[1]) + var cell_grid := TileUtil.px_to_grid(cell_px, grid_size, Vector2i.ZERO) + var tile_grid := TileUtil.px_to_grid(tile_px, tile_size, tile_source.margins, tile_source.separation) + + # Tile does not exist + if not tile_source.has_tile(tile_grid): + continue + + # Handle flipped tiles + var alternative_tile: int = 0 + var tile_flip := TileUtil.get_tile_flip_mask(int(tile.f)) + + if (tile_flip != 0): + alternative_tile = tile_flip + + # Handle alpha + if tile.a < 1.0: + var alternative_index := 1 + var alternative_exists := false + var alternative_count := tile_source.get_alternative_tiles_count(tile_grid) + + # Find alternate tile with same alpha + if alternative_count > alternative_index: + for i in range(alternative_index, alternative_count): + var data = tile_source.get_tile_data(tile_grid, i) + if is_equal_approx(data.modulate.a, tile.a): + # Reverse flip bools back into an int + var flip = TileUtil.get_tile_flip_mask(int(data.flip_h) + int(data.flip_v) * 2) + if tile_flip == flip: + alternative_index = i + alternative_exists = true + break + + # Create new tile + if not alternative_exists: + alternative_index = tile_source.create_alternative_tile(tile_grid, alternative_count) + var new_data = tile_source.get_tile_data(tile_grid, alternative_index) + TileUtil.copy_and_modify_tile_data( + new_data, + tile_source.get_tile_data(tile_grid, 0), + tilemap.tile_set.get_physics_layers_count(), + tilemap.tile_set.get_navigation_layers_count(), + tilemap.tile_set.get_occlusion_layers_count(), + tile_flip + ) + new_data.modulate.a = tile.a + + alternative_tile = alternative_index + + if not tilemap.get_cell_tile_data(cell_grid): + tilemap.set_cell(cell_grid, tile_source_id, tile_grid, alternative_tile) + else: + __place_overlapping_tile(tilemap, cell_grid, tile_source_id, tile_grid, alternative_tile) + +static func __place_overlapping_tile( + tilemap: TileMapLayer, + cell_grid: Vector2i, + tile_source_id: int, + tile_grid: Vector2i, + alternative_tile: int +) -> void: + var tilemap_child: TileMapLayer + var empty := false + + # Loop through existing children to find empty cell + for child in tilemap.get_children(): + if not child.get_cell_tile_data(cell_grid): + tilemap_child = child + empty = true + break + + # Create new child if no empty cell (or child) could be found + if not empty: + tilemap_child = LayerUtil.create_tilemap_child(tilemap) + tilemap.add_child(tilemap_child) + + # Set tile + tilemap_child.set_cell(cell_grid, tile_source_id, tile_grid, alternative_tile) diff --git a/godot/addons/ldtk-importer/src/layer.gd.uid b/godot/addons/ldtk-importer/src/layer.gd.uid new file mode 100644 index 0000000..9942345 --- /dev/null +++ b/godot/addons/ldtk-importer/src/layer.gd.uid @@ -0,0 +1 @@ +uid://46jgmlrl0r3s diff --git a/godot/addons/ldtk-importer/src/level.gd b/godot/addons/ldtk-importer/src/level.gd new file mode 100644 index 0000000..a4d8c0a --- /dev/null +++ b/godot/addons/ldtk-importer/src/level.gd @@ -0,0 +1,125 @@ +@tool + +const Util = preload("util/util.gd") +const LevelUtil = preload("util/level-util.gd") +const FieldUtil = preload("util/field-util.gd") +const PostImport = preload("post-import.gd") +const Layer = preload("layer.gd") + +static var base_directory: String + +static func build_levels( + world_data: Dictionary, + definitions: Dictionary, + base_dir: String, + external_levels: bool +) -> Array[LDTKLevel]: + + Util.timer_start(Util.DebugTime.GENERAL) + base_directory = base_dir + var levels: Array[LDTKLevel] = [] + + # Calculate level positions + var level_positions: Array + match world_data.worldLayout: + "LinearHorizontal": + var x = 0 + for level in world_data.levels: + level_positions.append(Vector2i(x, 0)) + x += level.pxWid + "LinearVertical": + var y := 0 + for level in world_data.levels: + level_positions.append(Vector2i(0, y)) + y += level.pxHei + "GridVania", "Free": + level_positions = world_data.levels.map( + func (current): + return Vector2i(current.worldX, current.worldY) + ) + _: + printerr("World Layout not supported: ", world_data.worldLayout) + + # Create levels + for level_index in range(world_data.levels.size()): + Util.timer_start(Util.DebugTime.GENERAL) + var level_data + var position: Vector2i = level_positions[level_index] + level_data = world_data.levels[level_index] + + if external_levels: + level_data = LevelUtil.get_external_level(level_data, base_dir) + + var level = create_level(level_data, position, definitions) + Util.timer_finish("Built Level", 2) + + if (Util.options.entities_post_import): + level = PostImport.run_entity_post_import(level, Util.options.entities_post_import) + + if (Util.options.level_post_import): + level = PostImport.run_level_post_import(level, Util.options.level_post_import) + + levels.append(level) + + Util.timer_finish("Built %s Levels" % levels.size(), 1) + return levels + +static func create_level( + level_data: Dictionary, + position: Vector2i, + definitions: Dictionary +) -> LDTKLevel: + var level_name: String = level_data.identifier + var level := LDTKLevel.new() + level.name = level_name + level.iid = level_data.iid + level.world_position = position + level.size = Vector2i(level_data.pxWid, level_data.pxHei) + level.bg_color = level_data.__bgColor + level.z_index = level_data.worldDepth + + if (Util.options.verbose_output): Util.print("block", level_name, 1) + Util.update_instance_reference(level_data.iid, level) + + var neighbours = level_data.__neighbours + + if not Util.options.pack_levels: + for neighbour in neighbours: + Util.add_unresolved_reference(neighbour, "levelIid", level) + + level.neighbours = neighbours + + # Create background image + if level_data.bgRelPath != null: + var path := "%s/%s" % [base_directory, level_data.bgRelPath] + var sprite := Sprite2D.new() + sprite.name = "BG Image" + sprite.centered = false + sprite.texture = load(path) + + # Calculate BG Position + var bgData: Dictionary = level_data.__bgPos + var pos: Array = bgData.topLeftPx + var scale: Array = bgData.scale + var region: Array = bgData.cropRect + sprite.region_enabled = true + sprite.position = Vector2i(pos[0], pos[1]) + sprite.scale = Vector2i(scale[0], scale[1]) + sprite.region_rect = Rect2i(region[0], region[1], region[2], region[3]) + + level.add_child(sprite) + + # Create fields + level.fields = FieldUtil.create_fields(level_data.fieldInstances, level) + + var layer_instances = level_data.layerInstances + if not layer_instances is Array: + push_error("level '%s' has no layer instances." % [level_name]) + return level + + # Create layers + var layers = Layer.create_layers(level_data, layer_instances, definitions) + for layer in layers: + level.add_child(layer) + + return level diff --git a/godot/addons/ldtk-importer/src/level.gd.uid b/godot/addons/ldtk-importer/src/level.gd.uid new file mode 100644 index 0000000..d4af28e --- /dev/null +++ b/godot/addons/ldtk-importer/src/level.gd.uid @@ -0,0 +1 @@ +uid://w5et03vhdql6 diff --git a/godot/addons/ldtk-importer/src/post-import.gd b/godot/addons/ldtk-importer/src/post-import.gd new file mode 100644 index 0000000..a9c79d7 --- /dev/null +++ b/godot/addons/ldtk-importer/src/post-import.gd @@ -0,0 +1,60 @@ +@tool + +const Util = preload("util/util.gd") + +static func run(element: Variant, script_path: String) -> Variant: + var element_type = typeof(element) + + if not script_path.is_empty(): + var script = load(script_path) + if not script or not script is GDScript: + printerr("Post-Import: '%s' is not a GDScript" % [script_path]) + return ERR_INVALID_PARAMETER + + script = script.new() + if not script.has_method("post_import"): + printerr("Post-Import: '%s' does not have a post_import() method" % [script_path]) + return ERR_INVALID_PARAMETER + + element = script.post_import(element) + + if element == null or typeof(element) != element_type: + printerr("Post-Import: Invalid scene returned from script.") + return ERR_INVALID_DATA + + return element + +static func run_tileset_post_import(tilesets: Dictionary, script_path: String) -> Dictionary: + Util.timer_start(Util.DebugTime.POST_IMPORT) + Util.print("tileset_post_import", str(tilesets), 1) + tilesets = run(tilesets, Util.options.tileset_post_import) + Util.timer_finish("Tileset Post-Import: Complete", 1) + return tilesets + +static func run_level_post_import(level: LDTKLevel, script_path: String) -> LDTKLevel: + Util.timer_start(Util.DebugTime.POST_IMPORT) + Util.print("level_post_import", level.name, 2) + level = run(level, Util.options.level_post_import) + Util.timer_finish("Level Post-Import: Complete", 2) + return level + +static func run_entity_post_import(level: LDTKLevel, script_path: String) -> LDTKLevel: + Util.timer_start(Util.DebugTime.POST_IMPORT) + var layers = level.get_children() + for layer in layers: + if layer is not LDTKEntityLayer: + continue + + var entityLayerName = layer.get_parent().name + "." + layer.name + Util.print("entity_post_import", entityLayerName, 3) + layer = run(layer, script_path) + + Util.timer_finish("Entity Post-Import: Complete", 3) + return level + +static func run_world_post_import(world: LDTKWorld, script_path: String) -> LDTKWorld: + Util.timer_start(Util.DebugTime.POST_IMPORT) + Util.print("world_post_import", world.name, 1) + world = run(world, Util.options.world_post_import) + Util.timer_finish("World Post-Import: Complete", 1) + return world diff --git a/godot/addons/ldtk-importer/src/post-import.gd.uid b/godot/addons/ldtk-importer/src/post-import.gd.uid new file mode 100644 index 0000000..1e81764 --- /dev/null +++ b/godot/addons/ldtk-importer/src/post-import.gd.uid @@ -0,0 +1 @@ +uid://bv4fn2n2ncw12 diff --git a/godot/addons/ldtk-importer/src/tileset.gd b/godot/addons/ldtk-importer/src/tileset.gd new file mode 100644 index 0000000..7f6f884 --- /dev/null +++ b/godot/addons/ldtk-importer/src/tileset.gd @@ -0,0 +1,330 @@ +@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 diff --git a/godot/addons/ldtk-importer/src/tileset.gd.uid b/godot/addons/ldtk-importer/src/tileset.gd.uid new file mode 100644 index 0000000..6e088eb --- /dev/null +++ b/godot/addons/ldtk-importer/src/tileset.gd.uid @@ -0,0 +1 @@ +uid://etx2miclpo5a diff --git a/godot/addons/ldtk-importer/src/util/definition_util.gd b/godot/addons/ldtk-importer/src/util/definition_util.gd new file mode 100644 index 0000000..2db8b41 --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/definition_util.gd @@ -0,0 +1,108 @@ +@tool + +const Util = preload("util.gd") + +# When building definitions we are only collecting data we need and do some pre-parsing to make the next steps easier. + +static func build_definitions(world_data: Dictionary) -> Dictionary: + var definitions := { + "enums": resolve_enum_definitions(world_data.defs.enums), + "entities": resolve_entity_definitions(world_data.defs.entities), + "layers": resolve_layer_definitions(world_data.defs.layers), + "tilesets": resolve_tileset_definitions(world_data.defs.tilesets, world_data.defs.layers), + "level_fields": resolve_level_field_definitions(world_data.defs.levelFields), + } + return definitions + +static func resolve_layer_definitions(layer_defs: Array) -> Dictionary: + var resolved_layer_defs := {} + + for layer_def in layer_defs: + resolved_layer_defs[layer_def.uid] = { + "uid": layer_def.uid, + "type": layer_def.type, + "identifier": layer_def.identifier, + "gridSize": layer_def.gridSize, + "offset": Vector2i(layer_def.pxOffsetX, layer_def.pxOffsetY), + "parallax": Vector2(layer_def.parallaxFactorX, layer_def.parallaxFactorY), + "parallaxScaling": layer_def.parallaxScaling, + "intGridValues": layer_def.intGridValues + } + + return resolved_layer_defs + +static func resolve_entity_definitions(entity_defs: Array) -> Dictionary: + var resolved_entity_defs := {} + + for entity_def in entity_defs: + resolved_entity_defs[entity_def.uid] = { + "identifier": entity_def.identifier, + "color": Color.from_string(entity_def.color, Color.MAGENTA), + "renderMode": entity_def.renderMode, + "hollow": entity_def.hollow, + "tags": entity_def.tags, + "field_defs": resolve_entity_field_defs(entity_def.fieldDefs), + "tile": entity_def.uiTileRect + } + + return resolved_entity_defs + +static func resolve_entity_field_defs(field_defs: Array) -> Dictionary: + var resolved_entity_field_defs := {} + + for field_def in field_defs: + resolved_entity_field_defs[int(field_def.uid)] = { + "identifier": field_def.identifier, + "type": field_def.__type, + } + + return resolved_entity_field_defs + +static func resolve_tileset_definitions(tileset_defs: Array, layer_defs: Array) -> Dictionary: + var resolved_tileset_defs := {} + + for tileset_def in tileset_defs: + resolved_tileset_defs[tileset_def.uid] = { + "uid": tileset_def.uid, + "identifier": tileset_def.identifier, + "relPath": tileset_def.relPath, + "gridSize": tileset_def.tileGridSize, + "pxWid": tileset_def.pxWid, + "pxHei": tileset_def.pxHei, + "spacing": tileset_def.spacing, + "padding": tileset_def.padding, + "tags": tileset_def.tags, + "enumTagUid": tileset_def.tagsSourceEnumUid, + "enumTags": tileset_def.enumTags, + "customData": tileset_def.customData, + "__cWid": tileset_def.__cWid, + "__cHei": tileset_def.__cHei, + } + return resolved_tileset_defs + +static func resolve_enum_definitions(enum_defs: Array) -> Dictionary: + var resolved_enum_defs := {} + + for enum_def in enum_defs: + var uid = enum_def.uid + var values := [] + for value in enum_def.values: + values.append({ + "value": value.id, + "color": Color.from_string(str(value.color), Color.MAGENTA), + "tileRect": value.tileRect + }) + resolved_enum_defs[uid] = values + + return resolved_enum_defs + +static func resolve_level_field_definitions(level_field_defs: Array) -> Dictionary: + var resolved_level_field_defs := {} + + for level_field_def in level_field_defs: + resolved_level_field_defs[level_field_def.uid] = { + "identifier": level_field_def.identifier, + "type": level_field_def.__type + } + + return resolved_level_field_defs diff --git a/godot/addons/ldtk-importer/src/util/definition_util.gd.uid b/godot/addons/ldtk-importer/src/util/definition_util.gd.uid new file mode 100644 index 0000000..cb0b456 --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/definition_util.gd.uid @@ -0,0 +1 @@ +uid://10dqdfkkqwa2 diff --git a/godot/addons/ldtk-importer/src/util/field-util.gd b/godot/addons/ldtk-importer/src/util/field-util.gd new file mode 100644 index 0000000..1fcb7c8 --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/field-util.gd @@ -0,0 +1,126 @@ +@tool + +const Util = preload("util.gd") +const TileUtil = preload("tile-util.gd") + +static var hitUnresolved := false +static var current_field_name: String = "N/A" + +static func create_fields(fields: Array, entity: Variant = null) -> Dictionary: + var dict := {} + for field in fields: + var key: String = field.__identifier + current_field_name = key + dict[key] = parse_field(field) + + if not Util.options.resolve_entityrefs: + hitUnresolved = false + continue + + if hitUnresolved and entity != null: + if dict[key] is Array: + for index in range(dict[key].size()): + Util.add_unresolved_reference(dict[key], index, entity) + else: + Util.add_unresolved_reference(dict, key, entity) + hitUnresolved = false + + return dict + +static func parse_field(field: Dictionary) -> Variant: + var value: Variant = field.__value + if value == null: + return null + + var type := field.__type as String + + # Handle enum string + var localEnum: String + if type.contains("LocalEnum"): + var regex = RegEx.new() + regex.compile("(?<=\\.)\\w+") + localEnum = regex.search(type).get_string() + + if type.begins_with("Array"): + type = "Array" + else: + type = "LocalEnum" + + # Match field type + match type: + "Int": + return int(value) as int + "Color": + return Color.from_string(value, Color.MAGENTA) as Color + "Point": + return __parse_point(value.cx, value.cy) as Vector2i + "Tile": + return __parse_tile(value) as AtlasTexture + "EntityRef": + hitUnresolved = true + return value.entityIid as String + "LocalEnum": + return __parse_enum(localEnum, value) as String + "Array": + return value + "Array": + return value.map( + func (color): + return Color.from_string(color, Color.MAGENTA) + ) + "Array": + return value.map( + func (point): + return Vector2i(point.cx, point.cy) + ) + "Array": + return value.map( + func(tile) -> AtlasTexture: + return __parse_tile(tile) + ) + "Array": + hitUnresolved = true + return value.map( + func (ref) -> String: + return ref.entityIid + ) + "Array": + var enums: Array[String] = [] + for index in range(value.size()): + var parsed_enum = __parse_enum(localEnum, value[index]) + enums.append(parsed_enum) + return enums + _: + return value + +static func __parse_point(x: int, y: int) -> Vector2i: + # NOTE: would convert gridcoords to pixelcoords here, but needs more data + # LDTKEntity currently converts it using LayerDefinition. + return Vector2i(x,y) + +static func __parse_enum(enum_str: String, value: String) -> String: + var result: String = "%s.%s" % [enum_str, value] + return result + +static func __parse_tile(value: Dictionary) -> AtlasTexture: + var texture := AtlasTexture.new() + var atlas: TileSetAtlasSource + + if Util.tileset_refs.has(int(value.tilesetUid)): + atlas = Util.tileset_refs[int(value.tilesetUid)] + + if atlas == null : + print("Tileset Refs: ", Util.tileset_refs) + print("FieldDef '%s' could not find Tileset with UID '%s'. Using empty texture instead. Please fix this in your LDtk file." % [current_field_name, value.tilesetUid]) + return texture + + texture.atlas = atlas.texture + + var coords = TileUtil.px_to_grid( + Vector2(value.x, value.y), + atlas.texture_region_size, + atlas.margins, + atlas.separation + ) + texture.region = atlas.get_tile_texture_region(coords) as Rect2i + return texture diff --git a/godot/addons/ldtk-importer/src/util/field-util.gd.uid b/godot/addons/ldtk-importer/src/util/field-util.gd.uid new file mode 100644 index 0000000..d7ec746 --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/field-util.gd.uid @@ -0,0 +1 @@ +uid://dp7qwjbyo1kx0 diff --git a/godot/addons/ldtk-importer/src/util/layer-util.gd b/godot/addons/ldtk-importer/src/util/layer-util.gd new file mode 100644 index 0000000..b82d35f --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/layer-util.gd @@ -0,0 +1,68 @@ +@tool + +const Util = preload("util.gd") +const EntityPlaceHolder = preload("../components/ldtk-entity.tscn") +const FieldUtil = preload("field-util.gd") + +# Counter reset per level, used when creating EntityPlacholders +static var placeholder_counts := {} + +static func parse_entity_instances( + entities: Array, + entity_defs: Dictionary, + pathResolver: Node2D +) -> Array: + return entities.map( + func(entity): + var definition = entity_defs[entity.defUid] + return { + "iid": entity.iid, + "identifier": entity.__identifier, + "smart_color": Color.from_string(entity.__smartColor, Color.WHITE), + "size": Vector2i(entity.width, entity.height), + "position": Vector2i(entity.px[0], entity.px[1]), + "pivot": Vector2(entity.__pivot[0], entity.__pivot[1]), + "fields": FieldUtil.create_fields(entity.fieldInstances, pathResolver), + "definition": definition, + } + ) + +static func create_entity_placeholder(layer: Node2D, data: Dictionary) -> LDTKEntity: + var placeholder: LDTKEntity = EntityPlaceHolder.instantiate() + var count: int = __placeholder_count(data.identifier) + placeholder.name = data.identifier + + if count > 1: + placeholder.name += str(count) + + # Set properties + for prop in data.keys(): + placeholder[prop] = data[prop] + + layer.add_child(placeholder) + return placeholder + +static func __placeholder_count(name: String) -> int: + if not name in placeholder_counts: + placeholder_counts[name] = 1 + else: + placeholder_counts[name] += 1 + return placeholder_counts[name] + +static func create_layer_tilemap(layer_data: Dictionary) -> TileMapLayer: + var grid_size = int(layer_data.__gridSize) + + var tilemap := TileMapLayer.new() + tilemap.name = layer_data.__identifier + tilemap.tile_set = Util.tilesets.get(grid_size, null) + var offset = Vector2(layer_data.__pxTotalOffsetX, layer_data.__pxTotalOffsetY) + tilemap.position = offset + + return tilemap + +static func create_tilemap_child(tilemap: TileMapLayer) -> TileMapLayer: + var child := TileMapLayer.new() + var count := tilemap.get_child_count() + 1 + child.name = tilemap.name + str(count) + child.tile_set = tilemap.tile_set + return child diff --git a/godot/addons/ldtk-importer/src/util/layer-util.gd.uid b/godot/addons/ldtk-importer/src/util/layer-util.gd.uid new file mode 100644 index 0000000..93ebfa9 --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/layer-util.gd.uid @@ -0,0 +1 @@ +uid://cm8kt5fyu4max diff --git a/godot/addons/ldtk-importer/src/util/level-util.gd b/godot/addons/ldtk-importer/src/util/level-util.gd new file mode 100644 index 0000000..348bc8a --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/level-util.gd @@ -0,0 +1,54 @@ +@tool + +const Util = preload("util.gd") + +static func get_world_position( + world_data: Dictionary, + level_data: Dictionary +) -> Vector2i: + + var layout = world_data.worldLayout + + if layout == "GridVania" or layout == "Free": + return Vector2i(level_data.worldX, level_data.worldY) + elif layout == "LinearHorizontal" or layout == "LinearVertical": + # List level uids in order. + var level_uids: Array = world_data.levels.map( + func(item): + return item.uid + ) + # Find level index + var index = level_uids.find(level_data.uid) + if index == 0: + return Vector2i(0,0) + + if layout == "LinearHorizontal": + var x: int = world_data.levels.slice(0, index).reduce( + func (accum, current): + return accum + current.pxWid + , 0) + return Vector2i(x, 0) + else: + var y: int = world_data.levels.slice(0, index).reduce( + func (accum, current): + return accum + current.pHei + , 0) + return Vector2i(0, y) + else: + push_warning("World layout not supported", world_data.worldLayout) + return Vector2i.ZERO + + +static func get_external_level( + level_data: Dictionary, + base_dir: String +) -> Dictionary: + + var level_file: String = base_dir + level_data.externalRelPath + var new_level_data: Dictionary = Util.parse_file(level_file) + if not new_level_data == null: + return new_level_data + else: + push_warning("Could not parse external level: ", level_file) + + return level_data diff --git a/godot/addons/ldtk-importer/src/util/level-util.gd.uid b/godot/addons/ldtk-importer/src/util/level-util.gd.uid new file mode 100644 index 0000000..7c25a3c --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/level-util.gd.uid @@ -0,0 +1 @@ +uid://oc513xtwqvf1 diff --git a/godot/addons/ldtk-importer/src/util/tile-util.gd b/godot/addons/ldtk-importer/src/util/tile-util.gd new file mode 100644 index 0000000..7b863db --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/tile-util.gd @@ -0,0 +1,126 @@ +@tool + +# Flip a vector based on a bitset +static func _flip_vector_array_with_bitset( + vecs: PackedVector2Array, + bitset: int +) -> PackedVector2Array: + var new_vecs = PackedVector2Array(vecs) + for point_idx in range(vecs.size()): + var new_vec = Vector2(vecs[point_idx]) + if bitset & 1: + new_vec.x = -new_vec.x + if bitset & 2: + new_vec.y = -new_vec.y + new_vecs[point_idx] = new_vec + return new_vecs + +# Copy over and rotate extra tiledata +static func copy_and_modify_tile_data( + tile_data: TileData, + orig_tile_data: TileData, + physics_layers_cnt: int, + navigation_layers_cnt: int, + occluder_layers_cnt: int, + bitset: int, +) -> void: + # Copy over physics + for pli in range(physics_layers_cnt): + var polygon_cnt = orig_tile_data.get_collision_polygons_count(pli) + if polygon_cnt == 0: + # We have no polygon for this layer + continue + for pi in range(polygon_cnt): + tile_data.add_collision_polygon(pli) + var points: PackedVector2Array = _flip_vector_array_with_bitset(orig_tile_data.get_collision_polygon_points(pli, pi), bitset) + tile_data.set_collision_polygon_points(pli, pi, points) + tile_data.set_constant_angular_velocity(pli, orig_tile_data.get_constant_angular_velocity(pli)) + var linvel = Vector2(orig_tile_data.get_constant_linear_velocity(pli)) + if bitset & TileSetAtlasSource.TRANSFORM_FLIP_H: + linvel.x = -linvel.x + if bitset & TileSetAtlasSource.TRANSFORM_FLIP_V: + linvel.y = -linvel.y + tile_data.set_constant_linear_velocity(pli, linvel) + + # Copy over navigation + for navi in range(navigation_layers_cnt): + var nav_polygon: NavigationPolygon = orig_tile_data.get_navigation_polygon(navi) + if nav_polygon == null: + # We have no polygon for this layer + continue + var new_polygon = NavigationPolygon.new() + for outline_idx in range(nav_polygon.get_outline_count()): + var vertices = _flip_vector_array_with_bitset(nav_polygon.get_outline(outline_idx), bitset) + new_polygon.add_outline(vertices) + new_polygon.make_polygons_from_outlines() + tile_data.set_navigation_polygon(navi, new_polygon) + + # Copy over occluder + for occi in range(occluder_layers_cnt): + var occluder: OccluderPolygon2D = orig_tile_data.get_occluder(occi) + if occluder == null: + # We have no polygon for this layer + continue + var new_occluder: OccluderPolygon2D = OccluderPolygon2D.new() + new_occluder.cull_mode = occluder.cull_mode + new_occluder.closed = occluder.closed + new_occluder.polygon = _flip_vector_array_with_bitset(occluder.polygon, bitset) + tile_data.set_occluder(occi, new_occluder) + + # Flip depending on bitset + if bitset & TileSetAtlasSource.TRANSFORM_FLIP_H: + tile_data.set_flip_h(true) + if bitset & TileSetAtlasSource.TRANSFORM_FLIP_V: + tile_data.set_flip_v(true) + +# Get Rect of Tile for an AtlasSource using LDTK tileset data +static func get_tile_region( + coords: Vector2i, + grid_size: int, + padding: int, + spacing: int, + grid_w: int +) -> Rect2i: + var pixel_coords = grid_to_px(coords, grid_size, padding, spacing) + return Rect2i(pixel_coords, Vector2i(grid_size, grid_size)) + +# Convert grid coords to pixel coords +static func grid_to_px( + grid_coords: Vector2i, + grid_size: int, + padding: int, + spacing: int +) -> Vector2i: + var x: int = padding + grid_coords.x * (grid_size + spacing) + var y: int = padding + grid_coords.y * (grid_size + spacing) + return Vector2i(x, y) + +# Converts px coords to grid coords +static func px_to_grid( + px_coords: Vector2, + grid_size: Vector2, + padding: Vector2 = Vector2.ZERO, + spacing: Vector2 = Vector2.ZERO +) -> Vector2i: + var x: int = round((px_coords.x - padding.x) / (grid_size.x + spacing.x)) + var y: int = round((px_coords.y - padding.y) / (grid_size.y + spacing.y)) + return Vector2i(x, y) + +# Convert TileId to grid coords +static func tileid_to_grid(tile_id: int, grid_w: int) -> Vector2i: + var y := int(tile_id / grid_w) + var x := tile_id % grid_w + return Vector2i(x, y) + +static func index_to_grid(index: int, grid_w: int) -> Vector2i: + var x: int = floor(index % grid_w) + var y: int = floor(index / grid_w) + return Vector2i(x, y) + +# Converts '1' to (TRANSFORM_FLIP_H), and so on. +static func get_tile_flip_mask(index: int) -> int: + # 0 -> 0 + # 1 -> TileSetAtlasSource.TRANSFORM_FLIP_H (4096) + # 2 -> TileSetAtlasSource.TRANSFORM_FLIP_V (8192) + # 3 -> TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_H (12_228) + return index << 12 diff --git a/godot/addons/ldtk-importer/src/util/tile-util.gd.uid b/godot/addons/ldtk-importer/src/util/tile-util.gd.uid new file mode 100644 index 0000000..f2575d9 --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/tile-util.gd.uid @@ -0,0 +1 @@ +uid://bp3rb0p6dsayi diff --git a/godot/addons/ldtk-importer/src/util/time-util.gd b/godot/addons/ldtk-importer/src/util/time-util.gd new file mode 100644 index 0000000..2de990b --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/time-util.gd @@ -0,0 +1,48 @@ +@tool + +## Helper Script to track performance of the importer. + +enum { SAVE, LOAD, GENERAL, TILES, POST_IMPORT, TOTAL } + +static var category_time := { + SAVE: 0, + LOAD: 0, + GENERAL: 0, + TILES: 0, + POST_IMPORT: 0, + TOTAL : 0, +} + +static var category_name := { + SAVE: "save", + LOAD: "load", + GENERAL : "general", + TILES: "tiles", + POST_IMPORT: "post-import", + TOTAL: "total" +} + +static func log_time(category: int, time: int = 0) -> void: + if category_time.has(category): + category_time[category] += time + else: + push_warning("No DebugTime Category '%s'" % [category_name[category]]) + +static func clear_time() -> void: + for category in category_time: + category_time[category] = 0 + +static func get_total_time() -> int: + var sum: int = 0 + for category in category_time: + if category != TOTAL: + sum += category_time[category] + return sum + +static func get_result() -> String: + var result: String = "Performance Results:" + for category in category_time: + if category != TOTAL: + result += "\n [color=#8ec07c]%s [color=slategray](%sms)[/color]" % [category_name[category], category_time[category]] + result.indent("\t") + return result diff --git a/godot/addons/ldtk-importer/src/util/time-util.gd.uid b/godot/addons/ldtk-importer/src/util/time-util.gd.uid new file mode 100644 index 0000000..a0a8c67 --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/time-util.gd.uid @@ -0,0 +1 @@ +uid://bynwlcmpnsjit diff --git a/godot/addons/ldtk-importer/src/util/util.gd b/godot/addons/ldtk-importer/src/util/util.gd new file mode 100644 index 0000000..b66f1b6 --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/util.gd @@ -0,0 +1,226 @@ +@tool + +const DebugTime = preload("time-util.gd") + +enum LDTK_VERSION { + FUTURE, + v1_5, + v1_4, + v1_3, + v1_2, + v1_0, + UNSUPPORTED +} +static var file_version = LDTK_VERSION.UNSUPPORTED + +# Stores import flags (used throughout the importer) +static var options := {} + +static func parse_file(source_file: String) -> Dictionary: + var json := FileAccess.open(source_file, FileAccess.READ) + if json == null: + push_error("\nFailed to open file: ", source_file) + return {} + var data := JSON.parse_string(json.get_as_text()) + return data + +static func check_version(version: String, latest_version: String) -> bool: + if version.begins_with("0."): + push_error("LDTK version out of date. Please update LDtk to ", latest_version) + file_version = LDTK_VERSION.UNSUPPORTED + return false + + var major_minor = version.substr(0, 3) + match major_minor: + "1.0", "1.1": + file_version = LDTK_VERSION.v1_0 + "1.2": + file_version = LDTK_VERSION.v1_2 + "1.3": + file_version = LDTK_VERSION.v1_3 + "1.4": + file_version = LDTK_VERSION.v1_4 + "1.5": + file_version = LDTK_VERSION.v1_5 + _: + push_warning("LDtk file version is newer than what is supported. Errors may occur.") + file_version = LDTK_VERSION.FUTURE + return true + +static func recursive_set_owner(node: Node, owner: Node) -> void: + node.set_owner(owner) + for child in node.get_children(): + # Child is NOT an instantiated scene - this would otherwise cause errors + if child.scene_file_path == "": + recursive_set_owner(child, owner) + else: + child.set_owner(owner) + +#region Performance Measurement + +static var last_time: int = 0 +static var time_history: Array[Dictionary] = [] + +static func timer_start(category: int = 0) -> int: + var t: int = Time.get_ticks_msec() + var d: int = t - last_time + last_time = t + + if time_history.size() > 0: + # Entering subcategory - log prev category up to here + var last: Dictionary = time_history[-1] + DebugTime.log_time(last.category, d) + + time_history.append({"category": category, "time": t, "init": t}) + return t + +static func timer_finish(message: String, indent: int = 0, doPrint: bool = true) -> int: + if time_history.size() == 0: + push_error("Unbalanced DebugTime stack") + var last: Dictionary = time_history.pop_back() + var t: int = Time.get_ticks_msec() + var d: int = t - last.time + last_time = t + DebugTime.log_time(last.category, d) + + if time_history.size() > 0: + time_history[-1].time = t + + if (doPrint and options.verbose_output): + # Print 'gross' duration for this block + var d2: int = t - last.init + print_time("item_info_time", message, d2, indent) + return d + +static func timer_reset() -> void: + last_time = 0 + time_history.clear() + DebugTime.clear_time() + +#endregion + +#region Debug Output + +const PRINT_SNIPPET := { + "import_start": "[bgcolor=#ffcc00][color=black][LDTK][/color][/bgcolor][color=#ffcc00] Start Import: [color=#fe8019][i]'%s'[/i][/color]", + "import_finish": "[bgcolor=#ffcc00][color=black][LDTK][/color][/bgcolor][color=#ffcc00] Finished Import. [color=slategray](Total Time: %sms)[/color]", + "item_ok" : "[color=#b8bb26]• %s ✔[/color]", + "item_fail": "[color=#fb4934]• %s ✘[/color]", + "item_info": "[color=#8ec07c]• %s [/color]", + "item_save": "[color=#ffcc00]• %s [/color]", + "item_post_import": "[color=tomato]‣ %s[/color]", + "block": "[color=#ffcc00]█[/color] [color=#fe8019]%s[/color]", + "item_ok_time": "[color=#b8bb26]• %s ✔[/color]\t[color=slategray](%sms)[/color]", + "item_fail_time": "[color=#fb4934]• %s ✘[/color]\t[color=slategray](%sms)[/color]", + "item_info_time": "[color=#8ec07c]• %s [/color]\t[color=slategray](%sms)[/color]", + "world_post_import": "[color=tomato]‣ World Post-Import: %s[/color]", + "level_post_import": "[color=tomato]‣ Level Post-Import: %s[/color]", + "tileset_post_import": "[color=tomato]‣ Tileset Post-Import: %s[/color]", + "entity_post_import": "[color=tomato]‣ Entity Post-Import: %s[/color]", +} + +static func nice_print(type: String, message: String, indent: int = 0) -> void: + if PRINT_SNIPPET.has(type): + var snippet: String = PRINT_SNIPPET[type] + snippet = snippet.indent(str("\t").repeat(indent)) + print_rich(snippet % [message]) + else: + print_rich(message) + +static func print(type: String, message: String, indent: int = 0) -> void: + nice_print(type, message, indent) + +static func print_time(type: String, message: String, time: int = -1, indent: int = 0) -> void: + if PRINT_SNIPPET.has(type): + var snippet: String = PRINT_SNIPPET[type] + snippet = snippet.indent(str("\t").repeat(indent)) + print_rich(snippet % [message, time]) + else: + print_rich(message) + +#endregion + +#region References +static var tilesets := {} +static var tileset_refs := {} +static var instance_refs := {} +static var unresolved_refs := [] +static var path_resolvers := [] + +static func update_instance_reference(iid: String, instance: Variant) -> void: + instance_refs[iid] = instance + +static func add_tileset_reference(uid: int, atlas: TileSetAtlasSource) -> void: + tileset_refs[uid] = atlas + +# This is useful for handling entity instances, as they might not exist yet when encountered +# or be overwritten at a later stage (e.g. post-import) when importing an LDTK level/world. +static func add_unresolved_reference( + object: Variant, + property: Variant, + node: Variant = object, + iid: String = str(object[property]) +) -> void: + + unresolved_refs.append({ + "object": object, + "property": property, + "node": node, + "iid": iid + }) + +static func handle_references() -> void: + resolve_references() + clean_references() + clean_resolvers() + +static func resolve_references() -> void: + var count := unresolved_refs.size() + if (count == 0 or not options.resolve_entityrefs): + if (options.verbose_output): nice_print("item_info", "No references to resolve", 1) + return + else: + if (options.verbose_output): nice_print("item_info", "Resolving %s references" % [count], 1) + + var solved_refcount := 0 + + for ref in unresolved_refs: + var iid: String = ref.iid + var object: Variant = ref.object # Expected: Node, Dict or Array + var property: Variant = ref.property # Expected: String or Int + var node: Variant = ref.node # Expected: Node, but needs to accept null + + if instance_refs.has(iid): + var instance = instance_refs[iid] + + if instance is Node and node is Node: + # BUG: When using 'Pack Levels', external references cannot be resolved at import time. (e.g. Level_0 -> Level_1) + # Internal references can resolve, but Godot pushes the error: Parameter "common_parent" is null. + # Currently it's a choice between a bunch of errors (that suppress other messages), or no resolving. + if true: #instance.owner != null and node.owner != null: + var path = node.get_path_to(instance) + if path: + object[property] = path + else: + nice_print("item_fail", "Cannot resolve ref (out-of-bounds?) '%s' '%s'" % [instance.name, node.name], 1) + continue + else: + object[property] = instance + + solved_refcount += 1 + + var leftover_refcount: int = unresolved_refs.size() - solved_refcount + if leftover_refcount > 0: + nice_print("item_info", "Could not resolve %s references, most likely non-existent entities." % [leftover_refcount], 1) + +static func clean_references() -> void: + tileset_refs.clear() + instance_refs.clear() + unresolved_refs.clear() + +static func clean_resolvers() -> void: + for resolver in path_resolvers: + resolver.free() + path_resolvers.clear() + +#endregion diff --git a/godot/addons/ldtk-importer/src/util/util.gd.uid b/godot/addons/ldtk-importer/src/util/util.gd.uid new file mode 100644 index 0000000..074c89d --- /dev/null +++ b/godot/addons/ldtk-importer/src/util/util.gd.uid @@ -0,0 +1 @@ +uid://ddr8yxjsfqgji diff --git a/godot/addons/ldtk-importer/src/world.gd b/godot/addons/ldtk-importer/src/world.gd new file mode 100644 index 0000000..1be674c --- /dev/null +++ b/godot/addons/ldtk-importer/src/world.gd @@ -0,0 +1,89 @@ +@tool + +const Util = preload("util/util.gd") +const PostImport = preload("post-import.gd") + +static func create_world( + name: String, + iid: String, + levels: Array, + base_dir: String +) -> LDTKWorld: + + Util.timer_start(Util.DebugTime.GENERAL) + var world = LDTKWorld.new() + world.name = name + world.iid = iid + + # Update World_Rect + var x1 = world.rect.position.x + var x2 = world.rect.end.x + var y1 = world.rect.position.y + var y2 = world.rect.end.y + + var worldDepths := {} + + for level in levels: + level.position = level.world_position + + if Util.options.group_world_layers: + var worldDepthLayer: LDTKWorldLayer + var z_index: int = level.z_index if (level is not PackedScene) else 0 + if not z_index in worldDepths: + worldDepthLayer = LDTKWorldLayer.new() + worldDepthLayer.name = "WorldLayer_" + str(z_index) + worldDepthLayer.depth = z_index + world.add_child(worldDepthLayer) + worldDepthLayer.set_owner(world) + worldDepths[z_index] = worldDepthLayer + else: + worldDepthLayer = worldDepths[z_index] + worldDepthLayer.add_child(level) + else: + world.add_child(level) + + x1 = min(x1, level.position.x) + y1 = min(y1, level.position.y) + x2 = max(x2, level.position.x + level.size.x) + y2 = max(y2, level.position.y + level.size.y) + + # Set owner - this ensures nodes get saved correctly + level.set_owner(world) + if not (Util.options.pack_levels): + Util.recursive_set_owner(level, world) + + # Sort WorldLayers based on depth + if not worldDepths.is_empty(): + var keys = worldDepths.keys() + keys.sort_custom(func(a,b): return a < b) + for i in range(keys.size()): + world.move_child(worldDepths[keys[i]], i) + + world.rect.position = Vector2i(x1, y1) + world.rect.end = Vector2i(x2, y2) + + Util.timer_finish("World Created", 1) + + # Post-Import + if (Util.options.world_post_import): + world = PostImport.run_world_post_import(world, Util.options.world_post_import) + + return world + +static func create_multi_world( + name: String, + iid: String, + worlds: Array[LDTKWorld] +) -> LDTKWorld: + + var multi_world = LDTKWorld.new() + multi_world.name = name + multi_world.iid = iid + + worlds.sort_custom(func(a, b): return a.depth < b.depth) + + for world in worlds: + multi_world.add_child(world) + Util.recursive_set_owner(world, multi_world) + + return multi_world diff --git a/godot/addons/ldtk-importer/src/world.gd.uid b/godot/addons/ldtk-importer/src/world.gd.uid new file mode 100644 index 0000000..6f83d3a --- /dev/null +++ b/godot/addons/ldtk-importer/src/world.gd.uid @@ -0,0 +1 @@ +uid://dxg0dj75iw7w8 diff --git a/assets/pipeline.sh b/godot/assets/pipeline.sh similarity index 100% rename from assets/pipeline.sh rename to godot/assets/pipeline.sh diff --git a/assets/processed/tilesets/sidewalk_128x128.png b/godot/assets/processed/tilesets/sidewalk_128x128.png similarity index 100% rename from assets/processed/tilesets/sidewalk_128x128.png rename to godot/assets/processed/tilesets/sidewalk_128x128.png diff --git a/godot/assets/processed/tilesets/sidewalk_128x128.png.import b/godot/assets/processed/tilesets/sidewalk_128x128.png.import new file mode 100644 index 0000000..8df3da8 --- /dev/null +++ b/godot/assets/processed/tilesets/sidewalk_128x128.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0k22dcuuv8uy" +path="res://.godot/imported/sidewalk_128x128.png-cde506a7daa48b45baa91d26762bc08d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/processed/tilesets/sidewalk_128x128.png" +dest_files=["res://.godot/imported/sidewalk_128x128.png-cde506a7daa48b45baa91d26762bc08d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/processed/tilesets/sidewalk_512x512.png b/godot/assets/processed/tilesets/sidewalk_512x512.png similarity index 100% rename from assets/processed/tilesets/sidewalk_512x512.png rename to godot/assets/processed/tilesets/sidewalk_512x512.png diff --git a/godot/assets/processed/tilesets/sidewalk_512x512.png.import b/godot/assets/processed/tilesets/sidewalk_512x512.png.import new file mode 100644 index 0000000..9a81528 --- /dev/null +++ b/godot/assets/processed/tilesets/sidewalk_512x512.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8sqa7vj6amn2" +path="res://.godot/imported/sidewalk_512x512.png-14776adf2872cddafc407ac4b59f47ee.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/processed/tilesets/sidewalk_512x512.png" +dest_files=["res://.godot/imported/sidewalk_512x512.png-14776adf2872cddafc407ac4b59f47ee.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/raw/residential.png b/godot/assets/raw/residential.png similarity index 100% rename from assets/raw/residential.png rename to godot/assets/raw/residential.png diff --git a/godot/assets/raw/residential.png.import b/godot/assets/raw/residential.png.import new file mode 100644 index 0000000..c9bc454 --- /dev/null +++ b/godot/assets/raw/residential.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dm00rxmhhswnc" +path="res://.godot/imported/residential.png-262e0878b74a2d5b43ab285d83624e92.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/raw/residential.png" +dest_files=["res://.godot/imported/residential.png-262e0878b74a2d5b43ab285d83624e92.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/raw/sidewalk.png b/godot/assets/raw/sidewalk.png similarity index 100% rename from assets/raw/sidewalk.png rename to godot/assets/raw/sidewalk.png diff --git a/godot/assets/raw/sidewalk.png.import b/godot/assets/raw/sidewalk.png.import new file mode 100644 index 0000000..200b3d0 --- /dev/null +++ b/godot/assets/raw/sidewalk.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5gsu4gxtaxrw" +path="res://.godot/imported/sidewalk.png-2c383e30cad907bc6b4c98e9b24edac2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/raw/sidewalk.png" +dest_files=["res://.godot/imported/sidewalk.png-2c383e30cad907bc6b4c98e9b24edac2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/godot/car.gd b/godot/car.gd index 4787826..10f825e 100644 --- a/godot/car.gd +++ b/godot/car.gd @@ -3,7 +3,7 @@ extends RigidBody2D @onready var cam = $Camera2D # zoom range: from close-in to far-out -var min_zoom = Vector2(1.0, 1.0) +var min_zoom = Vector2(0.5, 0.5) var max_zoom = Vector2(0.2, 0.2) var max_speed = 5000.0 # max expected speed diff --git a/godot/car.tscn b/godot/car.tscn index 7dbd2fb..f481f3f 100644 --- a/godot/car.tscn +++ b/godot/car.tscn @@ -3,7 +3,7 @@ [ext_resource type="Texture2D" uid="uid://cds01w4trqeei" path="res://assets/police_car.png" id="1_7822p"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_37kl0"] -size = Vector2(631, 1429) +size = Vector2(604, 1300) [node name="Car" type="Car"] mass = 1300.0 @@ -11,9 +11,9 @@ inertia = 5e+07 [node name="CollisionShape2D" type="CollisionShape2D" parent="."] position = Vector2(0.5, -0.5) -scale = Vector2(0.5, 0.5) +scale = Vector2(0.25, 0.25) shape = SubResource("RectangleShape2D_37kl0") [node name="Sprite2D" type="Sprite2D" parent="."] -scale = Vector2(0.5, 0.5) +scale = Vector2(0.25, 0.25) texture = ExtResource("1_7822p") diff --git a/godot/game.tscn b/godot/game.tscn index 0c5b949..54faee4 100644 --- a/godot/game.tscn +++ b/godot/game.tscn @@ -1,39 +1,21 @@ -[gd_scene load_steps=5 format=3 uid="uid://7713s03g7nxw"] +[gd_scene load_steps=3 format=3 uid="uid://7713s03g7nxw"] [ext_resource type="PackedScene" uid="uid://ukvcgdjxtkqw" path="res://car.tscn" id="1_80nbo"] -[ext_resource type="PackedScene" uid="uid://drkq82mmv8ofa" path="res://test_track.tscn" id="1_e2o6t"] -[ext_resource type="Script" uid="uid://bic64vi6lpelp" path="res://car.gd" id="4_7jktm"] -[ext_resource type="Texture2D" uid="uid://bxwhirvk4jjpl" path="res://assets/textures/curb_1.png" id="4_fc0e3"] +[ext_resource type="PackedScene" uid="uid://c38ikiqaivhh5" path="res://map.tscn" id="1_feb5d"] [node name="Node2D" type="Node2D"] -[node name="Node2D" parent="." instance=ExtResource("1_e2o6t")] -position = Vector2(1024, -7680) -rotation = 1.57079 - -[node name="Node2D2" parent="." instance=ExtResource("1_e2o6t")] -position = Vector2(1024, -17920) -rotation = 1.57079 - -[node name="Node2D3" parent="." instance=ExtResource("1_e2o6t")] -position = Vector2(1024, -28160) -rotation = 1.57079 - -[node name="Curb1" type="Sprite2D" parent="."] -position = Vector2(-1024, 1022.44) -rotation = 1.57079 -scale = Vector2(1.99747, 0.147406) -texture = ExtResource("4_fc0e3") +[node name="test" parent="." instance=ExtResource("1_feb5d")] +position = Vector2(-2048, -2560) [node name="Car" parent="." instance=ExtResource("1_80nbo")] controlled_by_player = true -script = ExtResource("4_7jktm") [node name="Camera2D" type="Camera2D" parent="Car"] position = Vector2(0, -1) scale = Vector2(0.1, 0.1) -zoom = Vector2(0.36, 0.36) +zoom = Vector2(0.5, 0.5) [node name="Car2" parent="." instance=ExtResource("1_80nbo")] -position = Vector2(512, -1536) -rotation = -1.0472 +position = Vector2(-512, -1536) +rotation = -1.83259 diff --git a/godot/levels/Level_0.scn b/godot/levels/Level_0.scn new file mode 100644 index 0000000..d16a660 Binary files /dev/null and b/godot/levels/Level_0.scn differ diff --git a/godot/map.tscn b/godot/map.tscn new file mode 100644 index 0000000..8a3b900 --- /dev/null +++ b/godot/map.tscn @@ -0,0 +1,5 @@ +[gd_scene load_steps=2 format=3 uid="uid://c38ikiqaivhh5"] + +[ext_resource type="PackedScene" uid="uid://bydclumuwqch8" path="res://test.ldtk" id="1_c7s6e"] + +[node name="test" instance=ExtResource("1_c7s6e")] diff --git a/godot/project.godot b/godot/project.godot index fa4c97a..3187622 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -15,6 +15,10 @@ run/main_scene="uid://7713s03g7nxw" config/features=PackedStringArray("4.4", "Forward Plus") config/icon="res://icon.svg" +[editor_plugins] + +enabled=PackedStringArray("res://addons/ldtk-importer/plugin.cfg") + [input] move_forward={ diff --git a/ldtk/test.ldtk b/godot/test.ldtk similarity index 99% rename from ldtk/test.ldtk rename to godot/test.ldtk index bcb9e56..daf27b8 100644 --- a/ldtk/test.ldtk +++ b/godot/test.ldtk @@ -226,7 +226,7 @@ "__cHei": 4, "identifier": "Sidewalk_128x128", "uid": 1, - "relPath": "../assets/processed/tilesets/sidewalk_128x128.png", + "relPath": "assets/processed/tilesets/sidewalk_128x128.png", "embedAtlas": null, "pxWid": 128, "pxHei": 128, @@ -245,7 +245,7 @@ "__cHei": 1, "identifier": "Residential", "uid": 6, - "relPath": "../assets/raw/residential.png", + "relPath": "assets/raw/residential.png", "embedAtlas": null, "pxWid": 1024, "pxHei": 1024, @@ -292,7 +292,7 @@ "__pxTotalOffsetX": 0, "__pxTotalOffsetY": 0, "__tilesetDefUid": 6, - "__tilesetRelPath": "../assets/raw/residential.png", + "__tilesetRelPath": "assets/raw/residential.png", "iid": "2baa2510-3740-11f0-80fd-75b2b5fa7dd7", "levelId": 0, "layerDefUid": 9, @@ -317,7 +317,7 @@ "__pxTotalOffsetX": 0, "__pxTotalOffsetY": 0, "__tilesetDefUid": 1, - "__tilesetRelPath": "../assets/processed/tilesets/sidewalk_128x128.png", + "__tilesetRelPath": "assets/processed/tilesets/sidewalk_128x128.png", "iid": "c0f40ef0-3740-11f0-80fd-29ce8cc19ef6", "levelId": 0, "layerDefUid": 2, diff --git a/godot/test.ldtk.import b/godot/test.ldtk.import new file mode 100644 index 0000000..974b54b --- /dev/null +++ b/godot/test.ldtk.import @@ -0,0 +1,37 @@ +[remap] + +importer="ldtk.import" +type="PackedScene" +uid="uid://bydclumuwqch8" +path="res://.godot/imported/test.ldtk-e10b9543a89b321d04f19d9114d464c5.scn" + +[deps] + +files=["res:///tilesets/tileset_1024px.res", "res:///tilesets/tileset_32px.res", "res:///levels/Level_0.scn", "res://.godot/imported/test.ldtk-e10b9543a89b321d04f19d9114d464c5.scn"] + +source_file="res://test.ldtk" +dest_files=["res://.godot/imported/test.ldtk-e10b9543a89b321d04f19d9114d464c5.scn", "res:///tilesets/tileset_1024px.res", "res:///tilesets/tileset_32px.res", "res:///levels/Level_0.scn", "res://.godot/imported/test.ldtk-e10b9543a89b321d04f19d9114d464c5.scn"] + +[params] + +World="" +group_world_layers=false +Level="" +pack_levels=true +Layer="" +layers_always_visible=false +Tileset="" +tileset_custom_data=false +integer_grid_tilesets=false +atlas_texture_type=0 +Entity="" +resolve_entityrefs=true +use_entity_placeholders=false +Post Import="" +tileset_post_import="" +entities_post_import="" +level_post_import="" +world_post_import="" +Debug="" +force_tileset_reimport=false +verbose_output=false diff --git a/godot/tilesets/tileset_1024px.res b/godot/tilesets/tileset_1024px.res new file mode 100644 index 0000000..bca8a6e Binary files /dev/null and b/godot/tilesets/tileset_1024px.res differ diff --git a/godot/tilesets/tileset_32px.res b/godot/tilesets/tileset_32px.res new file mode 100644 index 0000000..cce1cd2 Binary files /dev/null and b/godot/tilesets/tileset_32px.res differ diff --git a/justfile b/justfile index a169133..b778cd1 100644 --- a/justfile +++ b/justfile @@ -36,10 +36,10 @@ rip-youtube-audio URL: # Listen to the radio radio: - mpv --shuffle radio/ + mpv --shuffle audio/vehicle_radio # Run assets pipeline process-assets: #!/usr/bin/env sh - cd assets + cd godot/assets sh pipeline.sh