Help! I want to randomly spawn objects within the colored area.

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By raKdoS

-heres the image

Note: Tilemap comes in different shapes and sizes. Area2D can be positioned anywhere in the map

There is no image attached and we’ll probably need more information than just an image.

timothybrentwood | 2021-05-03 16:12

Sorry. Fixed the image.

raKdoS | 2021-05-04 05:06

:bust_in_silhouette: Reply From: scrubswithnosleeves

Add a collision shape to a scene and create the area you want to spawn the objects in.

Then in a script:

onready var spawnArea = $CollisionShape2D.shape.extents
onready var origin = $CollisionShape2D.global_position -  spawnArea


func gen_random_pos():
	var x = rand_range(origin.x, spawnArea.x)
	var y = rand_range(origin.y, spawnArea.y)
	
	return Vector2(x, y)

this will generate a random point within an area. If you don’t know how to spawn/instantiate objects, I recommend going through HeartBeast’s series on youtube.

Where do i add another collisionshape? on the tilemap?

raKdoS | 2021-05-04 01:52

:bust_in_silhouette: Reply From: scrubswithnosleeves

I Answered this live on youtube! https://www.youtube.com/watch?v=ZCcZ3-esGJ4

here is the github with the solution: GitHub - ACB-prgm/QA_Sessions: A LIve QA Session

:bust_in_silhouette: Reply From: timothybrentwood

Node structure:
Node Structure

Node2D Script:

extends Node2D

func _ready() -> void:
	$Area2D.connect("placed", $TileMap, "on_area2D_placed")

Area2D Script:

extends Area2D

signal placed(object)

onready var collision_shape = $CollisionShape2D

func _input(event: InputEvent) -> void:
	if event.is_action_released("ui_accept"):
		emit_signal("placed", self)

TileMap Script:

extends TileMap

func on_area2D_placed(obj:Area2D):
	var radius_squared = pow(obj.collision_shape.shape.radius, 2)
	var area_position = obj.position
	var tiles_colliding = []
	for tile in get_used_cells():
		if map_to_world(tile).distance_squared_to(area_position) <= radius_squared:
			tiles_colliding.append(tile)
			
	print(tiles_colliding)

The Node2D just connects the signal between the two. The Area2D provides access to its collision shape (for calculations) and emits the placed signal when you press enter, but this can be emitted at any time. The tile map iterates over all of its tiles and determines whether each tile is within the circle, if so it adds that tile to the tiles_colliding array.

Tile tiles_colliding array contains the map positions of all the tiles within your Area2D’s circle. It is not as precise as the colored area you drew but it’s the best you’re going to get without doing annoying geometry to find the secant line between your tile map and circle and creating a polygon based on that. Using the tiles_colliding array you can generate some kind of invisible object spawner object and place it in that tile.

Here was my attempt at making an actual polygon because I thought it would be fun… it works… somewhat…

extends Area2D

var ray_cast 
onready var collision_shape = $CollisionShape2D

func _ready() -> void:
	collision_shape.position = Vector2.ZERO
	ray_cast = RayCast2D.new()
	self.add_child(ray_cast)
	ray_cast.position = Vector2.ZERO
	ray_cast.enabled = true
	ray_cast.collide_with_areas = true
	
func get_shape_points() -> Array:
	var col_shape_radius = collision_shape.shape.radius
	
	var points = []
	# contains direction vectors to connect initial point to last touching point
	var vectors_for_points = []
	var counter = 1
	var last_collided_point = null
	
	for degree in range(359):
		var direction_vector = Vector2.RIGHT.rotated(deg2rad(degree)).normalized()
		ray_cast.set_cast_to(direction_vector * col_shape_radius)
		ray_cast.force_raycast_update()
		if ray_cast.is_colliding():
			var collision_point = ray_cast.get_collision_point()
			last_collided_point = collision_point
			# first collision? that's our starting point
			if points.empty():
				points.append(collision_point)
			# otherwise only grab every 5th point
			elif counter % 5 == 0:
				vectors_for_points.append(direction_vector)
				counter += 1
			else:
				counter += 1		
		else:
			# if we just stopped colliding, append our last collision point
			if last_collided_point:
				points.append(last_collided_point)
				last_collided_point = null
	
	# if we had at least one collision, interpolate the vectors to points for a polygon
	if not points.empty():
		var last_point = points.pop_back()
		for direction_vector in vectors_for_points:
			points.append(self.position + (direction_vector * col_shape_radius))
		points.append(last_point)
	
	return points

func _input(event: InputEvent) -> void:
	if event.is_action_released("mb_left"):
		var shape = Polygon2D.new()
		shape.set_polygon(PoolVector2Array(get_shape_points()))
		get_parent().add_child(shape)
		shape.color = Color.red
	elif event.is_action_released("mb_right"):
		self.position = get_global_mouse_position()

timothybrentwood | 2021-05-04 23:51

Wow. Works like a charm. This is what i needed. Thank you very much

raKdoS | 2021-05-05 03:14