extends TileMap @export var width := 60 @export var height := 32 @export var fill_percentage := 0.65 @export var solid_threshold := 7 @export var nonsolid_threshold := 4 @export var start_area_size := 5 @export var start_area_corner_size := 2 @export var cave_gen_iterations := 3 @export var cave_mine_size_threshold := 80 var tile_array : Array func generate() -> Dictionary: var start_time = Time.get_unix_time_from_system() randomize() setup_tile_data_array() fill_random_noise() for i in cave_gen_iterations: change_cells_by_neighbor_thresholds() set_borders_solid() prepare_player_start_area() connect_caves(get_caves()) for i in cave_gen_iterations: change_cells_by_neighbor_thresholds() set_borders_solid() tile_array_to_terrain() return {"time": Time.get_unix_time_from_system() - start_time, "free_tiles": get_free_tiles()} func get_free_tiles() -> Array: var free_tiles = [] for x in width: for y in height: if get_tile_non_solid(x, y): free_tiles.push_back(Vector2(x, y) * self.cell_quadrant_size * self.scale + (Vector2(self.cell_quadrant_size, self.cell_quadrant_size) / 2)) return free_tiles func setup_tile_data_array(): tile_array.clear() tile_array = [] for x in width: tile_array.append([]) for y in height: tile_array[x].append([]) func fill_random_noise(): for x in width: for y in height: if randf() < fill_percentage: set_tile_solid(x,y) else: set_tile_non_solid(x,y) pass func set_borders_solid(): for x in width: set_tile_solid(x,0) set_tile_solid(x,height-1) for y in height: set_tile_solid(0,y) set_tile_solid(width-1,y) pass func prepare_player_start_area(): @warning_ignore("integer_division") var center = Vector2i(width / 2, height / 2) for x in range(center.x - start_area_size, center.x + start_area_size): # Getting a P factor for the corner "radius" Decission var p := 0 if x <= center.x - start_area_size + start_area_corner_size: p = center.x - start_area_size + start_area_corner_size - x if x >= center.x + start_area_size - start_area_corner_size: p = x - ( center.x + start_area_size) + start_area_corner_size + 1 var p2 = p for y in range(center.y - start_area_size, center.y + start_area_size): # Decide if the tile is part of the Corner or not if !(p2 > 0 or p2 <= - (start_area_size*2 - p*2)): set_tile_non_solid(x,y) p2 -= 1 pass func change_cells_by_neighbor_thresholds(): for x in range(1, width-1): for y in range(1, height-1): # Count Solid Neighbor Cells var count := 0 #y-1 if get_tile_solid(x-1, y-1): count +=1 if get_tile_solid(x, y-1): count +=1 if get_tile_solid(x+1, y-1): count +=1 #y if get_tile_solid(x-1, y): count +=1 if get_tile_solid(x+1, y): count +=1 #y+1 if get_tile_solid(x-1, y+1): count +=1 if get_tile_solid(x, y+1): count +=1 if get_tile_solid(x+1, y+1): count +=1 # Check Threshold if count < nonsolid_threshold: set_tile_non_solid(x,y) if count >= solid_threshold: set_tile_solid(x,y) pass func get_caves() -> Array: # get caves var caves := [] for x in range (2, width-2): for y in range (2, height-2): if get_tile_non_solid(x, y): flood_fill(x,y, caves) for cave in caves: for tile in cave: set_tile_non_solid(tile.x,tile.y) return caves func flood_fill(tilex, tiley, caves) -> Array: var cave := [] var to_fill := [Vector2i(tilex, tiley)] while to_fill: var tile = to_fill.pop_back() if !cave.has(tile): cave.append(tile) set_tile_solid(tile.x,tile.y) #check adjacent cells var north = Vector2i(tile.x, tile.y+1) var south = Vector2i(tile.x, tile.y-1) var east = Vector2i(tile.x+1, tile.y) var west = Vector2i(tile.x-1, tile.y) for dir in [north,south,east,west]: if get_tile_non_solid(dir.x, dir.y): if !to_fill.has(dir) and !cave.has(dir): to_fill.append(dir) if cave.size() >= cave_mine_size_threshold: caves.append(cave) return caves func choose_rand(choices): randomize() var rand_index = randi() % choices.size() return choices[rand_index] func connect_caves(caves): var prev_cave = null var tunnel_caves = caves.duplicate() for cave in tunnel_caves: if prev_cave: var new_point = choose_rand(cave) var prev_point = choose_rand(prev_cave) # ensure not the same point if new_point != prev_point: create_tunnel(new_point, prev_point, cave) prev_cave = cave pass func create_tunnel(point1, point2, cave): randomize() # for randf var max_steps = 500 # so game won't hang if walk fails var steps = 0 var drunk_x = point2[0] var drunk_y = point2[1] while steps < max_steps and !cave.has(Vector2i(drunk_x, drunk_y)): steps += 1 # set initial dir weights var n = 1.0 var s = 1.0 var e = 1.0 var w = 1.0 var weight = 1 # weight the random walk against edges if drunk_x < point1.x: e += weight elif drunk_x > point1.x: w += weight if drunk_y < point1.y: s += weight elif drunk_y > point1.y: n += weight # normalize probabilities so they form a range from 0 to 1 var total = n + s + e + w n /= total s /= total e /= total w /= total var dx var dy # choose_rand the direction var choice = randf() if 0 <= choice and choice < n: dx = 0 dy = -1 elif n <= choice and choice < (n+s): dx = 0 dy = 1 elif (n+s) <= choice and choice < (n+s+e): dx = 1 dy = 0 else: dx = -1 dy = 0 # ensure not to walk past edge of map if (2 < drunk_x + dx and drunk_x + dx < width-2) and \ (2 < drunk_y + dy and drunk_y + dy < height-2): drunk_x += dx drunk_y += dy if get_tile_solid(drunk_x, drunk_y): set_tile_non_solid(drunk_x, drunk_y) #make tunnel wider set_tile_non_solid(drunk_x+1, drunk_y) set_tile_non_solid(drunk_x+1, drunk_y+1) pass func tile_array_to_terrain(): for x in width: for y in height: match tile_array[x][y]: 0: set_terrain_tile_non_solid(x,y) 1: set_terrain_tile_solid(x,y) pass # Getter func get_tile_solid(x : int, y : int) -> bool: return tile_array[x][y] == 1 func get_tile_non_solid(x : int, y : int) -> bool: return tile_array[x][y] == 0 # Setter func set_tile_solid(x : int, y : int): tile_array[x][y] = 1 pass func set_tile_non_solid(x : int, y : int): tile_array[x][y] = 0 pass func set_terrain_tile_solid(x : int, y : int): self.set_cells_terrain_connect(0, [Vector2i(x,y)], 0, 0, false) pass func set_terrain_tile_non_solid(x : int, y : int): self.set_cells_terrain_connect(0, [Vector2i(x,y)], 0, 1, false) pass