From 34ef0d4d15b611ba76e4aa345c5c5a8314d087f5 Mon Sep 17 00:00:00 2001 From: Emmanouil Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 9 Feb 2025 03:34:10 +0200 Subject: [PATCH] Add tile properties window, allowing you to change each tile's probability More options can be added there in the future. Perhaps we could even add Godot tileset data, once we add Godot tileset exporting. --- Translations/Translations.pot | 8 ++++++ src/Autoload/OpenSave.gd | 5 +++- src/Classes/TileSetCustom.gd | 48 ++++++++++++++++++++++++++++++++--- src/UI/TilesPanel.gd | 23 ++++++++++++----- src/UI/TilesPanel.tscn | 46 +++++++++++++++++++++++++++++++-- 5 files changed, 118 insertions(+), 12 deletions(-) diff --git a/Translations/Translations.pot b/Translations/Translations.pot index 6fdade8c6..3d7396c58 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -3551,3 +3551,11 @@ msgstr "" #: src/UI/TilesPanel.tscn msgid "Show empty tile:" msgstr "" + +#: src/UI/TilesPanel.tscn +msgid "Tile properties" +msgstr "" + +#: src/UI/TilesPanel.tscn +msgid "Probability:" +msgstr "" diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 97397a6e8..044d50245 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -287,7 +287,10 @@ func open_pxo_file(path: String, is_backup := false, replace_empty := true) -> v var image := Image.create_from_data( tile_size.x, tile_size.y, false, new_project.get_image_format(), image_data ) - tileset.add_tile(image, null, 0) + if j > tileset.tiles.size() - 1: + tileset.add_tile(image, null, 0) + else: + tileset.tiles[j].image = image for cel in new_project.get_all_pixel_cels(): if cel is CelTileMap: cel.find_times_used_of_tiles() diff --git a/src/Classes/TileSetCustom.gd b/src/Classes/TileSetCustom.gd index 39eb7dbcb..a59a3c79c 100644 --- a/src/Classes/TileSetCustom.gd +++ b/src/Classes/TileSetCustom.gd @@ -29,6 +29,8 @@ class Tile: var image: Image ## The amount of tiles this tile is being used in tilemaps. var times_used := 1 + ## The relative probability of this tile appearing when drawing random tiles. + var probability := 1.0 func _init(_image: Image) -> void: image = _image @@ -38,6 +40,14 @@ class Tile: func can_be_removed() -> bool: return times_used <= 0 + func serialize() -> Dictionary: + return {"times_used": times_used, "probability": probability} + + func deserialize(dict: Dictionary, skip_times_used := false) -> void: + if not skip_times_used: + times_used = dict.get("times_used", times_used) + probability = dict.get("probability", probability) + func _init(_tile_size: Vector2i, _name := "", add_empty_tile := true) -> void: tile_size = _tile_size @@ -150,10 +160,31 @@ func find_using_layers(project: Project) -> Array[LayerTileMap]: return tilemaps +func pick_random_tile(selected_tile_indices: Array[int]) -> int: + if selected_tile_indices.is_empty(): + for i in tiles.size(): + selected_tile_indices.append(i) + var sum := 0.0 + for i in selected_tile_indices: + sum += tiles[i].probability + var rand := randf_range(0.0, sum) + var current := 0.0 + for i in selected_tile_indices: + current += tiles[i].probability + if current >= rand: + return i + return selected_tile_indices[0] + + ## Serializes the data of this class into the form of a [Dictionary], ## which is used so the data can be stored in pxo files. func serialize() -> Dictionary: - return {"name": name, "tile_size": tile_size, "tile_amount": tiles.size()} + var dict := {"name": name, "tile_size": tile_size, "tile_amount": tiles.size()} + var tile_data := {} + for i in tiles.size(): + tile_data[i] = tiles[i].serialize() + dict["tile_data"] = tile_data + return dict ## Deserializes the data of a given [member dict] [Dictionary] into class data, @@ -161,6 +192,16 @@ func serialize() -> Dictionary: func deserialize(dict: Dictionary) -> void: name = dict.get("name", name) tile_size = str_to_var("Vector2i" + dict.get("tile_size")) + var tile_data := dict.get("tile_data", {}) as Dictionary + for i_str in tile_data: + var i := int(i_str) + var tile: Tile + if i > tiles.size() - 1: + tile = Tile.new(null) + tiles.append(tile) + else: + tile = tiles[i] + tile.deserialize(tile_data[i_str], true) ## Serializes the data of each tile in [member tiles] into the form of a [Dictionary], @@ -169,7 +210,7 @@ func serialize_undo_data() -> Dictionary: var dict := {} for tile in tiles: var image_data := tile.image.get_data() - dict[tile.image] = [image_data.compress(), image_data.size(), tile.times_used] + dict[tile.image] = [image_data.compress(), image_data.size(), tile.serialize()] return dict @@ -180,9 +221,10 @@ func deserialize_undo_data(dict: Dictionary, cel: CelTileMap) -> void: for image: Image in dict: var tile_data = dict[image] var buffer_size := tile_data[1] as int + var tile_dictionary := tile_data[2] as Dictionary var image_data := (tile_data[0] as PackedByteArray).decompress(buffer_size) image.set_data(tile_size.x, tile_size.y, false, image.get_format(), image_data) tiles[i] = Tile.new(image) - tiles[i].times_used = tile_data[2] + tiles[i].deserialize(tile_dictionary) i += 1 updated.emit(cel, -1) diff --git a/src/UI/TilesPanel.gd b/src/UI/TilesPanel.gd index b85ef490e..dfed4dfc9 100644 --- a/src/UI/TilesPanel.gd +++ b/src/UI/TilesPanel.gd @@ -24,7 +24,9 @@ static var selected_tile_index := 0: selected_tiles = [value] _call_update_brushes() get: - return selected_tiles.pick_random() + if is_instance_valid(current_tileset): + return current_tileset.pick_random_tile(selected_tiles) + return selected_tiles[0] static var selected_tiles: Array[int] = [0] static var is_flipped_h := false: set(value): @@ -38,7 +40,7 @@ static var is_transposed := false: set(value): is_transposed = value _call_update_brushes() -var current_tileset: TileSetCustom +static var current_tileset: TileSetCustom var button_size := 36: set(value): if button_size == value: @@ -61,6 +63,8 @@ var tile_index_menu_popped := 0 @onready var options: Popup = $Options @onready var tile_size_slider: ValueSlider = %TileSizeSlider @onready var tile_button_popup_menu: PopupMenu = $TileButtonPopupMenu +@onready var tile_properties: AcceptDialog = $TileProperties +@onready var tile_probability_slider: ValueSlider = %TileProbabilitySlider func _ready() -> void: @@ -197,7 +201,7 @@ func _on_tile_button_gui_input(event: InputEvent, index: int) -> void: tile_button_popup_menu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2.ONE)) tile_index_menu_popped = index tile_button_popup_menu.set_item_disabled( - 0, not current_tileset.tiles[index].can_be_removed() + 1, not current_tileset.tiles[index].can_be_removed() ) @@ -277,9 +281,12 @@ func _on_show_empty_tile_toggled(toggled_on: bool) -> void: func _on_tile_button_popup_menu_index_pressed(index: int) -> void: - if tile_index_menu_popped == 0: - return - if index == 0: # Delete + if index == 0: # Properties + tile_probability_slider.value = current_tileset.tiles[tile_index_menu_popped].probability + tile_properties.popup_centered() + elif index == 1: # Delete + if tile_index_menu_popped == 0: + return if current_tileset.tiles[tile_index_menu_popped].can_be_removed(): var undo_data := current_tileset.serialize_undo_data() current_tileset.tiles.remove_at(tile_index_menu_popped) @@ -295,3 +302,7 @@ func _on_tile_button_popup_menu_index_pressed(index: int) -> void: project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true)) project.undo_redo.add_do_method(Global.undo_or_redo.bind(false)) project.undo_redo.commit_action() + + +func _on_tile_probability_slider_value_changed(value: float) -> void: + current_tileset.tiles[tile_index_menu_popped].probability = value diff --git a/src/UI/TilesPanel.tscn b/src/UI/TilesPanel.tscn index e77ac7312..1ea9b8f40 100644 --- a/src/UI/TilesPanel.tscn +++ b/src/UI/TilesPanel.tscn @@ -343,8 +343,49 @@ button_pressed = true text = "On" [node name="TileButtonPopupMenu" type="PopupMenu" parent="."] -item_count = 1 -item_0/text = "Delete" +item_count = 2 +item_0/text = "Properties" +item_1/text = "Delete" +item_1/id = 1 + +[node name="TileProperties" type="AcceptDialog" parent="."] +title = "Tile properties" +size = Vector2i(300, 200) + +[node name="GridContainer" type="GridContainer" parent="TileProperties"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = -8.0 +offset_bottom = -49.0 +grow_horizontal = 2 +grow_vertical = 2 +columns = 2 + +[node name="TileProbabilityLabel" type="Label" parent="TileProperties/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Probability:" + +[node name="TileProbabilitySlider" type="TextureProgressBar" parent="TileProperties/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +focus_mode = 2 +mouse_default_cursor_shape = 2 +theme_type_variation = &"ValueSlider" +max_value = 10.0 +step = 0.001 +value = 1.0 +allow_greater = true +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +script = ExtResource("10_wfr6s") [connection signal="toggled" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/PlaceTiles" to="." method="_on_place_tiles_toggled"] [connection signal="pressed" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/RotateLeftButton" to="." method="_on_rotate_pressed" binds= [false]] @@ -358,3 +399,4 @@ item_0/text = "Delete" [connection signal="value_changed" from="Options/MarginContainer/ScrollContainer/GridContainer/TileSizeSlider" to="." method="_on_tile_size_slider_value_changed"] [connection signal="toggled" from="Options/MarginContainer/ScrollContainer/GridContainer/ShowEmptyTile" to="." method="_on_show_empty_tile_toggled"] [connection signal="index_pressed" from="TileButtonPopupMenu" to="." method="_on_tile_button_popup_menu_index_pressed"] +[connection signal="value_changed" from="TileProperties/GridContainer/TileProbabilitySlider" to="." method="_on_tile_probability_slider_value_changed"]