+1 vote

Hi, I want to select a random tile, inside in a determinate radius.
I am working in a procedural generator of islands, it work verry well :), and to place randomly tiles like trees for example, i would like use diferents ways, one of this could be put a tree, take his cor and randomly place others trees near in a radius

I thought 2 ways to do it:
1- make an array with all near tiles in a radius after, select randomly in the array
2- select a random cor point inside in a radius after, place in the nearest tile

radius like that

I don't know much about maths, less about cos() and sin(), how can I do one, both or others ways

English isn't my native language, I tried to do the better that I could :3

in Engine by (26 points)

I can help you, but I didn't the whole question.

let's say we have one point (a center), and a radius, for example 4 tiles aways from the center, how can I get the tiles that are in this imagin circle?, do you understand me?

1 Answer

+3 votes
Best answer

I put together an example script with a tilemap, showing how to obtain grid positions within a radius, and also a few different methods to pick a random position within a circle. (note that if you have an array of positions you can also pick a random item from it directlty).

extends Node


onready var _tilemap = $TileMap

var _radius_in_tiles = 4.0 # Note: decimal values work too, you may try
var _center_in_tiles = Vector2(5, 5) # Hardcoded, you may choose


func _ready():
    var positions = get_tile_positions_in_circle()
    for pos in positions:
        _tilemap.set_cellv(pos, 0)

    # Get random position around point. There are several ways to do it.
    var tile_position = pick_random_position_v1()
    # Remove a tile for testing
    _tilemap.set_cellv(tile_position, -1)

    # Or simply use the array computed earlier.
    tile_position = positions[randi() % len(positions)]
    _tilemap.set_cellv(tile_position, -1)


func get_tile_positions_in_circle():
    # Get the rectangle bounding the circle
    var radius_vec = Vector2(_radius_in_tiles, _radius_in_tiles)
    var min_pos = (_center_in_tiles - radius_vec).floor()
    var max_pos = (_center_in_tiles + radius_vec).ceil()

    # Convert to integer so we can use range for loop
    var min_x = int(min_pos.x)
    var max_x = int(max_pos.x)
    var min_y = int(min_pos.y)
    var max_y = int(max_pos.y)

    var positions = []

    # Gather all points that are within the radius
    for y in range(min_y, max_y):
        for x in range(min_x, max_x):
            var tile_pos = Vector2(x, y)
            if tile_pos.distance_to(_center_in_tiles) < _radius_in_tiles:
                 positions.append(tile_pos)

    return positions


func pick_random_position_v1():
    # Trigonometry solution.
    # It's fast and simple, but points have a higher chance to be near the center.
    # In some cases this is acceptable.
    var angle = rand_range(-PI, PI)
    var direction = Vector2(cos(angle), sin(angle))
    return _center_in_tiles + direction * _radius_in_tiles


func pick_random_position_v2():
    # Improved trigonometric solution.
    # Density of results will be the same at any distance from center.
    # https://programming.guide/random-point-within-circle.html
    var angle = rand_range(-PI, PI)
    var direction = Vector2(cos(angle), sin(angle))
    var distance = _radius_in_tiles * sqrt(rand_range(0.0, 1.0))
    return (_center_in_tiles + direction * distance).floor()


func pick_random_position_v3():
    # So-called "Monte-carlo" solution.
    # Generate random points until it is inside the radius.
    # Can be used in more complex shape scenarios as simple prototyping solution.
    for attempt in 100:
        var pos = _center_in_tiles + Vector2(
            rand_range(-_radius_in_tiles, _radius_in_tiles),
            rand_range(-_radius_in_tiles, _radius_in_tiles))
        if pos.distance_to(_center_in_tiles) > _radius_in_tiles:
            return pos.floor()
    return _center_in_tiles.floor()
by (28,876 points)
selected by

Thank you very much :3

I have one problem

# Gather all points that are within the radius
for y in range(min_y, max_y):
    for x in range(min_x, max_x):
        var tile_pos = Vector2(x, y)
        if tile_pos.distance_to(_center_in_tiles) < _radius_in_tiles:
             positions.append(tile_pos)

here at the end

if tile_pos.distance_to(_center_in_tiles) < _radius_in_tiles:
             positions.append(tile_pos)

shows an error that says: Invalid operands 'Vector2' and 'float' in operator '<'
I did somethig worng, or it's a mistake of you?

extends TileMap

var radio = 4.0
var centro = Vector2()

var positions = []

func _ready():
var rv = Vector2(radio, radio)
var min_pos = (centro - rv).floor()
var max_pos = (centro + rv).ceil()

var min_X = int(min_pos.x)
var min_Y = int(min_pos.y)
var max_X = int(max_pos.x)
var max_Y = int(max_pos.y)

for y in range(min_Y, max_Y):
    for x in range(min_X, max_X):
        var tile_pos = Vector2(x, y)
        if tile_pos.direction_to(centro) < radio:
            positions.append(tile_pos)

for i in range(positions.size()):
    set_cellv(positions[i], 0)

if tile_pos.direction_to(centro) < radio:

You wrote direction_to, it must be distance_to.

ho sorry, very stupid mistake

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.