extends TileMap @export var width := 128 @export var height := 75 @export var fill_percentage := 0.65 @export var solid_id := 0 @export var non_solid_id := 1 @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 func _ready(): var time = generate() print("time for generation: " + str(time)) pass func generate() -> float: var start_time = Time.get_unix_time_from_system() randomize() 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() return Time.get_unix_time_from_system() - start_time func fill_random_noise(): for x in width: for y in height: if randf() < fill_percentage: self.set_cell(0, Vector2i(x,y),solid_id, Vector2i(0, 0)) else: self.set_cell(0, Vector2i(x,y),non_solid_id, Vector2i(0, 0)) pass func set_borders_solid(): for x in width: self.set_cell(0, Vector2i(x,0),solid_id, Vector2i(0, 0)) self.set_cell(0, Vector2i(x,height-1),solid_id, Vector2i(0, 0)) for y in height: self.set_cell(0, Vector2i(0,y),solid_id, Vector2i(0, 0)) self.set_cell(0, Vector2i(width-1,y),solid_id, Vector2i(0, 0)) pass func prepare_player_start_area(): 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)): self.set_cell(0, Vector2i(x,y),non_solid_id, Vector2i(0, 0)) p2 -= 1 pass func change_cells_by_neighbor_thresholds(): for x in range(1, width): for y in range(1, height): # Count Solid Neighbor Cells var count := 0 #y-1 if self.get_cell_source_id(0, Vector2i(x-1, y-1)) == solid_id: count +=1 if self.get_cell_source_id(0, Vector2i(x, y-1)) == solid_id: count +=1 if self.get_cell_source_id(0, Vector2i(x+1, y-1)) == solid_id: count +=1 #y if self.get_cell_source_id(0, Vector2i(x-1, y)) == solid_id: count +=1 if self.get_cell_source_id(0, Vector2i(x+1, y)) == solid_id: count +=1 #y+1 if self.get_cell_source_id(0, Vector2i(x-1, y+1)) == solid_id: count +=1 if self.get_cell_source_id(0, Vector2i(x, y+1)) == solid_id: count +=1 if self.get_cell_source_id(0, Vector2i(x+1, y+1)) == solid_id: count +=1 # Check Threshold if count < nonsolid_threshold: self.set_cell(0, Vector2i(x,y),non_solid_id, Vector2i(0, 0)) if count >= solid_threshold: self.set_cell(0, Vector2i(x,y),solid_id, Vector2i(0, 0)) pass func get_caves() -> Array: # get caves var caves := [] for x in range (2, width-2): for y in range (2, height-2): if self.get_cell_source_id(0, Vector2i(x, y)) == non_solid_id: flood_fill(x,y, caves) for cave in caves: for tile in cave: self.set_cell(0, Vector2i(tile.x,tile.y),non_solid_id, Vector2i(0, 0)) 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) self.set_cell(0, Vector2i(tile.x,tile.y),solid_id, Vector2i(0, 0)) #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 self.get_cell_source_id(0, dir) == non_solid_id: 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 self.get_cell_source_id(0, Vector2i(drunk_x, drunk_y)) == solid_id: self.set_cell(0, Vector2i(drunk_x, drunk_y),non_solid_id, Vector2i(0, 0)) #make tunnel wider self.set_cell(0, Vector2i(drunk_x+1, drunk_y),non_solid_id, Vector2i(0, 0)) self.set_cell(0, Vector2i(drunk_x+1, drunk_y+1),non_solid_id, Vector2i(0, 0)) pass