Implement TileMap patterns palette

This commit is contained in:
Gilles Roudière 2021-09-29 17:48:27 +02:00
parent 4387f9645b
commit 1a95f893c4
18 changed files with 1163 additions and 400 deletions

View file

@ -763,10 +763,10 @@
</method>
<method name="set_drag_forwarding">
<return type="void" />
<argument index="0" name="target" type="Node" />
<argument index="0" name="target" type="Object" />
<description>
Forwards the handling of this control's drag and drop to [code]target[/code] node.
Forwarding can be implemented in the target node similar to the methods [method _get_drag_data], [method _can_drop_data], and [method _drop_data] but with two differences:
Forwards the handling of this control's drag and drop to [code]target[/code] object.
Forwarding can be implemented in the target object similar to the methods [method _get_drag_data], [method _can_drop_data], and [method _drop_data] but with two differences:
1. The function name must be suffixed with [b]_fw[/b]
2. The function must take an extra argument that is the control doing the forwarding
[codeblocks]

View file

@ -117,6 +117,14 @@
Returns the neighboring cell to the one at coordinates [code]coords[/code], indentified by the [code]neighbor[/code] direction. This method takes into account the different layouts a TileMap can take.
</description>
</method>
<method name="get_pattern">
<return type="TileMapPattern" />
<argument index="0" name="layer" type="int" />
<argument index="1" name="coords_array" type="Vector2i[]" />
<description>
Creates a new [TileMapPattern] from the given layer and set of cells.
</description>
</method>
<method name="get_surrounding_tiles">
<return type="Vector2i[]" />
<argument index="0" name="coords" type="Vector2i" />
@ -151,6 +159,15 @@
Returns if a layer Y-sorts its tiles.
</description>
</method>
<method name="map_pattern">
<return type="Vector2i" />
<argument index="0" name="position_in_tilemap" type="Vector2i" />
<argument index="1" name="coords_in_pattern" type="Vector2i" />
<argument index="2" name="pattern" type="TileMapPattern" />
<description>
Returns for the given coodinate [code]coords_in_pattern[/code] in a [TileMapPattern] the corresponding cell coordinates if the pattern was pasted at the [code]position_in_tilemap[/code] coordinates (see [method set_pattern]). This mapping is required as in half-offset tile shapes, the mapping might not work by calculating [code]position_in_tile_map + coords_in_pattern[/code]
</description>
</method>
<method name="map_to_world" qualifiers="const">
<return type="Vector2" />
<argument index="0" name="map_position" type="Vector2i" />
@ -237,6 +254,15 @@
Sets a layers Z-index value. This Z-index is added to each tile's Z-index value.
</description>
</method>
<method name="set_pattern">
<return type="void" />
<argument index="0" name="layer" type="int" />
<argument index="1" name="position" type="Vector2i" />
<argument index="2" name="pattern" type="TileMapPattern" />
<description>
Paste the given [TileMapPattern] at the given [code]position[/code] and [code]layer[/code] in the tile map.
</description>
</method>
<method name="world_to_map" qualifiers="const">
<return type="Vector2i" />
<argument index="0" name="world_position" type="Vector2" />

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="TileMapPattern" inherits="Resource" version="4.0">
<brief_description>
Holds a pattern to be copied from or pasted into [TileMap]s.
</brief_description>
<description>
This resource holds a set of cells to help bulk manipulations of [TileMap].
A pattern always start at the [code](0,0)[/code] coordinates and cannot have cells with negative coordinates.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_cell_alternative_tile" qualifiers="const">
<return type="int" />
<argument index="0" name="coords" type="Vector2i" />
<description>
Returns the tile alternative ID of the cell at [code]coords[/code].
</description>
</method>
<method name="get_cell_atlas_coords" qualifiers="const">
<return type="Vector2i" />
<argument index="0" name="coords" type="Vector2i" />
<description>
Returns the tile atlas coordinates ID of the cell at [code]coords[/code].
</description>
</method>
<method name="get_cell_source_id" qualifiers="const">
<return type="int" />
<argument index="0" name="coords" type="Vector2i" />
<description>
Returns the tile source ID of the cell at [code]coords[/code].
</description>
</method>
<method name="get_size" qualifiers="const">
<return type="Vector2i" />
<description>
Returns the size, in cells, of the pattern.
</description>
</method>
<method name="get_used_cells" qualifiers="const">
<return type="Vector2i[]" />
<description>
Returns the list of used cell coordinates in the pattern.
</description>
</method>
<method name="has_cell" qualifiers="const">
<return type="bool" />
<argument index="0" name="coords" type="Vector2i" />
<description>
Returns whether the pattern has a tile at the given coordinates.
</description>
</method>
<method name="is_empty" qualifiers="const">
<return type="bool" />
<description>
Returns whether the pattern is empty or not.
</description>
</method>
<method name="remove_cell">
<return type="void" />
<argument index="0" name="coords" type="Vector2i" />
<argument index="1" name="arg1" type="bool" />
<description>
Remove the cell at the given coordinates.
</description>
</method>
<method name="set_cell">
<return type="void" />
<argument index="0" name="coords" type="Vector2i" />
<argument index="1" name="source_id" type="int" default="-1" />
<argument index="2" name="atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" />
<argument index="3" name="alternative_tile" type="int" default="-1" />
<description>
Sets the tile indentifiers for the cell at coordinates [code]coords[/code]. See [method TileMap.set_cell].
</description>
</method>
<method name="set_size">
<return type="void" />
<argument index="0" name="size" type="Vector2i" />
<description>
Sets the size of the pattern.
</description>
</method>
</methods>
</class>

View file

@ -46,6 +46,14 @@
Occlusion layers allow assigning occlusion polygons to atlas tiles.
</description>
</method>
<method name="add_pattern">
<return type="int" />
<argument index="0" name="pattern" type="TileMapPattern" />
<argument index="1" name="index" type="int" default="-1" />
<description>
Adds a [TileMapPattern] to be stored in the TileSet resouce. If provided, insert it at the given [code]index[/code].
</description>
</method>
<method name="add_physics_layer">
<return type="void" />
<argument index="0" name="to_position" type="int" default="-1" />
@ -154,6 +162,19 @@
Returns the occlusion layers count.
</description>
</method>
<method name="get_pattern">
<return type="TileMapPattern" />
<argument index="0" name="index" type="int" default="-1" />
<description>
Returns the [TileMapPattern] at the given [code]index[/code].
</description>
</method>
<method name="get_patterns_count">
<return type="int" />
<description>
Returns the number of [TileMapPattern] this tile set handles.
</description>
</method>
<method name="get_physics_layer_collision_layer" qualifiers="const">
<return type="int" />
<argument index="0" name="layer_index" type="int" />
@ -374,6 +395,13 @@
Removes the occlusion layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly.
</description>
</method>
<method name="remove_pattern">
<return type="void" />
<argument index="0" name="index" type="int" />
<description>
Remove the [TileMapPattern] at the given index.
</description>
</method>
<method name="remove_physics_layer">
<return type="void" />
<argument index="0" name="layer_index" type="int" />

View file

@ -173,4 +173,22 @@ public:
EditorFontPreviewPlugin();
~EditorFontPreviewPlugin();
};
class EditorTileMapPatternPreviewPlugin : public EditorResourcePreviewGenerator {
GDCLASS(EditorTileMapPatternPreviewPlugin, EditorResourcePreviewGenerator);
mutable SafeFlag preview_done;
void _preview_done(const Variant &p_udata);
protected:
static void _bind_methods();
public:
virtual bool handles(const String &p_type) const override;
virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override;
EditorTileMapPatternPreviewPlugin();
~EditorTileMapPatternPreviewPlugin();
};
#endif // EDITORPREVIEWPLUGINS_H

View file

