Selaa lähdekoodia

Merge pull request #52539 from groud/implement_animated_tiles

Rémi Verschelde 4 vuotta sitten
vanhempi
commit
2ec1152b0f

+ 99 - 12
doc/classes/TileSetAtlasSource.xml

@@ -15,16 +15,6 @@
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>
 	<methods>
 	<methods>
-		<method name="can_move_tile_in_atlas" qualifiers="const">
-			<return type="bool" />
-			<argument index="0" name="atlas_coords" type="Vector2i" />
-			<argument index="1" name="new_atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" />
-			<argument index="2" name="new_size" type="Vector2i" default="Vector2i(-1, -1)" />
-			<description>
-				Returns true if the tile at the [code]atlas_coords[/code] coordinates can be moved to the [code]new_atlas_coords[/code] coordinates with the [code]new_size[/code] size. This functions returns false if a tile is already present in the given area, or if this area is outside the atlas boundaries.
-				If [code]new_atlas_coords[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's coordinates. If [code]new_size[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's size.
-			</description>
-		</method>
 		<method name="clear_tiles_outside_texture">
 		<method name="clear_tiles_outside_texture">
 			<return type="void" />
 			<return type="void" />
 			<description>
 			<description>
@@ -61,6 +51,49 @@
 				Returns the alternative ID a following call to [method create_alternative_tile] would return.
 				Returns the alternative ID a following call to [method create_alternative_tile] would return.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_tile_animation_columns" qualifiers="const">
+			<return type="int" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<description>
+				Returns how many columns the tile at [code]atlas_coords[/code] has in its animation layout.
+			</description>
+		</method>
+		<method name="get_tile_animation_frame_duration" qualifiers="const">
+			<return type="float" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<argument index="1" name="frame_index" type="int" />
+			<description>
+				Returns the animation frame duration of frame [code]frame_index[/code] for the tile at coordinates [code]atlas_coords[/code].
+			</description>
+		</method>
+		<method name="get_tile_animation_frames_count" qualifiers="const">
+			<return type="int" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<description>
+				Returns how many animation frames has the tile at coordinates [code]atlas_coords[/code].
+			</description>
+		</method>
+		<method name="get_tile_animation_separation" qualifiers="const">
+			<return type="Vector2i" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<description>
+				Returns the separation (as in the atlas grid) between each frame of an animated tile at coordinates [code]atlas_coords[/code].
+			</description>
+		</method>
+		<method name="get_tile_animation_speed" qualifiers="const">
+			<return type="float" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<description>
+				Returns the animation speed of the tile at coordinates [code]atlas_coords[/code].
+			</description>
+		</method>
+		<method name="get_tile_animation_total_duration" qualifiers="const">
+			<return type="float" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<description>
+				Returns the sum of the sum of the frame durations of the tile at coordinates [code]atlas_coords[/code]. This value needs to be divided by the animation speed to get the actual animation loop duration.
+			</description>
+		</method>
 		<method name="get_tile_at_coords" qualifiers="const">
 		<method name="get_tile_at_coords" qualifiers="const">
 			<return type="Vector2i" />
 			<return type="Vector2i" />
 			<argument index="0" name="atlas_coords" type="Vector2i" />
 			<argument index="0" name="atlas_coords" type="Vector2i" />
@@ -86,8 +119,21 @@
 		<method name="get_tile_texture_region" qualifiers="const">
 		<method name="get_tile_texture_region" qualifiers="const">
 			<return type="Rect2i" />
 			<return type="Rect2i" />
 			<argument index="0" name="atlas_coords" type="Vector2i" />
 			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<argument index="1" name="frame" type="int" default="0" />
+			<description>
+				Returns a tile's texture region in the atlas texture. For animated tiles, a [code]frame[/code] argument might be provided for the different frames of the animation.
+			</description>
+		</method>
+		<method name="has_room_for_tile" qualifiers="const">
+			<return type="bool" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<argument index="1" name="size" type="Vector2i" />
+			<argument index="2" name="animation_columns" type="int" />
+			<argument index="3" name="animation_separation" type="Vector2i" />
+			<argument index="4" name="frames_count" type="int" />
+			<argument index="5" name="ignored_tile" type="Vector2i" default="Vector2i(-1, -1)" />
 			<description>
 			<description>
-				Returns a tile's texture region in the atlas texture.
+				Returns whether there is enough room in an atlas to create/modify a tile with the given properties. If [code]ignored_tile[/code] is provided, act as is the given tile was not present in the atlas. This may be used when you want to modify a tile's properties.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="has_tiles_outside_texture">
 		<method name="has_tiles_outside_texture">
@@ -104,7 +150,7 @@
 			<description>
 			<description>
 				Move the tile and its alternatives at the [code]atlas_coords[/code] coordinates to the [code]new_atlas_coords[/code] coordinates with the [code]new_size[/code] size. This functions will fail if a tile is already present in the given area.
 				Move the tile and its alternatives at the [code]atlas_coords[/code] coordinates to the [code]new_atlas_coords[/code] coordinates with the [code]new_size[/code] size. This functions will fail if a tile is already present in the given area.
 				If [code]new_atlas_coords[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's coordinates. If [code]new_size[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's size.
 				If [code]new_atlas_coords[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's coordinates. If [code]new_size[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's size.
-				To avoid an error, first check if a move is possible using [method can_move_tile_in_atlas].
+				To avoid an error, first check if a move is possible using [method has_room_for_tile].
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="remove_alternative_tile">
 		<method name="remove_alternative_tile">
@@ -133,6 +179,47 @@
 				Calling this function with [code]alternative_id[/code] equals to 0 will fail, as the base tile alternative cannot be moved.
 				Calling this function with [code]alternative_id[/code] equals to 0 will fail, as the base tile alternative cannot be moved.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="set_tile_animation_columns">
+			<return type="void" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<argument index="1" name="frame_columns" type="int" />
+			<description>
+				Sets the number of columns in the animation layout of the tile at coordinates [code]atlas_coords[/code]. 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_frame_duration">
+			<return type="void" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<argument index="1" name="frame_index" type="int" />
+			<argument index="2" name="duration" type="float" />
+			<description>
+				Sets the animation frame duration of frame [code]frame_index[/code] for the tile at coordinates [code]atlas_coords[/code].
+			</description>
+		</method>
+		<method name="set_tile_animation_frames_count">
+			<return type="void" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<argument index="1" name="frames_count" type="int" />
+			<description>
+				Sets how many animation frames the tile at coordinates [code]atlas_coords[/code] has.
+			</description>
+		</method>
+		<method name="set_tile_animation_separation">
+			<return type="void" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<argument index="1" name="separation" type="Vector2i" />
+			<description>
+				Sets the margin (in grid tiles) between each tile in the animation layout of the tile at coordinates [code]atlas_coords[/code] has.
+			</description>
+		</method>
+		<method name="set_tile_animation_speed">
+			<return type="void" />
+			<argument index="0" name="atlas_coords" type="Vector2i" />
+			<argument index="1" name="speed" type="float" />
+			<description>
+				Sets the animation speed of the tile at coordinates [code]atlas_coords[/code] has.
+			</description>
+		</method>
 	</methods>
 	</methods>
 	<members>
 	<members>
 		<member name="margins" type="Vector2i" setter="set_margins" getter="get_margins" default="Vector2i(0, 0)">
 		<member name="margins" type="Vector2i" setter="set_margins" getter="get_margins" default="Vector2i(0, 0)">

+ 7 - 5
editor/plugins/tiles/atlas_merging_dialog.cpp

@@ -94,12 +94,14 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla
 				}
 				}
 
 
 				// Copy the texture.
 				// Copy the texture.
-				Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id);
-				Rect2 dst_rect_wide = Rect2i(new_tile_rect_in_altas.position * new_texture_region_size, new_tile_rect_in_altas.size * new_texture_region_size);
-				if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) {
-					output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height()));
+				for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
+					Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id, frame);
+					Rect2 dst_rect_wide = Rect2i(new_tile_rect_in_altas.position * new_texture_region_size, new_tile_rect_in_altas.size * new_texture_region_size);
+					if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) {
+						output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height()));
+					}
+					output_image->blit_rect(atlas_source->get_texture()->get_image(), src_rect, dst_rect_wide.get_center() - src_rect.size / 2);
 				}
 				}
