您的位置:首页 > 移动开发 > Unity3D

Unity2D - 6. 生成随机游戏地图 (1)

2018-03-29 15:46 716 查看
打算生成半随机rougelike游戏地图(即提前制作好大量的指定尺寸的房间,从中随机选择),先要有一种办法来生成随机尺寸的房间。

一开始考虑在矩形区域内随机生成不重叠的小矩形,然后……要如何生成不交叉的通路呢?

参考了网上一篇文章

https://www.cnblogs.com/Swallowtail/p/6181441.html 蝶之羽风暂留此 - 《unity3d随机地牢生成代码》

使用1000次for循环,作为以力破巧的方式(其实运算时间大头都在加载资源上),先使用python做了验证,主要思想是:

1. 地图中央生成第一个房间

2. 随机选择已有的一个房间,生成连到这个房间的一条走廊和另一个房间

3. 房间数到达预期或循环次数满时结束

from PIL import Image, ImageDraw
from random import randint, choice
import numpy as np

# 房间尺寸
room_size = [
[13, 13],
[15, 21],
[17, 17],
[17, 23],
[17, 27],
[23, 23],
[27, 27]
]

# 走廊参数
corridor_width = 5
corridor_offset = 2
min_corridor_len = 5
max_corridor_len = 20

# 瓦片类型
BLANK = 0
WALL = 1
ROOM = 2
CORRIDOR = 3

# 方向
LEFT = 0
RIGHT = 1
UP = 2
DOWN = 3
DIR = [LEFT, RIGHT, UP, DOWN]
LR = 0
UD = 1

class Dungeon():
def __init__(self, length:int, width:int, nrooms:int):
self.map_length = length
self.map_width = width
self.space = np.zeros((length, width), np.int)
self.rooms = list()
self.nrooms = nrooms
self.min_x = width
self.max_x = 0
self.min_y = length
self.max_y = 0

def in_space_x(self, x:int):
return 0 <= x < self.map_width

def in_space_y(self, y:int):
return 0 <= y < self.map_length

def set_cells(self, left:int, right:int, up:int, down:int, type:int):
for y in range(up, down+1):
for x in range(left, right+1):
self.space[y][x] = type

def area_unused(self, left:int, right:int, up:int, down:int):
tsum = 0
for y in range(up, down+1):
tsum += sum(self.space[y][left:right])

return True if tsum == 0 else False

def can_create(self, left:int, right:int, up:int, down:int):
return self.in_space_x(left) and self.in_space_x(right) \
and self.in_space_y(up) and self.in_space_y(down) \
and self.area_unused(left, right, up, down)

def new_room(self, left:int, right:int, up:int, down:int):
self.set_cells(left, right, up, down, WALL)
self.set_cells(left+1, right-1, up+1, down-1, ROOM)
self.rooms.append([left, right, up, down, 0])
if left < self.min_x:
self.min_x = left
if right > self.max_x:
self.max_x = right
if up < self.min_y:
self.min_y = up
if down > self.max_y:
self.max_y = down

def new_corridor(self, left:int, right:int, up:int, down:int, type:int):
self.set_cells(left, right, up, down, WALL)
if type == LR:
self.set_cells(left, right, up+1, down-1, CORRIDOR)
if type == UD:
self.set_cells(left+1, right-1, up, down, CORRIDOR)

def new_room_and_corridor(self):
length, width = choice(room_size)

# 第一个房间
if not len(self.rooms):
left = (self.map_width - width) // 2
right = left + width - 1
up = (self.map_length - length) // 2
down = up + length - 1
self.new_room(left, right, up, down)

else:
corridor_length = randint(min_corridor_len, max_corridor_len)
base_room = choice(self.rooms)
direction = choice(DIR)
c_direction = LR
if direction in [LEFT,
4000
RIGHT]:
if direction == LEFT:
c_right = base_room[LEFT] - 1
c_left = base_room[LEFT] - corridor_length + 2
right = c_left - 1
left = right - width + 1
else:
c_left = base_room[RIGHT] + 1
c_right = base_room[RIGHT] + corridor_length - 2
left = c_right + 1
right = left + width - 1

c_up = randint(base_room[UP] + corridor_offset,
base_room[DOWN] - corridor_offset - corridor_width)
c_down = c_up + corridor_width - 1
up = randint(c_up - length + 1 + corridor_offset + corridor_width,
c_up - corridor_offset)
down = up + length - 1

elif direction in [UP, DOWN]:
c_direction = UD
if direction == UP:
c_down = base_room[UP] - 1
c_up = base_room[UP] - corridor_length + 2
down = c_up - 1
up = down - length + 1
else:
c_up = base_room[DOWN] + 1
c_down = base_room[DOWN] + corridor_length - 2
up = c_down + 1
down = up + length - 1

c_left = randint(base_room[LEFT] + corridor_offset,
base_room[RIGHT] - corridor_offset - corridor_width)
c_right = c_left + corridor_width - 1
left = randint(c_left - width + 1 + corridor_offset + corridor_width,
c_left - corridor_offset)
right = left + width - 1

if self.can_create(c_left, c_right, c_up, c_down) \
and self.can_create(left, right, up, down):
self.new_corridor(c_left, c_right, c_up, c_down, c_direction)
self.new_room(left, right, up, down)

def generate(self, steps:int):
for i in range(steps):
self.new_room_and_corridor()
if len(self.rooms) == self.nrooms:
break

def output(self):
im = Image.new('L', (self.map_width, self.map_length), 0)
draw = ImageDraw.Draw(im)
for y in range(self.map_length):
for x in range(self.map_width):
pixel = self.space[y][x]
if pixel:
draw.point((x, y), int(255/6*pixel))

# im.show()
im.save('E:\\Desktop\\test.png')

if __name__ == '__main__':
dungeon = Dungeon(300, 300, 20)
dungeon.generate(1000)
dungeon.output()


输出图像如下



从图像中,感觉和预想中的游戏地图生成效果已经差不多了,但是还需要考虑以下问题:

* 指定每张地图上有一个开始房间

* 指定每张地图上有一个BOSS房间

* 指定每张地图上有一个商店房间

* 指定每张地图上有一到两个宝藏房间

* 因为每个房间只能有一条路与其他房间连通(平均),所以BOSS房间只能与一个房间连通,不然BOSS房间会阻断通往其他房间的道路,而BOSS房间清理BOSS后出现通往下一层的通道

* 开始房间不能和BOSS房间连通

为简化操作,可以将BOSS房间、商店房间、宝藏房间归类为特殊房间,指定为只能与一个房间连通的房间

开始房间为比较居中的一个房间(可以直接指定为第一个生成的房间)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息