Commit a6885e32 authored by tgw3ff's avatar tgw3ff
Browse files

Merge branch 'map-generator' into 'master'

Map generator

See merge request tgw3ff/2021-sp-cs4096-97-tdg!2
parents 265fdc1c 40fb21d8
source_md5="5e332500e8989b0f7b454ff6aeb64ca7"
dest_md5="760920115023187e8986388e47ea9bdf"
49477
C:\Users\Username\Desktop\Godot_v3.2.3-stable_mono_win64\Godot_v3.2.3-stable_mono_win64.exe
[gd_scene load_steps=3 format=2]
[ext_resource path="res://src/Tilemap.tscn" type="PackedScene" id=1]
[ext_resource path="res://src/Tilemap01.tscn" type="PackedScene" id=1]
[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( 288, 144 )
......@@ -2,7 +2,7 @@ extends Area2D
onready var ray = $RayCast2D
var tile_size = 8
var tile_size = 16
var inputs = {"right": Vector2.RIGHT,
"left": Vector2.LEFT,
"up": Vector2.UP,
......
......@@ -7,14 +7,15 @@
extents = Vector2( 8, 8 )
[node name="Player" type="Area2D"]
position = Vector2( 4, 4 )
position = Vector2( 8, 8 )
script = ExtResource( 1 )
[node name="player" type="Sprite" parent="."]
scale = Vector2( 2, 2 )
texture = ExtResource( 2 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
scale = Vector2( 0.45, 0.45 )
scale = Vector2( 0.95, 0.95 )
shape = SubResource( 1 )
[node name="RayCast2D" type="RayCast2D" parent="."]
......@@ -23,7 +24,7 @@ cast_to = Vector2( 0, 12 )
[node name="Camera2D" type="Camera2D" parent="."]
offset = Vector2( 4, 4 )
current = true
zoom = Vector2( 0.15, 0.15 )
zoom = Vector2( 0.5, 0.5 )
drag_margin_left = 0.01
drag_margin_top = 0.01
drag_margin_right = 0.01
......
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/tileset01.png-ea66d1373910c16fe7825c204d97bb8e.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/tileset01.png"
dest_files=[ "res://.import/tileset01.png-ea66d1373910c16fe7825c204d97bb8e.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=false
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0
......@@ -24,21 +24,30 @@ config/icon="res://icon.png"
left={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null)
]
}
right={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
]
}
up={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
]
}
down={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
]
}
generate_map={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777244,"unicode":0,"echo":false,"script":null)
]
}
......
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()
# 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
# 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():
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[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:
# # # #
# # # .
# . . .
# If we take '#' to be walls and '.' to be non-walls, then the wall tile
# in the center of this pattern would be a "north-west outer" - it juts
# "outward" from the wall into the room, so it will pick a tile that
# is seamless with the bordering north and west walls and with the
# south/east non-walls.
#
# 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.
#
# - Straight - these have walls on 3 main sides, and the remaining side
# is a non-wall. Diagonal neighbors are disregarded. For example, the
# pattern for a north-facing straight wall looks like this:
# o # o
# # # #
# o . o
# (where 'o' means we don't care about the value of that neighbor.)
#
#
# - Inner corner - Exactly one diagonal neighbor is non-wall, and the
# rest are walls:
# # # #
# # # #
# . # #
#
# - Outer corner - Three neighbors are non-walls, consisting of one
# diagonal neighbor, and the two neighbors they have in common, and
# the three neighbors that are directly opposite are walls:
# o # #
# . # #
# . . o
# - After some tests, this pattern was also found to be a valid
# outer corner arrangement in the case that an opposite facing
# outer corner is adjecent to the corner in question:
# o # #
# . # #
# # . o
# - Thus, this configuration will be used to determine outer corners
# instead:
# o # #
# . # #
# o . o
#
# - 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
#
# These neighbor states are flattened into an array of 8 booleans,
# starting with the north neighbor and going around clockwise.
# We can then use pattern-matching provided by GDScript's `match`
# statements to check for these patterns, where for each element,
# `true` means wall, `false` means non-wall, and `_` means don't care.
# The straight and corner patterns are duplicated four times, for their
# 90-degree rotational symmetry.
index = 0
var n = is_wall(x, y - 1)
var e = is_wall(x + 1, y)
var s = is_wall(x, y + 1)
var w = is_wall(x - 1, y)
var ne = is_wall(x + 1, y - 1)
var se = is_wall(x + 1, y + 1)
var sw = is_wall(x - 1, y + 1)
var nw = is_wall(x - 1, y - 1)
match [n, ne, e, se, s, sw, w, nw]:
[true, true, true, true, true, true, true, true]:
# Surrounded by walls - revert to a blank tile.
autotile = Vector2(4, 2)
[true, _, true, _, false, _, true, _]:
# North-facing straight
autotile = Vector2(1, 0)
[true, _, true, _, true, _, false, _]:
# East-facing straight
autotile = Vector2(2, 1)
[false, _, true, _, true, _, true, _]:
# South-facing straight
autotile = Vector2(1, 2)
[true, _, false, _, true, _, true, _]:
# West-facing straight
autotile = Vector2(0, 1)
[true, true, true, true, true, false, true, true]:
# North-east inward
autotile = Vector2(2, 0)
[true, true, true, true, true, true, true, false]:
# South-east inward
autotile = Vector2(2, 2)
[true, false, true, true, true, true, true, true]:
# South-west inward
autotile = Vector2(0, 2)
[true, true, true, false, true, true, true, true]:
# North-west inward
autotile = Vector2(0, 0)
[true, true, true, _, false, _, false, _]:
# North-east outward
autotile = Vector2(3, 1)
[false, _, true, true, true, _, false, _]:
# South-east outward
autotile = Vector2(3, 0)
[false, _, false, _, true, true, true, _]:
# South-west outward
autotile = Vector2(4, 0)
[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[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)
[gd_scene load_steps=34 format=2]
[gd_scene load_steps=35 format=2]
[ext_resource path="res://assets/tileset00.png" type="Texture" id=1]
[ext_resource path="res://src/Tilemap.gd" type="Script" id=2]
[sub_resource type="ConvexPolygonShape2D" id=1]
points = PoolVector2Array( 0, 0, 8, 0, 8, 8, 0, 8 )
......@@ -96,11 +97,11 @@ points = PoolVector2Array( 0, 0, 8, 0, 8, 8, 0, 8 )
points = PoolVector2Array( 0, 0, 8, 0, 8, 8, 0, 8 )
[sub_resource type="TileSet" id=32]
0/name = "tileset00.png 0"
0/name = "0 Walls"
0/texture = ExtResource( 1 )
0/tex_offset = Vector2( 0, 0 )
0/modulate = Color( 1, 1, 1, 1 )
0/region = Rect2( 0, 0, 40, 24 )
0/region = Rect2( 0, 0, 8, 8 )
0/tile_mode = 2
0/autotile/icon_coordinate = Vector2( 0, 0 )
0/autotile/tile_size = Vector2( 8, 8 )
......@@ -190,7 +191,7 @@ points = PoolVector2Array( 0, 0, 8, 0, 8, 8, 0, 8 )
"shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 )
} ]
0/z_index = 0
1/name = "tileset00.png 1"
1/name = "1 Bricks"
1/texture = ExtResource( 1 )
1/tex_offset = Vector2( 0, 0 )
1/modulate = Color( 1, 1, 1, 1 )
......@@ -211,7 +212,7 @@ points = PoolVector2Array( 0, 0, 8, 0, 8, 8, 0, 8 )
1/shape_one_way_margin = 0.0
1/shapes = [ ]
1/z_index = 0
2/name = "tileset00.png 2"
2/name = "2 Waters"
2/texture = ExtResource( 1 )
2/tex_offset = Vector2( 0, 0 )
2/modulate = Color( 1, 1, 1, 1 )
......@@ -311,7 +312,7 @@ points = PoolVector2Array( 0, 0, 8, 0, 8, 8, 0, 8 )
"shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 )
} ]