-				output_image->blit_rect(atlas_source->get_texture()->get_image(), src_rect, dst_rect_wide.get_center() - src_rect.size / 2);
 			}
 			}
 
 
 			// Compute the atlas offset.
 			// Compute the atlas offset.

+ 25 - 16
editor/plugins/tiles/tile_atlas_view.cpp

@@ -256,11 +256,15 @@ void TileAtlasView::_draw_base_tiles() {
 		for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
 		for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
 			Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
 			Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
 
 
-			// Update the y to max value.
-			Vector2i offset_pos = (margins + (atlas_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0));
+			for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) {
+				// Update the y to max value.
+				int animation_columns = tile_set_atlas_source->get_tile_animation_columns(atlas_coords);
+				Vector2i frame_coords = atlas_coords + (tile_set_atlas_source->get_tile_size_in_atlas(atlas_coords) + tile_set_atlas_source->get_tile_animation_separation(atlas_coords)) * ((animation_columns > 0) ? Vector2i(frame % animation_columns, frame / animation_columns) : Vector2i(frame, 0));
+				Vector2i offset_pos = (margins + (frame_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0));
 
 
-			// Draw the tile.
-			TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0);
+				// Draw the tile.
+				TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame);
+			}
 		}
 		}
 	}
 	}
 }
 }
@@ -326,13 +330,18 @@ void TileAtlasView::_draw_base_tiles_shape_grid() {
 	for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
 	for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
 		Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
 		Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
 		Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_effective_texture_offset(tile_id, 0);
 		Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_effective_texture_offset(tile_id, 0);
-		Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id);
 
 
-		// Draw only if the tile shape fits in the texture region
-		Transform2D tile_xform;
-		tile_xform.set_origin(texture_region.get_center() + in_tile_base_offset);
-		tile_xform.set_scale(tile_shape_size);
-		tile_set->draw_tile_shape(base_tiles_shape_grid, tile_xform, grid_color);
+		for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
+			Color color = grid_color;
+			if (frame > 0) {
+				color.a *= 0.3;
+			}
+			Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id);
+			Transform2D tile_xform;
+			tile_xform.set_origin(texture_region.get_center() + in_tile_base_offset);
+			tile_xform.set_scale(tile_shape_size);
+			tile_set->draw_tile_shape(base_tiles_shape_grid, tile_xform, color);
+		}
 	}
 	}
 }
 }
 
 
@@ -360,7 +369,7 @@ void TileAtlasView::_draw_alternatives() {
 			Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
 			Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
 			current_pos.x = 0;
 			current_pos.x = 0;
 			int y_increment = 0;
 			int y_increment = 0;
-			Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(atlas_coords);
+			Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(atlas_coords).size;
 			int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords);
 			int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords);
 			for (int j = 1; j < alternatives_count; j++) {
 			for (int j = 1; j < alternatives_count; j++) {
 				int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j);
 				int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j);
@@ -370,18 +379,18 @@ void TileAtlasView::_draw_alternatives() {
 				// Update the y to max value.
 				// Update the y to max value.
 				Vector2i offset_pos = current_pos;
 				Vector2i offset_pos = current_pos;
 				if (transposed) {
 				if (transposed) {
-					offset_pos = (current_pos + Vector2(texture_region.size.y, texture_region.size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
-					y_increment = MAX(y_increment, texture_region.size.x);
+					offset_pos = (current_pos + Vector2(texture_region_size.y, texture_region_size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
+					y_increment = MAX(y_increment, texture_region_size.x);
 				} else {
 				} else {
-					offset_pos = (current_pos + texture_region.size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
-					y_increment = MAX(y_increment, texture_region.size.y);
+					offset_pos = (current_pos + texture_region_size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
+					y_increment = MAX(y_increment, texture_region_size.y);
 				}
 				}
 
 
 				// Draw the tile.
 				// Draw the tile.
 				TileMap::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id);
 				TileMap::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id);
 
 
 				// Increment the x position.
 				// Increment the x position.
-				current_pos.x += transposed ? texture_region.size.y : texture_region.size.x;
+				current_pos.x += transposed ? texture_region_size.y : texture_region_size.x;
 			}
 			}
 			if (alternatives_count > 1) {
 			if (alternatives_count > 1) {
 				current_pos.y += y_increment;
 				current_pos.y += y_increment;

+ 14 - 2
editor/plugins/tiles/tile_map_editor.cpp

@@ -1456,13 +1456,25 @@ 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);
 	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 (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
 	for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
 		if (E->get().source_id == source_id && E->get().alternative_tile == 0) {
 		if (E->get().source_id == source_id && E->get().alternative_tile == 0) {
-			tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get().get_atlas_coords()), selection_color, false);
+			for (int frame = 0; frame < atlas->get_tile_animation_frames_count(E->get().get_atlas_coords()); frame++) {
+				Color color = selection_color;
+				if (frame > 0) {
+					color.a *= 0.3;
+				}
+				tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get().get_atlas_coords(), frame), color, false);
+			}
 		}
 		}
 	}
 	}
 
 
 	// Draw the hovered tile.
 	// Draw the hovered tile.
 	if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) {
 	if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) {
-		tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords()), Color(1.0, 1.0, 1.0), false);
+		for (int frame = 0; frame < atlas->get_tile_animation_frames_count(hovered_tile.get_atlas_coords()); frame++) {
+			Color color = Color(1.0, 1.0, 1.0);
+			if (frame > 0) {
+				color.a *= 0.3;
+			}
+			tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords(), frame), color, false);
+		}
 	}
 	}
 
 
 	// Draw the selection rect.
 	// Draw the selection rect.

+ 167 - 75
editor/plugins/tiles/tile_set_atlas_source_editor.cpp

@@ -135,45 +135,81 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na
 		const Vector2i &coords = tiles.front()->get().tile;
 		const Vector2i &coords = tiles.front()->get().tile;
 		const int &alternative = tiles.front()->get().alternative;
 		const int &alternative = tiles.front()->get().alternative;
 
 
