272 lines
6.5 KiB
GDScript
272 lines
6.5 KiB
GDScript
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()
|
|
prepare_player_start_area()
|
|
|
|
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) * self.scale / 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
|