392 lines
15 KiB
Plaintext
392 lines
15 KiB
Plaintext
|
|
extends Node3D
|
||
|
|
class_name LevelGenerator
|
||
|
|
|
||
|
|
var levelGrid: Array[Array]
|
||
|
|
@export var gridSize: int = 225
|
||
|
|
|
||
|
|
@export var doorBlock: PackedScene # Temporary
|
||
|
|
|
||
|
|
var rng := RandomNumberGenerator.new() #Get random values, usefull for random level generation huh
|
||
|
|
var currentMission: Mission
|
||
|
|
var doorSpawnPoints: Array[DoorPosition] #Where to put them doors
|
||
|
|
var doorBlockSpawnPoints: Array[DoorPosition] #Where to put them door blockers
|
||
|
|
|
||
|
|
func _ready() -> void:
|
||
|
|
initGrid() #Assign a new grid space object to each space of the 1x1m grid
|
||
|
|
|
||
|
|
#Center the world
|
||
|
|
position.x = -gridSize/2
|
||
|
|
position.z = -gridSize/2
|
||
|
|
|
||
|
|
currentMission = RescueMission.new() #Have something choose the mission type in this part later
|
||
|
|
|
||
|
|
#Biomes
|
||
|
|
var doorList: Array[DoorPosition] #Seccond Array contains door positions
|
||
|
|
for bPrio in currentMission.biomes.size():
|
||
|
|
doorList = placeBiomes(currentMission.biomes[bPrio])
|
||
|
|
spreadBiomes()
|
||
|
|
placeRooms(doorList)
|
||
|
|
|
||
|
|
#Doors
|
||
|
|
findValidDoors()
|
||
|
|
spawnDoors()
|
||
|
|
|
||
|
|
#Misc Utility
|
||
|
|
func initGrid() -> void:
|
||
|
|
for x in gridSize:
|
||
|
|
var newRow: Array
|
||
|
|
levelGrid.push_back(newRow)
|
||
|
|
for y in gridSize:
|
||
|
|
var newCell := GridCell.new()
|
||
|
|
newCell.position = Vector2i(x,y)
|
||
|
|
levelGrid[x].push_back(newCell)
|
||
|
|
|
||
|
|
func addGridCells(cell1: GridCell,cell2: GridCell) -> GridCell:
|
||
|
|
var returnCell: GridCell = GridCell.new()
|
||
|
|
returnCell.spaceTaken = cell1.spaceTaken or cell2.spaceTaken
|
||
|
|
returnCell.door = cell1.door or cell2.door
|
||
|
|
if cell1.door and !cell2.door:
|
||
|
|
returnCell.doorOrientation = cell1.doorOrientation
|
||
|
|
else:
|
||
|
|
returnCell.doorOrientation = cell2.doorOrientation
|
||
|
|
returnCell.position = cell1.position
|
||
|
|
returnCell.biome = cell1.biome
|
||
|
|
return returnCell
|
||
|
|
|
||
|
|
func addArrays2D(array1: Array[Array], array2: Array[Array], arr2pos: Vector2i = Vector2i(0,0)) -> void:
|
||
|
|
if array1.size() <= (arr2pos.x) + array2.size(): return
|
||
|
|
if array1[arr2pos.y].size() <= (arr2pos.y) + array2[0].size(): return
|
||
|
|
for x in array2.size():
|
||
|
|
for y in array2[x].size():
|
||
|
|
array1[x+arr2pos.x][y+arr2pos.y] = addGridCells(array1[x+arr2pos.x][y+arr2pos.y],array2[x][y])
|
||
|
|
|
||
|
|
func checkOverlapArrays2D(array1: Array[Array], array2: Array[Array], arr2pos: Vector2i = Vector2i(0,0)) -> bool:
|
||
|
|
if array1.size() <= arr2pos.x + array2.size() or arr2pos.x < 0: return true
|
||
|
|
if array1[arr2pos.y].size() <= arr2pos.y + array2[0].size() or arr2pos.y < 0: return true
|
||
|
|
for x in array2.size():
|
||
|
|
for y in array2[x].size():
|
||
|
|
if array1[x+arr2pos.x][y+arr2pos.y].spaceTaken and array2[x][y].spaceTaken: return true
|
||
|
|
return false
|
||
|
|
|
||
|
|
func checkBiomeOverlap(biome: String, pos: Vector2i, length: int, height: int) -> bool:
|
||
|
|
if levelGrid.size() <= pos.x + length or pos.x < 0: return true
|
||
|
|
if levelGrid[pos.y].size() <= pos.y + height or pos.y < 0: return true
|
||
|
|
for x in length:
|
||
|
|
for y in height:
|
||
|
|
if levelGrid[x+pos.x][y+pos.y].biome: return true
|
||
|
|
return false
|
||
|
|
|
||
|
|
func rotateArray2D(array: Array[Array], numberOfRotationsBy90Degrees: int) -> void:
|
||
|
|
var size: int = array.size()
|
||
|
|
var layerCount: int = size/2
|
||
|
|
|
||
|
|
for x in numberOfRotationsBy90Degrees%4:
|
||
|
|
for layer in layerCount:
|
||
|
|
var first: int = layer
|
||
|
|
var last: int = size - first - 1
|
||
|
|
|
||
|
|
for element in range(first, last):
|
||
|
|
var offset = element - first
|
||
|
|
|
||
|
|
var top = array[first][element]
|
||
|
|
var right = array[element][last]
|
||
|
|
var bot = array[last][last-offset]
|
||
|
|
var left = array[last-offset][first]
|
||
|
|
|
||
|
|
array[element][last] = top
|
||
|
|
array[last][last-offset] = right
|
||
|
|
array[last-offset][first] = bot
|
||
|
|
array[first][element] = left
|
||
|
|
|
||
|
|
func getDoorPosition(cell: GridCell, GridCellPosition: Vector2i) -> DoorPosition:
|
||
|
|
if !cell.door:
|
||
|
|
print("Tried to get door at cell that isnt a door")
|
||
|
|
return null
|
||
|
|
var returnDoorPosition := DoorPosition.new()
|
||
|
|
returnDoorPosition.pos = GridCellPosition
|
||
|
|
returnDoorPosition.orientation = cell.doorOrientation
|
||
|
|
return returnDoorPosition
|
||
|
|
|
||
|
|
func addObject(AddedObject:PackedScene, Parent: Node3D, Position: Vector3, Rotation: Vector3= Vector3(0,0,0)):
|
||
|
|
if !AddedObject:
|
||
|
|
print("tried to add object but packed scene is null")
|
||
|
|
return
|
||
|
|
var obj = AddedObject.instantiate()
|
||
|
|
Parent.add_child(obj)
|
||
|
|
obj.position = Position
|
||
|
|
obj.rotation = Rotation
|
||
|
|
return obj
|
||
|
|
|
||
|
|
func changeBiomeArea2D(biome: String,pos:Vector2i,length: int,height: int):
|
||
|
|
for x in length:
|
||
|
|
for y in height:
|
||
|
|
levelGrid[pos.x+x][pos.y+y].biome = biome
|
||
|
|
|
||
|
|
#Spawning Rooms
|
||
|
|
func rotateRoomData(roomData: RoomData, numberOfRotationsBy90Degrees: int) -> void:
|
||
|
|
rotateArray2D(roomData.roomGrid,numberOfRotationsBy90Degrees)
|
||
|
|
roomData.rotations = wrapi(roomData.rotations + numberOfRotationsBy90Degrees,0,4)
|
||
|
|
for door in roomData.doorPositions:
|
||
|
|
door.rotatePosRight(roomData.roomGrid.size(),numberOfRotationsBy90Degrees)
|
||
|
|
roomData.roomGrid[door.pos.x][door.pos.y].doorOrientation = wrapi(door.orientation-2,0,4)
|
||
|
|
|
||
|
|
func spawnRoom(roomDataInput: RoomData,pos: Vector2i,numberOfRotationsBy90Degrees: int = 0, centered: bool = false) -> Array[DoorPosition]: #Pos corresponds to the upper left corner of the room immage
|
||
|
|
var roomData: RoomData = roomDataInput.duplicateRoom()
|
||
|
|
var roomScene: PackedScene = load(roomData.roomSceneRef)
|
||
|
|
rotateRoomData(roomData,numberOfRotationsBy90Degrees)
|
||
|
|
var roomGrid := roomData.roomGrid
|
||
|
|
var doorPositions: Array[DoorPosition] = roomData.doorPositions
|
||
|
|
if centered:
|
||
|
|
pos = Vector2i(pos.x-(roomGrid.size()/2),pos.y-(roomGrid[0].size()/2))
|
||
|
|
|
||
|
|
addArrays2D(levelGrid,roomGrid,pos)
|
||
|
|
addObject(roomScene,self,Vector3(pos.x+(roomGrid.size()/2),0,pos.y+(roomGrid[0].size()/2)),Vector3(0,(roomData.rotations%4)*PI/2,0))
|
||
|
|
for door in doorPositions:
|
||
|
|
door.pos = pos + door.pos
|
||
|
|
|
||
|
|
return doorPositions
|
||
|
|
|
||
|
|
func getDoorFromRoom(roomData: RoomData,index: int) -> DoorPosition:
|
||
|
|
return roomData.doorPositions[index % roomData.doorPositions.size()]
|
||
|
|
|
||
|
|
func putRoomAtDoor(roomData: RoomData, door: DoorPosition,spawnDoorIndex: int) -> Array: ##Return array contains roomData, spawn Pos in that order
|
||
|
|
#Init Values:
|
||
|
|
var spawnDoor: DoorPosition = getDoorFromRoom(roomData,spawnDoorIndex) #Which door from the spawned room connects to the exiting room
|
||
|
|
var doorOrientationDifference : int = wrapi(spawnDoor.orientation - door.orientation,0,4) #Difference in orinet. between spawned and existing room
|
||
|
|
var numberOfRoomRotations: int = 0 #How many rotations are needed to make spawned rooms spawn door face the existing door
|
||
|
|
var spawnPos: Vector2i
|
||
|
|
var doorOffset: Vector2i #Offset by 1 space so the doors are next to eachother and not inside eachoter
|
||
|
|
|
||
|
|
#Find number of rotations and then rotate the room
|
||
|
|
if !doorOrientationDifference == 2:
|
||
|
|
if doorOrientationDifference == 1:
|
||
|
|
numberOfRoomRotations = 3
|
||
|
|
if doorOrientationDifference == 0:
|
||
|
|
numberOfRoomRotations = 2
|
||
|
|
if doorOrientationDifference == 3:
|
||
|
|
numberOfRoomRotations = 1
|
||
|
|
rotateRoomData(roomData,numberOfRoomRotations)
|
||
|
|
|
||
|
|
#Get new values for spawnDoor and set door offset
|
||
|
|
spawnDoor = getDoorFromRoom(roomData,spawnDoorIndex)
|
||
|
|
match spawnDoor.orientation:
|
||
|
|
0: doorOffset = Vector2i(0,-1)
|
||
|
|
1: doorOffset = Vector2i(1,0)
|
||
|
|
2: doorOffset = Vector2i(0,1)
|
||
|
|
3: doorOffset = Vector2i(-1,0)
|
||
|
|
|
||
|
|
#Set spawn pos and return values
|
||
|
|
spawnPos = door.pos - spawnDoor.pos + doorOffset
|
||
|
|
var returnArray: Array = [roomData,spawnPos]
|
||
|
|
return returnArray
|
||
|
|
|
||
|
|
func spawnRoomAtDoor(roomDataInput: Array[RoomData], door: DoorPosition, biome: String) -> Array[DoorPosition]:
|
||
|
|
var returnArray: Array[DoorPosition]
|
||
|
|
var roomData: RoomData = null
|
||
|
|
var spawnDoorIndex: int = -1
|
||
|
|
#Choose the room to use
|
||
|
|
var rooms: Array[RoomData] = roomDataInput.duplicate()
|
||
|
|
rooms.shuffle()
|
||
|
|
for room in rooms:
|
||
|
|
#Randomise door checking order
|
||
|
|
var doorIndices: Array[int]
|
||
|
|
for x in room.doorPositions.size():
|
||
|
|
doorIndices.push_back(x)
|
||
|
|
doorIndices.shuffle()
|
||
|
|
|
||
|
|
#Check if doorIndex fits and assign roomData and spawnDoorIndex if it does
|
||
|
|
for doorIndex in doorIndices:
|
||
|
|
if checkIfRoomFits(room,door,doorIndex, biome):
|
||
|
|
roomData = room.duplicateRoom()
|
||
|
|
spawnDoorIndex = doorIndex
|
||
|
|
break
|
||
|
|
|
||
|
|
#Check if a room has been found
|
||
|
|
if !roomData or spawnDoorIndex == -1:
|
||
|
|
return returnArray
|
||
|
|
|
||
|
|
#Get Spawn Info and spawn room
|
||
|
|
var spawnInfo: Array = putRoomAtDoor(roomData,door,spawnDoorIndex)
|
||
|
|
spawnRoom(spawnInfo[0],spawnInfo[1])
|
||
|
|
returnArray = spawnInfo[0].doorPositions
|
||
|
|
for x in returnArray.size():
|
||
|
|
returnArray[x].pos = spawnInfo[1] + returnArray[x].pos
|
||
|
|
return returnArray
|
||
|
|
|
||
|
|
func checkIfBiomeFits(length: int, height: int, pos: Vector2i, biome: String) -> bool:
|
||
|
|
if levelGrid.size() <= pos.x + length or pos.x < 0: return true
|
||
|
|
if levelGrid[pos.y].size() <= pos.y + height or pos.y < 0: return true
|
||
|
|
for x in length:
|
||
|
|
for y in height:
|
||
|
|
if levelGrid[x+pos.x][y+pos.y].biome != biome: return false
|
||
|
|
return true
|
||
|
|
|
||
|
|
func checkIfRoomFits(roomDataInput: RoomData,door: DoorPosition,spawnDoorIndex: int, biome: String) -> bool:
|
||
|
|
var roomData: RoomData = roomDataInput.duplicateRoom()
|
||
|
|
var spawnInfo: Array = putRoomAtDoor(roomData,door,spawnDoorIndex)
|
||
|
|
return !checkOverlapArrays2D(levelGrid,spawnInfo[0].roomGrid,spawnInfo[1]) and checkIfBiomeFits(spawnInfo[0].roomGrid.size(),spawnInfo[0].roomGrid[0].size(),spawnInfo[1],biome)
|
||
|
|
|
||
|
|
func isOutOfArray2DBounds(sizeX: int, sizeY: int, pos: Vector2i) -> bool:
|
||
|
|
if pos.x >= sizeX or pos.x < 0 or pos.y >= sizeY or pos.y < 0:
|
||
|
|
return true
|
||
|
|
else:
|
||
|
|
return false
|
||
|
|
|
||
|
|
func isValidDoor(pos:Vector2i) -> bool: # Checks if the door connects to another door using its orientation,
|
||
|
|
#if this function finds a valid door, it removes the door status from the other door to avoid duplicated doors
|
||
|
|
match levelGrid[pos.x][pos.y].doorOrientation:
|
||
|
|
0:
|
||
|
|
if isOutOfArray2DBounds(levelGrid.size(),levelGrid[0].size(),Vector2i(pos.x,pos.y-1)): return false
|
||
|
|
if !levelGrid[pos.x][pos.y-1].door: return false
|
||
|
|
else: levelGrid[pos.x][pos.y-1].door = false
|
||
|
|
1:
|
||
|
|
if isOutOfArray2DBounds(levelGrid.size(),levelGrid[0].size(),Vector2i(pos.x+1,pos.y)): return false
|
||
|
|
if !levelGrid[pos.x+1][pos.y].door: return false
|
||
|
|
else: levelGrid[pos.x+1][pos.y].door = false
|
||
|
|
2:
|
||
|
|
if isOutOfArray2DBounds(levelGrid.size(),levelGrid[0].size(),Vector2i(pos.x,pos.y+1)): return false
|
||
|
|
if !levelGrid[pos.x][pos.y+1].door: return false
|
||
|
|
else: levelGrid[pos.x][pos.y+1].door = false
|
||
|
|
3:
|
||
|
|
if isOutOfArray2DBounds(levelGrid.size(),levelGrid[0].size(),Vector2i(pos.x-1,pos.y)): return false
|
||
|
|
if !levelGrid[pos.x-1][pos.y].door: return false
|
||
|
|
else: levelGrid[pos.x-1][pos.y].door = false
|
||
|
|
return true
|
||
|
|
|
||
|
|
func findValidDoors() -> void:
|
||
|
|
for x in levelGrid.size():
|
||
|
|
for y in levelGrid[x].size():
|
||
|
|
if levelGrid[x][y].door:
|
||
|
|
var newDoorObject := DoorPosition.new()
|
||
|
|
newDoorObject.pos = Vector2i(x,y)
|
||
|
|
newDoorObject.orientation = levelGrid[x][y].doorOrientation
|
||
|
|
if isValidDoor(Vector2i(x,y)):
|
||
|
|
doorSpawnPoints.push_back(newDoorObject)
|
||
|
|
else:
|
||
|
|
doorBlockSpawnPoints.push_back(newDoorObject)
|
||
|
|
|
||
|
|
func spawnDoors() -> void:
|
||
|
|
for doorSpawn in doorSpawnPoints:
|
||
|
|
debugCubeAtPos(doorSpawn.pos)
|
||
|
|
for doorBlockSpawn in doorBlockSpawnPoints:
|
||
|
|
addObject(doorBlock,self,Vector3(doorBlockSpawn.pos.x+0.5,0,doorBlockSpawn.pos.y+0.5),Vector3(0,doorBlockSpawn.orientation*-PI/2,0))
|
||
|
|
pass
|
||
|
|
|
||
|
|
func generateBiomePos(biome: String,starterRoomSize: Vector2i) -> Vector2i:
|
||
|
|
var genPos: Vector2i
|
||
|
|
var possiblePos: Array[Vector2i]
|
||
|
|
|
||
|
|
#Find all possible Pos without a biome
|
||
|
|
for x in levelGrid.size():
|
||
|
|
for cell in levelGrid[x]:
|
||
|
|
if !cell.biome:
|
||
|
|
possiblePos.push_back(cell.position)
|
||
|
|
|
||
|
|
#Go through all pos untill one works
|
||
|
|
genPos = possiblePos[rng.randi_range(0,possiblePos.size()-1)]
|
||
|
|
while checkBiomeOverlap(biome,genPos,starterRoomSize.x,starterRoomSize.y):
|
||
|
|
possiblePos.erase(genPos)
|
||
|
|
genPos = possiblePos[rng.randi_range(0,possiblePos.size()-1)]
|
||
|
|
|
||
|
|
return genPos
|
||
|
|
|
||
|
|
#Biomes
|
||
|
|
func getBiome(biomeName: String) -> Biome:
|
||
|
|
for prio in currentMission.biomes.size():
|
||
|
|
for biome in currentMission.biomes[prio]:
|
||
|
|
if biome.name == biomeName:
|
||
|
|
return biome
|
||
|
|
return
|
||
|
|
|
||
|
|
func placeBiomes(biomes: Array[Biome]) -> Array[DoorPosition]:
|
||
|
|
var returnDoorLists: Array[DoorPosition] = []
|
||
|
|
|
||
|
|
for biome in biomes:
|
||
|
|
var starterRoomSize: Vector2i = Vector2i(biome.starterRoom.roomGrid.size(),biome.starterRoom.roomGrid[0].size())
|
||
|
|
var generatePos: Vector2i = generateBiomePos(biome.name,starterRoomSize)
|
||
|
|
|
||
|
|
changeBiomeArea2D(biome.name,generatePos,starterRoomSize.x,starterRoomSize.y)
|
||
|
|
|
||
|
|
#Spawn Starter room and add doors to list
|
||
|
|
var doorList: Array[DoorPosition]
|
||
|
|
doorList = spawnRoom(biome.starterRoom,generatePos)
|
||
|
|
returnDoorLists += doorList
|
||
|
|
|
||
|
|
return returnDoorLists
|
||
|
|
|
||
|
|
func fillCell(cellBiome: String,pos: Vector2i) -> GridCell:
|
||
|
|
if isOutOfArray2DBounds(levelGrid.size(),levelGrid[0].size(),pos): return
|
||
|
|
if !levelGrid[pos.x][pos.y].biome:
|
||
|
|
levelGrid[pos.x][pos.y].biome = cellBiome
|
||
|
|
return levelGrid[pos.x][pos.y]
|
||
|
|
return
|
||
|
|
|
||
|
|
func spreadCell(cell: GridCell,pos: Vector2i) -> Array[GridCell]:
|
||
|
|
var returnCells: Array[GridCell] = []
|
||
|
|
returnCells.push_back(fillCell(cell.biome,pos + Vector2i(0,-1)))
|
||
|
|
returnCells.push_back(fillCell(cell.biome,pos + Vector2i(1,0)))
|
||
|
|
returnCells.push_back(fillCell(cell.biome,pos + Vector2i(0,1)))
|
||
|
|
returnCells.push_back(fillCell(cell.biome,pos + Vector2i(-1,0)))
|
||
|
|
while returnCells.has(null):
|
||
|
|
returnCells.erase(null)
|
||
|
|
return returnCells
|
||
|
|
|
||
|
|
func allCellsHaveBiome() -> bool:
|
||
|
|
for x in levelGrid.size():
|
||
|
|
for cell in levelGrid[x]:
|
||
|
|
if !cell.biome: return false
|
||
|
|
return true
|
||
|
|
|
||
|
|
func spreadBiomes() -> void:
|
||
|
|
#Get all current biome grid cells -> Put them in starterCells
|
||
|
|
var startCells: Array[GridCell] = []
|
||
|
|
for x in levelGrid.size():
|
||
|
|
for y in levelGrid[x].size():
|
||
|
|
if levelGrid[x][y].biome:
|
||
|
|
startCells.push_back(levelGrid[x][y])
|
||
|
|
|
||
|
|
#Spread them by one -> put the new ones in a new array
|
||
|
|
while !allCellsHaveBiome():
|
||
|
|
startCells.shuffle()
|
||
|
|
var newCells: Array[GridCell] = []
|
||
|
|
for cell in startCells:
|
||
|
|
if rng.randf() * getBiome(cell.biome).spread > 0.49:
|
||
|
|
newCells += spreadCell(cell,cell.position)
|
||
|
|
else:
|
||
|
|
newCells.push_back(cell)
|
||
|
|
#New cells now contains all that were newly assigned a biome and the ones that didnt spread
|
||
|
|
startCells = newCells
|
||
|
|
|
||
|
|
func placeRooms(doorList: Array[DoorPosition]) -> void:
|
||
|
|
var nextDoors: Array[DoorPosition] = []
|
||
|
|
|
||
|
|
#YES I KNOW A DO WHILE LOOP IS BETTER HERE BUT GODOT DOESNT HAVE IT
|
||
|
|
for door in doorList:
|
||
|
|
var doorBiome: Biome = getBiome(levelGrid[door.pos.x][door.pos.y].biome)
|
||
|
|
nextDoors += spawnRoomAtDoor(doorBiome.roomList.rooms,door,doorBiome.name)
|
||
|
|
doorList = nextDoors
|
||
|
|
|
||
|
|
while !doorList.is_empty():
|
||
|
|
for door in doorList:
|
||
|
|
var doorBiome: Biome = getBiome(levelGrid[door.pos.x][door.pos.y].biome)
|
||
|
|
nextDoors += spawnRoomAtDoor(doorBiome.roomList.rooms,door,doorBiome.name)
|
||
|
|
doorList = nextDoors
|
||
|
|
nextDoors = []
|
||
|
|
|
||
|
|
#Debug Functions
|
||
|
|
func printLevelGrid() -> void:
|
||
|
|
for x in levelGrid.size():
|
||
|
|
for y in levelGrid[x].size():
|
||
|
|
if levelGrid[x][y].spaceTaken:
|
||
|
|
debugCubeAtPos(Vector2(x,y))
|
||
|
|
|
||
|
|
func printBiomeGrid() -> void:
|
||
|
|
for x in levelGrid.size():
|
||
|
|
for y in levelGrid[x].size():
|
||
|
|
if levelGrid[x][y].biome == "test":
|
||
|
|
debugCubeAtPos(Vector2(x,y))
|
||
|
|
|
||
|
|
func debugCubeAtPos(pos: Vector2):
|
||
|
|
var debugCube: PackedScene = preload("res://test/debugCube.tscn")
|
||
|
|
addObject(debugCube,self,Vector3(pos.x+0.5,0,pos.y+0.5))
|