-		if (alternative == 0 && p_name == "atlas_coords") {
-			Vector2i as_vector2i = Vector2i(p_value);
-			ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, as_vector2i), false);
-
-			if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) {
-				tiles_set_atlas_source_editor->selection.clear();
-				tiles_set_atlas_source_editor->selection.insert({ as_vector2i, 0 });
-				tiles_set_atlas_source_editor->_update_tile_id_label();
-			}
-
-			tile_set_atlas_source->move_tile_in_atlas(coords, as_vector2i);
-			tiles.clear();
-			tiles.insert({ as_vector2i, 0 });
-			emit_signal(SNAME("changed"), "atlas_coords");
-			return true;
-		} else if (alternative == 0 && p_name == "size_in_atlas") {
-			Vector2i as_vector2i = Vector2i(p_value);
-			ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i), false);
+		if (alternative == 0) {
+			Vector<String> components = String(p_name).split("/", true, 2);
+			if (p_name == "atlas_coords") {
+				Vector2i as_vector2i = Vector2i(p_value);
+				bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(as_vector2i, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
+				ERR_FAIL_COND_V(!has_room_for_tile, false);
+
+				if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) {
+					tiles_set_atlas_source_editor->selection.clear();
+					tiles_set_atlas_source_editor->selection.insert({ as_vector2i, 0 });
+					tiles_set_atlas_source_editor->_update_tile_id_label();
+				}
 
 
-			tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i);
-			emit_signal(SNAME("changed"), "size_in_atlas");
-			return true;
-		} else if (alternative > 0 && p_name == "alternative_id") {
-			int as_int = int(p_value);
-			ERR_FAIL_COND_V(as_int < 0, false);
-			ERR_FAIL_COND_V_MSG(tile_set_atlas_source->has_alternative_tile(coords, as_int), false, vformat("Cannot change alternative tile ID. Another alternative exists with id %d for tile at coords %s.", as_int, coords));
-
-			if (tiles_set_atlas_source_editor->selection.front()->get().alternative == alternative) {
-				tiles_set_atlas_source_editor->selection.clear();
-				tiles_set_atlas_source_editor->selection.insert({ coords, as_int });
+				tile_set_atlas_source->move_tile_in_atlas(coords, as_vector2i);
+				tiles.clear();
+				tiles.insert({ as_vector2i, 0 });
+				emit_signal(SNAME("changed"), "atlas_coords");
+				return true;
+			} else if (p_name == "size_in_atlas") {
+				Vector2i as_vector2i = Vector2i(p_value);
+				bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, as_vector2i, tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
+				ERR_FAIL_COND_V(!has_room_for_tile, false);
+				tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i);
+				emit_signal(SNAME("changed"), "size_in_atlas");
+				return true;
+			} else if (p_name == "animation_columns") {
+				bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), p_value, tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
+				ERR_FAIL_COND_V(!has_room_for_tile, false);
+				tile_set_atlas_source->set_tile_animation_columns(coords, p_value);
+				emit_signal(SNAME("changed"), "animation_columns");
+				return true;
+			} else if (p_name == "animation_separation") {
+				bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), p_value, tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
+				ERR_FAIL_COND_V(!has_room_for_tile, false);
+				tile_set_atlas_source->set_tile_animation_separation(coords, p_value);
+				emit_signal(SNAME("changed"), "animation_separation");
+				return true;
+			} else if (p_name == "animation_speed") {
+				tile_set_atlas_source->set_tile_animation_speed(coords, p_value);
+				emit_signal(SNAME("changed"), "animation_speed");
+				return true;
+			} else if (p_name == "animation_frames_count") {
+				bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), p_value, coords);
+				ERR_FAIL_COND_V(!has_room_for_tile, false);
+				tile_set_atlas_source->set_tile_animation_frames_count(coords, p_value);
+				notify_property_list_changed();
+				emit_signal(SNAME("changed"), "animation_separation");
+				return true;
+			} else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) {
+				int frame = components[0].trim_prefix("animation_frame_").to_int();
+				ERR_FAIL_INDEX_V(frame, tile_set_atlas_source->get_tile_animation_frames_count(coords), false);
+				if (components[1] == "duration") {
+					tile_set_atlas_source->set_tile_animation_frame_duration(coords, frame, p_value);
+					return true;
+				}
 			}
 			}
+		} else if (alternative > 0) {
+			if (p_name == "alternative_id") {
+				int as_int = int(p_value);
+				ERR_FAIL_COND_V(as_int < 0, false);
+				ERR_FAIL_COND_V_MSG(tile_set_atlas_source->has_alternative_tile(coords, as_int), false, vformat("Cannot change alternative tile ID. Another alternative exists with id %d for tile at coords %s.", as_int, coords));
+
+				if (tiles_set_atlas_source_editor->selection.front()->get().alternative == alternative) {
+					tiles_set_atlas_source_editor->selection.clear();
+					tiles_set_atlas_source_editor->selection.insert({ coords, as_int });
+				}
 
 
-			int previous_alternative_tile = alternative;
-			tiles.clear();
-			tiles.insert({ coords, as_int }); // tiles must be updated before.
-			tile_set_atlas_source->set_alternative_tile_id(coords, previous_alternative_tile, as_int);
+				int previous_alternative_tile = alternative;
+				tiles.clear();
+				tiles.insert({ coords, as_int }); // tiles must be updated before.
+				tile_set_atlas_source->set_alternative_tile_id(coords, previous_alternative_tile, as_int);
 
 
-			emit_signal(SNAME("changed"), "alternative_id");
-			return true;
+				emit_signal(SNAME("changed"), "alternative_id");
+				return true;
+			}
 		}
 		}
 	}
 	}
 
 
@@ -206,15 +242,41 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na
 		const Vector2i &coords = tiles.front()->get().tile;
 		const Vector2i &coords = tiles.front()->get().tile;
 		const int &alternative = tiles.front()->get().alternative;
 		const int &alternative = tiles.front()->get().alternative;
 
 
-		if (alternative == 0 && p_name == "atlas_coords") {
-			r_ret = coords;
-			return true;
-		} else if (alternative == 0 && p_name == "size_in_atlas") {
-			r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords);
-			return true;
-		} else if (alternative > 0 && p_name == "alternative_id") {
-			r_ret = alternative;
-			return true;
+		if (alternative == 0) {
+			Vector<String> components = String(p_name).split("/", true, 2);
+			if (p_name == "atlas_coords") {
+				r_ret = coords;
+				return true;
+			} else if (p_name == "size_in_atlas") {
+				r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords);
+				return true;
+			} else if (p_name == "animation_columns") {
+				r_ret = tile_set_atlas_source->get_tile_animation_columns(coords);
+				return true;
+			} else if (p_name == "animation_separation") {
+				r_ret = tile_set_atlas_source->get_tile_animation_separation(coords);
+				return true;
+			} else if (p_name == "animation_speed") {
+				r_ret = tile_set_atlas_source->get_tile_animation_speed(coords);
+				return true;
+			} else if (p_name == "animation_frames_count") {
+				r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords);
+				return true;
+			} else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) {
+				int frame = components[0].trim_prefix("animation_frame_").to_int();
+				if (components[1] == "duration") {
+					if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) {
+						return false;
+					}
+					r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame);
+					return true;
+				}
+			}
+		} else if (alternative > 0) {
+			if (p_name == "alternative_id") {
+				r_ret = alternative;
+				return true;
+			}
 		}
 		}
 	}
 	}
 
 
@@ -246,6 +308,20 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro
 		if (tiles.front()->get().alternative == 0) {
 		if (tiles.front()->get().alternative == 0) {
 			p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, ""));
 			p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, ""));
 			p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, ""));
 			p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, ""));
+
+			// Animation.
+			p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP));
+			p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, ""));
+			p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, ""));
+			p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, ""));
+			p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_"));
+			if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) {
+				p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY));
+			} else {
+				for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) {
+					p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, ""));
+				}
+			}
 		} else {
 		} else {
 			p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, ""));
 			p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, ""));
 		}
 		}