@ -36,6 +36,7 @@
#include "editor/editor_scale.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#include "scene/2d/camera_2d.h"
#include "scene/gui/center_container.h"
#include "scene/gui/split_container.h"
@ -43,31 +44,11 @@
#include "core/math/geometry_2d.h"
#include "core/os/keyboard.h"
void TileMapEditorTilesPlugin::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED:
select_tool_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons")));
paint_tool_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
line_tool_button->set_icon(get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons")));
rect_tool_button->set_icon(get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons")));
bucket_tool_button->set_icon(get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons")));
picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
missing_atlas_texture_icon = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons"));
break;
case NOTIFICATION_VISIBILITY_CHANGED:
_stop_dragging();
break;
}
}
void TileMapEditorTilesPlugin::tile_set_changed() {
_update_fix_selected_and_hovered();
_update_tile_set_sources_list();
_update_bottom_panel();
_update_source_display();
_update_patterns_list();
}
void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) {
@ -125,8 +106,19 @@ void TileMapEditorTilesPlugin::_update_toolbar() {
}
}
Control *TileMapEditorTilesPlugin::get_toolbar() const {
return toolbar;
Vector<TileMapEditorPlugin::TabData> TileMapEditorTilesPlugin::get_tabs() const {
Vector<TileMapEditorPlugin::TabData> tabs;
tabs.push_back({ toolbar, tiles_bottom_panel });
tabs.push_back({ toolbar, patterns_bottom_panel });
return tabs;
}
void TileMapEditorTilesPlugin::_tab_changed() {
if (tiles_bottom_panel->is_visible_in_tree()) {
_update_selection_pattern_from_tileset_tiles_selection();
} else if (patterns_bottom_panel->is_visible_in_tree()) {
_update_selection_pattern_from_tileset_pattern_selection();
}
}
void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
@ -173,7 +165,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
// Scene collection source.
TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
if (scene_collection_source) {
texture = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"));
texture = tiles_bottom_panel->get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"));
if (item_text.is_empty()) {
item_text = vformat(TTR("Scene Collection Source (ID: %d)"), source_id);
}
@ -205,7 +197,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current());
}
void TileMapEditorTilesPlugin::_update_bottom_panel() {
void TileMapEditorTilesPlugin::_update_source_display() {
// Update the atlas display.
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
@ -252,6 +244,81 @@ void TileMapEditorTilesPlugin::_update_bottom_panel() {
}
}
void TileMapEditorTilesPlugin::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return;
}
Ref<TileSet> tile_set = tile_map->get_tileset();
if (!tile_set.is_valid()) {
return;
}
if (ED_IS_SHORTCUT("tiles_editor/paste", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
select_last_pattern = true;
int new_pattern_index = tile_set->get_patterns_count();
undo_redo->create_action(TTR("Add TileSet pattern"));
undo_redo->add_do_method(*tile_set, "add_pattern", tile_map_clipboard, new_pattern_index);
undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index);
undo_redo->commit_action();
patterns_item_list->accept_event();
}
if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
Vector<int> selected = patterns_item_list->get_selected_items();
undo_redo->create_action(TTR("Remove TileSet patterns"));
for (int i = 0; i < selected.size(); i++) {
int pattern_index = selected[i];
undo_redo->add_do_method(*tile_set, "remove_pattern", pattern_index);
undo_redo->add_undo_method(*tile_set, "add_pattern", tile_set->get_pattern(pattern_index), pattern_index);
}
undo_redo->commit_action();
patterns_item_list->accept_event();
}
}
void TileMapEditorTilesPlugin::_pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture) {
// TODO optimize ?
for (int i = 0; i < patterns_item_list->get_item_count(); i++) {
if (patterns_item_list->get_item_metadata(i) == p_pattern) {
patterns_item_list->set_item_icon(i, p_texture);
break;
}
}
}
void TileMapEditorTilesPlugin::_update_patterns_list() {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return;
}
Ref<TileSet> tile_set = tile_map->get_tileset();
if (!tile_set.is_valid()) {
return;
}
// Recreate the items.
patterns_item_list->clear();
for (int i = 0; i < tile_set->get_patterns_count(); i++) {
int id = patterns_item_list->add_item("");
patterns_item_list->set_item_metadata(id, tile_set->get_pattern(i));
TilesEditor::get_singleton()->queue_pattern_preview(tile_set, tile_set->get_pattern(i), callable_mp(this, &TileMapEditorTilesPlugin::_pattern_preview_done));
}
// Update the label visibility.
patterns_help_label->set_visible(patterns_item_list->get_item_count() == 0);
// Added a new pattern, thus select the last one.
if (select_last_pattern) {
patterns_item_list->select(tile_set->get_patterns_count() - 1);
patterns_item_list->grab_focus();
_update_selection_pattern_from_tileset_pattern_selection();
}
select_last_pattern = false;
}
void TileMapEditorTilesPlugin::_update_atlas_view() {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
@ -304,7 +371,7 @@ void TileMapEditorTilesPlugin::_update_scenes_collection_view() {
Variant udata = i;
EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata);
} else {
item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")));
item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), tiles_bottom_panel->get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")));
}
scene_tiles_list->set_item_metadata(item_index, scene_id);
@ -360,19 +427,32 @@ void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_s
}
}
_update_selection_pattern_from_tileset_selection();
_update_selection_pattern_from_tileset_tiles_selection();
}
void TileMapEditorTilesPlugin::_scenes_list_nothing_selected() {
scene_tiles_list->deselect_all();
tile_set_selection.clear();
tile_map_selection.clear();
selection_pattern->clear();
_update_selection_pattern_from_tileset_selection();
selection_pattern.instantiate();
_update_selection_pattern_from_tileset_tiles_selection();
}
void TileMapEditorTilesPlugin::_update_theme() {
select_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons")));
paint_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
line_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons")));
rect_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons")));
bucket_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons")));
picker_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
erase_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
missing_atlas_texture_icon = tiles_bottom_panel->get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons"));
}
bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
if (!is_visible_in_tree()) {
if (!(tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree())) {
// If the bottom editor is not visible, we ignore inputs.
return false;
}
@ -400,7 +480,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
if (ED_IS_SHORTCUT("tiles_editor/cut", p_event) || ED_IS_SHORTCUT("tiles_editor/copy", p_event)) {
// Fill in the clipboard.
if (!tile_map_selection.is_empty()) {
memdelete(tile_map_clipboard);
tile_map_clipboard.instantiate();
TypedArray<Vector2i> coords_array;
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
coords_array.push_back(E->get());
@ -633,7 +713,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
Vector2i tile_shape_size = tile_set->get_tile_size();
// Draw the selection.
if (is_visible_in_tree() && tool_buttons_group->get_pressed_button() == select_tool_button) {
if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && tool_buttons_group->get_pressed_button() == select_tool_button) {
// In select mode, we only draw the current selection if we are modifying it (pressing control or shift).
if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) {
// Do nothing
@ -645,7 +725,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
}
// Handle the preview of the tiles to be placed.
if (is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered.
if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered.
Map<Vector2i, TileMapCell> preview;
Rect2i drawn_grid_rect;
@ -679,21 +759,23 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
}
tile_map->draw_cells_outline(p_overlay, to_draw, Color(1.0, 1.0, 1.0), xform);
} else if (drag_type == DRAG_TYPE_MOVE) {
// Preview when moving.
Vector2i top_left;
if (!tile_map_selection.is_empty()) {
top_left = tile_map_selection.front()->get();
}
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
top_left = top_left.min(E->get());
}
Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
offset = tile_map->world_to_map(drag_last_mouse_pos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
if (!(patterns_item_list->is_visible_in_tree() && patterns_item_list->has_point(patterns_item_list->get_local_mouse_position()))) {
// Preview when moving.
Vector2i top_left;
if (!tile_map_selection.is_empty()) {
top_left = tile_map_selection.front()->get();
}
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
top_left = top_left.min(E->get());
}
Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
offset = tile_map->world_to_map(drag_last_mouse_pos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
for (int i = 0; i < selection_used_cells.size(); i++) {
Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern);
preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
for (int i = 0; i < selection_used_cells.size(); i++) {
Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern);
preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
}
}
} else if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) {
// Preview when pasting.
@ -832,7 +914,7 @@ void TileMapEditorTilesPlugin::_mouse_exited_viewport() {
CanvasItemEditor::get_singleton()->update_viewport();
}
TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_pattern) {
TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(Ref<TileMapPattern> p_pattern) {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return TileMapCell();
@ -896,9 +978,10 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_
}
// Get or create the pattern.
TileMapPattern erase_pattern;
erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern;
Ref<TileMapPattern> erase_pattern;
erase_pattern.instantiate();
erase_pattern->set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
Ref<TileMapPattern> pattern = _is_erasing() ? erase_pattern : selection_pattern;
Map<Vector2i, TileMapCell> output;
if (!pattern->is_empty()) {
@ -948,9 +1031,11 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start
rect.size += Vector2i(1, 1);
// Get or create the pattern.
TileMapPattern erase_pattern;
erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern;
Ref<TileMapPattern> erase_pattern;
erase_pattern.instantiate();
erase_pattern->set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
Ref<TileMapPattern> pattern = _is_erasing() ? erase_pattern : selection_pattern;
Map<Vector2i, TileMapCell> err_output;
ERR_FAIL_COND_V(pattern->is_empty(), err_output);
@ -1007,9 +1092,10 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
}
// Get or create the pattern.
TileMapPattern erase_pattern;
erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern;
Ref<TileMapPattern> erase_pattern;
erase_pattern.instantiate();
erase_pattern->set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
Ref<TileMapPattern> pattern = _is_erasing() ? erase_pattern : selection_pattern;
if (!pattern->is_empty()) {
TileMapCell source = tile_map->get_cell(tile_map_layer, p_coords);
@ -1153,60 +1239,80 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
_update_tileset_selection_from_selection_pattern();
} break;
case DRAG_TYPE_MOVE: {
Vector2i top_left;
if (!tile_map_selection.is_empty()) {
top_left = tile_map_selection.front()->get();
}
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
top_left = top_left.min(E->get());
}
if (patterns_item_list->is_visible_in_tree() && patterns_item_list->has_point(patterns_item_list->get_local_mouse_position())) {
// Restore the cells.
for (KeyValue<Vector2i, TileMapCell> kv : drag_modified) {
tile_map->set_cell(tile_map_layer, kv.key, kv.value.source_id, kv.value.get_atlas_coords(), kv.value.alternative_tile);
}
Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
// Creating a pattern in the pattern list.
select_last_pattern = true;
int new_pattern_index = tile_set->get_patterns_count();
undo_redo->create_action(TTR("Add TileSet pattern"));
undo_redo->add_do_method(*tile_set, "add_pattern", selection_pattern, new_pattern_index);
undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index);
undo_redo->commit_action();
} else {
// Get the top-left cell.
Vector2i top_left;
if (!tile_map_selection.is_empty()) {
top_left = tile_map_selection.front()->get();
}
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
top_left = top_left.min(E->get());
}
TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
// Get the offset from the mouse.
Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
Vector2i coords;
Map<Vector2i, TileMapCell> cells_undo;
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords));
}
TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
Map<Vector2i, TileMapCell> cells_do;
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
cells_do[coords] = TileMapCell();
}
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
}
undo_redo->create_action(TTR("Move tiles"));
// Move the tiles.
for (const KeyValue<Vector2i, TileMapCell> &E : cells_do) {
undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile);
}
for (const KeyValue<Vector2i, TileMapCell> &E : cells_undo) {
undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile);
}
// Build the list of cells to undo.
Vector2i coords;
Map<Vector2i, TileMapCell> cells_undo;
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords));
}
// Update the selection.
undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
tile_map_selection.clear();
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
tile_map_selection.insert(coords);
// Build the list of cells to do.
Map<Vector2i, TileMapCell> cells_do;
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
cells_do[coords] = TileMapCell();
}
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
}
// Move the tiles.
undo_redo->create_action(TTR("Move tiles"));
for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) {
undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) {
undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
// Update the selection.
undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
tile_map_selection.clear();
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
tile_map_selection.insert(coords);
}
undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
undo_redo->commit_action();
}
undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
undo_redo->commit_action();
} break;
case DRAG_TYPE_PICK: {
Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
rect.size += Vector2i(1, 1);
memdelete(selection_pattern);
TypedArray<Vector2i> coords_array;
for (int x = rect.position.x; x < rect.get_end().x; x++) {
for (int y = rect.position.y; y < rect.get_end().y; y++) {
@ -1216,11 +1322,10 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
}
}
}
selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array);
if (!selection_pattern->is_empty()) {
Ref<TileMapPattern> new_selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array);
if (!new_selection_pattern->is_empty()) {
selection_pattern = new_selection_pattern;
_update_tileset_selection_from_selection_pattern();
} else {
_update_selection_pattern_from_tileset_selection();
}
picker_button->set_pressed(false);
} break;
@ -1288,8 +1393,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
tile_set_selection.clear();
patterns_item_list->deselect_all();
tile_map_selection.clear();
selection_pattern->clear();
selection_pattern.instantiate();
return;
}
@ -1299,8 +1405,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
tile_set_selection.clear();
patterns_item_list->deselect_all();
tile_map_selection.clear();
selection_pattern->clear();
selection_pattern.instantiate();
return;
}
@ -1310,8 +1417,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
tile_set_selection.clear();
patterns_item_list->deselect_all();
tile_map_selection.clear();
selection_pattern->clear();
selection_pattern.instantiate();
return;
}
@ -1339,8 +1447,10 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
if (!tile_map_selection.is_empty()) {
_update_selection_pattern_from_tilemap_selection();
} else if (tiles_bottom_panel->is_visible_in_tree()) {
_update_selection_pattern_from_tileset_tiles_selection();
} else {
_update_selection_pattern_from_tileset_selection();
_update_selection_pattern_from_tileset_pattern_selection();
}
}
@ -1373,9 +1483,14 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection(
return;
}
Ref<TileSet> tile_set = tile_map->get_tileset();
if (!tile_set.is_valid()) {
return;
}
ERR_FAIL_INDEX(tile_map_layer, tile_map->get_layers_count());
memdelete(selection_pattern);
selection_pattern.instantiate();
TypedArray<Vector2i> coords_array;
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
@ -1384,7 +1499,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection(
selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array);
}
void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection() {
void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_selection() {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return;
@ -1399,7 +1514,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
tile_map_selection.clear();
// Clear the selected pattern.
selection_pattern->clear();
selection_pattern.instantiate();
// Group per source.
Map<int, List<const TileMapCell *>> per_source;
@ -1457,6 +1572,30 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
CanvasItemEditor::get_singleton()->update_viewport();
}
void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection() {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return;
}
Ref<TileSet> tile_set = tile_map->get_tileset();
if (!tile_set.is_valid()) {
return;
}
// Clear the tilemap selection.
tile_map_selection.clear();
// Clear the selected pattern.
selection_pattern.instantiate();
if (patterns_item_list->get_selected_items().size() >= 1) {
selection_pattern = patterns_item_list->get_item_metadata(patterns_item_list->get_selected_items()[0]);
}
CanvasItemEditor::get_singleton()->update_viewport();
}
void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern() {
tile_set_selection.clear();
TypedArray<Vector2i> used_cells = selection_pattern->get_used_cells();
@ -1466,7 +1605,7 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern(
tile_set_selection.insert(TileMapCell(selection_pattern->get_cell_source_id(coords), selection_pattern->get_cell_atlas_coords(coords), selection_pattern->get_cell_alternative_tile(coords)));
}
}
_update_bottom_panel();
_update_source_display();
tile_atlas_control->update();
alternative_tiles_control->update();
}
@ -1616,7 +1755,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0));
}
}
_update_selection_pattern_from_tileset_selection();
_update_selection_pattern_from_tileset_tiles_selection();
} else { // Released
if (tile_set_dragging_selection) {
if (!mb->is_shift_pressed()) {
@ -1653,7 +1792,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
}
}
}
_update_selection_pattern_from_tileset_selection();
_update_selection_pattern_from_tileset_tiles_selection();
}
tile_set_dragging_selection = false;
}
@ -1773,7 +1912,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In
tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile));
}
}
_update_selection_pattern_from_tileset_selection();
_update_selection_pattern_from_tileset_tiles_selection();
}
tile_atlas_control->update();
alternative_tiles_control->update();
@ -1806,8 +1945,9 @@ void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer
// Clear the selection.
tile_set_selection.clear();
patterns_item_list->deselect_all();
tile_map_selection.clear();
selection_pattern->clear();
selection_pattern.instantiate();
}
tile_map_layer = p_tile_map_layer;
@ -1829,9 +1969,13 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), KEY_ESCAPE);
ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE);
// --- Initialize references ---
tile_map_clipboard.instantiate();
selection_pattern.instantiate();
// --- Toolbar ---
toolbar = memnew(HBoxContainer);
toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
toolbar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer);
@ -1942,39 +2086,44 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
paint_tool_button->set_pressed(true);
_update_toolbar();
// --- Bottom panel ---
set_name("Tiles");
// --- Bottom panel tiles ---
tiles_bottom_panel = memnew(VBoxContainer);
tiles_bottom_panel->connect("tree_entered", callable_mp(this, &TileMapEditorTilesPlugin::_update_theme));
tiles_bottom_panel->connect("theme_changed", callable_mp(this, &TileMapEditorTilesPlugin::_update_theme));
tiles_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_stop_dragging));
tiles_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_tab_changed));
tiles_bottom_panel->set_name(TTR("Tiles"));
missing_source_label = memnew(Label);
missing_source_label->set_text(TTR("This TileMap's TileSet has no source configured. Edit the TileSet resource to add one."));
missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
missing_source_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
missing_source_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
missing_source_label->set_align(Label::ALIGN_CENTER);
missing_source_label->set_valign(Label::VALIGN_CENTER);
missing_source_label->hide();
add_child(missing_source_label);
tiles_bottom_panel->add_child(missing_source_label);
atlas_sources_split_container = memnew(HSplitContainer);
atlas_sources_split_container->set_h_size_flags(SIZE_EXPAND_FILL);
atlas_sources_split_container->set_v_size_flags(SIZE_EXPAND_FILL);
add_child(atlas_sources_split_container);
atlas_sources_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
atlas_sources_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tiles_bottom_panel->add_child(atlas_sources_split_container);
sources_list = memnew(ItemList);
sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE);
sources_list->set_h_size_flags(SIZE_EXPAND_FILL);
sources_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
sources_list->set_stretch_ratio(0.25);
sources_list->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1));
sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_bottom_panel).unbind(1));
sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_source_display).unbind(1));
sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_sources_lists_current));
sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_sources_list), varray(sources_list));
atlas_sources_split_container->add_child(sources_list);
// Tile atlas source.
tile_atlas_view = memnew(TileAtlasView);
tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL);
tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL);
tile_atlas_view->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tile_atlas_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tile_atlas_view->set_texture_grid_visible(false);
tile_atlas_view->set_tile_shape_grid_visible(false);
tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform));
@ -1994,8 +2143,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
// Scenes collection source.
scene_tiles_list = memnew(ItemList);
scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL);
scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL);
scene_tiles_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
scene_tiles_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
scene_tiles_list->set_drag_forwarding(this);
scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI);
scene_tiles_list->connect("multi_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_multi_selected));
@ -2006,30 +2155,42 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
// Invalid source label.
invalid_source_label = memnew(Label);
invalid_source_label->set_text(TTR("Invalid source selected."));
invalid_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
invalid_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
invalid_source_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
invalid_source_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
invalid_source_label->set_align(Label::ALIGN_CENTER);
invalid_source_label->set_valign(Label::VALIGN_CENTER);
invalid_source_label->hide();
atlas_sources_split_container->add_child(invalid_source_label);
_update_bottom_panel();
// --- Bottom panel patterns ---
patterns_bottom_panel = memnew(VBoxContainer);
patterns_bottom_panel->set_name(TTR("Patterns"));
patterns_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_tab_changed));
int thumbnail_size = 64;
patterns_item_list = memnew(ItemList);
patterns_item_list->set_max_columns(0);
patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP);
patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2);
patterns_item_list->set_max_text_lines(2);
patterns_item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
patterns_item_list->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_patterns_item_list_gui_input));
patterns_item_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection).unbind(1));
patterns_item_list->connect("item_activated", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection));
patterns_item_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection));
patterns_bottom_panel->add_child(patterns_item_list);
patterns_help_label = memnew(Label);
patterns_help_label->set_text(TTR("Drag and drop or paste a TileMap selection here to store a pattern."));
patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER);
patterns_item_list->add_child(patterns_help_label);
// Update.
_update_source_display();
}
TileMapEditorTilesPlugin::~TileMapEditorTilesPlugin() {
memdelete(selection_pattern);
memdelete(tile_map_clipboard);
}
void TileMapEditorTerrainsPlugin::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED:
paint_tool_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
break;
}
}
void TileMapEditorTerrainsPlugin::tile_set_changed() {
@ -2052,8 +2213,10 @@ void TileMapEditorTerrainsPlugin::_update_toolbar() {
}
}
Control *TileMapEditorTerrainsPlugin::get_toolbar() const {
return toolbar;
Vector<TileMapEditorPlugin::TabData> TileMapEditorTerrainsPlugin::get_tabs() const {
Vector<TileMapEditorPlugin::TabData> tabs;
tabs.push_back({ toolbar, main_vbox_container });
return tabs;
}
Map<Vector2i, TileSet::CellNeighbor> TileMapEditorTerrainsPlugin::Constraint::get_overlapping_coords_and_peering_bits() const {
@ -2811,7 +2974,7 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() {
}
bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
if (!is_visible_in_tree()) {
if (!main_vbox_container->is_visible_in_tree()) {
// If the bottom editor is not visible, we ignore inputs.
return false;
}
@ -3083,13 +3246,13 @@ void TileMapEditorTerrainsPlugin::_update_terrains_tree() {
TreeItem *terrain_set_tree_item = terrains_tree->create_item();
String matches;
if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) {
terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchCornersAndSides"), SNAME("EditorIcons")));
terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchCornersAndSides"), SNAME("EditorIcons")));
matches = String(TTR("Matches Corners and Sides"));
} else if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchCorners"), SNAME("EditorIcons")));
terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchCorners"), SNAME("EditorIcons")));
matches = String(TTR("Matches Corners Only"));
} else {
terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchSides"), SNAME("EditorIcons")));
terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchSides"), SNAME("EditorIcons")));
matches = String(TTR("Matches Sides Only"));
}
terrain_set_tree_item->set_text(0, vformat("Terrain Set %d (%s)", terrain_set_index, matches));
@ -3194,6 +3357,12 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() {
}
}
void TileMapEditorTerrainsPlugin::_update_theme() {
paint_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
picker_button->set_icon(main_vbox_container->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
erase_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
}
void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) {
_stop_dragging(); // Avoids staying in a wrong drag state.
@ -3206,15 +3375,18 @@ void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_la
}
TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
set_name("Terrains");
main_vbox_container = memnew(VBoxContainer);
main_vbox_container->connect("tree_entered", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_theme));
main_vbox_container->connect("theme_changed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_theme));
main_vbox_container->set_name("Terrains");
HSplitContainer *tilemap_tab_terrains = memnew(HSplitContainer);
tilemap_tab_terrains->set_h_size_flags(SIZE_EXPAND_FILL);
tilemap_tab_terrains->set_v_size_flags(SIZE_EXPAND_FILL);
add_child(tilemap_tab_terrains);
tilemap_tab_terrains->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tilemap_tab_terrains->set_v_size_flags(Control::SIZE_EXPAND_FILL);
main_vbox_container->add_child(tilemap_tab_terrains);
terrains_tree = memnew(Tree);
terrains_tree->set_h_size_flags(SIZE_EXPAND_FILL);
terrains_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL);
terrains_tree->set_stretch_ratio(0.25);
terrains_tree->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
terrains_tree->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
@ -3223,7 +3395,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
tilemap_tab_terrains->add_child(terrains_tree);
terrains_tile_list = memnew(ItemList);
terrains_tile_list->set_h_size_flags(SIZE_EXPAND_FILL);
terrains_tile_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
terrains_tile_list->set_max_columns(0);
terrains_tile_list->set_same_column_width(true);
terrains_tile_list->set_fixed_icon_size(Size2(30, 30) * EDSCALE);
@ -3290,7 +3462,7 @@ void TileMapEditor::_notification(int p_what) {
if (is_visible_in_tree() && tileset_changed_needs_update) {
_update_bottom_panel();
_update_layers_selection();
tile_map_editor_plugins[tabs->get_current_tab()]->tile_set_changed();
tabs_plugins[tabs_bar->get_current_tab()]->tile_set_changed();
CanvasItemEditor::get_singleton()->update_viewport();
tileset_changed_needs_update = false;
}
@ -3414,14 +3586,11 @@ void TileMapEditor::_update_bottom_panel() {
// Update the visibility of controls.
missing_tileset_label->set_visible(!tile_set.is_valid());
if (!tile_set.is_valid()) {
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
tile_map_editor_plugins[i]->hide();
}
} else {
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab());
}
for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
tabs_data[tab_index].panel->hide();
}
if (tile_set.is_valid()) {
tabs_data[tabs_bar->get_current_tab()].panel->show();
}
}
@ -3504,27 +3673,25 @@ void TileMapEditor::_tile_map_changed() {
void TileMapEditor::_tab_changed(int p_tab_id) {
// Make the plugin edit the correct tilemap.
tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer);
// Update toolbar.
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
tile_map_editor_plugins[i]->get_toolbar()->set_visible(i == p_tab_id);
for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
tabs_data[tab_index].toolbar->hide();
}
tabs_data[p_tab_id].toolbar->show();
// Update visible panel.
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map || !tile_map->get_tileset().is_valid()) {
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
tile_map_editor_plugins[i]->hide();
}
} else {
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab());
}
for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
tabs_data[tab_index].panel->hide();
}
if (tile_map && tile_map->get_tileset().is_valid()) {
tabs_data[tabs_bar->get_current_tab()].panel->show();
}
// Graphical update.
tile_map_editor_plugins[tabs->get_current_tab()]->update();
tabs_data[tabs_bar->get_current_tab()].panel->update();
CanvasItemEditor::get_singleton()->update_viewport();
}
@ -3611,7 +3778,7 @@ void TileMapEditor::_update_layers_selection() {
layers_selection_button->set_custom_minimum_size(min_button_size);
layers_selection_button->update();
tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer);
}
void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) {
@ -3697,7 +3864,7 @@ bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
return true;
}
return tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_gui_input(p_event);
return tabs_plugins[tabs_bar->get_current_tab()]->forward_canvas_gui_input(p_event);
}
void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
@ -3832,7 +3999,7 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
}*/
// Draw the plugins.
tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay);
tabs_plugins[tabs_bar->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay);
}
void TileMapEditor::edit(TileMap *p_tile_map) {
@ -3866,7 +4033,7 @@ void TileMapEditor::edit(TileMap *p_tile_map) {
_update_layers_selection();
// Call the plugins.
tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer);
_tile_map_changed();
}
@ -3883,24 +4050,31 @@ TileMapEditor::TileMapEditor() {
tile_map_editor_plugins.push_back(memnew(TileMapEditorTerrainsPlugin));
// Tabs.
tabs = memnew(Tabs);
tabs->set_clip_tabs(false);
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
tabs->add_tab(tile_map_editor_plugins[i]->get_name());
tabs_bar = memnew(Tabs);
tabs_bar->set_clip_tabs(false);
for (int plugin_index = 0; plugin_index < tile_map_editor_plugins.size(); plugin_index++) {
Vector<TileMapEditorPlugin::TabData> tabs_vector = tile_map_editor_plugins[plugin_index]->get_tabs();
for (int tab_index = 0; tab_index < tabs_vector.size(); tab_index++) {
tabs_bar->add_tab(tabs_vector[tab_index].panel->get_name());
tabs_data.push_back(tabs_vector[tab_index]);
tabs_plugins.push_back(tile_map_editor_plugins[plugin_index]);
}
}
tabs->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed));
tabs_bar->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed));
// --- TileMap toolbar ---
tile_map_toolbar = memnew(HBoxContainer);
tile_map_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
// Tabs.
tile_map_toolbar->add_child(tabs);
tile_map_toolbar->add_child(tabs_bar);
// Tabs toolbars.
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
tile_map_editor_plugins[i]->get_toolbar()->hide();
tile_map_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar());
for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
tabs_data[tab_index].toolbar->hide();
if (!tabs_data[tab_index].toolbar->get_parent()) {
tile_map_toolbar->add_child(tabs_data[tab_index].toolbar);
}
}
// Wide empty separation control.
@ -3956,11 +4130,11 @@ TileMapEditor::TileMapEditor() {
missing_tileset_label->hide();
add_child(missing_tileset_label);
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
add_child(tile_map_editor_plugins[i]);
tile_map_editor_plugins[i]->set_h_size_flags(SIZE_EXPAND_FILL);
tile_map_editor_plugins[i]->set_v_size_flags(SIZE_EXPAND_FILL);
tile_map_editor_plugins[i]->set_visible(i == 0);
for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
add_child(tabs_data[tab_index].panel);
tabs_data[tab_index].panel->set_v_size_flags(SIZE_EXPAND_FILL);
tabs_data[tab_index].panel->set_visible(tab_index == 0);
tabs_data[tab_index].panel->set_h_size_flags(SIZE_EXPAND_FILL);
}
_tab_changed(0);
@ -3970,4 +4144,7 @@ TileMapEditor::TileMapEditor() {
}
TileMapEditor::~TileMapEditor() {
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
memdelete(tile_map_editor_plugins[i]);
}
}

