1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-13 01:03:07 +00:00

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.
This commit is contained in:
Emmanouil Papadeas 2025-02-09 03:34:10 +02:00
parent f2f465a111
commit 34ef0d4d15
5 changed files with 118 additions and 12 deletions

View file

@ -3551,3 +3551,11 @@ msgstr ""
#: src/UI/TilesPanel.tscn #: src/UI/TilesPanel.tscn
msgid "Show empty tile:" msgid "Show empty tile:"
msgstr "" msgstr ""
#: src/UI/TilesPanel.tscn
msgid "Tile properties"
msgstr ""
#: src/UI/TilesPanel.tscn
msgid "Probability:"
msgstr ""

View file

@ -287,7 +287,10 @@ func open_pxo_file(path: String, is_backup := false, replace_empty := true) -> v
var image := Image.create_from_data( var image := Image.create_from_data(
tile_size.x, tile_size.y, false, new_project.get_image_format(), image_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(): for cel in new_project.get_all_pixel_cels():
if cel is CelTileMap: if cel is CelTileMap:
cel.find_times_used_of_tiles() cel.find_times_used_of_tiles()

View file

@ -29,6 +29,8 @@ class Tile:
var image: Image var image: Image
## The amount of tiles this tile is being used in tilemaps. ## The amount of tiles this tile is being used in tilemaps.
var times_used := 1 var times_used := 1
## The relative probability of this tile appearing when drawing random tiles.
var probability := 1.0
func _init(_image: Image) -> void: func _init(_image: Image) -> void:
image = _image image = _image
@ -38,6 +40,14 @@ class Tile:
func can_be_removed() -> bool: func can_be_removed() -> bool:
return times_used <= 0 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: func _init(_tile_size: Vector2i, _name := "", add_empty_tile := true) -> void:
tile_size = _tile_size tile_size = _tile_size
@ -150,10 +160,31 @@ func find_using_layers(project: Project) -> Array[LayerTileMap]:
return tilemaps 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], ## Serializes the data of this class into the form of a [Dictionary],
## which is used so the data can be stored in pxo files. ## which is used so the data can be stored in pxo files.
func serialize() -> Dictionary: 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, ## Deserializes the data of a given [member dict] [Dictionary] into class data,
@ -161,6 +192,16 @@ func serialize() -> Dictionary:
func deserialize(dict: Dictionary) -> void: func deserialize(dict: Dictionary) -> void:
name = dict.get("name", name) name = dict.get("name", name)
tile_size = str_to_var("Vector2i" + dict.get("tile_size")) 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], ## 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 := {} var dict := {}
for tile in tiles: for tile in tiles:
var image_data := tile.image.get_data() 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 return dict
@ -180,9 +221,10 @@ func deserialize_undo_data(dict: Dictionary, cel: CelTileMap) -> void:
for image: Image in dict: for image: Image in dict:
var tile_data = dict[image] var tile_data = dict[image]
var buffer_size := tile_data[1] as int 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) 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) image.set_data(tile_size.x, tile_size.y, false, image.get_format(), image_data)
tiles[i] = Tile.new(image) tiles[i] = Tile.new(image)
tiles[i].times_used = tile_data[2] tiles[i].deserialize(tile_dictionary)
i += 1 i += 1
updated.emit(cel, -1) updated.emit(cel, -1)

View file

@ -24,7 +24,9 @@ static var selected_tile_index := 0:
selected_tiles = [value] selected_tiles = [value]
_call_update_brushes() _call_update_brushes()
get: 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 selected_tiles: Array[int] = [0]
static var is_flipped_h := false: static var is_flipped_h := false:
set(value): set(value):
@ -38,7 +40,7 @@ static var is_transposed := false:
set(value): set(value):
is_transposed = value is_transposed = value
_call_update_brushes() _call_update_brushes()
var current_tileset: TileSetCustom static var current_tileset: TileSetCustom
var button_size := 36: var button_size := 36:
set(value): set(value):
if button_size == value: if button_size == value:
@ -61,6 +63,8 @@ var tile_index_menu_popped := 0
@onready var options: Popup = $Options @onready var options: Popup = $Options
@onready var tile_size_slider: ValueSlider = %TileSizeSlider @onready var tile_size_slider: ValueSlider = %TileSizeSlider
@onready var tile_button_popup_menu: PopupMenu = $TileButtonPopupMenu @onready var tile_button_popup_menu: PopupMenu = $TileButtonPopupMenu
@onready var tile_properties: AcceptDialog = $TileProperties
@onready var tile_probability_slider: ValueSlider = %TileProbabilitySlider
func _ready() -> void: 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_button_popup_menu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2.ONE))
tile_index_menu_popped = index tile_index_menu_popped = index
tile_button_popup_menu.set_item_disabled( 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: func _on_tile_button_popup_menu_index_pressed(index: int) -> void:
if tile_index_menu_popped == 0: if index == 0: # Properties
return tile_probability_slider.value = current_tileset.tiles[tile_index_menu_popped].probability
if index == 0: # Delete 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(): if current_tileset.tiles[tile_index_menu_popped].can_be_removed():
var undo_data := current_tileset.serialize_undo_data() var undo_data := current_tileset.serialize_undo_data()
current_tileset.tiles.remove_at(tile_index_menu_popped) 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_undo_method(Global.undo_or_redo.bind(true))
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false)) project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
project.undo_redo.commit_action() project.undo_redo.commit_action()
func _on_tile_probability_slider_value_changed(value: float) -> void:
current_tileset.tiles[tile_index_menu_popped].probability = value

View file

@ -343,8 +343,49 @@ button_pressed = true
text = "On" text = "On"
[node name="TileButtonPopupMenu" type="PopupMenu" parent="."] [node name="TileButtonPopupMenu" type="PopupMenu" parent="."]
item_count = 1 item_count = 2
item_0/text = "Delete" 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="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]] [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="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="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="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"]