@@ -846,7 +922,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 						CursorShape cursor_shape = CURSOR_ARROW;
 						CursorShape cursor_shape = CURSOR_ARROW;
 						bool can_grow[4];
 						bool can_grow[4];
 						for (int i = 0; i < 4; i++) {
 						for (int i = 0; i < 4; i++) {
-							can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
+							can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile);
 							can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
 							can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
 						}
 						}
 						for (int i = 0; i < 4; i++) {
 						for (int i = 0; i < 4; i++) {
@@ -869,7 +945,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 				Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
 				Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
 				new_rect.size += Vector2i(1, 1);
 				new_rect.size += Vector2i(1, 1);
 				// Check if the new tile can fit in the new rect.
 				// Check if the new tile can fit in the new rect.
-				if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) {
+				if (tile_set_atlas_source->has_room_for_tile(new_rect.position, new_rect.size, tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) {
 					// Move and resize the tile.
 					// Move and resize the tile.
 					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
 					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
 					drag_current_tile = new_rect.position;
 					drag_current_tile = new_rect.position;
@@ -908,7 +984,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 				Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
 				Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
 				Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset);
 				Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset);
 				coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
 				coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
-				if (drag_current_tile != coords && tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, coords)) {
+				if (drag_current_tile != coords && tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile), tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) {
 					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords);
 					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords);
 					selection.clear();
 					selection.clear();
 					selection.insert({ coords, 0 });
 					selection.insert({ coords, 0 });
@@ -948,7 +1024,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 					new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1)));
 					new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1)));
 				}
 				}
 
 
-				if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) {
+				if (tile_set_atlas_source->has_room_for_tile(new_rect.position, new_rect.size, tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) {
 					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
 					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
 					selection.clear();
 					selection.clear();
 					selection.insert({ new_rect.position, 0 });
 					selection.insert({ new_rect.position, 0 });
@@ -1056,7 +1132,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 								CursorShape cursor_shape = CURSOR_ARROW;
 								CursorShape cursor_shape = CURSOR_ARROW;
 								bool can_grow[4];
 								bool can_grow[4];
 								for (int i = 0; i < 4; i++) {
 								for (int i = 0; i < 4; i++) {
-									can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
+									can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile);
 									can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
 									can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
 								}
 								}
 								for (int i = 0; i < 4; i++) {
 								for (int i = 0; i < 4; i++) {
@@ -1511,37 +1587,45 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
 			TileSelection selected = E->get();
 			TileSelection selected = E->get();
 			if (selected.alternative == 0) {
 			if (selected.alternative == 0) {
 				// Draw the rect.
 				// Draw the rect.
-				Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
-				tile_atlas_control->draw_rect(region, selection_color, false);
+				for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(selected.tile); frame++) {
+					Color color = selection_color;
+					if (frame > 0) {
+						color.a *= 0.3;
+					}
+					Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile, frame);
+					tile_atlas_control->draw_rect(region, color, false);
+				}
 			}
 			}
 		}
 		}
 
 
 		if (selection.size() == 1) {
 		if (selection.size() == 1) {
 			// Draw the resize handles (only when it's possible to expand).
 			// Draw the resize handles (only when it's possible to expand).
 			TileSelection selected = selection.front()->get();
 			TileSelection selected = selection.front()->get();
-			Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
-			Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
-			Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
-			Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
-			Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
-			Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
-			bool can_grow[4];
-			for (int i = 0; i < 4; i++) {
-				can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
-				can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
-			}
-			for (int i = 0; i < 4; i++) {
-				Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i];
-				if (can_grow[i] && can_grow[(i + 3) % 4]) {
-					tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false);
-				} else {
-					tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false);
+			if (selected.alternative == 0) {
+				Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
+				Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
+				Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
+				Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
+				Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
+				Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
+				bool can_grow[4];
+				for (int i = 0; i < 4; i++) {
+					can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile);
+					can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
 				}
 				}
-				Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4];
-				if (can_grow[i]) {
-					tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
-				} else {
-					tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
+				for (int i = 0; i < 4; i++) {
+					Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i];
+					if (can_grow[i] && can_grow[(i + 3) % 4]) {
+						tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false);
+					} else {
+						tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false);
+					}
+					Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4];
+					if (can_grow[i]) {
+						tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
+					} else {
+						tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
+					}
 				}
 				}
 			}
 			}
 		}
 		}
@@ -1550,7 +1634,9 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
 	if (drag_type == DRAG_TYPE_REMOVE_TILES) {
 	if (drag_type == DRAG_TYPE_REMOVE_TILES) {
 		// Draw the tiles to be removed.
 		// Draw the tiles to be removed.
 		for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) {
 		for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) {
-			tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get()), Color(0.0, 0.0, 0.0), false);
+			for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(E->get()); frame++) {
+				tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get(), frame), Color(0.0, 0.0, 0.0), false);
+			}
 		}
 		}
 	} else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) {
 	} else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) {
 		// Draw tiles to be removed.
 		// Draw tiles to be removed.
@@ -1617,7 +1703,13 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
 			Vector2i hovered_tile = tile_set_atlas_source->get_tile_at_coords(hovered_base_tile_coords);
 			Vector2i hovered_tile = tile_set_atlas_source->get_tile_at_coords(hovered_base_tile_coords);
 			if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) {
 			if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) {
 				// Draw existing hovered tile.
 				// Draw existing hovered tile.
-				tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile), Color(1.0, 1.0, 1.0), false);
+				for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(hovered_tile); frame++) {
+					Color color = Color(1.0, 1.0, 1.0);
+					if (frame > 0) {
+						color.a *= 0.3;
+					}
+					tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile, frame), color, false);
+				}
 			} else {
 			} else {
 				// Draw empty tile, only in add/remove tiles mode.
 				// Draw empty tile, only in add/remove tiles mode.
 				if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
 				if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {

+ 35 - 12
scene/2d/tile_map.cpp

@@ -891,7 +891,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List
 							modulate.a *= 0.3;
 							modulate.a *= 0.3;
 						}
 						}
 					}
 					}
-					draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, modulate);
+					draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, modulate);
 
 
 					// --- Occluders ---
 					// --- Occluders ---
 					for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
 					for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
@@ -1008,15 +1008,19 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
 	}
 	}
 }
 }
 
 
-void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) {
+void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation) {
 	ERR_FAIL_COND(!p_tile_set.is_valid());
 	ERR_FAIL_COND(!p_tile_set.is_valid());
 	ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id));
 	ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id));
 	ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords));
 	ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords));
 	ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile));
 	ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile));
-
 	TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id);
 	TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id);
 	TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
 	TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
 	if (atlas_source) {
 	if (atlas_source) {
+		// Check for the frame.
+		if (p_frame >= 0) {
+			ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords));
+		}
+
 		// Get the texture.
 		// Get the texture.
 		Ref<Texture2D> tex = atlas_source->get_texture();
 		Ref<Texture2D> tex = atlas_source->get_texture();
 		if (!tex.is_valid()) {
 		if (!tex.is_valid()) {
@@ -1032,13 +1036,16 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe
 		// Get tile data.
 		// Get tile data.
 		TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile));
 		TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile));
 
 
-		// Compute the offset
-		Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords);
+		// Get the tile modulation.
+		Color modulate = tile_data->get_modulate();
+		modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a);
+
+		// Compute the offset.
 		Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(p_atlas_coords, p_alternative_tile);
 		Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(p_atlas_coords, p_alternative_tile);
 
 
