程序自動生成地圖

簡介

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進行疊加,得到了一個比較有高低起伏,又有細節的波形。如果是二維的噪聲,得到的就是一張二維的噪聲圖,主要的代碼如下


[csharp] view plain copy
  1. for(int i = 0; i< octaves; i++)  
  2.  {  
  3. float sampleX = x / scale * frequency;  
  4.     float sampleY = y / scale * frequency;  
  5.   
  6.     float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;  
  7.     noiseHeight += perlinValue * amplitude;  
  8.   
  9.      amplitude *= persistance;  
  10.      frequency *= lacunarity;  
  11. }  



下圖是疊加3個octave的結果


3D地形生成

3D地形的生成主要是生成mesh,在Unity3D中生成一個三角形的代碼方法如下
[csharp] view plain copy
  1. viod GenerateTriangle  
  2. {  
  3.      mesh = new Mesh();  
  4.         uvs = new Vector2[3];  
  5.   
  6.         colors = new Color[3];  
  7.         colors[0] = Color.red;  
  8.         colors[1] = Color.green;  
  9.         colors[2] = Color.blue;  
  10.   
  11.         Vector3[] vertices = new Vector3[3];  
  12.         vertices[0] = new Vector3(-1, 0, 0);  
  13.         vertices[1] = new Vector3(0, 1.732f, 0);  
  14.         vertices[2] = new Vector3(1, 0, 0);  
  15.         mesh.vertices = vertices;  
  16.   
  17.         Vector3[] normals = new Vector3[3];  
  18.         normals[0] = Vector3.back;  
  19.         normals[1] = Vector3.back;  
  20.         normals[2] = Vector3.back;  
  21.   
  22.         uvs[0] = new Vector2(0, 0);  
  23.         uvs[1] = new Vector2(0, 1);  
  24.         uvs[2] = new Vector2(1, 1);  
  25.   
  26.         int[] triangles = new int[3] { 0, 1, 2 };  
  27.         mesh.triangles = triangles;  
  28.         mesh.normals = normals;  
  29.         mesh.uv = uvs;  
  30.         mesh.colors = colors;  
  31.         GetComponent<MeshFilter>().mesh = mesh;  
  32. }  




得到的結果如下



從上面例子可以得出創建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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章