当前位置:网站首页>[Godot][GDScript] 2D cave map randomly generated
[Godot][GDScript] 2D cave map randomly generated
2022-07-31 03:03:00 【cheap meow】
照搬 Unity 的做法 https://blog.csdn.net/PriceCheap/article/details/125962352
版本:Godot_v4.0-alpha13_win64
The script is attached at GridMap 节点上,节点的 Meshibrary At least one element is required
MapGenerator.gd
class_name MapGenerator
extends GridMap
enum TileType {
Empty, Wall}
@export var width : int = 64
@export var height : int = 36
@export var mapSeed : String # 随机种子
@export var useRandomSeed : bool = true
@export_range(0.0,1.0) var threshold : float = 0.5 # 随机填充百分比,越大洞越小
@export_range(0,20) var smoothLevel : int = 4 # 平滑程度
@export var wallThresholdSize : int = 50 # 清除小墙体的阈值
@export var roomThresholdSize : int = 50 # 清除小孔的的阈值
@export var passageWidth : int = 4 # 通道(房间与房间直接)宽度
@export var borderSize : int = 1
var map : Array # 地图集,Empty为空洞,Wall为实体墙
var upDownLeftRight : Array = [Vector2i(0,1), Vector2i(0,-1), Vector2i(-1,0), Vector2i(1,0)]
var survivingRooms : Array # 存放最后实际有效的空洞房间
const Room = preload("Room.gd")
func _ready():
GenerateMap()
SetGridMap()
func _input(event):
if event is InputEventMouseButton:
match event.button_index:
MOUSE_BUTTON_LEFT:
survivingRooms.clear()
GenerateMap()
clear()
SetGridMap()
# 生成随机地图
func GenerateMap():
for x in range(width):
map.append([])
for y in range(height):
map[x].append(TileType.Empty)
RandomFillMap()
for i in range(smoothLevel):
SmoothMap()
# 清除小洞,小墙
ProcessMap()
# 连接各个幸存房间
ConnectAllRoomsToMainRoom(survivingRooms)
# 设置 3d 瓦片地图
func SetGridMap():
for x in range(width):
for y in range(height):
if map[x][y] == TileType.Empty:
set_cell_item(Vector3i(x,0,y),0,0)
# 随机填充地图
func RandomFillMap():
if useRandomSeed:
mapSeed = Time.get_datetime_string_from_system()
seed(mapSeed.hash())
for x in range(width):
for y in range(height):
if x == 0 || x == width - 1 || y == 0 || y == height - 1:
map[x][y] = TileType.Wall
else:
map[x][y] = TileType.Wall if randf_range(0,1) < threshold else TileType.Empty
# 平滑地图
func SmoothMap():
for x in range(width):
for y in range(height):
var neighbourWallTiles = GetSurroundingWallCount(x, y)
if neighbourWallTiles > 4: # Surrounded by more than four walls,That itself is the wall
map[x][y] = TileType.Wall
elif neighbourWallTiles < 4: #Surrounding greater than four is empty,It is also empty
map[x][y] = TileType.Empty
# 还有如果四四开,那就保持不变.
# 获取该点周围 8 个点为实体墙(map[x][y] == 1)的个数
func GetSurroundingWallCount(x, y):
var wallCount = 0
for nx in [x-1, x+1]:
for ny in [y-1, y+1]:
if nx >= 0 && nx < width && ny >= 0 && ny < height:
wallCount += 1 if map[x][y] == TileType.Wall else 0
else:
wallCount += 1
return wallCount
# 加工地图,清除小洞,小墙,连接房间.
func ProcessMap():
# 获取最大房间的索引
var currentIndex : int = 0
var maxIndex : int = 0
var maxSize : int = 0
# 获取墙区域
var wallRegions = GetRegions(TileType.Wall)
for wallRegion in wallRegions:
if wallRegion.size() < wallThresholdSize:
for tile in wallRegion:
map[tile.x][tile.y] = TileType.Empty # 把小于阈值的都铲掉
# 获取空洞区域
var roomRegions = GetRegions(TileType.Empty)
for roomRegion in roomRegions:
if roomRegion.size() < roomThresholdSize:
for tile in roomRegion:
map[tile.x][tile.y] = TileType.Wall # 把小于阈值的都填充
else:
var sRoom = Room.new(roomRegion, map)
survivingRooms.append(sRoom) # 添加到幸存房间列表里
if maxSize < roomRegion.size():
maxSize = roomRegion.size()
maxIndex = currentIndex # 找出最大房间的索引
currentIndex += 1
if survivingRooms.size() == 0:
print_debug("No Survived Rooms Here!!")
else:
survivingRooms[maxIndex].isMainRoom = true # 最大房间就是主房间
survivingRooms[maxIndex].isAccessibleFromMainRoom = true
# 获取区域
func GetRegions(tileType):
var regions : Array
var mapFlags = BitMap.new()
mapFlags.create(Vector2(width, height))
for x in range(width):
for y in range(height):
if mapFlags.get_bit(Vector2(x, y)) == false && map[x][y] == tileType:
regions.append(GetRegionTiles(x, y, tileType, mapFlags))
return regions
# 从这个点开始获取区域,广度优先算法
func GetRegionTiles(startX, startY, tileType, mapFlags):
var tiles : Array
var quene1 : Array
var quene2 : Array
quene1.append(Vector2i(startX, startY))
mapFlags.set_bit(Vector2(startX, startY), true)
while quene1.size() > 0:
var tile = quene1.pop_back()
tiles.append(tile)
# 遍历上下左右四格
for i in range(4):
var x = tile.x + upDownLeftRight[i].x;
var y = tile.y + upDownLeftRight[i].y;
if IsInMapRange(x, y) && mapFlags.get_bit(Vector2(x, y)) == false && map[x][y] == tileType:
mapFlags.set_bit(Vector2(x, y), true)
quene2.append(Vector2i(x, y))
if quene1.size() == 0:
quene1 = quene2
quene2 = Array()
return tiles
# 把所有房间都连接到主房间
func ConnectAllRoomsToMainRoom(allRooms):
for room in allRooms:
ConnectToClosestRoom(room, allRooms)
var count = 0
for room in allRooms:
if room.isAccessibleFromMainRoom:
count += 1
if count != allRooms.size():
ConnectAllRoomsToMainRoom(allRooms)
# 连接本房间与距离自己最近的一个与自己尚未连接的房间
# 可能找不到满足条件的待连接房间
func ConnectToClosestRoom(roomA, roomListB):
var bestDistance : int = 9223372036854775807
var bestTileA : Vector2i
var bestTileB : Vector2i
var bestRoomB
var hasChecked = false
for roomB in roomListB:
if roomA == roomB || roomA.IsConnected(roomB):
continue
for tileA in roomA.edgeTiles:
for tileB in roomB.edgeTiles:
var distanceBetweenRooms = (tileA - tileB).length_squared()
# 如果找到更近的(相对roomA)房间,更新最短路径.
if distanceBetweenRooms < bestDistance:
bestDistance = distanceBetweenRooms
bestTileA = tileA
bestTileB = tileB
bestRoomB = roomB
if bestRoomB != null:
CreatePassage(roomA, bestRoomB, bestTileA, bestTileB)
# 创建两个房间的通道
func CreatePassage(roomA, roomB, tileA, tileB):
roomA.ConnectRooms(roomB)
var line = GetLine(tileA, tileB)
for coord in line:
DrawCircle(coord, passageWidth)
# 获取两点直接线段经过的点
func GetLine(from, to):
var line : Array
var x = from.x
var y = from.y
var dx = to.x - from.x
var dy = to.y - from.y
var inverted = false
var step = sign(dx)
var gradientStep = sign(dy)
var longest = abs(dx)
var shortest = abs(dy)
if longest < shortest:
inverted = true
longest = abs(dy)
shortest = abs(dx)
step = sign(dy)
gradientStep = sign(dx)
var gradientAccumulation = longest / 2 # 梯度积累,最长边的一半.
for i in range(longest):
line.append(Vector2i(x, y))
if inverted:
y += step
else:
x += step
gradientAccumulation += shortest # 梯度每次增长为短边的长度.
if gradientAccumulation >= longest:
if inverted:
x += gradientStep
else:
y += gradientStep
gradientAccumulation -= longest
return line
# 以点c为原点,r为半径,画圈(拆墙)
func DrawCircle(c, r):
for x in range(-r, r):
for y in range(-r, r):
if x * x + y * y <= r * r:
var drawX = c.x + x
var drawY = c.y + y
if IsInMapRange(drawX, drawY):
map[drawX][drawY] = TileType.Empty
# 判断坐标是否在地图里,不管墙还是洞
func IsInMapRange(x, y):
return x >= 0 && x < width && y >= 0 && y < height
The script does not need to be attached to the node
Room.gd
class_name Room
extends Object
enum TileType {
Empty, Wall}
var tiles : Array = [] # 所有坐标
var edgeTiles : Array = [] # 靠边的坐标
var connectedRooms : Array = [] # 与其直接相连的房间.
var isAccessibleFromMainRoom : bool = false # 是否能连接到主房间
var isMainRoom : bool = false # 是否主房间(最大的房间)
var upDownLeftRight : Array = [Vector2i(0,1), Vector2i(0,-1), Vector2i(-1,0), Vector2i(1,0)]
func _init(roomTiles, map):
tiles = roomTiles
UpdateEdgeTiles(map)
# 更新房间边缘瓦片集
func UpdateEdgeTiles(map):
edgeTiles.clear()
# 遍历上下左右四格,判断是否有墙
for tile in tiles:
for i in range(4):
var x = tile.x + upDownLeftRight[i].x
var y = tile.y + upDownLeftRight[i].y
if map[x][y] == TileType.Wall && !edgeTiles.has(tile):
edgeTiles.append(tile)
# Mark the connectivity relative to the main room
func MarkAccessibleFromMainRoom():
# Mark yourself as being able to connect to the main room
if !isAccessibleFromMainRoom:
isAccessibleFromMainRoom = true
# The rooms connected to you can connect to the main room
for connectedRoom in connectedRooms:
connectedRoom.MarkAccessibleFromMainRoom()
# 连接房间
func ConnectRooms(roomB):
# Pass the connection token
if isAccessibleFromMainRoom:
roomB.MarkAccessibleFromMainRoom()
elif roomB.isAccessibleFromMainRoom:
MarkAccessibleFromMainRoom()
# Pass the connection behavior
connectedRooms.append(roomB)
roomB.connectedRooms.append(self)
# 是否连接另一个房间
func IsConnected(otherRoom):
if connectedRooms.find(otherRoom) == -1:
return false
else:
return true
本来 TileType
和 upDownLeftRight
Should be made a global variable
但是 4.0-alpha 的 autoload There is a bit of a problem with the settings,我就没有用
边栏推荐
- 学习DAVID数据库(1)
- TCP/IP四层模型
- TCP/IP four-layer model
- Uninstallation of mysql5.7.37 under CentOS7 [perfect solution]
- Graphical lower_bound & upper_bound
- 【Cocos Creator 3.5】缓动系统停止所有动画
- 【CV项目调试】CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT问题
- CentOS7下mysql5.7.37的卸载【完美方案】
- 12 磁盘相关命令
- YOLOV5 study notes (3) - detailed explanation of network module
猜你喜欢
【Android】Room —— SQLite的替代品
Addition and Subtraction of Scores in LeetCode Medium Questions
关于 mysql8.0数据库中主键位id,使用replace插入id为0时,实际id插入后自增导致数据重复插入 的解决方法
SQL injection Less54 (limited number of SQL injection + union injection)
LeetCode中等题之分数加减运算
MultipartFile文件上传
CentOS7下mysql5.7.37的安装【完美方案】
Moxa NPort 设备缺陷可能使关键基础设施遭受破坏性攻击
19. Support Vector Machines - Intuitive Understanding of Optimization Objectives and Large Spacing
【C语言】三子棋(经典解法+一览图)
随机推荐
STM32问题合集
Crypto Firms Offer Offer To Theft Hackers: Keep A Little, Give The Rest
Clustering index, and what is the difference between a clustering index
[Godot][GDScript] 二维洞穴地图随机生成
C#远程调试
The simulation application of common mode inductance is here, full of dry goods for everyone
6. Display comments and replies
execsnoop 工具
刚出道“一战成名”,安全、舒适一个不落
IIR滤波器和FIR滤波器
Basic learning about Redis related content
SQALE 是什么
Project (5) - Small target detection tph-yolov5
分布式与集群是什么 ? 区别是什么?
【C语言】进制转换一般方法
Installation of mysql5.7.37 under CentOS7 [perfect solution]
下载jar包的好地方
Chapter 9 SVM Practice
Huawei od dice js
7年经验,功能测试工程师该如何一步步提升自己的能力呢?