How to Spawn enemies using Vector2 position stored in array ? while digging map tiles

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

Thank you for responding to my previous questionIs there an more efficient way for spawning enemies in a Procedurally generated map ? unlike this .
I got the code for procedural map generation from here recursor. there is explanation there for how the code works as well.
I will share the code so that you guys can get a better understanding of my problem.

Here is the gd script for my map random direction Digger:-

extends Node2D
class_name Digger

var dir:Vector2 = Vector2.ZERO
var pos:Vector2 = Vector2.ZERO

const  all_dirs = [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT]
const left_right = [Vector2.LEFT, Vector2.RIGHT]
const up_down = [Vector2.UP, Vector2.DOWN]

#percentage chance to keep current direction
var keep_dir : float
var die_chance : float
var is_dead := false
var die_if_all_floor := false
var die_if_tile_is_floor := false
var can_backtrack := false
var map:Map

func _init(map_info:Map, init_pos:Vector2, continue_dir_prob:float = 0.3, chance_to_die:float = 0.005, initial_dir: Vector2 = Vector2.ZERO):
	map = map_info
	keep_dir = clamp(continue_dir_prob, 0, 1)
	die_chance = clamp(chance_to_die, 0, 1)
	pos = init_pos
	if initial_dir == Vector2.ZERO:
		rand_dir()
	else:
		dir = initial_dir

func move():
	if is_dead:
		return
	
	pos += dir
	if randf() <= die_chance:
		is_dead = true
	_pick_dir()

func dig(dig_tile:int = 0):
	if die_if_tile_is_floor && map.get_cell(pos.x, pos.y) == dig_tile:
		is_dead = true
	elif not map.set_cell(pos.x, pos.y, dig_tile):
		is_dead = true
	elif die_if_all_floor && is_all_adjacent(dig_tile):
		is_dead = true

func _pick_dir():
	if randf() > keep_dir:
		var new_dir = dir
		while new_dir == dir:
			rand_dir()

func is_all_adjacent(tile_id:int = 0):
	return get_cell(pos + Vector2.UP) == tile_id && \
	get_cell(pos + Vector2.DOWN) == tile_id && \
	get_cell(pos + Vector2.LEFT) == tile_id && \
	get_cell(pos + Vector2.RIGHT) == tile_id 

func get_cell(cell_pos:Vector2):
	return map.tiles.get_cell(cell_pos.x, cell_pos.y)

func rand_dir():
	if can_backtrack || dir == Vector2.ZERO:
		dir = all_dirs[randi() % all_dirs.size()]
	else:
		if dir.x == 0:
			dir = left_right[randi() % left_right.size()]
		else:
			dir = up_down[randi() % up_down.size()]

Here is the gd script for my Map looks like :-

extends Node2D
class_name Map

var tiles1: TileMap
var tiles2: TileMap
var tiles3: TileMap
var size: Vector2 setget ,get_size
#how much border should there be at the edge (digger will not dig past this)
var border : Vector2

func get_size():
	return size

func _init(tmap1:TileMap, tmap2:TileMap, tmap3:TileMap, map_size:Vector2, borderSize:Vector2 = Vector2.ZERO):
	tiles1 = tmap1
	tiles2 = tmap2
	tiles3 = tmap3
	size = map_size
	border = borderSize

func clear():
	tiles1.clear()
	tiles2.clear()
	tiles3.clear()

func world_center():
	return tiles1.map_to_world(size / 2.0)

func center_coord():
	return size / 2

func set_cell(x:int, y:int, tile:int):
	if _is_valid(x,y):
		tiles1.set_cell(x,y,tile)
		tiles2.set_cell(x,y,tile)
		tiles3.set_cell(x,y,tile)
		return true
	else:
		return false

func get_cell(x:int, y:int):
	return tiles1.get_cell(x,y)
	return tiles2.get_cell(x,y)
	return tiles3.get_cell(x,y)

func _is_valid(x:int, y:int):
	return x >= border.x && x < size.x - border.x \
	&& y >= border.y && y < size.y - border.y

func fill(tile:int):
	for y in size.y:
		for x in size.x:
			tiles1.set_cell(x, y, tile)
			tiles2.set_cell(x, y, tile)
			tiles3.set_cell(x, y, tile)

func update_bitmask_all():
	tiles1.update_bitmask_region(Vector2.ZERO, size)
	tiles2.update_bitmask_region(Vector2.ZERO, size)
	tiles3.update_bitmask_region(Vector2.ZERO, size)

func get_percent_tile(tile:int):
	var t = tiles1.get_used_cells_by_id(tile).size()
	return t / (size.x * size.y)

Here is the gd script of the Main file:-

extends Control

const FLOOR = -1
const WALL = 0

