Commit 40fb21d8 authored by tgw3ff's avatar tgw3ff
Browse files

Wall Validation and Randomized Terrain Tiles

Added an algorithm in the Tileset.gd script that knows a set of 3x3 matrixes
that are not allowed to appear in the map, as they will cause texture
issues. The algorithm then replaces these occurences with matrices that
are allowed by the tileset. There are a few edge cases where there isn't a
texture for certain wall configurations, but it is considerably more rare now.

A quick algorithm also sprinkles at random some of the terrain tiles. Some are
collidable (like the boulders), and some are not (such as the grass). I will
replace this system for a much more robust one in the future.
parent 22c7b79a
50896
C:\Users\Username\Desktop\Godot_v3.2.3-stable_mono_win64\Godot_v3.2.3-stable_mono_win64.exe
......@@ -4,8 +4,9 @@
[ext_resource path="res://actors/Player.tscn" type="PackedScene" id=2]
[node name="Main" type="Node2D"]
position = Vector2( 224, 112 )
[node name="TileMap" parent="." instance=ExtResource( 1 )]
[node name="Player" parent="." instance=ExtResource( 2 )]
position = Vector2( 248, 144 )
position = Vector2( 288, 144 )
......@@ -2,21 +2,46 @@ extends TileMap
# 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]
}
# Called when the node enters the scene tree for the first time.
func _ready():
generate_map()
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()
# # #####
......@@ -26,14 +51,14 @@ func _process(delta):
# # ###### ##### # # # # # # # ##### ###### # # # #####
# # # # # # # # # ## # # # # # # # # # #
# # # # # ##### ###### # # ###### # # # # # #### # #
# Prepares grid by sizing it and filling with random 1's or 0's (according to fill_percent)
func initialize_grid():
randomize()
grid = []
for w in width:
for h in height:
grid.append([])
for h in height:
for w in width:
var value = 0
if (w == 0 || w == width - 1 || h == 0 || h == height - 1):
value = 1
......@@ -43,7 +68,7 @@ func initialize_grid():
value = 1
elif value > fill_percent:
value = 0
grid[w].append(value)
grid[h].append(value)
# Makes the randomly filled grid transmogrify into a cave network
......@@ -52,9 +77,9 @@ func smooth_grid():
for y in range(0, height - 1):
var neighborWallTiles = get_surrounding_wall_count(x, y)
if neighborWallTiles > 4:
grid[x][y] = 1
grid[y][x] = 1
elif neighborWallTiles < 4:
grid[x][y] = 0
grid[y][x] = 0
# Returns the number of filled cells in the grid that surround the given coordinate
......@@ -64,18 +89,24 @@ func get_surrounding_wall_count(grid_x, grid_y):
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[x][y]
wall_count += grid[y][x]
else:
wall_count += 1
return wall_count
# Custom cell-clear command that doesn't mess up the setting of atlas tiles
func custom_clear():
var cells = get_used_cells()
for cell in cells:
set_cell(cell.x, cell.y, -1)
# 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
......@@ -86,6 +117,8 @@ func generate_map():
for i in range(0, 5):
smooth_grid()
replace_invalids()
for x in width:
for y in height:
update_tile(x, y)
......@@ -93,27 +126,118 @@ func generate_map():
update_dirty_quadrants()
# Prints the tile index and atlas/autotile coord of the tile with the given coords
func print_get_cell_autotile_coord(x, y):
print("Cell x%s y%s = %s, %s" % [x, y, get_cell(x, y), get_cell_autotile_coord(x, y)])
func is_wall(x, y):
return get_value(x, y) == 1
# Generate a new map on keypress
func _unhandled_input(event):
if event.is_action_pressed("generate_map"):
generate_map()
# 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]
func get_value(x, y):
# If it is outside the grid, clamp the coordinates,
# 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[x][y]
# 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
func is_wall(x, y):
return get_value(x, y) == 1
# 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)
......@@ -125,10 +249,30 @@ func is_wall(x, y):
# # # # # # # # # # # # #
# # ###### ###### # ###### # # #### ###### # #
# Custom cell-clear command that doesn't mess up the setting of atlas tiles
func custom_clear():
var cells = get_used_cells()
for cell in cells:
set_cell(cell.x, cell.y, -1)
# Prints the tile index and atlas/autotile coord of the tile with the given coords
func print_get_cell_autotile_coord(x, y):
print("Cell x%s y%s = %s, %s" % [x, y, get_cell(x, y), get_cell_autotile_coord(x, y)])
func get_value(x, y):
# If it is outside the grid, clamp the coordinates,
# 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]
func update_tile(x, y):
var index := -1
var autotile := Vector2.ZERO
if grid[x][y] == 1:
if grid[y][x] == 1:
# 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:
......@@ -141,7 +285,7 @@ func update_tile(x, y):
# is seamless with the bordering north and west walls and with the
# south/east non-walls.
#
# There are currently 4 known wall patterns (not including rotational symmetry):
# There are currently 6 known wall patterns (not including rotational symmetry):
# - Surrounded - these have walls on all sides. Since they don't border terrain,
# they're (currently) not rendered at all.
#
......@@ -177,8 +321,14 @@ func update_tile(x, y):
# o # #
# . # #
# o . o
# - Isolated wall - This configuration is a catch all for any walls that
# do not fall into the previous configurations:
#
# - Isolated wall - This wall is free floating and isolated
# o . o
# . # .
# o . o
#
# - Catch all wall - This configuration is a catch all for any walls
# that do not fall into the previous configurations:
# o o o
# o # o
# o o o
......@@ -241,13 +391,41 @@ func update_tile(x, y):
[true, _, false, _, false, _, true, true]:
# North-west outward
autotile = Vector2(4, 1)
[false, _, false, _, false, _, false, _]:
# Isolated wall
autotile = Vector2(3, 2)
_:
# Catch all wall
print('Missing tile for ', x, ',', y)
autotile = Vector2(3, 2)
if grid[x][y] == 0:
index = 0
autotile = Vector2(1, 1)
#TODO terrain tiles
if grid[y][x] == 0:
# Randomize floor or terrain tile
var terrain = rand_range(0.0, 100.0)
if 0.0 <= terrain and terrain < 30.0:
index = 1
autotile = Vector2(4, 2)
elif 30.0 <= terrain and terrain < 45.0:
index = 0
autotile = Vector2(1, 1)
elif 45.0 <= terrain and terrain < 55.0:
index = 3
autotile = Vector2(1, 1)
elif 55.0 <= terrain and terrain < 65.0:
index = 3
autotile = Vector2(0, 1)
elif 65.0 <= terrain and terrain < 75.0:
index = 3
autotile = Vector2(1, 0)
elif 75.0 <= terrain and terrain < 80.0:
index = 3
autotile = Vector2(0, 0)
elif 80.0 <= terrain and terrain < 95.0:
index = 4
autotile = Vector2(1, 0)
elif 95.0 <= terrain and terrain < 100.0:
index = 4
autotile = Vector2(0, 0)
set_cell(x, y, index, false, false, false, autotile)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment