当前位置:网站首页>[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,我就没有用
边栏推荐
- Mathematics to solve the problem - circular linked list
- The difference between link and @import
- TCP详解(三)
- 5. SAP ABAP OData 服务如何支持 $filter (过滤)操作
- Basic learning about Redis related content
- 12 Disk related commands
- [Godot][GDScript] 二维洞穴地图随机生成
- 华为分布式存储FusionStorage知识点总结【面试篇】
- 跨专业考研难度大?“上岸”成功率低?这份实用攻略请收下!
- 冒泡排序、选择排序、直接插入排序、二分法查找
猜你喜欢
【编译原理】递归下降语法分析设计原理与实现
跨专业考研难度大?“上岸”成功率低?这份实用攻略请收下!
局域网电脑硬件信息收集工具
8. Unified exception handling (controller notifies @ControllerAdvice global configuration class, @ExceptionHandler handles exceptions uniformly)
YOLOV5 study notes (2) - environment installation + operation + training
Classic linked list OJ strong training problem - fast and slow double pointer efficient solution
Why is String immutable?
7. List of private messages
你们程序员为什么不靠自己的项目谋生?而必须为其他人打工?
5. SAP ABAP OData 服务如何支持 $filter (过滤)操作
随机推荐
10 Permission introduction
IIR滤波器和FIR滤波器
Installation of mysql5.7.37 under CentOS7 [perfect solution]
php 网站的多语言设置(IP地址区分国内国外)
15. Website Statistics
12 磁盘相关命令
软件积累 -- 截图软件ScreenToGif
WebSocket Session is null
f.grid_sample
try-catch中含return
【C语言基础】解决C语言error: expected ‘;‘, ‘,‘ or ‘)‘ before ‘&‘ token
IDEA 注释报红解决
Office automation case: how to automatically generate period data?
How to build a private yum source
一份高质量的测试用例如何养成?
Mysql 45讲学习笔记(二十三)MYSQL怎么保证数据不丢
Is interprofessional examination difficult?Low success rate of "going ashore"?Please accept this practical guide!
10. Redis implements likes (Set) and obtains the total number of likes
Number 16, top posts
共模电感的仿真应用来了,满满的干货送给大家