└── addons └── tile_spawner ├── .gitignore ├── LICENSE ├── README.md ├── icon_bake.png ├── icon_bake.png.import ├── plugin.cfg ├── tile_spawner.gd ├── tile_spawner_controls.tscn ├── tile_spawner_icon.png ├── tile_spawner_icon.png.import ├── tile_spawner_logo.png ├── tile_spawner_mapping.gd ├── tile_spawner_mapping_icon.png ├── tile_spawner_mapping_icon.png.import └── tile_spawner_plugin.gd /addons/tile_spawner/.gitignore: -------------------------------------------------------------------------------- 1 | *.svg 2 | *.svg.import 3 | -------------------------------------------------------------------------------- /addons/tile_spawner/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Daniel Snider 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /addons/tile_spawner/README.md: -------------------------------------------------------------------------------- 1 | # Tile Spawner 2 | 3 | Tile Spawner lets you spawn node instances from a TileMap. Manually placing hundreds of assets is slow and error-prone. TileMaps are easy to add, remove, and manipulate tiles. Tile Spawner is a node that lets you map tiles from a TileSet to scenes in your project and preprocess a TileMap into Node instances as if you had placed them yourself. 4 | 5 | Godot can get sluggish when placing lots of instances. Baking ahead-of-time allows you to pay that cost once instead of each time a user launches your scene. 6 | 7 | # How to use 8 | 9 | To use Tile Spawner, create a JSON mapping between the tiles in your TileMap and scene paths in your project. Then point a Tile Spawner node to a TileMap and bake. See below for further details. 10 | 11 | ## TileSet 12 | 13 | You need a tileset to represent the things you want to spawn. Usually these things are objects and enemies in your game, but can be anything you need that is more complicated than a Sprite + StaticBody + NavigationMesh (which is all that is offered by a TileMap). 14 | 15 | The tiles should be named well so that they'll be easy to reference in the mapping json. 16 | 17 | ## Mapping file 18 | 19 | Create a JSON file e.g. 20 | 21 | ``` 22 | { 23 | "Coin": { 24 | "scene": "res://objects/coin.tscn" 25 | }, 26 | "Bricks": { 27 | "scene": "res://objects/bricks.tscn" 28 | }, 29 | "Turtle": { 30 | "scene": "res://enemies/turtle.tscn" 31 | } 32 | } 33 | ``` 34 | 35 | The keys are the *name of the tile in the TileSet* and the value under the `scene` key is the resource path to the scene that Tile Spawner will intance when spawning. 36 | 37 | ## TileMap 38 | 39 | In your scene, add a TileMap node using the tileset created and mapped above. Paint some tiles. 40 | 41 | ## TileSpawner 42 | 43 | In your scene, add a TileSpawner node. Do not add it as a child of the TileMap. Select the TileSpawner and change the following properties: 44 | 45 | * Assign the "Source Tilemap" to the above created TileMap. 46 | * Set the mapping to the above created mapping json file. 47 | * Press the `Bake tiles to nodes` on the control bar. 48 | 49 | From here you can repeatedly modify the TileMap and bake. 50 | 51 | # TileSpawner properties 52 | 53 | ## Source Tilemap 54 | 55 | Set this to the TileMap node that you want to spawn nodes from. 56 | 57 | ## Mapping 58 | 59 | Set this to a JSON file that describes which tiles spawn which scenes. See the above tutorial for the required format. 60 | 61 | ## Clear Children Before Baking 62 | 63 | If set, the TileSpawner will clear its children before spawning nodes from a tilemap. Generally this is something you'll want to leave enabled as it makes it easier to quickly adjust the tilemap then bake multiple times. 64 | 65 | ## Spawn at Runtime 66 | 67 | If set, the TileSpawner will bake the nodes when the scene loads. Since baking takes a significant amount of time if the number of nodes is large enough, you should only use this while rapidly editing a TileMap. 68 | 69 | Don't ship with this checked; instead bake the nodes with the `Bake tiles to nodes` button in the control bar. 70 | 71 | ## Target Node 72 | 73 | By default, nodes are spawned as children of the TileSpawner. This allows you to change where the nodes will spawn instead. 74 | 75 | ## Grid Alignment 76 | 77 | In some cases where pixel-perfection matters and you have odd tile sizes, you might need to snap the spawned object coordinates to whole numbers. 78 | 79 | * `None` will not modify the coordinates 80 | * `Round` will round the coordinates to the nearest whole number 81 | * `Floor` will round the coordinates down to the nearest whole number 82 | * `Up` will round the coordinates up to the nearest whole number 83 | * `Trunacte` will remove anything past the decimal point in the coordinates 84 | -------------------------------------------------------------------------------- /addons/tile_spawner/icon_bake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qtip/tile-spawner/04f34bd46d638bf8ad4ee45b436e1f8021e11996/addons/tile_spawner/icon_bake.png -------------------------------------------------------------------------------- /addons/tile_spawner/icon_bake.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon_bake.png-4ad30a2a0d8454bae144d3d3b4379168.stex" 6 | 7 | [deps] 8 | 9 | source_file="res://addons/tile_spawner/icon_bake.png" 10 | source_md5="f0b8f63f79b69217b105c0d6442625f6" 11 | 12 | dest_files=[ "res://.import/icon_bake.png-4ad30a2a0d8454bae144d3d3b4379168.stex" ] 13 | dest_md5="2a39922b7e67d51d5ba49b3a64708d92" 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/normal_map=0 21 | flags/repeat=0 22 | flags/filter=true 23 | flags/mipmaps=false 24 | flags/anisotropic=false 25 | flags/srgb=2 26 | process/fix_alpha_border=true 27 | process/premult_alpha=false 28 | process/HDR_as_SRGB=false 29 | stream=false 30 | size_limit=0 31 | detect_3d=true 32 | svg/scale=1.0 33 | -------------------------------------------------------------------------------- /addons/tile_spawner/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Tile Spawner" 4 | description="Spawn node instances from a Tilemap" 5 | author="Daniel Snider" 6 | version="1.0.0" 7 | script="tile_spawner_plugin.gd" 8 | 9 | -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node2D 3 | 4 | const TileSpawnerPlugin = preload("./tile_spawner_plugin.gd") 5 | 6 | export(NodePath) var source_tilemap = null setget set_source_tilemap_path 7 | export(String, FILE, "*.json") var mapping 8 | export(bool) var clear_children_before_baking = true 9 | export(bool) var spawn_at_runtime = false 10 | export(NodePath) var target_node = "." setget set_target_node_path 11 | export(int, "None", "Round", "Floor", "Ceiling", "Truncate") var grid_alignment = TileSpawnerPlugin.Align.NONE 12 | 13 | var _source_tilemap = null 14 | var _target_node = null 15 | 16 | func _enter_tree(): 17 | # Mark this node to help fetch it later 18 | add_to_group(TileSpawnerPlugin.UUID_TILE_SPAWNER) 19 | 20 | func _exit_tree(): 21 | remove_from_group(TileSpawnerPlugin.UUID_TILE_SPAWNER) 22 | 23 | func _ready(): 24 | _update_source_tilemap_field() 25 | _update_target_node_field() 26 | 27 | if _source_tilemap != null and not Engine.is_editor_hint(): 28 | # At runtime, hide the source tilemap and show the new nodes so that you 29 | # can leave the tilemap showing while editing rapidly 30 | _source_tilemap.visible = false 31 | visible = true 32 | 33 | # Spawn nodes from tilemap at runtime desired 34 | if spawn_at_runtime: 35 | TileSpawnerPlugin.spawn_from_tilemap(get_tree(), self) 36 | 37 | # source_tilemap helpers 38 | 39 | func get_source_tilemap(): 40 | return _source_tilemap 41 | 42 | func set_source_tilemap_path(path): 43 | source_tilemap = path 44 | if is_inside_tree(): 45 | _update_source_tilemap_field() 46 | 47 | func _update_source_tilemap_field(): 48 | if source_tilemap == null: 49 | # Unset the node if nothing is set for the node path 50 | _source_tilemap = null 51 | return 52 | 53 | var node = get_node(source_tilemap) 54 | if node == null or not node is TileMap: 55 | # Fail if the node is the wrong type 56 | print("Error: Source TileMap must be a TileMap node!") 57 | source_tilemap = null 58 | return 59 | 60 | # Update the node 61 | _source_tilemap = node 62 | 63 | # target_node helpers 64 | 65 | func get_target_node(): 66 | return _target_node 67 | 68 | func set_target_node_path(path): 69 | target_node = path 70 | if is_inside_tree(): 71 | _update_target_node_field() 72 | 73 | func _update_target_node_field(): 74 | if target_node == null: 75 | # Unset the node if nothing is set for the node path 76 | _target_node = null 77 | return 78 | 79 | var node = get_node(target_node) 80 | if not node is CanvasItem: 81 | # Fail if the node is the wrong type 82 | print("Error: Target node must be a CanvasItem!") 83 | target_node = null 84 | return 85 | 86 | # Update the node 87 | _target_node = node -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner_controls.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/tile_spawner/icon_bake.png" type="Texture" id=1] 4 | 5 | [node name="Tile spawner controls" type="HBoxContainer" index="0"] 6 | 7 | anchor_left = 0.0 8 | anchor_top = 0.0 9 | anchor_right = 0.0 10 | anchor_bottom = 0.0 11 | rect_pivot_offset = Vector2( 0, 0 ) 12 | rect_clip_content = false 13 | mouse_filter = 1 14 | mouse_default_cursor_shape = 0 15 | size_flags_horizontal = 1 16 | size_flags_vertical = 1 17 | alignment = 0 18 | _sections_unfolded = [ "Size Flags" ] 19 | 20 | [node name="v_separator" type="VSeparator" parent="." index="0"] 21 | 22 | anchor_left = 0.0 23 | anchor_top = 0.0 24 | anchor_right = 0.0 25 | anchor_bottom = 0.0 26 | margin_right = 4.0 27 | margin_bottom = 30.0 28 | rect_pivot_offset = Vector2( 0, 0 ) 29 | rect_clip_content = false 30 | mouse_filter = 0 31 | mouse_default_cursor_shape = 0 32 | size_flags_horizontal = 1 33 | size_flags_vertical = 1 34 | 35 | [node name="bake_button" type="Button" parent="." index="1"] 36 | 37 | anchor_left = 0.0 38 | anchor_top = 0.0 39 | anchor_right = 0.0 40 | anchor_bottom = 0.0 41 | margin_left = 8.0 42 | margin_right = 169.0 43 | margin_bottom = 30.0 44 | rect_pivot_offset = Vector2( 0, 0 ) 45 | rect_clip_content = false 46 | focus_mode = 2 47 | mouse_filter = 0 48 | mouse_default_cursor_shape = 0 49 | size_flags_horizontal = 1 50 | size_flags_vertical = 1 51 | toggle_mode = false 52 | enabled_focus_mode = 2 53 | shortcut = null 54 | group = null 55 | text = "Bake tiles to nodes" 56 | icon = ExtResource( 1 ) 57 | flat = true 58 | align = 1 59 | 60 | 61 | -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qtip/tile-spawner/04f34bd46d638bf8ad4ee45b436e1f8021e11996/addons/tile_spawner/tile_spawner_icon.png -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner_icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/tile_spawner_icon.png-b250a8072d75d468c7f27bb2084ffac8.stex" 6 | 7 | [deps] 8 | 9 | source_file="res://addons/tile_spawner/tile_spawner_icon.png" 10 | source_md5="187ce8783d00995a9729881f571eb16e" 11 | 12 | dest_files=[ "res://.import/tile_spawner_icon.png-b250a8072d75d468c7f27bb2084ffac8.stex" ] 13 | dest_md5="4df50b735214f53b9b2a0b01ce9d0c0a" 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/normal_map=0 21 | flags/repeat=0 22 | flags/filter=true 23 | flags/mipmaps=false 24 | flags/anisotropic=false 25 | flags/srgb=2 26 | process/fix_alpha_border=true 27 | process/premult_alpha=false 28 | process/HDR_as_SRGB=false 29 | stream=false 30 | size_limit=0 31 | detect_3d=true 32 | svg/scale=1.0 33 | -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qtip/tile-spawner/04f34bd46d638bf8ad4ee45b436e1f8021e11996/addons/tile_spawner/tile_spawner_logo.png -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner_mapping.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | 3 | var entries = [] 4 | 5 | func _ready(): 6 | # Called every time the node is added to the scene. 7 | # Initialization here 8 | pass 9 | 10 | #func _process(delta): 11 | # # Called every frame. Delta is time since last frame. 12 | # # Update game logic here. 13 | # pass 14 | -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner_mapping_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qtip/tile-spawner/04f34bd46d638bf8ad4ee45b436e1f8021e11996/addons/tile_spawner/tile_spawner_mapping_icon.png -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner_mapping_icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/tile_spawner_mapping_icon.png-8a89bed749e409fc42d39cdd2ee6ca9e.stex" 6 | 7 | [deps] 8 | 9 | source_file="res://addons/tile_spawner/tile_spawner_mapping_icon.png" 10 | source_md5="904129ee07464558916b59231371b728" 11 | 12 | dest_files=[ "res://.import/tile_spawner_mapping_icon.png-8a89bed749e409fc42d39cdd2ee6ca9e.stex" ] 13 | dest_md5="3cbd1c8f54efd22097edb06a9f7c3bee" 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/normal_map=0 21 | flags/repeat=0 22 | flags/filter=true 23 | flags/mipmaps=false 24 | flags/anisotropic=false 25 | flags/srgb=2 26 | process/fix_alpha_border=true 27 | process/premult_alpha=false 28 | process/HDR_as_SRGB=false 29 | stream=false 30 | size_limit=0 31 | detect_3d=true 32 | svg/scale=1.0 33 | -------------------------------------------------------------------------------- /addons/tile_spawner/tile_spawner_plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | const TileSpawnerControls = preload("res://addons/tile_spawner/tile_spawner_controls.tscn") 5 | 6 | enum Align { 7 | NONE, 8 | ROUND, 9 | FLOOR, 10 | CEIL, 11 | TRUNCATE 12 | } 13 | 14 | const UUID_TILE_SPAWNER_CONTROLS = "be2d8857-0633-431f-bc92-302da269bccd" 15 | const UUID_TILE_SPAWNER = "ddfc5ec5-af82-4234-8aca-24678ee59958" 16 | 17 | func _enter_tree(): 18 | # Listen to selection changes so we can show custom buttons 19 | var selection = get_editor_interface().get_selection().connect("selection_changed", self, "selection_changed") 20 | 21 | # Register TileSpawner node 22 | add_custom_type("TileSpawner", "Node2D", load("res://addons/tile_spawner/tile_spawner.gd"), load("res://addons/tile_spawner/tile_spawner_icon.png")) 23 | 24 | # Register TileSpawnerMapping resource 25 | # add_custom_type("TileSpawnerMapping", "Resource", preload("res://addons/tile_spawner/tile_spawner_mapping.gd"), preload("res://addons/tile_spawner/tile_spawner_mapping_icon.png")) 26 | 27 | func _exit_tree(): 28 | # Clean up any UI changes 29 | remove_tile_spawner_controls() 30 | 31 | # Unregister TileSpawnerMapping resource 32 | #remove_custom_type("TileSpawnerMapping") 33 | 34 | # Unregister TileSpawner 35 | remove_custom_type("TileSpawner") 36 | 37 | # Stop listening for selection changes 38 | get_editor_interface().get_selection().disconnect("selection_changed", self, "selection_changed") 39 | 40 | func selection_changed(): 41 | # When the current selection changes, check to see what's now selected 42 | var selected_nodes = get_editor_interface().get_selection().get_selected_nodes() 43 | 44 | # Only show the controls when one node is selected. 45 | # Note: Perhaps this check isn't necessary 46 | if selected_nodes == null or selected_nodes.size() != 1: 47 | remove_tile_spawner_controls() 48 | return 49 | 50 | var related_tile_spawners = filter_related_tile_spawners(selected_nodes) 51 | if related_tile_spawners.size() > 0: 52 | add_tile_spawner_controls() 53 | else: 54 | remove_tile_spawner_controls() 55 | 56 | # Given a tile_map, find the tile_spawners that are targeting it. 57 | func get_source_tile_spawners(tile_map): 58 | var tile_spawners = [] 59 | for tile_spawner in get_tree().get_nodes_in_group(UUID_TILE_SPAWNER): 60 | if tile_spawner.get_source_tilemap() == tile_map: 61 | tile_spawners.push_back(tile_spawner) 62 | return tile_spawners 63 | 64 | # Given a list of nodes, return all related tile_spawners, either directly or 65 | # indirectly from the list 66 | func filter_related_tile_spawners(nodes): 67 | var tile_spawners = [] 68 | for node in nodes: 69 | 70 | # Add a node if it is a tile spawner 71 | if node.is_in_group(UUID_TILE_SPAWNER): 72 | tile_spawners.push_back(node) 73 | continue 74 | 75 | # Add any tile_spawners targeting this node. 76 | var source_tile_spawners = get_source_tile_spawners(node) 77 | tile_spawners = array_extend(tile_spawners, source_tile_spawners) 78 | 79 | return tile_spawners 80 | 81 | static func array_extend(array1, array2): 82 | for item in array2: 83 | array1.push_back(item) 84 | return array1 85 | 86 | func undo_spawn(tile_spawner, old_children): 87 | free_children(tile_spawner) 88 | var scene_root = get_tree().get_edited_scene_root() 89 | for child in old_children: 90 | tile_spawner.add_child(child) 91 | child.set_owner(scene_root) 92 | 93 | 94 | func bake_button_pressed(): 95 | # Get all nodes selected 96 | var selected_nodes = get_editor_interface().get_selection().get_selected_nodes() 97 | 98 | # Only bake when one node is selected. 99 | # Note: Perhaps this check isn't necessary 100 | if selected_nodes == null or selected_nodes.size() != 1: 101 | print("Error: Trying to bake with multiple nodes selected") 102 | return 103 | 104 | var related_tile_spawners = filter_related_tile_spawners(selected_nodes) 105 | if related_tile_spawners.size() <= 0: 106 | print("Error: Trying to bake without a TileSpawner or TileMap selected") 107 | return 108 | 109 | var undo_redo = get_undo_redo() 110 | undo_redo.create_action("Bake Tile Spawner") 111 | 112 | # Do the tilemap spawning 113 | for tile_spawner in related_tile_spawners: 114 | undo_redo.add_undo_method(self, "undo_spawn", tile_spawner, tile_spawner.get_children()) 115 | undo_redo.add_do_method(self, "spawn_from_tilemap", get_tree(), tile_spawner) 116 | spawn_from_tilemap(get_tree(), tile_spawner) 117 | 118 | undo_redo.commit_action() 119 | 120 | func add_tile_spawner_controls(): 121 | # If the tile spawner controls are already present, don't add them again 122 | if len(get_tree().get_nodes_in_group(UUID_TILE_SPAWNER_CONTROLS)) > 0: 123 | return 124 | 125 | # Add the controls to the editor 126 | var tile_spawner_controls = TileSpawnerControls.instance() 127 | tile_spawner_controls.add_to_group(UUID_TILE_SPAWNER_CONTROLS) 128 | add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, tile_spawner_controls) 129 | 130 | # Listen for events from the controls so we can react to them 131 | tile_spawner_controls.get_node("bake_button").connect("pressed", self, "bake_button_pressed") 132 | 133 | func remove_tile_spawner_controls(): 134 | for tile_spawner_controls in get_tree().get_nodes_in_group(UUID_TILE_SPAWNER_CONTROLS): 135 | # Remove the controls from the editor 136 | remove_control_from_container(CONTAINER_CANVAS_EDITOR_MENU, tile_spawner_controls) 137 | 138 | # Free the controls 139 | tile_spawner_controls.queue_free() 140 | 141 | static func spawn_from_tilemap(tree, tile_spawner): 142 | # Validate & get tilemap node 143 | var source_tilemap = tile_spawner.get_source_tilemap() 144 | if source_tilemap == null or not source_tilemap is TileMap: 145 | # Ensure the node select for source tilemap is correct 146 | print("Error: Source tilemap must be a TileMap!") 147 | return 148 | 149 | # Validate & get target node 150 | var target_node = tile_spawner.get_target_node() 151 | if target_node == null or not target_node is CanvasItem: 152 | # Ensure the node select for source tilemap is correct 153 | print("Error: Target node must be a CanvasItem!") 154 | return 155 | 156 | # Validate & get the mapping 157 | var mapping_path = tile_spawner.mapping 158 | var mapping_file = File.new() 159 | if mapping_path == null or not mapping_file.file_exists(mapping_path): 160 | # Make sure the mapping file exists 161 | print("Error: Mapping file for TileSpawner does not exist!") 162 | return 163 | if mapping_file.open(tile_spawner.mapping, File.READ) != OK: 164 | # Make sure the mapping file opened 165 | print("Error: Could not open the mapping file for TileSpawner!") 166 | return 167 | var mapping_json = JSON.parse(mapping_file.get_as_text()) 168 | mapping_file.close() 169 | if typeof(mapping_json.result) != TYPE_DICTIONARY: 170 | # Make sure the mapping file is formatted correctly 171 | print("Error: Mapping file for TileSpawner must be a JSON object!") 172 | return 173 | var mapping = mapping_json.result 174 | 175 | # Using the tilemap's cell_tile_origin, find an offset for each node 176 | var origin_offset = Vector2() 177 | if source_tilemap.cell_tile_origin == TileMap.TILE_ORIGIN_TOP_LEFT: 178 | origin_offset = source_tilemap.cell_size / 2 179 | elif source_tilemap.cell_tile_origin == TileMap.TILE_ORIGIN_CENTER: 180 | origin_offset = source_tilemap.cell_size 181 | elif source_tilemap.cell_tile_origin == TileMap.TILE_ORIGIN_BOTTOM_LEFT: 182 | origin_offset = Vector2(source_tilemap.cell_size.x / 2, source_tilemap.cell_size.y * 3 / 2) 183 | 184 | # Optionally, clear children in the target node 185 | if tile_spawner.clear_children_before_baking: 186 | unparent_children(target_node) 187 | 188 | # For each tile, spawn a child into the target node 189 | for cellv in source_tilemap.get_used_cells(): 190 | # Figure out the tile name for this cell 191 | var tile_set = source_tilemap.tile_set 192 | var tile_index = source_tilemap.get_cellv(cellv) 193 | var tile_name = tile_set.tile_get_name(tile_index) 194 | 195 | # Ignore tiles with no mapping 196 | if not mapping.has(tile_name): 197 | continue 198 | 199 | # Get the mapping entry for this tile name 200 | var mapping_entry = mapping[tile_name] 201 | 202 | # Using the entry, find the scene path for this tile name 203 | var scene_path = mapping_entry['scene'] 204 | 205 | # Add the child 206 | var child = spawn_child(tree.get_edited_scene_root(), target_node, scene_path) 207 | 208 | # Find the transform for the tile 209 | var orientation_transform = get_cell_orientation_transform(source_tilemap, cellv) 210 | var tile_transform = source_tilemap.global_transform * orientation_transform 211 | 212 | # Compute origin of the tile 213 | var origin = source_tilemap.global_transform.xform(source_tilemap.map_to_world(cellv) + origin_offset) 214 | # Snap origin to pixel grid 215 | origin = snap_to_pixel_grid(origin, tile_spawner.grid_alignment) 216 | tile_transform.origin = origin 217 | 218 | # Set the transform 219 | child.global_transform = tile_transform 220 | 221 | # Given a vector and an alignment type, return a new, aligned version 222 | # of that vector 223 | static func snap_to_pixel_grid(vec, align): 224 | if align == Align.ROUND: 225 | return Vector2(round(vec.x), round(vec.y)) 226 | elif align == Align.FLOOR: 227 | return Vector2(floor(vec.x), floor(vec.y)) 228 | elif align == Align.CEIL: 229 | return Vector2(ceil(vec.x), ceil(vec.y)) 230 | elif align == Align.TRUNCATE: 231 | return Vector2(int(vec.x), int(vec.y)) 232 | else: 233 | return vec 234 | 235 | # Given a TileMap and a cell coordinate, return a Transform that represents 236 | # the given rotation/mirroring etc. 237 | static func get_cell_orientation_transform(tile_map, cellv): 238 | var transform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(0, 0)) 239 | if tile_map.is_cell_transposed(cellv.x, cellv.y): 240 | var x_axis = Vector2(transform.x.x, transform.x.y) 241 | transform.x = -Vector2(transform.y.x, transform.y.y) 242 | transform.y = -x_axis 243 | if tile_map.is_cell_x_flipped(cellv.x, cellv.y): 244 | transform.x = -transform.x 245 | if tile_map.is_cell_y_flipped(cellv.x, cellv.y): 246 | transform.y = -transform.y 247 | 248 | return transform 249 | 250 | # Given a node, free all children of that node 251 | static func free_children(parent_node): 252 | for child_node in parent_node.get_children(): 253 | child_node.free() 254 | 255 | # Given a node, detach but do not free all children of that node 256 | static func unparent_children(parent_node): 257 | var nodes = [] 258 | for child_node in parent_node.get_children(): 259 | parent_node.remove_child(child_node) 260 | child_node.set_owner(null) 261 | nodes.push_back(child_node) 262 | return nodes 263 | 264 | static func spawn_child(scene_root, parent, scene_path): 265 | var node 266 | node = load(scene_path).instance() 267 | node.filename = scene_path 268 | parent.add_child(node) 269 | node.position = Vector2(0,0) 270 | node.set_owner(scene_root) 271 | node.set_name(node.name) 272 | return node 273 | --------------------------------------------------------------------------------