0 votes

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

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

in Engine by (58 points)
edited by

1 Answer

0 votes

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),
           ]
by (166 points)

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).

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.

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 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 webmaster@godotengine.org with your username.