《DFQ》开发随录——随机地图

  欢迎参与讨论,转载请注明出处。

前言

  虽然先前未曾严明,但《DFQ》的全称为《DungeonFighterQuest》,由字面上便可得出,这是一款《DNF》的同人游戏,那么《DFQ》的地图自然向《DNF》看齐了。而《DNF》的地图众所周知,具有一定的复杂度,在以往的作品开发过程中便是采取了手动制作的方式,可谓十分的费时费力。于是在《DFQ》便采用了生成随机地图的方式,与市面上许多独立游戏的做法不谋而合,毕竟手动做地图实在是太辛苦了(汗)。本文便记录其中心得。

地图结构

map

  如上图所示,这便是一张随机生成的地图,它拥有以下组成:

  • 远景层:地图最底的背景,图中表现为山水。
  • 近景层:地图较近的背景,图中表现为树林。
  • 边上层:地图的上边界,拥有若干地图物件。
  • 地表层:地图的地板,图中表现为草地。
  • 边下层:地图的下边界,拥有若干地图物件。
  • 活动层:地图的主体,拥有若干活动的地图物件。

  在配置中以这种形式组成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
return {
info = {
width = {1440, 1280, 1024}, -- 宽度随机选择
height = {600, 736}, -- 高度随机选择
theme = "lorien", -- 地图主体
type = "dungeon", -- 地图类型
bgm = "lorien", -- 背景音乐
bgs = "forest1", -- 背景音效
name = {
cn = "洛兰",
kr = "로리엔",
jp = "ロリエン",
en = "Lorien"
} -- 用于显示的地图名称,拥有中日韩英四语
},
floorHorizon = 327, -- 地表层起始Y坐标
scope = {
x = 16,
y = 368
}, -- 可行走区域起始坐标
far = "$A/far", -- 远景层
near = "$A/near", -- 近景层
floor = {
left = "$A/tile/0",
middle = "$A/tile/2",
right = "$A/tile/1",
bottom = "$A/tile/3"
}, -- 地表层
sprite = { -- 图片
up = {
"$A/flower/0"
...
}, -- 边上层
floor = {
"$A/grass/0",
...
} -- 地表层物件
},
actor = { -- 活动对象
down = {
"$A/tree/0",
...
}, -- 边下层
article = {
"$A/tree/1",
...
} -- 活动层
}
}

  接下来将对逐层进行分析。

远/近景层

  远景层与近景层的机制完全一致,所以可以拿来一起说明。当然之所以会分为两个层次而非合并,是因为远景与近景关于摄像机移动时的相对移动速度不一样,以此形成纵深感。但在地图生成这一块,它们的机制是一致的:

1
2
far = "$A/far", -- 远景层
near = "$A/near", -- 近景层

  它们都是加载一张图片,然后根据地图的宽度进行平铺操作即可。在最后阶段会渲染成一张成品长图,这是一种优化方法。

边上层

  边上层为地图的上边界,拥有若干地图物件。这里的地图物件与其他层的并不一样,在配置里它的划分是sprite,仅仅是单纯的图片罢了:

1
2
3
4
5
6
sprite = { -- 图片
up = {
"$A/flower/0"
...
}, -- 边上层
}

  因为这些物件不需要与角色产生什么互动,最后也会如远/近景层一般,渲染成大块的成图。
  关于物件的放置,会采取生成宽高为100的格子铺满整行,并随机在这些格子上放置物件,如下图所示:
up

边下层

  边下层与边上层类似,但是生成的地图物件为活动对象(actor):

1
2
3
4
5
6
actor = { -- 活动对象
down = {
"$A/tree/0",
...
}, -- 边下层
}

  边下层的地图物件需要作为活动对象主要是因为某些物件会遮挡人物,所以需要采取靠近后透明化的措施。于是不方便作为单纯的图片。
  物件放置方面与边上层一致,这里不再复述,如下图所示:
down

地表层

  地表层即地图的地板,远/近景层类似,也是采取平铺的方针。但是在元素上更为多样:

1
2
3
4
5
6
floor = {
left = "$A/tile/0",
middle = "$A/tile/2",
right = "$A/tile/1",
bottom = "$A/tile/3"
}, -- 地表层

  地表层的图片分为左中右下四种,左右两种为于地图边缘进行随机选择(左/右或中),中为默认选择,下为平铺Y方向。
  除此之外,地表层还会拥有一些类似边上层的地图物件:

1
2
3
4
5
6
sprite = { -- 图片
floor = {
"$A/grass/0",
...
} -- 地表层物件
},

  这些物件也是不会与人物有所交互,最终与整个地表层渲染成大图。与边上/边下层类似,地表层物件的放置会XY平铺宽高为64的格子,以此放置:
floor

活动层

  活动层即地图的主体,活动对象的放置层,诸如障碍、宝箱、怪物等皆置于此。放置的规则与地表层物件一致,与地表层物件的不同之处在于,活动层存在一些拥有障碍的物件:
obstacle

  如上图所示,《DFQ》采用的障碍方式为传统的格子流,这种形式便于配合类似A星的寻路算法。但如此存在障碍格子与物件素材的匹配问题,这方面都需要手动设置好。以及需要警惕因障碍范围过大且恰好四周都是障碍物件围住了人物的情况,好在实际上并不存在这样的物件(障碍并不会很大),并不需要为此做特殊措施。

随机问题

  在处理诸如物件放置的问题时,切忌采用遍历+随机数判断的形式。因为这是不符合概率论的(存在放置数量上限),如此便会导致地图左边的元素多于右边(右边存在轮不到的可能)。所以得采取将格子存储在一个list中,以list[math.random(1, #list)]的方式提取要放置的格子,如此即可保证几率均等了。

后记

  对于一些需要个性添加的元素(地图特效、通行门、BOSS),一般会采取编写专门的处理函数进行添加。对于一些需要固定化的地图,也可以采取生成后输出成文件以加载使用。目前这套很明显的缺点在于无法生成崎岖不一的地形,不过目前暂无需求,且日后再看吧。