View file

@ -33,17 +33,24 @@
#include "tile_atlas_view.h"
#include "core/os/thread.h"
#include "core/typedefs.h"
#include "editor/editor_node.h"
#include "scene/2d/tile_map.h"
#include "scene/gui/box_container.h"
#include "scene/gui/tabs.h"
class TileMapEditorPlugin : public VBoxContainer {
class TileMapEditorPlugin : public Object {
public:
virtual Control *get_toolbar() const {
return memnew(Control);
struct TabData {
Control *toolbar;
Control *panel;
};
virtual Vector<TabData> get_tabs() const {
return Vector<TabData>();
};
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; };
virtual void forward_canvas_draw_over_viewport(Control *p_overlay){};
virtual void tile_set_changed(){};
@ -106,7 +113,7 @@ private:
Map<Vector2i, TileMapCell> drag_modified;
bool rmb_erasing = false;
TileMapCell _pick_random_tile(const TileMapPattern *p_pattern);
TileMapCell _pick_random_tile(Ref<TileMapPattern> p_pattern);
Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos);
Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell);
Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous);
@ -115,20 +122,25 @@ private:
///// Selection system. /////
Set<Vector2i> tile_map_selection;
TileMapPattern *tile_map_clipboard = memnew(TileMapPattern);
TileMapPattern *selection_pattern = memnew(TileMapPattern);
Ref<TileMapPattern> tile_map_clipboard;
Ref<TileMapPattern> selection_pattern;
void _set_tile_map_selection(const TypedArray<Vector2i> &p_selection);
TypedArray<Vector2i> _get_tile_map_selection() const;
Set<TileMapCell> tile_set_selection;
void _update_selection_pattern_from_tilemap_selection();
void _update_selection_pattern_from_tileset_selection();
void _update_selection_pattern_from_tileset_tiles_selection();
void _update_selection_pattern_from_tileset_pattern_selection();
void _update_tileset_selection_from_selection_pattern();
void _update_fix_selected_and_hovered();
void _fix_invalid_tiles_in_tile_map_selection();
///// Bottom panel. ////.
///// Bottom panel common ////
void _tab_changed();
///// Bottom panel tiles ////
VBoxContainer *tiles_bottom_panel;
Label *missing_source_label;
Label *invalid_source_label;
@ -137,7 +149,7 @@ private:
Ref<Texture2D> missing_atlas_texture_icon;
void _update_tile_set_sources_list();
void _update_bottom_panel();
void _update_source_display();
// Atlas sources.
TileMapCell hovered_tile;
@ -167,15 +179,26 @@ private:
void _scenes_list_multi_selected(int p_index, bool p_selected);
void _scenes_list_nothing_selected();
///// Bottom panel patterns ////
VBoxContainer *patterns_bottom_panel;
ItemList *patterns_item_list;
Label *patterns_help_label;
void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event);
void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture);
bool select_last_pattern = false;
void _update_patterns_list();
// General
void _update_theme();
// Update callback
virtual void tile_set_changed() override;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
virtual Control *get_toolbar() const override;
virtual Vector<TabData> get_tabs() const override;
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
@ -205,6 +228,9 @@ private:
void _update_toolbar();
// Main vbox.
VBoxContainer *main_vbox_container;
// TileMap editing.
enum DragType {
DRAG_TYPE_NONE = 0,
@ -281,16 +307,13 @@ private:
void _update_terrains_cache();
void _update_terrains_tree();
void _update_tiles_list();
void _update_theme();
// Update callback
virtual void tile_set_changed() override;
protected:
void _notification(int p_what);
// static void _bind_methods();
public:
virtual Control *get_toolbar() const override;
virtual Vector<TabData> get_tabs() const override;
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
//virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
@ -328,7 +351,9 @@ private:
// Bottom panel.
Label *missing_tileset_label;
Tabs *tabs;
Tabs *tabs_bar;
LocalVector<TileMapEditorPlugin::TabData> tabs_data;
LocalVector<TileMapEditorPlugin *> tabs_plugins;
void _update_bottom_panel();
// TileMap.

View file

@ -156,9 +156,9 @@ void TileSetEditor::_update_sources_list(int force_selected_id) {
texture = atlas_source->get_texture();
if (item_text.is_empty()) {
if (texture.is_valid()) {
item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id);
item_text = vformat("%s (ID:%d)", texture->get_path().get_file(), source_id);
} else {
item_text = vformat(TTR("No Texture Atlas Source (id:%d)"), source_id);
item_text = vformat(TTR("No Texture Atlas Source (ID:%d)"), source_id);
}
}
}
@ -168,13 +168,13 @@ void TileSetEditor::_update_sources_list(int force_selected_id) {
if (scene_collection_source) {
texture = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"));
if (item_text.is_empty()) {
item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id);
item_text = vformat(TTR("Scene Collection Source (ID:%d)"), source_id);
}
}
// Use default if not valid.
if (item_text.is_empty()) {
item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id);
item_text = vformat(TTR("Unknown Type Source (ID:%d)"), source_id);
}
if (!texture.is_valid()) {
texture = missing_texture_texture;
@ -327,6 +327,7 @@ void TileSetEditor::_notification(int p_what) {
tile_set->set_edited(true);
}
_update_sources_list();
_update_patterns_list();
tile_set_changed_needs_update = false;
}
break;
@ -335,10 +336,56 @@ void TileSetEditor::_notification(int p_what) {
}
}
void TileSetEditor::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(!tile_set.is_valid());
if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
Vector<int> selected = patterns_item_list->get_selected_items();
undo_redo->create_action(TTR("Remove TileSet patterns"));
for (int i = 0; i < selected.size(); i++) {
int pattern_index = selected[i];
undo_redo->add_do_method(*tile_set, "remove_pattern", pattern_index);
undo_redo->add_undo_method(*tile_set, "add_pattern", tile_set->get_pattern(pattern_index), pattern_index);
}
undo_redo->commit_action();
patterns_item_list->accept_event();
}
}
void TileSetEditor::_pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture) {
// TODO optimize ?
for (int i = 0; i < patterns_item_list->get_item_count(); i++) {
if (patterns_item_list->get_item_metadata(i) == p_pattern) {
patterns_item_list->set_item_icon(i, p_texture);
break;
}
}
}
void TileSetEditor::_update_patterns_list() {
ERR_FAIL_COND(!tile_set.is_valid());
// Recreate the items.
patterns_item_list->clear();
for (int i = 0; i < tile_set->get_patterns_count(); i++) {
int id = patterns_item_list->add_item("");
patterns_item_list->set_item_metadata(id, tile_set->get_pattern(i));
TilesEditor::get_singleton()->queue_pattern_preview(tile_set, tile_set->get_pattern(i), callable_mp(this, &TileSetEditor::_pattern_preview_done));
}
// Update the label visibility.
patterns_help_label->set_visible(patterns_item_list->get_item_count() == 0);
}
void TileSetEditor::_tile_set_changed() {
tile_set_changed_needs_update = true;
}
void TileSetEditor::_tab_changed(int p_tab_changed) {
split_container->set_visible(p_tab_changed == 0);
patterns_item_list->set_visible(p_tab_changed == 1);
}
void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) {
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
ERR_FAIL_COND(!undo_redo);
@ -582,6 +629,7 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
if (tile_set.is_valid()) {
tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
_update_sources_list();
_update_patterns_list();
}
tile_set_atlas_source_editor->hide();
@ -594,8 +642,20 @@ TileSetEditor::TileSetEditor() {
set_process_internal(true);
// Tabs.
tabs_bar = memnew(Tabs);
tabs_bar->set_clip_tabs(false);
tabs_bar->add_tab(TTR("Tiles"));
tabs_bar->add_tab(TTR("Patterns"));
tabs_bar->connect("tab_changed", callable_mp(this, &TileSetEditor::_tab_changed));
tile_set_toolbar = memnew(HBoxContainer);
tile_set_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
tile_set_toolbar->add_child(tabs_bar);
//// Tiles ////
// Split container.
HSplitContainer *split_container = memnew(HSplitContainer);
split_container = memnew(HSplitContainer);
split_container->set_name(TTR("Tiles"));
split_container->set_h_size_flags(SIZE_EXPAND_FILL);
split_container->set_v_size_flags(SIZE_EXPAND_FILL);
@ -681,6 +741,24 @@ TileSetEditor::TileSetEditor() {
split_container_right_side->add_child(tile_set_scenes_collection_source_editor);
tile_set_scenes_collection_source_editor->hide();
//// Patterns ////
int thumbnail_size = 64;
patterns_item_list = memnew(ItemList);
patterns_item_list->set_max_columns(0);
patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP);
patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2);
patterns_item_list->set_max_text_lines(2);
patterns_item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
patterns_item_list->connect("gui_input", callable_mp(this, &TileSetEditor::_patterns_item_list_gui_input));
add_child(patterns_item_list);
patterns_item_list->hide();
patterns_help_label = memnew(Label);
patterns_help_label->set_text(TTR("Add new patterns in the TileMap editing mode."));
patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER);
patterns_item_list->add_child(patterns_help_label);
// Registers UndoRedo inspector callback.
EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element));
EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback));