-		// Compute the destination rectangle in the CanvasItem.
+		// Get destination rect.
 		Rect2 dest_rect;
 		Rect2 dest_rect;
-		dest_rect.size = source_rect.size;
+		dest_rect.size = atlas_source->get_tile_texture_region(p_atlas_coords).size;
 		dest_rect.size.x += FP_ADJUST;
 		dest_rect.size.x += FP_ADJUST;
 		dest_rect.size.y += FP_ADJUST;
 		dest_rect.size.y += FP_ADJUST;
 
 
@@ -1057,12 +1064,28 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe
 			dest_rect.size.y = -dest_rect.size.y;
 			dest_rect.size.y = -dest_rect.size.y;
 		}
 		}
 
 
-		// Get the tile modulation.
-		Color modulate = tile_data->get_modulate();
-		modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a);
-
 		// Draw the tile.
 		// Draw the tile.
-		tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
+		if (p_frame >= 0) {
+			Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, p_frame);
+			tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
+		} else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) {
+			Rect2i source_rect = atlas_source->get_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 {
+			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;
+			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, 0.0);
+
+				Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, 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);
+		}
 	}
 	}
 }
 }
 
 

+ 1 - 1
scene/2d/tile_map.h

@@ -305,7 +305,7 @@ public:
 	void set_quadrant_size(int p_size);
 	void set_quadrant_size(int p_size);
 	int get_quadrant_size() const;
 	int get_quadrant_size() const;
 
 
-	static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0));
+	static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0));
 
 
 	// Layers management.
 	// Layers management.
 	int get_layers_count() const;
 	int get_layers_count() const;

+ 300 - 91
scene/resources/tile_set.cpp

@@ -3119,7 +3119,7 @@ Vector2i TileSetAtlasSource::get_atlas_grid_size() const {
 }
 }
 
 
 bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) {
 bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) {
-	Vector<String> components = String(p_name).split("/", true, 2);
+	Vector<String> components = String(p_name).split("/", true, 3);
 
 
 	// Compute the vector2i if we have coordinates.
 	// Compute the vector2i if we have coordinates.
 	Vector<String> coords_split = components[0].split(":");
 	Vector<String> coords_split = components[0].split(":");
@@ -3138,8 +3138,32 @@ bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value)
 			// Properties.
 			// Properties.
 			if (components[1] == "size_in_atlas") {
 			if (components[1] == "size_in_atlas") {
 				move_tile_in_atlas(coords, coords, p_value);
 				move_tile_in_atlas(coords, coords, p_value);
+				return true;
 			} else if (components[1] == "next_alternative_id") {
 			} else if (components[1] == "next_alternative_id") {
 				tiles[coords].next_alternative_id = p_value;
 				tiles[coords].next_alternative_id = p_value;
+				return true;
+			} else if (components[1] == "animation_columns") {
+				set_tile_animation_columns(coords, p_value);
+				return true;
+			} else if (components[1] == "animation_separation") {
+				set_tile_animation_separation(coords, p_value);
+				return true;
+			} else if (components[1] == "animation_speed") {
+				set_tile_animation_speed(coords, p_value);
+				return true;
+			} else if (components[1] == "animation_frames_count") {
+				set_tile_animation_frames_count(coords, p_value);
+				return true;
+			} else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) {
+				int frame = components[1].trim_prefix("animation_frame_").to_int();
+				if (components[2] == "duration") {
+					if (frame >= get_tile_animation_frames_count(coords)) {
+						set_tile_animation_frames_count(coords, frame + 1);
+					}
+					set_tile_animation_frame_duration(coords, frame, p_value);
+					return true;
+				}
+				return false;
 			} else if (components[1].is_valid_int()) {
 			} else if (components[1].is_valid_int()) {
 				int alternative_id = components[1].to_int();
 				int alternative_id = components[1].to_int();
 				if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) {
 				if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) {
@@ -3185,6 +3209,28 @@ bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const {
 				} else if (components[1] == "next_alternative_id") {
 				} else if (components[1] == "next_alternative_id") {
 					r_ret = tiles[coords].next_alternative_id;
 					r_ret = tiles[coords].next_alternative_id;
 					return true;
 					return true;
+				} else if (components[1] == "animation_columns") {
+					r_ret = get_tile_animation_columns(coords);
+					return true;
+				} else if (components[1] == "animation_separation") {
+					r_ret = get_tile_animation_separation(coords);
+					return true;
+				} else if (components[1] == "animation_speed") {
+					r_ret = get_tile_animation_speed(coords);
+					return true;
+				} else if (components[1] == "animation_frames_count") {
+					r_ret = get_tile_animation_frames_count(coords);
+					return true;
+				} else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) {
+					int frame = components[1].trim_prefix("animation_frame_").to_int();
+					if (frame < 0 || frame >= get_tile_animation_frames_count(coords)) {
+						return false;
+					}
+					if (components[2] == "duration") {
+						r_ret = get_tile_animation_frame_duration(coords, frame);
+						return true;
+					}
+					return false;
 				} else if (components[1].is_valid_int()) {
 				} else if (components[1].is_valid_int()) {
 					int alternative_id = components[1].to_int();
 					int alternative_id = components[1].to_int();
 					if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) {
 					if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) {
@@ -3226,6 +3272,40 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
 		}
 		}
 		tile_property_list.push_back(property_info);
 		tile_property_list.push_back(property_info);
 
 
+		// animation_columns.
+		property_info = PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+		if (E_tile->get().animation_columns == 0) {
+			property_info.usage ^= PROPERTY_USAGE_STORAGE;
+		}
+		tile_property_list.push_back(property_info);
+
+		// animation_separation.
+		property_info = PropertyInfo(Variant::INT, "animation_separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+		if (E_tile->get().animation_separation == Vector2i()) {
+			property_info.usage ^= PROPERTY_USAGE_STORAGE;
+		}
+		tile_property_list.push_back(property_info);
+
+		// animation_speed.
+		property_info = PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+		if (E_tile->get().animation_speed == 1.0) {
+			property_info.usage ^= PROPERTY_USAGE_STORAGE;
+		}
+		tile_property_list.push_back(property_info);
+
+		// animation_frames_count.
+		tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NETWORK));
+
+		// animation_frame_*.
+		bool store_durations = tiles[E_tile->key()].animation_frames_durations.size() >= 2;
+		for (int i = 0; i < (int)tiles[E_tile->key()].animation_frames_durations.size(); i++) {
+			property_info = PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+			if (!store_durations) {
+				property_info.usage ^= PROPERTY_USAGE_STORAGE;
+			}
+			tile_property_list.push_back(property_info);
+		}
+
 		for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) {
 		for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) {
 			// Add a dummy property to show the alternative exists.
 			// Add a dummy property to show the alternative exists.
 			tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
 			tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
@@ -3234,11 +3314,10 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
 			List<PropertyInfo> alternative_property_list;
 			List<PropertyInfo> alternative_property_list;
 			E_alternative->get()->get_property_list(&alternative_property_list);
 			E_alternative->get()->get_property_list(&alternative_property_list);
 			for (PropertyInfo &alternative_property_info : alternative_property_list) {
 			for (PropertyInfo &alternative_property_info : alternative_property_list) {
-				bool valid;
-				Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name, &valid);
+				Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name);
 				Variant value = E_alternative->get()->get(alternative_property_info.name);
 				Variant value = E_alternative->get()->get(alternative_property_info.name);
-				if (valid && value == default_value) {
-					property_info.usage ^= PROPERTY_USAGE_STORAGE;
+				if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
+					alternative_property_info.usage ^= PROPERTY_USAGE_STORAGE;
 				}
 				}
 				alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative->key()), alternative_property_info.name);
 				alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative->key()), alternative_property_info.name);
 				tile_property_list.push_back(alternative_property_info);
 				tile_property_list.push_back(alternative_property_info);
