简介
PCG是程序生成游戏内容的简称,它使用了随机或者伪随机数的技术,给游戏带来了无限的可能。相比于传统的由设计师将游戏世界中的一草一木都精心配制,PCG的方法是去配置一些生成的规则,然后由生成算法自动去生成游戏世界。
在过去的时候,由于游戏主机和PC性能的限制,PCG的内容非常的简单,比如随机地牢或者游戏的地图,但近年来随着sandbox品类的游戏的兴起,比如风靡全球的Minecraft,PCG能够发挥的作用越来越大。
下面就以程序生成地牢和程序生成地形为例,讲述一些PCG的一些基本方法。
程序生成地牢
Rogue最早使用程序生成技术的游戏之一,它最大的特性就是动态程序生成,这让玩家每次玩游戏都有着完全不一样的体验。由这个游戏诞生出了一种以程序生成技术为代表的游戏种类,称之为“rougelike”。
那么如果去由程序生成一个地牢呢?
我们首先定义出一些地牢中的一些基本组件,它们有:
房间:它有一个或者多个出口;
走廊:它是一个很窄很长的区域,拥有两个出口,它也可能是一个斜坡;
连接处:是一个有着三个以上出口的小的空间;
下面是几个简单的组件模型,在每个组件的出口,都有一个mark,标记了位置和旋转量,用于组件的匹配.
现在只要将它们遵循一定的规则拼接在一起,就可以生成地牢了,这些规则有:
1. 房间只能链接走廊;
2. 走廊链接房间或者连接处;
3. 连接处只能连接走廊
接下来是生成算法的详细描述
1. 初始化一个启示模块(可以选择有最多出口数的模块);
2. 对每个未连接的出口生成一个符合条件的模块;
3. 重新建立一个目前未连接的出口的列表;
4. 重复步骤2和步骤3.
下图分别是初始状态,迭代三次和迭代六次的结果
一些值得探讨的问题
上面的随机算法只是生成了地牢的框架,还需要生成很多其他的地牢要素,比如在地上可以打碎的罐子,墙上忽明忽暗的火炬,还有突然从身后窜出来的史莱姆。这些要素的生成的方法和前面的大同小异,简单的说就是在组件的地面或者墙上标记上一些mark点,在这些mark点上随机的去生成一些匹配的要素即可,下面是两个程序生成的房间的例子。
程序生成地形
许多开放世界游戏内容通常都包含了一个生成系统,这个系统通过一个种子去生成游戏世界。随机生成系统通常都会随机生成一些地形和生态,然后基于这些去分布资源生物等,这方面的代表作当属今年8月即将发售的游戏No Man’s Sky,该游戏中通常包含了数以亿计程序生成的星球,每个星球上都有着不同的植物系统,生物系统,甚至还有长相不同的外星人。
下面就简述一下程序生成地形的技术,主要包括两个部分,一个是高度图的生成,之后是Mesh的生成。
通过噪声生成高度图
对于一个一维Coherent noise,对于每一个x值都有一个y值与其对应,如下图,
如果用这个曲线来表示地形的话,由于曲线过于平滑,没有细节,就显得很不真实,这里的做法是通过将多个波形叠加,来得出一个比较真实的曲线,每一个叠加的波形都称之为octave。
f(x) = noise(p) + Persistence ^ 1 * noise(Lacunarity ^1 * p) + Persistence ^ 2 * noise(Lacunarity ^2 * p) + ...
最终的曲线主要由两个参数控制,一个是Lacunarity表示频率的增加量,另一个是Persistence表示增幅的减小量,每个octave的波形都有着不同的意义,如下图所示
通过将多个Octave进行叠加,得到了一个比较有高低起伏,又有细节的波形。如果是二维的噪声,得到的就是一张二维的噪声图,主要的代码如下
for(int i = 0; i< octaves; i++)
{
float sampleX = x / scale * frequency;
float sampleY = y / scale * frequency;
float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
noiseHeight += perlinValue * amplitude;
amplitude *= persistance;
frequency *= lacunarity;
}
下图是叠加3个octave的结果
3D地形生成
3D地形的生成主要是生成mesh,在Unity3D中生成一个三角形的代码方法如下
viod GenerateTriangle
{
mesh = new Mesh();
uvs = new Vector2[3];
colors = new Color[3];
colors[0] = Color.red;
colors[1] = Color.green;
colors[2] = Color.blue;
Vector3[] vertices = new Vector3[3];
vertices[0] = new Vector3(-1, 0, 0);
vertices[1] = new Vector3(0, 1.732f, 0);
vertices[2] = new Vector3(1, 0, 0);
mesh.vertices = vertices;
Vector3[] normals = new Vector3[3];
normals[0] = Vector3.back;
normals[1] = Vector3.back;
normals[2] = Vector3.back;
uvs[0] = new Vector2(0, 0);
uvs[1] = new Vector2(0, 1);
uvs[2] = new Vector2(1, 1);
int[] triangles = new int[3] { 0, 1, 2 };
mesh.triangles = triangles;
mesh.normals = normals;
mesh.uv = uvs;
mesh.colors = colors;
GetComponent().mesh = mesh;
}
得到的结果如下
从上面例子可以得出创建mesh的过程就是生成一系列三角形的过程,而每个三角形都包含了每个顶点的坐标,顶点顺序,法线,uv坐标,顶点颜色。
现在每一个像素点就是一个三角形的顶点,那么需要生成的顶点的个数为,v = w * h,三角形数量 t = (w-1)(h-1) * 2个,每个顶点间的距离为1,则可以生成一个w*h的平面。
接下来,可以将噪声的值对应到网格顶点的y值,同时乘以一个放大系数,
接下来尝试为顶点着色,可以直接根据噪声值来对应顶点的颜色,这里设定值低于0.3的为深海,0.3-0.4之间的为浅水,0.4-0.45为沙地,0.45-0.55为草坪,0.55-0.6为深色的草坪,0.6-0.7为浅色岩石,0.7-0.9为深色岩石, 0.9-1.0为山顶,每一种区域都对应不同的颜色下面是给顶点加上颜色之后同时计算了三角形的normal之后的结果
一些值得探讨的问题
上面所描述的只是最基本的地形生成过程,通常随机地形的生成还包括很多需要去解决的问题,比如无缝大地形,地形的LOD,多线程生成优化等,下图是通过LOD减少Mesh中三角形数量。
LOD = 0
LOD=4
LOD=8
在更加复杂的生成系统中,还需要去生成洞穴,植被,生物群落等内容,这些高级内容不论是对生成算法还是对系统的架构都有着很大的技术挑战。
小结
本文简单的介绍了两种游戏中的程序生成技术,然而PCG可以做的更多,比如程序生成的AI,程序生成的音效等等。如果将PCG与其他的游戏要素进行融合,将会得到更多的可能性,但同时也带来了很大的技术和美术上的挑战。
参考
A Real-Time Procedural Universe, Part One: Generating Planetary Bodies
Procedural generation Wiki