Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Ping Pong" tile animation mode. #82253

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion doc/classes/TileSetAtlasSource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@
Returns how many columns the tile at [param atlas_coords] has in its animation layout.
</description>
</method>
<method name="get_tile_animation_explicit_start_frame" qualifiers="const">
<return type="int" />
<param index="0" name="atlas_coords" type="Vector2i" />
<description>
Returns the explicit start frame of the animation at [param atlas_coords]. See also [method set_tile_animation_explicit_start_frame].
</description>
</method>
<method name="get_tile_animation_frame_duration" qualifiers="const">
<return type="float" />
<param index="0" name="atlas_coords" type="Vector2i" />
Expand Down Expand Up @@ -217,6 +224,14 @@
Sets the number of columns in the animation layout of the tile at coordinates [param atlas_coords]. If set to 0, then the different frames of the animation are laid out as a single horizontal line in the atlas.
</description>
</method>
<method name="set_tile_animation_explicit_start_frame">
<return type="void" />
<param index="0" name="atlas_coords" type="Vector2i" />
<param index="1" name="frame_index" type="int" />
<description>
Sets the explicit start frame of the animation at [param atlas_coords] to [param frame_index]. See also [method get_tile_animation_explicit_start_frame].
</description>
</method>
<method name="set_tile_animation_frame_duration">
<return type="void" />
<param index="0" name="atlas_coords" type="Vector2i" />
Expand Down Expand Up @@ -284,7 +299,13 @@
<constant name="TILE_ANIMATION_MODE_RANDOM_START_TIMES" value="1" enum="TileAnimationMode">
Tile animations start at random times, looking varied.
</constant>
<constant name="TILE_ANIMATION_MODE_MAX" value="2" enum="TileAnimationMode">
<constant name="TILE_ANIMATION_MODE_EXPLICIT_START_FRAME" value="2" enum="TileAnimationMode">
Tile animations start at the explcitly selected start frame.
</constant>
<constant name="TILE_ANIMATION_MODE_PING_PONG" value="3" enum="TileAnimationMode">
Tile animations reverse at end instead of looping back to starting frame.
</constant>
<constant name="TILE_ANIMATION_MODE_MAX" value="4" enum="TileAnimationMode">
Represents the size of the [enum TileAnimationMode] enum.
</constant>
<constant name="TRANSFORM_FLIP_H" value="4096">
Expand Down
29 changes: 23 additions & 6 deletions editor/plugins/tiles/tile_map_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1866,21 +1866,23 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() {
Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0);
for (const TileMapCell &E : tile_set_selection) {
if (E.source_id == source_id && E.alternative_tile == 0) {
for (int frame = 0; frame < atlas->get_tile_animation_frames_count(E.get_atlas_coords()); frame++) {
Vector2i coords = E.get_atlas_coords();
for (int frame = 0; frame < atlas->get_tile_animation_frames_count(coords); frame++) {
Color color = selection_color;
if (frame > 0) {
if (frame != (atlas->get_tile_animation_mode(coords) == TileSetAtlasSource::TILE_ANIMATION_MODE_EXPLICIT_START_FRAME ? atlas->get_tile_animation_explicit_start_frame(coords) : 0)) {
color.a *= 0.3;
}
TilesEditorUtils::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(E.get_atlas_coords(), frame), color);
TilesEditorUtils::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(coords, frame), color);
}
}
}

// Draw the hovered tile.
if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) {
for (int frame = 0; frame < atlas->get_tile_animation_frames_count(hovered_tile.get_atlas_coords()); frame++) {
Color color = Color(1.0, 0.8, 0.0, frame == 0 ? 0.6 : 0.3);
TilesEditorUtils::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(hovered_tile.get_atlas_coords(), frame), color);
Vector2i coords = hovered_tile.get_atlas_coords();
for (int frame = 0; frame < atlas->get_tile_animation_frames_count(coords); frame++) {
Color color = Color(1.0, 0.8, 0.0, frame == (atlas->get_tile_animation_mode(coords) == TileSetAtlasSource::TILE_ANIMATION_MODE_EXPLICIT_START_FRAME ? atlas->get_tile_animation_explicit_start_frame(coords) : 0) ? 0.6 : 0.3);
TilesEditorUtils::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(coords, frame), color);
}
}

Expand Down Expand Up @@ -1945,6 +1947,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
Vector2i original_coords = coords;
if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
coords = atlas->get_tile_at_coords(coords);
if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
Expand Down Expand Up @@ -1975,6 +1978,20 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0));
}
}