@@ -3257,33 +3336,27 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector
 	// Create a tile if it does not exists.
 	// Create a tile if it does not exists.
 	ERR_FAIL_COND(p_atlas_coords.x < 0 || p_atlas_coords.y < 0);
 	ERR_FAIL_COND(p_atlas_coords.x < 0 || p_atlas_coords.y < 0);
 	ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0);
 	ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0);
-	for (int x = 0; x < p_size.x; x++) {
-		for (int y = 0; y < p_size.y; y++) {
-			Vector2i coords = p_atlas_coords + Vector2i(x, y);
-			ERR_FAIL_COND_MSG(tiles.has(coords), vformat("Cannot create tile at position %s with size %s. Already a tile present at %s.", p_atlas_coords, p_size, coords));
-		}
-	}
+
+	bool room_for_tile = has_room_for_tile(p_atlas_coords, p_size, 1, Vector2i(), 1);
+	ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile, tiles are already present in the space the tile would cover.");
+
+	// Initialize the tile data.
+	TileAlternativesData tad;
+	tad.size_in_atlas = p_size;
+	tad.animation_frames_durations.push_back(1.0);
+	tad.alternatives[0] = memnew(TileData);
+	tad.alternatives[0]->set_tile_set(tile_set);
+	tad.alternatives[0]->set_allow_transform(false);
+	tad.alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed));
+	tad.alternatives[0]->notify_property_list_changed();
+	tad.alternatives_ids.append(0);
 
 
 	// Create and resize the tile.
 	// Create and resize the tile.
-	tiles.insert(p_atlas_coords, TileSetAtlasSource::TileAlternativesData());
+	tiles.insert(p_atlas_coords, tad);
 	tiles_ids.append(p_atlas_coords);
 	tiles_ids.append(p_atlas_coords);
 	tiles_ids.sort();
 	tiles_ids.sort();
 
 
-	tiles[p_atlas_coords].size_in_atlas = p_size;
-	tiles[p_atlas_coords].alternatives[0] = memnew(TileData);
-	tiles[p_atlas_coords].alternatives[0]->set_tile_set(tile_set);
-	tiles[p_atlas_coords].alternatives[0]->set_allow_transform(false);
-	tiles[p_atlas_coords].alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed));
-	tiles[p_atlas_coords].alternatives[0]->notify_property_list_changed();
-	tiles[p_atlas_coords].alternatives_ids.append(0);
-
-	// Add all covered positions to the mapping cache
-	for (int x = 0; x < p_size.x; x++) {
-		for (int y = 0; y < p_size.y; y++) {
-			Vector2i coords = p_atlas_coords + Vector2i(x, y);
-			_coords_mapping_cache[coords] = p_atlas_coords;
-		}
-	}
+	_create_coords_mapping_cache(p_atlas_coords);
 
 
 	emit_signal(SNAME("changed"));
 	emit_signal(SNAME("changed"));
 }
 }
@@ -3292,14 +3365,7 @@ void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) {
 	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
 	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
 
 
 	// Remove all covered positions from the mapping cache
 	// Remove all covered positions from the mapping cache
-	Size2i size = tiles[p_atlas_coords].size_in_atlas;
-
-	for (int x = 0; x < size.x; x++) {
-		for (int y = 0; y < size.y; y++) {
-			Vector2i coords = p_atlas_coords + Vector2i(x, y);
-			_coords_mapping_cache.erase(coords);
-		}
-	}
+	_clear_coords_mapping_cache(p_atlas_coords);
 
 
 	// Free tile data.
 	// Free tile data.
 	for (Map<int, TileData *>::Element *E_tile_data = tiles[p_atlas_coords].alternatives.front(); E_tile_data; E_tile_data = E_tile_data->next()) {
 	for (Map<int, TileData *>::Element *E_tile_data = tiles[p_atlas_coords].alternatives.front(); E_tile_data; E_tile_data = E_tile_data->next()) {
@@ -3326,6 +3392,118 @@ Vector2i TileSetAtlasSource::get_tile_at_coords(Vector2i p_atlas_coords) const {
 	return _coords_mapping_cache[p_atlas_coords];
 	return _coords_mapping_cache[p_atlas_coords];
 }
 }
 
 
+void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns) {
+	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	ERR_FAIL_COND(p_frame_columns < 0);
+
+	TileAlternativesData &tad = tiles[p_atlas_coords];
+	bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, p_frame_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords);
+	ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover.");
+
+	_clear_coords_mapping_cache(p_atlas_coords);
+
+	tiles[p_atlas_coords].animation_columns = p_frame_columns;
+
+	_create_coords_mapping_cache(p_atlas_coords);
+
+	emit_signal(SNAME("changed"));
+}
+
+int TileSetAtlasSource::get_tile_animation_columns(const Vector2i p_atlas_coords) const {
+	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	return tiles[p_atlas_coords].animation_columns;
+}
+
+void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation) {
+	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	ERR_FAIL_COND(p_separation.x < 0 || p_separation.y < 0);
+
+	TileAlternativesData &tad = tiles[p_atlas_coords];
+	bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, p_separation, tad.animation_frames_durations.size(), p_atlas_coords);
+	ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover.");
+
+	_clear_coords_mapping_cache(p_atlas_coords);
+
+	tiles[p_atlas_coords].animation_separation = p_separation;
+
+	_create_coords_mapping_cache(p_atlas_coords);
+
+	emit_signal(SNAME("changed"));
+}
+
+Vector2i TileSetAtlasSource::get_tile_animation_separation(const Vector2i p_atlas_coords) const {
+	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	return tiles[p_atlas_coords].animation_separation;
+}
+
+void TileSetAtlasSource::set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed) {
+	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	ERR_FAIL_COND(p_speed <= 0);
+
+	tiles[p_atlas_coords].animation_speed = p_speed;
+
+	emit_signal(SNAME("changed"));
+}
+
+real_t TileSetAtlasSource::get_tile_animation_speed(const Vector2i p_atlas_coords) const {
+	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1.0, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	return tiles[p_atlas_coords].animation_speed;
+}
+
+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);
+
+	TileAlternativesData &tad = tiles[p_atlas_coords];
+	bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, tad.animation_separation, p_frames_count, p_atlas_coords);
+	ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover.");
+
+	_clear_coords_mapping_cache(p_atlas_coords);
+
+	int old_size = tiles[p_atlas_coords].animation_frames_durations.size();
+	tiles[p_atlas_coords].animation_frames_durations.resize(p_frames_count);
+	for (int i = old_size; i < p_frames_count; i++) {
+		tiles[p_atlas_coords].animation_frames_durations[i] = 1.0;
+	}
+
+	_create_coords_mapping_cache(p_atlas_coords);
+
+	notify_property_list_changed();
+
+	emit_signal(SNAME("changed"));
+}
+
+int TileSetAtlasSource::get_tile_animation_frames_count(const Vector2i p_atlas_coords) const {
+	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	return tiles[p_atlas_coords].animation_frames_durations.size();
+}
+
+void TileSetAtlasSource::set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration) {
+	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());
+	ERR_FAIL_COND(p_duration <= 0.0);
+
+	tiles[p_atlas_coords].animation_frames_durations[p_frame_index] = p_duration;
+
+	emit_signal(SNAME("changed"));
+}
+
+real_t TileSetAtlasSource::get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const {
+	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	ERR_FAIL_INDEX_V(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size(), 0.0);
+	return tiles[p_atlas_coords].animation_frames_durations[p_frame_index];
+}
+
+real_t TileSetAtlasSource::get_tile_animation_total_duration(const Vector2i p_atlas_coords) const {
+	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+
+	real_t sum = 0.0;
+	for (int frame = 0; frame < (int)tiles[p_atlas_coords].animation_frames_durations.size(); frame++) {
+		sum += tiles[p_atlas_coords].animation_frames_durations[frame];
+	}
+	return sum;
+}
+
 Vector2i TileSetAtlasSource::get_tile_size_in_atlas(Vector2i p_atlas_coords) const {
 Vector2i TileSetAtlasSource::get_tile_size_in_atlas(Vector2i p_atlas_coords) const {
 	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(-1, -1), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
 	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(-1, -1), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
 
 
@@ -3341,16 +3519,34 @@ Vector2i TileSetAtlasSource::get_tile_id(int p_index) const {
 	return tiles_ids[p_index];
 	return tiles_ids[p_index];
 }
 }
 
 
-Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords) const {
+bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile) const {
+	for (int frame = 0; frame < p_frames_count; frame++) {
+		Vector2i frame_coords = p_atlas_coords + (p_size + p_animation_separation) * ((p_animation_columns > 0) ? Vector2i(frame % p_animation_columns, frame / p_animation_columns) : Vector2i(frame, 0));
+		for (int x = 0; x < p_size.x; x++) {
+			for (int y = 0; y < p_size.y; y++) {
+				Vector2i coords = frame_coords + Vector2i(x, y);
+				if (_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] != p_ignored_tile) {
+					return false;
+				}
+			}
+		}
+	}
+	return true;
+}
+
+Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const {
 	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
 	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+	ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i());