View file

@ -46,7 +46,13 @@ class TileSetEditor : public VBoxContainer {
private:
Ref<TileSet> tile_set;
bool tile_set_changed_needs_update = false;
HSplitContainer *split_container;
// Tabs.
HBoxContainer *tile_set_toolbar;
Tabs *tabs_bar;
// Tiles.
Label *no_source_selected_label;
TileSetAtlasSourceEditor *tile_set_atlas_source_editor;
TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor;
@ -69,7 +75,16 @@ private:
AtlasMergingDialog *atlas_merging_dialog;
TileProxiesManagerDialog *tile_proxies_manager_dialog;
// Patterns.
ItemList *patterns_item_list;
Label *patterns_help_label;
void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event);
void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture);
bool select_last_pattern = false;
void _update_patterns_list();
void _tile_set_changed();
void _tab_changed(int p_tab_changed);
void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos);
void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
@ -82,6 +97,8 @@ public:
_FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; }
void edit(Ref<TileSet> p_tile_set);
Control *get_toolbar() { return tile_set_toolbar; };
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;

View file

@ -30,17 +30,18 @@
#include "tiles_editor_plugin.h"
#include "core/os/mutex.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#include "scene/2d/tile_map.h"
#include "scene/resources/tile_set.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/control.h"
#include "scene/gui/separator.h"
#include "scene/resources/tile_set.h"
#include "tile_set_editor.h"
@ -65,6 +66,99 @@ void TilesEditor::_notification(int p_what) {
}
}
void TilesEditor::_pattern_preview_done(const Variant &p_udata) {
pattern_preview_done.set();
}
void TilesEditor::_thread_func(void *ud) {
TilesEditor *te = (TilesEditor *)ud;
te->_thread();
}
void TilesEditor::_thread() {
pattern_thread_exited.clear();
while (!pattern_thread_exit.is_set()) {
pattern_preview_sem.wait();
pattern_preview_mutex.lock();
if (pattern_preview_queue.size()) {
QueueItem item = pattern_preview_queue.front()->get();
pattern_preview_queue.pop_front();
pattern_preview_mutex.unlock();
int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
thumbnail_size *= EDSCALE;
Vector2 thumbnail_size2 = Vector2(thumbnail_size, thumbnail_size);
if (item.pattern.is_valid() && !item.pattern->is_empty()) {
// Generate the pattern preview
SubViewport *viewport = memnew(SubViewport);
viewport->set_size(thumbnail_size2);
viewport->set_disable_input(true);
viewport->set_transparent_background(true);
viewport->set_update_mode(SubViewport::UPDATE_ONCE);
TileMap *tile_map = memnew(TileMap);
tile_map->set_tileset(item.tile_set);
tile_map->set_pattern(0, Vector2(), item.pattern);
viewport->add_child(tile_map);
TypedArray<Vector2i> used_cells = tile_map->get_used_cells(0);
Rect2 encompassing_rect = Rect2();
encompassing_rect.set_position(tile_map->map_to_world(used_cells[0]));
for (int i = 0; i < used_cells.size(); i++) {
Vector2i cell = used_cells[i];
Vector2 world_pos = tile_map->map_to_world(cell);
encompassing_rect.expand_to(world_pos);
// Texture.
Ref<TileSetAtlasSource> atlas_source = tile_set->get_source(tile_map->get_cell_source_id(0, cell));
if (atlas_source.is_valid()) {
Vector2i coords = tile_map->get_cell_atlas_coords(0, cell);
int alternative = tile_map->get_cell_alternative_tile(0, cell);
Vector2 center = world_pos - atlas_source->get_tile_effective_texture_offset(coords, alternative);
encompassing_rect.expand_to(center - atlas_source->get_tile_texture_region(coords).size / 2);
encompassing_rect.expand_to(center + atlas_source->get_tile_texture_region(coords).size / 2);
}
}
Vector2 scale = thumbnail_size2 / MAX(encompassing_rect.size.x, encompassing_rect.size.y);
tile_map->set_scale(scale);
tile_map->set_position(-(scale * encompassing_rect.get_center()) + thumbnail_size2 / 2);
// Add the viewport at the lasst moment to avoid rendering too early.
EditorNode::get_singleton()->add_child(viewport);
pattern_preview_done.clear();
RS::get_singleton()->request_frame_drawn_callback(const_cast<TilesEditor *>(this), "_pattern_preview_done", Variant());
while (!pattern_preview_done.is_set()) {
OS::get_singleton()->delay_usec(10);
}
Ref<Image> image = viewport->get_texture()->get_image();
Ref<ImageTexture> image_texture;
image_texture.instantiate();
image_texture->create_from_image(image);
// Find the index for the given pattern. TODO: optimize.
Variant args[] = { item.pattern, image_texture };
const Variant *args_ptr[] = { &args[0], &args[1] };
Variant r;
Callable::CallError error;
item.callback.call(args_ptr, 2, r, error);
viewport->queue_delete();
} else {
pattern_preview_mutex.unlock();
}
}
}
pattern_thread_exited.set();
}
void TilesEditor::_tile_map_changed() {
tile_map_changed_needs_update = true;
}
@ -83,6 +177,7 @@ void TilesEditor::_update_editors() {
// Set editors visibility.
tilemap_toolbar->set_visible(!tileset_tilemap_switch_button->is_pressed());
tilemap_editor->set_visible(!tileset_tilemap_switch_button->is_pressed());
tileset_toolbar->set_visible(tileset_tilemap_switch_button->is_pressed());
tileset_editor->set_visible(tileset_tilemap_switch_button->is_pressed());
// Enable/disable the switch button.
@ -150,6 +245,16 @@ void TilesEditor::synchronize_atlas_view(Object *p_current) {
}
}
void TilesEditor::queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback) {
ERR_FAIL_COND(!p_tile_set.is_valid());
ERR_FAIL_COND(!p_pattern.is_valid());
{
MutexLock lock(pattern_preview_mutex);
pattern_preview_queue.push_back({ p_tile_set, p_pattern, p_callback });
}
pattern_preview_sem.post();
}
void TilesEditor::edit(Object *p_object) {
// Disconnect to changes.
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
@ -192,6 +297,7 @@ void TilesEditor::edit(Object *p_object) {
}
void TilesEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_pattern_preview_done", "pattern"), &TilesEditor::_pattern_preview_done);
}
TilesEditor::TilesEditor(EditorNode *p_editor) {
@ -229,12 +335,27 @@ TilesEditor::TilesEditor(EditorNode *p_editor) {
tileset_editor->hide();
add_child(tileset_editor);
tileset_toolbar = tileset_editor->get_toolbar();
toolbar->add_child(tileset_toolbar);
// Pattern preview generation thread.
pattern_preview_thread.start(_thread_func, this);
// Initialization.
_update_switch_button();
_update_editors();
}
TilesEditor::~TilesEditor() {
if (pattern_preview_thread.is_started()) {
pattern_thread_exit.set();
pattern_preview_sem.post();
while (!pattern_thread_exited.is_set()) {
OS::get_singleton()->delay_usec(10000);
RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on visual server
}
pattern_preview_thread.wait_to_finish();
}
}
///////////////////////////////////////////////////////////////

View file

@ -53,6 +53,7 @@ private:
Control *tilemap_toolbar;
TileMapEditor *tilemap_editor;
Control *tileset_toolbar;
TileSetEditor *tileset_editor;
void _update_switch_button();
@ -63,6 +64,23 @@ private:
float atlas_view_zoom = 1.0;
Vector2 atlas_view_scroll = Vector2();
// Patterns preview generation.
struct QueueItem {
Ref<TileSet> tile_set;
Ref<TileMapPattern> pattern;
Callable callback;
};
List<QueueItem> pattern_preview_queue;
Mutex pattern_preview_mutex;
Semaphore pattern_preview_sem;
Thread pattern_preview_thread;
SafeFlag pattern_thread_exit;
SafeFlag pattern_thread_exited;
mutable SafeFlag pattern_preview_done;
void _pattern_preview_done(const Variant &p_udata);
static void _thread_func(void *ud);
void _thread();
void _tile_map_changed();
protected:
@ -82,6 +100,9 @@ public:
void set_atlas_view_transform(float p_zoom, Vector2 p_scroll);
void synchronize_atlas_view(Object *p_current);
// Pattern preview API.
void queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback);
void edit(Object *p_object);
TilesEditor(EditorNode *p_editor);

