Add place_random_tiles method to place tiles randomly across a specified region

This commit is contained in:
SirQuartz 2024-07-28 20:17:32 -04:00
parent 88d9325065
commit ef3e706a05
3 changed files with 119 additions and 0 deletions

View File

@ -185,6 +185,19 @@
[b]Note:[/b] This does not trigger a direct update of the [TileMapLayer], the update will be done at the end of the frame as usual (unless you call [method update_internals]).
</description>
</method>
<method name="place_random_tiles">
<return type="void" />
<param index="0" name="atlas_coords_array" type="Array" />
<param index="1" name="region" type="Rect2i" />
<param index="2" name="scattering_probability" type="float" />
<param index="3" name="replace_tile" type="bool" />
<description>
Places random tiles within a specified region.
The [param atlas_coords_array] is an array of [Vector4i] elements, where the first element is the source ID of the atlas, the second element is the X-index, and the third element is the Y-index for the desired atlas tile, the fourth element is the optional alternative tile to use. If the atlas source ID is a scene collection, the first element is the scene collection source ID, the next element is the scene ID within that collection, and the last two values are ignored.
The [param scattering_probability] is a [float] from 0 to 1 that defines the probability of placing a tile in a cell. If set to [code]0[/code], all cells in the region will be filled with a random tile from the [param atlas_coords_array].
The [param replace_tile] parameter defines whether to replace existing tiles in the region. If set to [code]true[/code], existing tiles will be replaced with random tiles from the [param atlas_coords_array], otherwise, no existing tiles will be replaced.
</description>
</method>
<method name="set_cell">
<return type="void" />
<param index="0" name="coords" type="Vector2i" />

View File

@ -1764,6 +1764,7 @@ void TileMapLayer::_bind_methods() {
// --- Cells manipulation ---
// Generic cells manipulations and access.
ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapLayer::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(0));
ClassDB::bind_method(D_METHOD("place_random_tiles", "atlas_coords_array", "region", "scattering_probability", "replace_tile"), &TileMapLayer::place_random_tiles);
ClassDB::bind_method(D_METHOD("erase_cell", "coords"), &TileMapLayer::erase_cell);
ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMapLayer::fix_invalid_tiles);
ClassDB::bind_method(D_METHOD("clear"), &TileMapLayer::clear);
@ -2355,6 +2356,110 @@ void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vec
used_rect_cache_dirty = true;
}
void TileMapLayer::place_random_tiles(Array atlas_coords_array, Rect2i region, float scattering_probability, bool replace_tile) {
// Validate the input
if (atlas_coords_array.is_empty()) {
return;
}
// Initialize the maximum coordinates for the region
int max_x = region.position.x + region.size.x;
int max_y = region.position.y + region.size.y;
// Precompute the sum of probabilities and store them in a vector for quick access
Vector<double> probabilities;
probabilities.resize(atlas_coords_array.size());
double sum = 0.0;
for (int i = 0; i < atlas_coords_array.size(); ++i) {
Vector4i tile_info = atlas_coords_array[i];
int source_id = tile_info.x;
int scene_id = tile_info.y; // Used for scene collections
Vector2i atlas_coords(tile_info.z, tile_info.w); // Used for 2D tile atlases
Ref<TileSetSource> source = tile_set->get_source(source_id);
// Check if the source is a TileSetAtlasSource
Ref<TileSetAtlasSource> atlas_source = source;
if (atlas_source.is_valid()) {
if (atlas_source->has_tile(atlas_coords)) {
TileData *tile_data = atlas_source->get_tile_data(atlas_coords, 0);
if (tile_data) {
sum += tile_data->get_probability();
probabilities.set(i, sum);
}
}
} else {
// Handle scene collections
Ref<TileSetScenesCollectionSource> scenes_collection_source = source;
if (scenes_collection_source.is_valid()) {
if (scenes_collection_source->has_scene_tile_id(scene_id)) {
// For scene collections, we dont need to calculate probability
sum += 1.0; // Treat each scene as having equal probability
probabilities.set(i, sum);
}
}
}
}
// Loop through each cell in the specified region
for (int x = region.position.x; x < max_x; ++x) {
for (int y = region.position.y; y < max_y; ++y) {
// Use the scattering value to decide whether to place a tile
if (Math::randf() > scattering_probability) {
continue; // Skip placing a tile in this cell
}
// Check if we should replace existing tiles
if (!replace_tile) {
int existing_source_id = get_cell_source_id(Vector2i(x, y));
if (existing_source_id != -1) {
continue; // Skip this cell if it already has a tile and we shouldn't replace it
}
}
// Generate a random number to pick a tile based on precomputed probabilities
double picked = Math::randf() * sum;
for (int i = 0; i < probabilities.size(); ++i) {
if (picked <= probabilities[i]) {
Vector4i tile_info = atlas_coords_array[i];
int source_id = tile_info.x;
int scene_id = tile_info.y; // Used for scene collections
Vector2i atlas_coords(tile_info.z, tile_info.w); // Used for 2D tile atlases
Ref<TileSetSource> source = tile_set->get_source(source_id);
Ref<TileSetAtlasSource> atlas_source = source;
if (atlas_source.is_valid()) {
if (atlas_source->has_tile(atlas_coords)) {
set_cell(Vector2i(x, y), source_id, atlas_coords, 0);
}
} else {
// Handle scene collections
Ref<TileSetScenesCollectionSource> scenes_collection_source = source;
if (scenes_collection_source.is_valid()) {
if (scenes_collection_source->has_scene_tile_id(scene_id)) {
Ref<PackedScene> scene = scenes_collection_source->get_scene_tile_scene(scene_id);
if (scene.is_valid()) {
// Create an instance of the scene
Node2D *node = Object::cast_to<Node2D>(scene->instantiate());
if (node) {
// Set the position of the node
Vector2 local_position = map_to_local(Vector2(x, y));
node->set_position(local_position);
add_child(node);
}
}
}
}
}
break;
}
}
}
}
}
void TileMapLayer::erase_cell(const Vector2i &p_coords) {
set_cell(p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
}

View File

@ -425,6 +425,7 @@ public:
// --- Cells manipulation ---
// Generic cells manipulations and data access.
void set_cell(const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i &p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0);
void place_random_tiles(Array atlas_coords_array, Rect2i region, float scattering_probability, bool replace_tile);
void erase_cell(const Vector2i &p_coords);
void fix_invalid_tiles();
void clear();