+
+	const TileAlternativesData &tad = tiles[p_atlas_coords];
 
 
-	Vector2i size_in_atlas = tiles[p_atlas_coords].size_in_atlas;
+	Vector2i size_in_atlas = tad.size_in_atlas;
 	Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1));
 	Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1));
 
 
-	Vector2 origin = margins + (p_atlas_coords * (texture_region_size + separation));
+	Vector2i frame_coords = p_atlas_coords + (size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0));
+	Vector2 origin = margins + (frame_coords * (texture_region_size + separation));
 
 
 	return Rect2(origin, region_size);
 	return Rect2(origin, region_size);
-	;
 }
 }
 
 
 Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const {
 Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const {
@@ -3369,56 +3565,23 @@ Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_
 	return effective_texture_offset;
 	return effective_texture_offset;
 }
 }
 
 
-bool TileSetAtlasSource::can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) const {
-	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
-
-	Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords;
-	if (new_atlas_coords.x < 0 || new_atlas_coords.y < 0) {
-		return false;
-	}
-
-	Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas;
-	ERR_FAIL_COND_V(size.x <= 0 || size.y <= 0, false);
-
-	Size2i grid_size = get_atlas_grid_size();
-	if (new_atlas_coords.x + size.x > grid_size.x || new_atlas_coords.y + size.y > grid_size.y) {
-		return false;
-	}
-
-	Rect2i new_rect = Rect2i(new_atlas_coords, size);
-	// Check if the new tile can fit in the new rect.
-	for (int x = new_rect.position.x; x < new_rect.get_end().x; x++) {
-		for (int y = new_rect.position.y; y < new_rect.get_end().y; y++) {
-			Vector2i coords = get_tile_at_coords(Vector2i(x, y));
-			if (coords != p_atlas_coords && coords != TileSetSource::INVALID_ATLAS_COORDS) {
-				return false;
-			}
-		}
-	}
-
-	return true;
-}
-
 void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) {
 void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) {
-	bool can_move = can_move_tile_in_atlas(p_atlas_coords, p_new_atlas_coords, p_new_size);
-	ERR_FAIL_COND_MSG(!can_move, vformat("Cannot move tile at position %s with size %s. Tile already present.", p_new_atlas_coords, p_new_size));
+	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+
+	TileAlternativesData &tad = tiles[p_atlas_coords];
 
 
 	// Compute the actual new rect from arguments.
 	// Compute the actual new rect from arguments.
 	Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords;
 	Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords;
-	Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas;
+	Vector2i new_size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tad.size_in_atlas;
 
 
-	if (new_atlas_coords == p_atlas_coords && size == tiles[p_atlas_coords].size_in_atlas) {
+	if (new_atlas_coords == p_atlas_coords && new_size == tad.size_in_atlas) {
 		return;
 		return;
 	}
 	}
 
 
-	// Remove all covered positions from the mapping cache.
-	Size2i old_size = tiles[p_atlas_coords].size_in_atlas;
-	for (int x = 0; x < old_size.x; x++) {
-		for (int y = 0; y < old_size.y; y++) {
-			Vector2i coords = p_atlas_coords + Vector2i(x, y);
-			_coords_mapping_cache.erase(coords);
-		}
-	}
+	bool room_for_tile = has_room_for_tile(new_atlas_coords, new_size, tad.animation_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords);
+	ERR_FAIL_COND_MSG(!room_for_tile, vformat("Cannot move tile at position %s with size %s. Tile already present.", new_atlas_coords, new_size));
+
+	_clear_coords_mapping_cache(p_atlas_coords);
 
 
 	// Move the tile and update its size.
 	// Move the tile and update its size.
 	if (new_atlas_coords != p_atlas_coords) {
 	if (new_atlas_coords != p_atlas_coords) {
@@ -3429,15 +3592,9 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_
 		tiles_ids.append(new_atlas_coords);
 		tiles_ids.append(new_atlas_coords);
 		tiles_ids.sort();
 		tiles_ids.sort();
 	}
 	}
-	tiles[new_atlas_coords].size_in_atlas = size;
+	tiles[new_atlas_coords].size_in_atlas = new_size;
 
 
-	// Add all covered positions to the mapping cache again.
-	for (int x = 0; x < size.x; x++) {
-		for (int y = 0; y < size.y; y++) {
-			Vector2i coords = new_atlas_coords + Vector2i(x, y);
-			_coords_mapping_cache[coords] = new_atlas_coords;
-		}
-	}
+	_create_coords_mapping_cache(new_atlas_coords);
 
 
 	emit_signal(SNAME("changed"));
 	emit_signal(SNAME("changed"));
 }
 }
@@ -3566,12 +3723,25 @@ void TileSetAtlasSource::_bind_methods() {
 	// Base tiles
 	// Base tiles
 	ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1)));
 	ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1)));
 	ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative
 	ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative
-	ClassDB::bind_method(D_METHOD("can_move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::can_move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1)));
 	ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1)));
 	ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1)));
 	ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas);
 	ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas);
 
 
+	ClassDB::bind_method(D_METHOD("has_room_for_tile", "atlas_coords", "size", "animation_columns", "animation_separation", "frames_count", "ignored_tile"), &TileSetAtlasSource::has_room_for_tile, DEFVAL(INVALID_ATLAS_COORDS));
+
 	ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords);
 	ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords);
 
 
