Commit 2596498a authored by tgw3ff's avatar tgw3ff
Browse files

Create MapGenerator.gd Script and Reformat Numeric Tile Representation

The code for the map generation is now seperate from the tile placement code.
The map generation code is located in the new MapGenerator.gd script.

In the 2D array of ints that represented the map, 0's equated to empty spaces
and 1's equated to walls. Now the value assigned to the enum EMPTY equates to
empty spaces and the value assigned to the enum WALL in the map generator script
equates to walls. This serves to future proof the code as enums for the rest of
the possible tiles, such as WATER, BRICK, and ROCK_T (rock terrain), are also
present.
parent 497e2fca
51813
C:\Users\Username\Desktop\Godot_v3.2.3-stable_mono_win64\Godot_v3.2.3-stable_mono_win64.exe
......@@ -16,7 +16,7 @@ _global_script_class_icons={
[application]
config/name="Cs 4096 - 97 Top Down Game"
run/main_scene="res://Main.tscn"
run/main_scene="res://test.tscn"
config/icon="res://icon.png"
[input]
......
# Declare member variables here
# Enumerator that stores which integers represent which type of tile
# The pairings also equate to the corresponding tile group's index in the
# atlas tileset
enum {
EMPTY = -1,
WALL,
BRICK,
WATER,
PLANT_T,
ROCK_T,
WATER_T
}
# Dictionary of sub matrices that are not allowed to appear in the final grid/map
# Keys = Invalid matrix
# Values = Check for rotations?, horizontal mirrors?, vertical mirrors?
var invalidArrangments = {
[ [WALL ,EMPTY,EMPTY],
[WALL ,WALL ,EMPTY],
[WALL ,EMPTY,EMPTY] ] : [true, false, false],
[ [EMPTY,WALL ,WALL ],
[WALL ,WALL ,WALL ],
[WALL ,WALL ,EMPTY] ] : [false, true, false],
[ [EMPTY,WALL ,EMPTY],
[WALL ,WALL ,WALL ],
[EMPTY,WALL ,EMPTY] ] : [true, false, false],
[ [EMPTY,EMPTY,EMPTY],
[EMPTY,WALL ,EMPTY],
[WALL ,WALL ,EMPTY] ] : [true, true, true],
[ [EMPTY,EMPTY,EMPTY],
[EMPTY,WALL ,WALL ],
[WALL ,WALL ,EMPTY] ] : [true, true, true],
[ [WALL ,EMPTY,EMPTY],
[EMPTY,WALL ,WALL ],
[WALL ,WALL ,EMPTY] ] : [true, true, true],
[ [EMPTY,WALL ,EMPTY],
[EMPTY,WALL ,WALL ],
[WALL ,WALL ,EMPTY] ] : [true, true, true],
[ [EMPTY,WALL ,WALL ],
[EMPTY,WALL ,WALL ],
[WALL ,EMPTY,EMPTY] ] : [true, true, true],
[ [WALL ,WALL ,EMPTY],
[EMPTY,WALL ,EMPTY],
[EMPTY,EMPTY,WALL ] ] : [true, true, true],
[ [WALL ,EMPTY,EMPTY],
[WALL ,WALL ,WALL ],
[EMPTY,WALL ,WALL ] ] : [true, true, true],
[ [EMPTY,WALL ,EMPTY],
[WALL ,WALL ,WALL ],
[WALL ,WALL ,WALL ] ] : [true, true, true],
[ [WALL ,WALL ,EMPTY],
[WALL ,WALL ,EMPTY],
[EMPTY,WALL ,WALL ] ] : [true, true, true],
[ [WALL ,WALL ,EMPTY],
[EMPTY,WALL ,EMPTY],
[WALL ,WALL ,WALL ] ] : [true, true, true],
[ [WALL ,WALL ,EMPTY],
[EMPTY,WALL ,EMPTY],
[EMPTY,WALL ,WALL ] ] : [true, true, true]
}
# The sub matrix to replace invalid arrangements with
const FILLED = [ [WALL ,WALL ,WALL ],
[WALL ,WALL ,WALL ],
[WALL ,WALL ,WALL ] ]
# This variable is used to store a 2D array that represents the level map
var grid = []
func _init():
pass
# Prepares grid by sizing it and filling with random 1's or 0's (according to fill_percent)
func initialize_grid(fill_percent, width, height):
randomize()
grid = []
for h in height:
grid.append([])
for w in width:
var value = EMPTY
if (w == 0 || w == width - 1 || h == 0 || h == height - 1):
value = WALL
else :
value = randi()%101
if value <= fill_percent:
value = WALL
elif value > fill_percent:
value = EMPTY
grid[h].append(value)
# Returns the number of filled cells in the grid that surround the given coordinate
func get_surrounding_wall_count(grid_x, grid_y, width, height):
var wall_count = 0;
for x in range(grid_x - 1, grid_x + 2):
for y in range(grid_y - 1, grid_y + 2):
if (x >= 0 && x < width && y >= 0 && y < height):
if (x != grid_x || y != grid_y):
if grid[y][x] == WALL:
wall_count += 1
else:
wall_count += 1
return wall_count
# Makes the randomly filled grid transmogrify into a cave network
func smooth_grid(width, height):
for x in range(0, width - 1):
for y in range(0, height - 1):
var neighborWallTiles = get_surrounding_wall_count(x, y, width, height)
if neighborWallTiles > 4:
grid[y][x] = WALL
elif neighborWallTiles < 4:
grid[y][x] = EMPTY
# Return a matrix of the cell with the given coords and its surrounding 8 cells
func get_surrounding(grid_x, grid_y):
var surrounding = [ [],
[],
[] ]
for y in range(grid_y - 1, grid_y + 2):
for x in range(grid_x - 1, grid_x + 2):
surrounding[y - grid_y + 1].append(grid[y][x])
return surrounding
# Returns the value of the given matrix if it were rotated 90 degrees clockwise
# n times
func rotate_matrix(matrix=[[]], n=1):
var m_height = matrix.size()
var m_width = matrix[0].size()
var p_height = 0
var p_width = 0
var matrix_p = []
# If the original matrix's dmensions are the same or the matrix is to be
# rotated n number of times then the new matrix's dimensions will not
# change from the original
if (m_width == m_height) || (n % 2):
p_width = m_width
p_height = m_height
else: # Otherwise, the new matrix will have swapped dimensions
p_width = m_height
p_height = m_width
# Resize and fill the new matrix with empty tiless
for w in p_width:
matrix_p.append([])
for h in p_height:
matrix_p[w].append(EMPTY)
# Unfortunately, GDscript does not have switch case statements :/
# Fill new matrix with original rotated...
if n % 4 == 0: # 0 degrees
matrix_p = matrix
elif n % 4 == 1: # 90 degrees clockwise
for i in m_height:
for j in m_width:
matrix_p[j][m_height - 1 - i] = matrix[i][j]
elif n % 4 == 2: # 180 degrees (simulated by mirroring horizontally then vertically
matrix_p = mirror_matrix(mirror_matrix(matrix, false), true)
elif n % 4 == 3: # 260 degrees (utilizing nested recursion)
matrix_p = rotate_matrix(rotate_matrix(matrix, 2), 1)
return matrix_p
# Returns the value of the given matrix if it were mirrored
# The boolean signifies wether the mirror is vertically or not
func mirror_matrix(matrix=[[]], v=false):
var m_height = matrix.size()
var m_width = matrix[0].size()
var matrix_p = []
# Resize and fill the new matrix with empty tiles
for h in m_height:
matrix_p.append([])
for w in m_width:
matrix_p[h].append(EMPTY)
for x in range(0, m_width):
for y in range(0, m_height):
if v:
matrix_p[y][x] = matrix[m_height - 1 - y][x]
elif !v:
matrix_p[y][x] = matrix[y][m_width - 1 - x]
return matrix_p
# Replaces a 3x3 submatrix with a center of grid_x, grid_y with the given replacement
func replace_3x3_grid(grid_x, grid_y, replacement):
for r_y in range(0, 3):
for r_x in range(0, 3):
grid[grid_y + r_y - 1][grid_x + r_x - 1] = replacement[r_y][r_x]
# Replace invalid submatrices in the grid/map
func replace_invalids(width, height):
var valid = false
var focus = []
var i = 0
var c = 0
# We will be checking each cell and their surrounding 8 cells, thus the
# grid/map border cells will not be evaluated to prevent invalid array
# indexing
while !valid: # This will be repeated until there is a run where no invalids are found
valid = true
for y in range(1, height - 2):
for x in range(1, width - 2):
focus = get_surrounding(x, y)
for key in invalidArrangments:
# Check and see if the sub matrix in focus matches the key
if focus == key:
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][0]: # Otherwise check if it matches a rotation of key?
for n in range(1, 4):
var rotated_key = rotate_matrix(key, n)
if focus == rotated_key:
valid = false
replace_3x3_grid(x, y, FILLED)
# What if mirroring is allowed in tandum with rotations?
elif focus == mirror_matrix(rotated_key, false) && (invalidArrangments[key][1] || invalidArrangments[key][2]):
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][1]: # horizontally mirrored key?
if focus == mirror_matrix(key, false):
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][2]: # vertically mirrored key?
if focus == mirror_matrix(key, true):
valid = true
replace_3x3_grid(x, y, FILLED)
# Generates a new map
func generate_map(fill_percent, width, height):
initialize_grid(fill_percent, width, height)
for i in range(0, 5):
smooth_grid(width, height)
replace_invalids(width, height)
# Returns the grid member variable
func get_grid():
return grid
extends TileMap
const MAPGEN = preload("res://src/MapGenerator.gd")
var map_gen = MAPGEN.new()
# Declare member variables here
const FILLED = [ [1,1,1],
[1,1,1],
[1,1,1] ]
export(int, 100) var fill_percent = 45
export var width = 50
export var height = 40
var grid = []
# Dictionary of sub matrices that are not allowed to appear in the final grid/map
# Keys = Invalid matrix
# Values = Check for rotations?, horizontal mirrors?, vertical mirrors?
var invalidArrangments = {
[ [1,0,0],
[1,1,0],
[1,0,0] ] : [true, false, false],
[ [0,1,1],
[1,1,1],
[1,1,0] ] : [false, true, false],
[ [0,1,0],
[1,1,1],
[0,1,0] ] : [true, false, false],
[ [0,0,0],
[0,1,0],
[1,1,0] ] : [true, true, true],
[ [0,0,0],
[0,1,1],
[1,1,0] ] : [true, true, true],
[ [1,0,0],
[0,1,1],
[1,1,0] ] : [true, true, true],
[ [0,1,0],
[0,1,1],
[1,1,0] ] : [true, true, true]
}
# Called when the node enters the scene tree for the first time.
func _ready():
generate_map()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
pass # Repkace with function body.
# Generate a new map on keypress
func _unhandled_input(event):
if event.is_action_pressed("generate_map"):
generate_map()
# # #####
## ## ## ##### # # ###### # # ###### ##### ## ##### #### #####
# # # # # # # # # # ## # # # # # # # # # # #
# # # # # # # # #### ##### # # # ##### # # # # # # # # #
# # ###### ##### # # # # # # # ##### ###### # # # #####
# # # # # # # # # ## # # # # # # # # # #
# # # # # ##### ###### # # ###### # # # # # #### # #
# Prepares grid by sizing it and filling with random 1's or 0's (according to fill_percent)
func initialize_grid():
randomize()
grid = []
for h in height:
grid.append([])
for w in width:
var value = 0
if (w == 0 || w == width - 1 || h == 0 || h == height - 1):
value = 1
else :
value = randi()%101
if value <= fill_percent:
value = 1
elif value > fill_percent:
value = 0
grid[h].append(value)
# Makes the randomly filled grid transmogrify into a cave network
func smooth_grid():
for x in range(0, width - 1):
for y in range(0, height - 1):
var neighborWallTiles = get_surrounding_wall_count(x, y)
if neighborWallTiles > 4:
grid[y][x] = 1
elif neighborWallTiles < 4:
grid[y][x] = 0
# Returns the number of filled cells in the grid that surround the given coordinate
func get_surrounding_wall_count(grid_x, grid_y):
var wall_count = 0;
for x in range(grid_x - 1, grid_x + 2):
for y in range(grid_y - 1, grid_y + 2):
if (x >= 0 && x < width && y >= 0 && y < height):
if (x != grid_x || y != grid_y):
wall_count += grid[y][x]
else:
wall_count += 1
return wall_count
# Return a matrix of the cell with the given coords and its surrounding 8 cells
func get_surrounding(grid_x, grid_y):
var surrounding = [ [],
[],
[] ]
for y in range(grid_y - 1, grid_y + 2):
for x in range(grid_x - 1, grid_x + 2):
surrounding[y - grid_y + 1].append(grid[y][x])
return surrounding
# Generates a new map
func generate_map():
custom_clear()
initialize_grid()
for i in range(0, 5):
smooth_grid()
replace_invalids()
for x in width:
for y in height:
update_tile(x, y)
update_dirty_quadrants()
func is_wall(x, y):
return get_value(x, y) == 1
var gen = true
# Replaces a 3x3 submatrix with a center of grid_x, grid_y with the given replacement
func replace_3x3_grid(grid_x, grid_y, replacement):
for r_y in range(0, 3):
for r_x in range(0, 3):
grid[grid_y + r_y - 1][grid_x + r_x - 1] = replacement[r_y][r_x]
# Returns the value of the given matrix if it were mirrored
# The boolean signifies wether the mirror is vertically or not
func mirror_matrix(matrix=[[]], v=false):
var m_height = matrix.size()
var m_width = matrix[0].size()
var matrix_p = []
# Resize and fill the new matrix with 0's
for h in m_height:
matrix_p.append([])
for w in m_width:
matrix_p[h].append(0)
for x in range(0, m_width):
for y in range(0, m_height):
if v:
matrix_p[y][x] = matrix[m_height - 1 - y][x]
elif !v:
matrix_p[y][x] = matrix[y][m_width - 1 - x]
return matrix_p
# Returns the value of the given matrix if it were rotated 90 degrees clockwise
# n times
func rotate_matrix(matrix=[[]], n=1):
var m_height = matrix.size()
var m_width = matrix[0].size()
var p_height = 0
var p_width = 0
var matrix_p = []
# If the original matrix's dmensions are the same or the matrix is to be
# rotated n number of times then the new matrix's dimensions will not
# change from the original
if (m_width == m_height) || (n % 2):
p_width = m_width
p_height = m_height
else: # Otherwise, the new matrix will have swapped dimensions
p_width = m_height
p_height = m_width
# Resize and fill the new matrix with 0's
for w in p_width:
matrix_p.append([])
for h in p_height:
matrix_p[w].append(0)
# Unfortunately, GDscript does not have switch case statements :/
# Fill new matrix with original rotated...
if n % 4 == 0: # 0 degrees
matrix_p = matrix
elif n % 4 == 1: # 90 degrees clockwise
for i in m_height:
for j in m_width:
matrix_p[j][m_height - 1 - i] = matrix[i][j]
elif n % 4 == 2: # 180 degrees (simulated by mirroring horizontally then vertically
matrix_p = mirror_matrix(mirror_matrix(matrix, false), true)
elif n % 4 == 3: # 260 degrees (utilizing nested recursion)
matrix_p = rotate_matrix(rotate_matrix(matrix, 2), 1)
return matrix_p
# Replace invalid submatrices in the grid/map
func replace_invalids():
var valid = false
var focus = []
var i = 0
var c = 0
# We will be checking each cell and their surrounding 8 cells, thus the
# grid/map border cells will not be evaluated to prevent invalid array
# indexing
while !valid: # This will be repeated until there is a run where no invalids are found
valid = true
for y in range(1, height - 2):
for x in range(1, width - 2):
focus = get_surrounding(x, y)
for key in invalidArrangments:
# Check and see if the sub matrix in focus matches the key
if focus == key:
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][0]: # Otherwise check if it matches a rotation of key?
for n in range(1, 4):
var rotated_key = rotate_matrix(key, n)
if focus == rotated_key:
valid = false
replace_3x3_grid(x, y, FILLED)
# What if mirroring is allowed in tandum with rotations?
elif focus == mirror_matrix(rotated_key, false) && (invalidArrangments[key][1] || invalidArrangments[key][2]):
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][1]: # horizontally mirrored key?
if focus == mirror_matrix(key, false):
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][2]: # vertically mirrored key?
if focus == mirror_matrix(key, true):
valid = true
replace_3x3_grid(x, y, FILLED)
####### ######
# # # ###### # # # ## #### ###### #####
# # # # # # # # # # # # # #
# # # ##### ###### # # # # ##### # #
# # # # # # ###### # # #####
# # # # # # # # # # # # #
# # ###### ###### # ###### # # #### ###### # #
# Custom cell-clear command that doesn't mess up the setting of atlas tiles
func custom_clear():
......@@ -275,13 +26,17 @@ func get_value(x, y):
# effectively returning the value of the nearest element in the grid.
x = clamp(x, 0, width - 1)
y = clamp(y, 0, height - 1)
return grid[y][x]
return map_gen.get_grid()[y][x]
func is_wall(x, y):
return get_value(x, y) == map_gen.WALL
func update_tile(x, y):
var index := -1
var index = map_gen.EMPTY
var autotile := Vector2.ZERO
if grid[y][x] == 1:
if map_gen.get_grid()[y][x] == map_gen.WALL:
# Wall tiles are picked based on the states of their neighbors.
# This large match-statement is searching for certain "known" 3x3 patterns
# of walls that map to a certain tile. For example, given this map:
......@@ -350,7 +105,7 @@ func update_tile(x, y):
# The straight and corner patterns are duplicated four times, for their
# 90-degree rotational symmetry.
index = 0
index = map_gen.WALL
var n = is_wall(x, y - 1)
var e = is_wall(x + 1, y)
var s = is_wall(x, y + 1)
......@@ -406,35 +161,66 @@ func update_tile(x, y):
_:
# Catch all wall
print('Missing tile for ', x, ',', y)
print(map_gen.get_grid()[y-1][x-1], ", ", map_gen.get_grid()[y-1][x], ", ", map_gen.get_grid()[y-1][x+1])
print(map_gen.get_grid()[y][x-1], ", ", map_gen.get_grid()[y][x], ", ", map_gen.get_grid()[y][x+1])
print(map_gen.get_grid()[y+1][x-1], ", ", map_gen.get_grid()[y+1][x], ", ", map_gen.get_grid()[y+1][x+1])
autotile = Vector2(3, 2)
if grid[y][x] == 0:
if map_gen.get_grid()[y][x] == map_gen.EMPTY:
# Randomize floor or terrain tile
var terrain = rand_range(0.0, 100.0)
if 0.0 <= terrain and terrain < 30.0:
index = 1
index = map_gen.BRICK
autotile = Vector2(4, 2)
elif 30.0 <= terrain and terrain < 45.0:
index = 0
index = map_gen.WALL
autotile = Vector2(1, 1)
elif 45.0 <= terrain and terrain < 55.0:
index = 3
index = map_gen.PLANT_T
autotile = Vector2(1, 1)
elif 55.0 <= terrain and terrain < 65.0:
index = 3
index = map_gen.PLANT_T