// Set the explicit start frame if needed.
if (atlas->get_tile_animation_mode(coords) == TileSetAtlasSource::TILE_ANIMATION_MODE_EXPLICIT_START_FRAME) {
int frames_count = atlas->get_tile_animation_frames_count(coords);
if (frames_count > 1) {
int columns = atlas->get_tile_animation_columns(coords);
if (columns > 0) {
int rows = (frames_count + columns - 1) / columns;
atlas->set_tile_animation_explicit_start_frame(coords, original_coords.x - coords.x + columns * (original_coords.y - coords.y % rows));
} else {
atlas->set_tile_animation_explicit_start_frame(coords, original_coords.x - coords.x);
}
}
}
_update_selection_pattern_from_tileset_tiles_selection();
} else { // Released
if (tile_set_dragging_selection) {
Expand Down
2 changes: 1 addition & 1 deletion editor/plugins/tiles/tile_set_atlas_source_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro
p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_columns")));
p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("animation_separation")));
p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("animation_speed")));
p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_mode"), PROPERTY_HINT_ENUM, "Default,Random Start Times"));
p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_mode"), PROPERTY_HINT_ENUM, "Default,Random Start Times,Explicit Start Frame,Ping Pong"));
p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_frames_count"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_"));
// Not optimal, but returns value for the first tile. This is similar to what MultiNodeEdit does.
if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) {
Expand Down
58 changes: 45 additions & 13 deletions scene/2d/tile_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,18 +359,38 @@ void TileMapLayer::_rendering_update() {
}

const Vector2 local_tile_pos = tile_map_node->map_to_local(cell_data.coords);
const Vector2 atlas_coords = cell_data.cell.get_atlas_coords();

// Random animation offset.
real_t random_animation_offset = 0.0;
if (atlas_source->get_tile_animation_mode(cell_data.cell.get_atlas_coords()) != TileSetAtlasSource::TILE_ANIMATION_MODE_DEFAULT) {
auto get_tile_hash = [this, local_tile_pos]() {
Array to_hash;
to_hash.push_back(local_tile_pos);
to_hash.push_back(get_instance_id()); // Use instance id as a random hash
random_animation_offset = RandomPCG(to_hash.hash()).randf();
to_hash.append(local_tile_pos);
to_hash.append(get_instance_id());
return to_hash.hash();
};

// Calculate the tile animation offset based on tile animation mode.
real_t animation_offset = 0.0;
if (atlas_source->get_tile_animation_mode(atlas_coords) == TileSetAtlasSource::TILE_ANIMATION_MODE_RANDOM_START_TIMES) {
animation_offset = RandomPCG(get_tile_hash()).randf();
} else if (atlas_source->get_tile_animation_mode(atlas_coords) == TileSetAtlasSource::TILE_ANIMATION_MODE_EXPLICIT_START_FRAME) {
uint32_t tile_hash = get_tile_hash();
int explicit_start_frame = 0;
if (explicit_start_frames.has(tile_hash)) {
explicit_start_frame = explicit_start_frames[tile_hash];
} else {
explicit_start_frame = atlas_source->get_tile_animation_explicit_start_frame(atlas_coords);
explicit_start_frames[tile_hash] = explicit_start_frame;
}
real_t speed = atlas_source->get_tile_animation_speed(atlas_coords);
real_t explicit_start_offset = 0.0;
for (int frame = 0; frame <= explicit_start_frame; frame++) {
explicit_start_offset += atlas_source->get_tile_animation_frame_duration(atlas_coords, frame) / speed;
}
animation_offset += explicit_start_offset;
}

// Drawing the tile in the canvas item.
tile_map_node->draw_tile(ci, local_tile_pos - ci_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, tile_map_node->get_self_modulate(), tile_data, random_animation_offset);
tile_map_node->draw_tile(ci, local_tile_pos - ci_position, tile_set, cell_data.cell.source_id, atlas_coords, cell_data.cell.alternative_tile, -1, tile_map_node->get_self_modulate(), tile_data, animation_offset);
}
} else {
// Free the quadrant.
Expand Down Expand Up @@ -3120,19 +3140,31 @@ void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<
Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0);
tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
} else {
int frame_count = atlas_source->get_tile_animation_frames_count(p_atlas_coords);
real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords);
real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed;
bool ping_pong = atlas_source->get_tile_animation_mode(p_atlas_coords) == TileSetAtlasSource::TILE_ANIMATION_MODE_PING_PONG;
int loop_frame_count = ping_pong ? 2 * frame_count - 2 : frame_count; // Adjust frame count for ping pong mode

real_t total_duration = 0.0;
for (int frame = 0; frame < loop_frame_count; frame++) {
int actual_frame = frame < frame_count ? frame : loop_frame_count - frame; // Calculate actual frame considering ping pong mode

real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, actual_frame) / speed;
total_duration += frame_duration;
}

real_t time = 0.0;
for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) {
real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame) / speed;
RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, time, time + frame_duration, p_animation_offset);
for (int frame = 0; frame < loop_frame_count; frame++) {
int actual_frame = frame < frame_count ? frame : loop_frame_count - frame; // Calculate actual frame considering ping pong mode

real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, actual_frame) / speed;
RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, total_duration, time, time + frame_duration, p_animation_offset);

Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame);
Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, actual_frame);
tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());

