How to get random positions around a StaticBody3D?

Godot Version

v4.4.stable.official.4c311cbee

Question

I have a StaticBody3D with its CollisionShape3D assigned, this represents a building, which is just a (7,5,3) box shape at the moment.
I’m trying to find a good strategy to pick a few random points around the building where hostile characters will take position to attack it.
Having a NavigationRegion3D which has an offset of 0.5 from the colliders, I need that these points will be a few more distant to have the navigation working properly. Anyway, the hostile characters should be fairly close to the building since they could not have ranged weapons.
What could be a good way to pick those random positions?

Unless you want them to be able to come in from literally any direction (including vertical), this is a fairly simple problem.

The “any direction” problem is harder because it’s somewhat difficult to pick a random spot on a sphere without distribution bias problems.

Picking a random attack angle on a circle, though? That’s just randf_range(0.0, TAU). Maybe something like:

# origin   -> building position
# distance -> how far away to spawn

func spawn_location(origin: Vector3, distance: float) -> Vector3:
    var angle:    float   = randf_range(0.0, TAU)
    var base_vec: Vector3 = Vector3.FORWARD * distance
    var rot_vec:  Vector3 = base_vec.rotated(Vector3.UP, angle)
    return origin + rot_vec

I’ve expanded that out so the steps are obvious, though in actual code you’d probably want to condense it to something more like:

func spawn_location(origin: Vector3, distance: float) -> Vector3:
    return origin + (Vector3.FORWARD * distance).rotated(Vector3.UP, randf_ranged(0.0, TAU))

You might want to add a step after that which compares that point to the nav mesh and moves it to the closest acceptable point on the nav mesh.

Thank you for your answer.
My buildings have a rectangular plant, if I take positions on a circumference centered on the building center I would have incongruent distances from the building, closer near the corners and farther away along the sides. I need the same distance anywhere.
I’ve written this function which seems to work.

func get_attack_position(building: Building) -> Vector3:
	var side = ["right", "left", "front", "back"]
	# Offset from the building's walls
	var offset = 0.7
	
	# Building's right wall
	var right_wall = building.width/2
	# Building's left wall
	var left_wall = -building.width/2
	# Building's front wall
	var front_wall = building.depth/2
	# Building's back wall
	var back_wall = -building.depth/2
	
	var x_position: float
	var z_position: float
	
	# Get a random side of the building
	match side[randi() % side.size()]:
		"right":
			# Set the right wall offset
			x_position = right_wall + offset
			# Pick a random value in the range
			z_position = randf_range(back_wall - offset, front_wall + offset)
		"left":
			# Set the left wall offset
			x_position = left_wall - offset
			# Pick a random value in the range
			z_position = randf_range(back_wall - offset, front_wall + offset)
		"front":
			# Set the front wall offset
			z_position = front_wall + offset
			# Pick a random value in the range
			x_position = randf_range(left_wall - offset, right_wall + offset)
		"back":
			# Set the front wall offset
			z_position = back_wall - offset
			# Pick a random value in the range
			x_position = randf_range(left_wall - offset, right_wall + offset)
	
	# Random coordinates in the local system (building located on (0, 0, 0) and no rotation)
	var local_position = Vector3(
		x_position, 
		building.global_position.y, 
		z_position
	)
	
	# Apply rotation to the random point according to the building rotation
	var local_rotated = local_position.rotated(Vector3.UP, deg_to_rad(building.rotation_degrees.y))
	
	# Translate the position where the building is located in the world and return it
	return Vector3(
		building.global_position.x + local_rotated.x,
		building.global_position.y + local_rotated.y,
		building.global_position.z + local_rotated.z
	)

In this function I think the building positioned on the world origin with no rotation, then I ideally divide the possible attacking positions along 4 segments, 1 for each side of the building with an offset added to them. The offset is how far I want the characters from the walls.
With the match I pick one of the 4 sides and get its coordinate, in example if the building size is (7, 5, 3) and the match picks the right side, 4.2 (which is 7/2 + 0.7) will be the fixed coordinate, then I pick a random value between the back wall -2.2, and the front wall 2.2.
Lets say that 1.7 is picked, the attacking position will be (4.2, 0, 1.7) which is on the right side of the building, at a distance of 0.7 from the wall.
Then I apply the building rotation to this point and finally I translate this position adding the building’s real position on the world.
I hope I could correctly explain my solution.

P.S. How should I post the code to have it highlighted? I see my code as just formatted text.

If you want them to come in perpendicular to the walls, this should work fine, though you won’t get any attacking the corners. If you want the corners as well, you could use a voronoi regions scheme. If you have a polygon like a rectangle, you can divide the world around it into areas that are closest to the edges or closest to the corners. If your polygon is rectangular and axis aligned, this is really easy; overlap in one dimension means closest to an edge, overlap in no dimensions means closest to a corner. If you’re on a corner, calculate distance from that corner. If you’re on an edge, do what you’ve already done. A random angle with correction for distance using this scheme would get you attacks on the corners as well.

There are other schemes you could use too, but if what you have works for you, great!

I’m not sure (it seems to autodetect, but unreliably…). I think if you do:

```gdscript
[...]
```

It forces the highlighting.

1 Like

Thank you again.
The hostiles in my game will be roaming around the map all the time, thus when they decide to attack they could come from any direction and there are chances that they will also attack close to a corner with my solution, in any case, I don’t need that precise control on where they will attack, just random places will be fine.
In the meanwhile I’ve slightly optimized my code removing some not necessary variables.