const gridSize = Vector2(160,80)
const gridSize_floor = Vector2(80,50)
var diggers = []
onready var tilemapo = $Mapo
onready var tilemapi = $Mapi
onready var tilemapj = $Mapj

var tile
var map: Map


func _ready():
	load_game()

func load_game():
	randomize()
	map = Map.new(tilemapo, tilemapi, tilemapj, gridSize, Vector2(1,1))
	gen_map()
	$Player.position = map.world_center() + Vector2.ONE * 16
	set_process(true)

func dig_map(digTile:int = FLOOR):
	while diggers.size() > 0:
		for i in range(diggers.size() - 1, -1, -1):
			diggers[i].dig(digTile)
			diggers[i].move()
			if diggers[i].is_dead:
				diggers.remove(i)

func _process(delta):
	if Input.is_action_just_pressed("ui_accept"):
		get_tree().reload_current_scene()
		_ready()
	elif Input.is_action_just_pressed("ui_cancel"):
		get_tree().quit()
    func gen_map():
	map.clear()
	map.fill(WALL)
	#diggers.append(Digger.new(map, map.center_coord(), 0.25, .0003))
	#diggers.append(Digger.new(map, Vector2.ZERO, 0.35, .0005))
	diggers.append(Digger.new(map, map.center_coord(), 0.55, .01, Vector2.LEFT))
	diggers.append(Digger.new(map, map.center_coord() + Vector2.RIGHT, 0.55, .01, Vector2.RIGHT))
	diggers.append(Digger.new(map, map.center_coord() + Vector2.UP, 0.55, .01, Vector2.UP))
	diggers.append(Digger.new(map, map.center_coord() + Vector2.DOWN, 0.55, .01, Vector2.DOWN))
	dig_map(FLOOR)
	
	map.update_bitmask_all()

In order to efficiently spawn enemies i have to record, every single position or cell the digger moves on, and then add enemies inside the recorded cell positions during spawning.
My question is how to append every single Vector2 position the digger has dug into an array, So that the array can be later used to spawn enemies in those vector2 positions.

Can i use tile id to spawn enemies as well ? if so how ?.

Explanation for Digger.gd :-

Variables dir is the direction the digger has to dig
Variable pos is the position of the digger

The const all_dirs ,left_right ,up_down are used in rand_dir() at the bottom to pick the direction the digger is going to move

keep_dir is percentage chance to keep current direction
die_chance is percentage chance the digger will terminate itself.

can_backtrack is used in rand_dir() function as a condition if for the digger to backtrack

_init is used to initialized all the value to the above variables

move() and dig(dig_tile:int = 0) is used in dig_map(digTile:int = FLOOR) function in the Main Gd script

is_all_adjacent(tile_id:int = 0) gets the Vector2 positions of the cells adjacent to the diggers

:bust_in_silhouette: Reply From: KohuGaly

The simplest way is to include some additional logic to the set_cell() method of the Map. It already receives position of the tile and tile_id and is called every time the Map is changed. It is the perfect place to put it.
In this case, it might be best to use a dictionary to record the points, because its keys behave like a set (every element only occurs once, with no duplicates, and any element can be added, modified or removed in constant time, independent of the size of the set).

var spawn_tiles={}

func set_cell(x:int, y:int, tile:int):
    if _is_valid(x,y):
        tiles1.set_cell(x,y,tile)
        tiles2.set_cell(x,y,tile)
        tiles3.set_cell(x,y,tile)
        if tile==FLOOR:
             spawn_tiles[Vector2(x,y)]=FLOOR
        else:
             spawn_tiles.remove(Vector2(x,y))
        return true
    else:
        return false

Alternative, simpler approach is to just use .get_used_cells_by_id() method on one of the tilemaps to get positions of all cells that are FLOOR, for example. I’d personally go with this.

Also, minor tip: In get_cell() method, you have 3 return statements after one another. Return statements terminate a function - Any code after the return statement will not be executed. So your method only returns the first value and the remaining two are ignored. If you want to return 3 different values from a function together, you have to return them as array:

func get_cell(x:int, y:int):
    return [
           tiles1.get_cell(x,y),
           tiles2.get_cell(x,y),
           tiles3.get_cell(x,y),
           ]

I tried your method and what happen was as the 4 digger moves around they overlap on cells on the grid and add a duplicate key/value into the dictionary. I could add a loop to check the duplicate values and remove them from the dictionary, but loping is time consuming and will hurt the over all loading performance.
Another problem is i get [output overflow] when print(spawn_tiles).

2Dfanatic | 2020-06-16 21:54

That’s weird. It shouldn’t be possible to add duplicate keys into the dictionary. If you insert new value under a key that is already in the dictionary, it should just replace the old value.
print(spawn_tiles) doesn’t work because the dictionary is too big to be printed onto the console. That’s expected unless the map is tiny.

KohuGaly | 2020-06-17 10:50