+	ClassDB::bind_method(D_METHOD("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns);
+	ClassDB::bind_method(D_METHOD("get_tile_animation_columns", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_columns);
+	ClassDB::bind_method(D_METHOD("set_tile_animation_separation", "atlas_coords", "separation"), &TileSetAtlasSource::set_tile_animation_separation);
+	ClassDB::bind_method(D_METHOD("get_tile_animation_separation", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_separation);
+	ClassDB::bind_method(D_METHOD("set_tile_animation_speed", "atlas_coords", "speed"), &TileSetAtlasSource::set_tile_animation_speed);
+	ClassDB::bind_method(D_METHOD("get_tile_animation_speed", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_speed);
+	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);
+	ClassDB::bind_method(D_METHOD("get_tile_animation_frame_duration", "atlas_coords", "frame_index"), &TileSetAtlasSource::get_tile_animation_frame_duration);
+	ClassDB::bind_method(D_METHOD("get_tile_animation_total_duration", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_total_duration);
+
 	// Alternative tiles
 	// Alternative tiles
 	ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE));
 	ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE));
 	ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile);
 	ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile);
@@ -3584,7 +3754,7 @@ void TileSetAtlasSource::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size);
 	ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size);
 	ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture);
 	ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture);
 	ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture);
 	ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture);
-	ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords"), &TileSetAtlasSource::get_tile_texture_region);
+	ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0));
 }
 }
 
 
 TileSetAtlasSource::~TileSetAtlasSource() {
 TileSetAtlasSource::~TileSetAtlasSource() {
@@ -3618,6 +3788,45 @@ void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coo
 	};
 	};
 }
 }
 
 
+void TileSetAtlasSource::_clear_coords_mapping_cache(Vector2i p_atlas_coords) {
+	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+	TileAlternativesData &tad = tiles[p_atlas_coords];
+	for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) {
+		Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0));
+		for (int x = 0; x < tad.size_in_atlas.x; x++) {
+			for (int y = 0; y < tad.size_in_atlas.y; y++) {
+				Vector2i coords = frame_coords + Vector2i(x, y);
+				if (!_coords_mapping_cache.has(coords)) {
+					WARN_PRINT(vformat("TileSetAtlasSource has no cached tile at position %s, the position cache might be corrupted.", coords));
+				} else {
+					if (_coords_mapping_cache[coords] != p_atlas_coords) {
+						WARN_PRINT(vformat("The position cache at position %s is pointing to a wrong tile, the position cache might be corrupted.", coords));
+					}
+					_coords_mapping_cache.erase(coords);
+				}
+			}
+		}
+	}
+}
+
+void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) {
+	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+
+	TileAlternativesData &tad = tiles[p_atlas_coords];
+	for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) {
+		Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0));
+		for (int x = 0; x < tad.size_in_atlas.x; x++) {
+			for (int y = 0; y < tad.size_in_atlas.y; y++) {
+				Vector2i coords = frame_coords + Vector2i(x, y);
+				if (_coords_mapping_cache.has(coords)) {
+					WARN_PRINT(vformat("The cache already has a tile for position %s, the position cache might be corrupted.", coords));
+				}
+				_coords_mapping_cache[coords] = p_atlas_coords;
+			}
+		}
+	}
+}
+
 /////////////////////////////// TileSetScenesCollectionSource //////////////////////////////////////
 /////////////////////////////// TileSetScenesCollectionSource //////////////////////////////////////
 
 
 void TileSetScenesCollectionSource::_compute_next_alternative_id() {
 void TileSetScenesCollectionSource::_compute_next_alternative_id() {

+ 30 - 6
scene/resources/tile_set.h

@@ -447,16 +447,23 @@ public:
 class TileSetAtlasSource : public TileSetSource {
 class TileSetAtlasSource : public TileSetSource {
 	GDCLASS(TileSetAtlasSource, TileSetSource);
 	GDCLASS(TileSetAtlasSource, TileSetSource);
 
 
-public:
+private:
 	struct TileAlternativesData {
 	struct TileAlternativesData {
 		Vector2i size_in_atlas = Vector2i(1, 1);
 		Vector2i size_in_atlas = Vector2i(1, 1);
 		Vector2i texture_offset;
 		Vector2i texture_offset;
+
+		// Animation
+		int animation_columns = 0;
+		Vector2i animation_separation;
+		real_t animation_speed = 1.0;
+		LocalVector<real_t> animation_frames_durations;
+
+		// Alternatives
 		Map<int, TileData *> alternatives;
 		Map<int, TileData *> alternatives;
 		Vector<int> alternatives_ids;
 		Vector<int> alternatives_ids;
 		int next_alternative_id = 1;
 		int next_alternative_id = 1;
 	};
 	};
 
 
-private:
 	Ref<Texture2D> texture;
 	Ref<Texture2D> texture;
 	Vector2i margins;
 	Vector2i margins;
 	Vector2i separation;
 	Vector2i separation;
@@ -471,6 +478,9 @@ private:
 
 
 	void _compute_next_alternative_id(const Vector2i p_atlas_coords);
 	void _compute_next_alternative_id(const Vector2i p_atlas_coords);
 
 
+	void _create_coords_mapping_cache(Vector2i p_atlas_coords);
+	void _clear_coords_mapping_cache(Vector2i p_atlas_coords);
+
 protected:
 protected:
 	bool _set(const StringName &p_name, const Variant &p_value);
 	bool _set(const StringName &p_name, const Variant &p_value);
 	bool _get(const StringName &p_name, Variant &r_ret) const;
 	bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -513,18 +523,32 @@ public:
 	Vector2i get_texture_region_size() const;
 	Vector2i get_texture_region_size() const;
 
 
 	// Base tiles.
 	// Base tiles.
-	void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); // Create a tile if it does not exists, or add alternative tile if it does.
-	void remove_tile(Vector2i p_atlas_coords); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative
+	void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1));
+	void remove_tile(Vector2i p_atlas_coords);
 	virtual bool has_tile(Vector2i p_atlas_coords) const override;
 	virtual bool has_tile(Vector2i p_atlas_coords) const override;
-	bool can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)) const;
 	void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1));
 	void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1));
 	Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const;
 	Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const;
 
 
 	virtual int get_tiles_count() const override;
 	virtual int get_tiles_count() const override;
 	virtual Vector2i get_tile_id(int p_index) const override;
 	virtual Vector2i get_tile_id(int p_index) const override;
 
 
+	bool has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile = INVALID_ATLAS_COORDS) const;
+
 	Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const;
 	Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const;
 
 
+	// Animation.
+	void set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns);
+	int get_tile_animation_columns(const Vector2i p_atlas_coords) const;
+	void set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation);
+	Vector2i get_tile_animation_separation(const Vector2i p_atlas_coords) const;
+	void set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed);
+	real_t get_tile_animation_speed(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);
+	real_t get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const;
+	real_t get_tile_animation_total_duration(const Vector2i p_atlas_coords) const;
+
 	// Alternative tiles.
 	// Alternative tiles.
 	int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1);
 	int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1);
 	void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile);
 	void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile);
@@ -542,7 +566,7 @@ public:
 	Vector2i get_atlas_grid_size() const;
 	Vector2i get_atlas_grid_size() const;
 	bool has_tiles_outside_texture();
 	bool has_tiles_outside_texture();
 	void clear_tiles_outside_texture();
 	void clear_tiles_outside_texture();
-	Rect2i get_tile_texture_region(Vector2i p_atlas_coords) const;
+	Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const;
 	Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const;
 	Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const;
 
 
 	~TileSetAtlasSource();
 	~TileSetAtlasSource();