View file

@ -34,98 +34,6 @@
#include "servers/navigation_server_2d.h"
void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords));
size = size.max(p_coords + Vector2i(1, 1));
pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile);
}
bool TileMapPattern::has_cell(const Vector2i &p_coords) const {
return pattern.has(p_coords);
}
void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) {
ERR_FAIL_COND(!pattern.has(p_coords));
pattern.erase(p_coords);
if (p_update_size) {
size = Vector2i();
for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
size = size.max(E.key + Vector2i(1, 1));
}
}
}
int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const {
ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE);
return pattern[p_coords].source_id;
}
Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const {
ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS);
return pattern[p_coords].get_atlas_coords();
}
int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const {
ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE);
return pattern[p_coords].alternative_tile;
}
TypedArray<Vector2i> TileMapPattern::get_used_cells() const {
// Returns the cells used in the tilemap.
TypedArray<Vector2i> a;
a.resize(pattern.size());
int i = 0;
for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
Vector2i p(E.key.x, E.key.y);
a[i++] = p;
}
return a;
}
Vector2i TileMapPattern::get_size() const {
return size;
}
void TileMapPattern::set_size(const Vector2i &p_size) {
for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
Vector2i coords = E.key;
if (p_size.x <= coords.x || p_size.y <= coords.y) {
ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords));
};
}
size = p_size;
}
bool TileMapPattern::is_empty() const {
return pattern.is_empty();
};
void TileMapPattern::clear() {
size = Vector2i();
pattern.clear();
};
void TileMapPattern::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell);
ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell);
ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id);
ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords);
ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile);
ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells);
ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size);
ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size);
ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty);
}
Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) {
// Transform to stacked layout.
Vector2i output = p_coords;
@ -1788,11 +1696,12 @@ int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bo
return E->get().alternative_tile;
}
TileMapPattern *TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) {
Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) {
ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr);
ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr);
TileMapPattern *output = memnew(TileMapPattern);
Ref<TileMapPattern> output;
output.instantiate();
if (p_coords_array.is_empty()) {
return output;
}
@ -1841,7 +1750,7 @@ TileMapPattern *TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_
return output;
}
Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern) {
Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, Ref<TileMapPattern> p_pattern) {
ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i());
Vector2i output = p_position_in_tilemap + p_coords_in_pattern;
@ -1864,7 +1773,7 @@ Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_
return output;
}
void TileMap::set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern) {
void TileMap::set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
ERR_FAIL_COND(!tile_set.is_valid());
@ -3076,6 +2985,10 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid);
ClassDB::bind_method(D_METHOD("get_pattern", "layer", "coords_array"), &TileMap::get_pattern);
ClassDB::bind_method(D_METHOD("map_pattern", "position_in_tilemap", "coords_in_pattern", "pattern"), &TileMap::map_pattern);
ClassDB::bind_method(D_METHOD("set_pattern", "layer", "position", "pattern"), &TileMap::set_pattern);
ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles);
ClassDB::bind_method(D_METHOD("clear_layer", "layer"), &TileMap::clear_layer);
ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear);
@ -3092,7 +3005,7 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_dirty_quadrants"), &TileMap::_update_dirty_quadrants);
ClassDB::bind_method(D_METHOD("_set_tile_data", "layer"), &TileMap::_set_tile_data);
ClassDB::bind_method(D_METHOD("_set_tile_data", "layer", "data"), &TileMap::_set_tile_data);
ClassDB::bind_method(D_METHOD("_get_tile_data", "layer"), &TileMap::_get_tile_data);
ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileMap::_tile_set_changed_deferred_update);