time += frame_duration;
}
RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions scene/2d/tile_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ class TileMapLayer : public RefCounted {
RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const;
RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const;

// Explicit start frames.
HashMap<uint32_t, int> explicit_start_frames;

public:
// TileMap node.
void set_tile_map(TileMap *p_tile_map);
Expand Down
25 changes: 25 additions & 0 deletions scene/resources/tile_set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3947,6 +3947,9 @@ bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value)
} else if (components[1] == "animation_mode") {
set_tile_animation_mode(coords, VariantCaster<TileSetAtlasSource::TileAnimationMode>::cast(p_value));
return true;
} else if (components[1] == "animation_explicit_start_frame") {
set_tile_animation_explicit_start_frame(coords, p_value);
return true;
} else if (components[1] == "animation_frames_count") {
set_tile_animation_frames_count(coords, p_value);
return true;
Expand Down Expand Up @@ -4017,6 +4020,9 @@ bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const {
} else if (components[1] == "animation_mode") {
r_ret = get_tile_animation_mode(coords);
return true;
} else if (components[1] == "animation_explicit_start_frame") {
r_ret = get_tile_animation_explicit_start_frame(coords);
return true;
} else if (components[1] == "animation_frames_count") {
r_ret = get_tile_animation_frames_count(coords);
return true;
Expand Down Expand Up @@ -4099,6 +4105,9 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
}
tile_property_list.push_back(property_info);

// animation_explicit_start_frame.
tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_explicit_start_frame", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));

// animation_frames_count.
tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));

Expand Down Expand Up @@ -4275,6 +4284,18 @@ TileSetAtlasSource::TileAnimationMode TileSetAtlasSource::get_tile_animation_mod
return tiles[p_atlas_coords].animation_mode;
}

void TileSetAtlasSource::set_tile_animation_explicit_start_frame(const Vector2i p_atlas_coords, int p_frame_index) {
ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
ERR_FAIL_INDEX(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size());

tiles[p_atlas_coords].animation_explicit_start_frame = p_frame_index;
}

int TileSetAtlasSource::get_tile_animation_explicit_start_frame(const Vector2i p_atlas_coords) const {
ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 0, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
return tiles[p_atlas_coords].animation_explicit_start_frame;
}

void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count) {
ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
ERR_FAIL_COND(p_frames_count < 1);
Expand Down Expand Up @@ -4650,6 +4671,8 @@ void TileSetAtlasSource::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tile_animation_speed", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_speed);
ClassDB::bind_method(D_METHOD("set_tile_animation_mode", "atlas_coords", "mode"), &TileSetAtlasSource::set_tile_animation_mode);
ClassDB::bind_method(D_METHOD("get_tile_animation_mode", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_mode);
ClassDB::bind_method(D_METHOD("set_tile_animation_explicit_start_frame", "atlas_coords", "frame_index"), &TileSetAtlasSource::set_tile_animation_explicit_start_frame);
ClassDB::bind_method(D_METHOD("get_tile_animation_explicit_start_frame", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_explicit_start_frame);
ClassDB::bind_method(D_METHOD("set_tile_animation_frames_count", "atlas_coords", "frames_count"), &TileSetAtlasSource::set_tile_animation_frames_count);
ClassDB::bind_method(D_METHOD("get_tile_animation_frames_count", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_frames_count);
ClassDB::bind_method(D_METHOD("set_tile_animation_frame_duration", "atlas_coords", "frame_index", "duration"), &TileSetAtlasSource::set_tile_animation_frame_duration);
Expand All @@ -4675,6 +4698,8 @@ void TileSetAtlasSource::_bind_methods() {

BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_DEFAULT)
BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_RANDOM_START_TIMES)
BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_EXPLICIT_START_FRAME)
BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_PING_PONG)
BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_MAX)

BIND_CONSTANT(TRANSFORM_FLIP_H)
Expand Down
5 changes: 5 additions & 0 deletions scene/resources/tile_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,8 @@ class TileSetAtlasSource : public TileSetSource {
enum TileAnimationMode {
TILE_ANIMATION_MODE_DEFAULT,
TILE_ANIMATION_MODE_RANDOM_START_TIMES,
TILE_ANIMATION_MODE_EXPLICIT_START_FRAME,
TILE_ANIMATION_MODE_PING_PONG,
TILE_ANIMATION_MODE_MAX,
};

Expand All @@ -615,6 +617,7 @@ class TileSetAtlasSource : public TileSetSource {
Vector2i animation_separation;
real_t animation_speed = 1.0;
TileSetAtlasSource::TileAnimationMode animation_mode = TILE_ANIMATION_MODE_DEFAULT;
int animation_explicit_start_frame = 0;
LocalVector<real_t> animation_frames_durations;

// Alternatives
Expand Down Expand Up @@ -719,6 +722,8 @@ class TileSetAtlasSource : public TileSetSource {
real_t get_tile_animation_speed(const Vector2i p_atlas_coords) const;
void set_tile_animation_mode(const Vector2i p_atlas_coords, const TileSetAtlasSource::TileAnimationMode p_mode);
TileSetAtlasSource::TileAnimationMode get_tile_animation_mode(const Vector2i p_atlas_coords) const;
void set_tile_animation_explicit_start_frame(const Vector2i p_atlas_coords, int p_frame_index);
int get_tile_animation_explicit_start_frame(const Vector2i p_atlas_coords) const;
void set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count);
int get_tile_animation_frames_count(const Vector2i p_atlas_coords) const;
void set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration);
Expand Down