View file

@ -37,51 +37,6 @@
class TileSetAtlasSource;
union TileMapCell {
struct {
int32_t source_id : 16;
int16_t coord_x : 16;
int16_t coord_y : 16;
int32_t alternative_tile : 16;
};
uint64_t _u64t;
TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) {
source_id = p_source_id;
set_atlas_coords(p_atlas_coords);
alternative_tile = p_alternative_tile;
}
Vector2i get_atlas_coords() const {
return Vector2i(coord_x, coord_y);
}
void set_atlas_coords(const Vector2i &r_coords) {
coord_x = r_coords.x;
coord_y = r_coords.y;
}
bool operator<(const TileMapCell &p_other) const {
if (source_id == p_other.source_id) {
if (coord_x == p_other.coord_x) {
if (coord_y == p_other.coord_y) {
return alternative_tile < p_other.alternative_tile;
} else {
return coord_y < p_other.coord_y;
}
} else {
return coord_x < p_other.coord_x;
}
} else {
return source_id < p_other.source_id;
}
}
bool operator!=(const TileMapCell &p_other) const {
return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile);
}
};
struct TileMapQuadrant {
struct CoordsWorldComparator {
_ALWAYS_INLINE_ bool operator()(const Vector2i &p_a, const Vector2i &p_b) const {
@ -150,32 +105,6 @@ struct TileMapQuadrant {
}
};
class TileMapPattern : public Object {
GDCLASS(TileMapPattern, Object);
Vector2i size;
Map<Vector2i, TileMapCell> pattern;
protected:
static void _bind_methods();
public:
void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0);
bool has_cell(const Vector2i &p_coords) const;
void remove_cell(const Vector2i &p_coords, bool p_update_size = true);
int get_cell_source_id(const Vector2i &p_coords) const;
Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
int get_cell_alternative_tile(const Vector2i &p_coords) const;
TypedArray<Vector2i> get_used_cells() const;
Vector2i get_size() const;
void set_size(const Vector2i &p_size);
bool is_empty() const;
void clear();
};
class TileMap : public Node2D {
GDCLASS(TileMap, Node2D);
@ -349,9 +278,9 @@ public:
Vector2i get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
int get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
TileMapPattern *get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern);
void set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern);
Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
void set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern);
// Not exposed to users
TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;

View file

@ -740,7 +740,7 @@ bool Control::has_point(const Point2 &p_point) const {
return Rect2(Point2(), get_size()).has_point(p_point);
}
void Control::set_drag_forwarding(Node *p_target) {
void Control::set_drag_forwarding(Object *p_target) {
if (p_target) {
data.drag_owner = p_target->get_instance_id();
} else {

View file

@ -355,7 +355,7 @@ public:
virtual Size2 get_minimum_size() const;
virtual Size2 get_combined_minimum_size() const;
virtual bool has_point(const Point2 &p_point) const;
virtual void set_drag_forwarding(Node *p_target);
virtual void set_drag_forwarding(Object *p_target);
virtual Variant get_drag_data(const Point2 &p_point);
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
virtual void drop_data(const Point2 &p_point, const Variant &p_data);

View file

@ -685,6 +685,7 @@ void register_scene_types() {
GDREGISTER_VIRTUAL_CLASS(TileSetSource);
GDREGISTER_CLASS(TileSetAtlasSource);
GDREGISTER_CLASS(TileSetScenesCollectionSource);
GDREGISTER_CLASS(TileMapPattern);
GDREGISTER_CLASS(TileData);
GDREGISTER_CLASS(TileMap);
GDREGISTER_CLASS(ParallaxBackground);

View file

@ -31,6 +31,7 @@
#include "tile_set.h"
#include "core/core_string_names.h"
#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
#include "core/templates/local_vector.h"
@ -39,6 +40,189 @@
#include "scene/resources/convex_polygon_shape_2d.h"
#include "servers/navigation_server_2d.h"
/////////////////////////////// TileMapPattern //////////////////////////////////////
void TileMapPattern::_set_tile_data(const Vector<int> &p_data) {
int c = p_data.size();
const int *r = p_data.ptr();
int offset = 3;
ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data.");
clear();
for (int i = 0; i < c; i += offset) {
const uint8_t *ptr = (const uint8_t *)&r[i];
uint8_t local[12];
for (int j = 0; j < 12; j++) {
local[j] = ptr[j];
}
#ifdef BIG_ENDIAN_ENABLED
SWAP(local[0], local[3]);
SWAP(local[1], local[2]);
SWAP(local[4], local[7]);
SWAP(local[5], local[6]);
SWAP(local[8], local[11]);
SWAP(local[9], local[10]);
#endif
int16_t x = decode_uint16(&local[0]);
int16_t y = decode_uint16(&local[2]);
uint16_t source_id = decode_uint16(&local[4]);
uint16_t atlas_coords_x = decode_uint16(&local[6]);
uint16_t atlas_coords_y = decode_uint16(&local[8]);
uint16_t alternative_tile = decode_uint16(&local[10]);
set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile);
}
emit_signal(SNAME("changed"));
}
Vector<int> TileMapPattern::_get_tile_data() const {
// Export tile data to raw format
Vector<int> data;
data.resize(pattern.size() * 3);
int *w = data.ptrw();
// Save in highest format
int idx = 0;
for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
uint8_t *ptr = (uint8_t *)&w[idx];
encode_uint16((int16_t)(E.key.x), &ptr[0]);
encode_uint16((int16_t)(E.key.y), &ptr[2]);
encode_uint16(E.value.source_id, &ptr[4]);
encode_uint16(E.value.coord_x, &ptr[6]);
encode_uint16(E.value.coord_y, &ptr[8]);
encode_uint16(E.value.alternative_tile, &ptr[10]);
idx += 3;
}
return data;
}
void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords));
size = size.max(p_coords + Vector2i(1, 1));
pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile);
emit_changed();
}
bool TileMapPattern::has_cell(const Vector2i &p_coords) const {
return pattern.has(p_coords);
}
void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) {
ERR_FAIL_COND(!pattern.has(p_coords));
pattern.erase(p_coords);
if (p_update_size) {
size = Vector2i();
for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
size = size.max(E.key + Vector2i(1, 1));
}
}
emit_changed();
}
int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const {
ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE);
return pattern[p_coords].source_id;
}
Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const {
ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS);
return pattern[p_coords].get_atlas_coords();
}
int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const {
ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE);
return pattern[p_coords].alternative_tile;
}
TypedArray<Vector2i> TileMapPattern::get_used_cells() const {
// Returns the cells used in the tilemap.
TypedArray<Vector2i> a;
a.resize(pattern.size());
int i = 0;
for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
Vector2i p(E.key.x, E.key.y);
a[i++] = p;
}
return a;
}
Vector2i TileMapPattern::get_size() const {
return size;
}
void TileMapPattern::set_size(const Vector2i &p_size) {
for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
Vector2i coords = E.key;
if (p_size.x <= coords.x || p_size.y <= coords.y) {
ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords));
};
}
size = p_size;
emit_changed();
}
bool TileMapPattern::is_empty() const {
return pattern.is_empty();
};
void TileMapPattern::clear() {
size = Vector2i();
pattern.clear();
emit_changed();
};
bool TileMapPattern::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "tile_data") {
if (p_value.is_array()) {
_set_tile_data(p_value);
return true;
}
return false;
}
return false;
}
bool TileMapPattern::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "tile_data") {
r_ret = _get_tile_data();
return true;
}
return false;
}
void TileMapPattern::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
}
void TileMapPattern::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_tile_data", "data"), &TileMapPattern::_set_tile_data);
ClassDB::bind_method(D_METHOD("_get_tile_data"), &TileMapPattern::_get_tile_data);
ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell);
ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell);
ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id);
ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords);
ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile);
ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells);
ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size);
ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size);
ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty);
}
/////////////////////////////// TileSet //////////////////////////////////////
const int TileSet::INVALID_SOURCE = -1;
@ -982,6 +1166,36 @@ void TileSet::clear_tile_proxies() {
emit_changed();
}
int TileSet::add_pattern(Ref<TileMapPattern> p_pattern, int p_index) {
ERR_FAIL_COND_V(!p_pattern.is_valid(), -1);
ERR_FAIL_COND_V_MSG(p_pattern->is_empty(), -1, "Cannot add an empty pattern to the TileSet.");
for (unsigned int i = 0; i < patterns.size(); i++) {
ERR_FAIL_COND_V_MSG(patterns[i] == p_pattern, -1, "TileSet has already this pattern.");
}
ERR_FAIL_COND_V(p_index > (int)patterns.size(), -1);
if (p_index < 0) {
p_index = patterns.size();
}
patterns.insert(p_index, p_pattern);
emit_changed();
return p_index;
}
Ref<TileMapPattern> TileSet::get_pattern(int p_index) {
ERR_FAIL_INDEX_V(p_index, (int)patterns.size(), Ref<TileMapPattern>());
return patterns[p_index];
}
void TileSet::remove_pattern(int p_index) {
ERR_FAIL_INDEX(p_index, (int)patterns.size());
patterns.remove(p_index);
emit_changed();
}
int TileSet::get_patterns_count() {
return patterns.size();
}
Vector<Vector2> TileSet::get_tile_shape_polygon() {
Vector<Vector2> points;
if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
@ -2483,6 +2697,12 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
return true;
}
return false;
} else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) {
int pattern_index = components[0].trim_prefix("pattern_").to_int();
for (int i = patterns.size(); i <= pattern_index; i++) {
add_pattern(p_value);
}
return true;
}
#ifndef DISABLE_DEPRECATED
@ -2606,6 +2826,13 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const {
return true;
}
return false;
} else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) {
int pattern_index = components[0].trim_prefix("pattern_").to_int();
if (pattern_index < 0 || pattern_index >= (int)patterns.size()) {
return false;
}
r_ret = patterns[pattern_index];
return true;
}
return false;
@ -2686,6 +2913,11 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/source_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/coords_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/alternative_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
// Patterns.
for (unsigned int pattern_index = 0; pattern_index < patterns.size(); pattern_index++) {
p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("pattern_%d", pattern_index), PROPERTY_HINT_RESOURCE_TYPE, "TileMapPattern", PROPERTY_USAGE_NOEDITOR));
}
}
void TileSet::_validate_property(PropertyInfo &property) const {
@ -2799,6 +3031,12 @@ void TileSet::_bind_methods() {
ClassDB::bind_method(D_METHOD("cleanup_invalid_tile_proxies"), &TileSet::cleanup_invalid_tile_proxies);
ClassDB::bind_method(D_METHOD("clear_tile_proxies"), &TileSet::clear_tile_proxies);
// Patterns
ClassDB::bind_method(D_METHOD("add_pattern", "pattern", "index"), &TileSet::add_pattern, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_pattern", "index"), &TileSet::get_pattern, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("remove_pattern", "index"), &TileSet::remove_pattern);
ClassDB::bind_method(D_METHOD("get_patterns_count"), &TileSet::get_patterns_count);
ADD_GROUP("Rendering", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping");
ADD_ARRAY("occlusion_layers", "occlusion_layer_");

View file

@ -60,6 +60,84 @@ class TileSetPluginAtlasRendering;
class TileSetPluginAtlasPhysics;
class TileSetPluginAtlasNavigation;
union TileMapCell {
struct {
int32_t source_id : 16;
int16_t coord_x : 16;
int16_t coord_y : 16;
int32_t alternative_tile : 16;
};
uint64_t _u64t;
TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = Vector2i(-1, -1), int p_alternative_tile = -1) { // default are INVALID_SOURCE, INVALID_ATLAS_COORDS, INVALID_TILE_ALTERNATIVE
source_id = p_source_id;
set_atlas_coords(p_atlas_coords);
alternative_tile = p_alternative_tile;
}
Vector2i get_atlas_coords() const {
return Vector2i(coord_x, coord_y);
}
void set_atlas_coords(const Vector2i &r_coords) {
coord_x = r_coords.x;
coord_y = r_coords.y;
}
bool operator<(const TileMapCell &p_other) const {
if (source_id == p_other.source_id) {
if (coord_x == p_other.coord_x) {
if (coord_y == p_other.coord_y) {
return alternative_tile < p_other.alternative_tile;
} else {
return coord_y < p_other.coord_y;
}
} else {
return coord_x < p_other.coord_x;
}
} else {
return source_id < p_other.source_id;
}
}
bool operator!=(const TileMapCell &p_other) const {
return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile);
}
};
class TileMapPattern : public Resource {
GDCLASS(TileMapPattern, Resource);
Vector2i size;
Map<Vector2i, TileMapCell> pattern;
void _set_tile_data(const Vector<int> &p_data);
Vector<int> _get_tile_data() const;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0);
bool has_cell(const Vector2i &p_coords) const;
void remove_cell(const Vector2i &p_coords, bool p_update_size = true);
int get_cell_source_id(const Vector2i &p_coords) const;
Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
int get_cell_alternative_tile(const Vector2i &p_coords) const;
TypedArray<Vector2i> get_used_cells() const;
Vector2i get_size() const;
void set_size(const Vector2i &p_size);
bool is_empty() const;
void clear();
};
class TileSet : public Resource {
GDCLASS(TileSet, Resource);
@ -245,6 +323,8 @@ private:
int next_source_id = 0;
// ---------------------
LocalVector<Ref<TileMapPattern>> patterns;
void _compute_next_source_id();
void _source_changed();
@ -384,6 +464,12 @@ public:
void cleanup_invalid_tile_proxies();
void clear_tile_proxies();
// Patterns.
int add_pattern(Ref<TileMapPattern> p_pattern, int p_index = -1);
Ref<TileMapPattern> get_pattern(int p_index);
void remove_pattern(int p_index);
int get_patterns_count();
// Helpers
Vector<Vector2> get_tile_shape_polygon();
void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>());