本文主要是用mesh實現簡單的地形。暫時先繪制三種地形:高山、平原、水域。
首先要做的是網格的細化:
上一篇已經實現了單個六邊形的繪制,實現方式是將六邊形分割成6個等邊三角形,然后分別繪制。
現在需要將每個三角形再次細化,將一個三角形細化為4個小三角形。
如下圖:
?
細化原理如下圖:
?
在上一篇文章中,封裝了三角形繪制的函數:
?
private void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3)? ?? ???{? ?? ?? ?? ?? ? int count = triangles.Count;? ?? ?? ?? ?? ? vertices.Add(v1);? ?? ?? ?? ?? ? triangles.Add(count++);? ?? ?? ?? ?? ? vertices.Add(v2);? ?? ?? ?? ?? ? triangles.Add(count++);? ?? ?? ?? ?? ? vertices.Add(v3);? ?? ?? ?? ?? ? triangles.Add(count++);? ?? ???}
復制代碼
其中v1,v2,v3如圖所示,而v4,v5,v6分別為三條邊的中點。
所以有:
Vector3 v4 = Vector3.Lerp(v1, v2, 0.5f);
Vector3 v5 = Vector3.Lerp(v2, v3, 0.5f);
Vector3 v6 = Vector3.Lerp(v1, v3, 0.5f);
所以新的4個三角形分別為:(v1, v4, v6)(v4, v2, v5)(v4, v5, v6)(v3, v6, v5)
于是,新寫一個遞歸函數用于細化(原函數保留):
?
? ?? ?? ?private void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3, int time)? ?? ???{? ?? ?? ?? ?? ? if (time == 0)? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(v1, v2, v3);? ?? ?? ?? ?? ? }? ?? ?? ?? ?? ? else? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?time--;? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v4 = Vector3.Lerp(v1, v2, 0.5f);? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v5 = Vector3.Lerp(v2, v3, 0.5f);? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v6 = Vector3.Lerp(v1, v3, 0.5f);? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(v1, v4, v6, time);? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(v4, v2, v5, time);? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(v4, v5, v6, time);? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(v3, v6, v5, time);? ?? ?? ?? ?? ? }? ?? ???}? ?? ???/// <summary>? ?? ???/// 繪制地形? ?? ???/// </summary>? ?? ???public void Draw(HexTerrian type)? ?? ???{? ?? ?? ?? ?? ? ……? ?? ?? ?? ?? ? for (HexDirection dir = HexDirection.NE; dir <= HexDirection.NW; dir++)? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v1 = HexMetrics.corners[(int)dir];? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v2 = HexMetrics.corners[(int)dir + 1];? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(center, v1, v2, 2);? ?? ?? ?? ?? ? }? ?? ?? ?? ?? ? UpdateMesh();? ?? ???}
復制代碼
其中time指的是細化次數。
?
但是,如今生成的圖片還是一個六邊形,與原來沒有變化。原因是,沒有對圖片上的點做擾動處理,所以雖然生成的時候是細化了生成的,但拼在一起還是平的。
現加入擾動處理的函數:
?
private Vector3 Perturb(Vector3 pos)? ?? ???{? ?? ?? ?? ?? ? float level = 0.5f;? ?? ?? ?? ?? ? Vector3 localPos = transform.localPosition + pos;? ?? ?? ?? ?? ? pos.x += level * (Mathf.PerlinNoise(localPos.x, localPos.z) - 0.5f);? ?? ?? ?? ?? ? pos.y += level * (Mathf.PerlinNoise(localPos.x + 1f, localPos.z + 1f) - 0.5f);? ?? ?? ?? ?? ? pos.z += level * (Mathf.PerlinNoise(localPos.x + 2f, localPos.z + 2f) - 0.5f);? ?? ?? ?? ?? ? return pos;? ?? ???}
復制代碼
這里用的是unity自帶的柏林噪聲函數 Mathf.PerlinNoise(float x,float y),這個算法會根據x以及y的值生成一個隨機的函數,固定的x和y,生成的隨機值是固定的。所以暫時先用這個做擾動。
Mathf.PerlinNoise(float x,float y)得出的是0到1的一個值。
所以減去0.5。得到的是-0.5到0.5的一個值。
level是一個擾動的參數,擾動后,單個坐標最多偏移0.25個單位,這樣顯示出來的就是一個有輕微褶皺的地形圖片。
將在AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3, int time)函數中繪制三角形的部分加入擾動:
?
? ?? ?? ?private void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3, int time)? ?? ???{? ?? ?? ?? ?? ? if (time == 0)? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(Perturb(v1), Perturb(v2), Perturb(v3));? ?? ?? ?? ?? ? }? ?? ?? ?? ?? ? ………………? ?? ???}
復制代碼
運行后,我們得到如下的圖:
?
然后再調一下材質的顏色,本游戲采用的是lowpoly風格,該風格的反射很弱,所以需要把材質上的specular hightlight的鉤去掉,然后再調整一下圖片的顏色:
?
有一點泥土的感覺了,暫時先這么用著吧。
下一步要解決的問題是,目前只是一個單面的六邊形,將這個六邊形繪制成一個棱柱,表現會更好一些,當然,棱柱的底就不畫了,反正看不到。
首先寫一個函數用于繪制一個矩形:
?
/// <summary>? ?? ???/// v3??v4? ?? ???///? ?? ???/// v1??v2? ?? ???/// </summary>? ?? ???/// <param name="v1"></param>? ?? ???/// <param name="v2"></param>? ?? ???/// <param name="v3"></param>? ?? ???/// <param name="v4"></param>? ?? ???private void AddSquare(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)? ?? ???{? ?? ?? ?? ?? ? AddTriangle(v1, v3, v2);? ?? ?? ?? ?? ? AddTriangle(v3, v4, v2);? ?? ???}
復制代碼
v1,v2,v3,v4的位置備注所示。
如果不考慮細化,那么繪制邊上的面的函數就可以如下表示:
?
/// <summary>? ?? ???/// 繪制地形? ?? ???/// </summary>? ?? ???public void Draw(HexTerrian type)? ?? ???{? ?? ?? ?? ?? ? ……………………? ?? ?? ?? ?? ? for (HexDirection dir = HexDirection.NE; dir <= HexDirection.NW; dir++)? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v1 = HexMetrics.corners[(int)dir];? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v2 = HexMetrics.corners[(int)dir + 1];? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(center, v1, v2, 2);? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v3 = v1 + 5f * Vector3.down;? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v4 = v2 + 5f * Vector3.down;? ?? ?? ?? ?? ?? ?? ?? ?AddSquare(v1, v2, v3, v4);? ?? ?? ?? ?? ? }? ?? ?? ?? ?? ? ……………………? ?? ???}
復制代碼
接下來考慮分形的做法,先看下示意圖:
?
然后是代碼:
?
private void AddEdge(Vector3 v1, Vector3 v2, int time)? ?? ???{? ?? ?? ?? ?? ? if (time == 0)? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?v1 = Perturb(v1);? ?? ?? ?? ?? ?? ?? ?? ?v2 = Perturb(v2);? ?? ?? ?? ?? ?? ?? ?? ?float d = 5f;? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v3 = v1 + d * Vector3.down;? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v4 = v2 + d * Vector3.down;? ?? ?? ?? ?? ?? ?? ?? ?AddSquare(v1, v2, v3, v4);? ?? ?? ?? ?? ? }? ?? ?? ?? ?? ? else? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?time--;? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v5 = Vector3.Lerp(v1, v2, 0.5f);? ?? ?? ?? ?? ?? ?? ?? ?AddEdge(v1, v5, time);? ?? ?? ?? ?? ?? ?? ?? ?AddEdge(v5, v2, time);? ?? ?? ?? ?? ? }? ?? ???}
復制代碼
因為v3,v4是通過v1,v2計算得來的,所以輸入就只需要v1,v2就行了。
因為點的擾動是最后計算的,所以會和六邊形的邊重合,手游賬號買賣不會出現錯位。
效果如下圖:
接下來是河流和高山地塊。
先定義一個地形類型的枚舉:
?
public enum HexTerrian{? ?? ???Water,? ?? ???Plain,? ?? ???Mountain,}
復制代碼
然后在HexCell類中加入地形類型,用于表示當前地形的類型,同時定義一個材質的數組用于保存不同地形對應的材質,并在繪制函數中加入地形參數:
?
public class HexCell : MonoBehaviour {? ?? ???……? ?? ???public HexTerrian terr;? ?? ???public Material[] materials;? ?? ???……? ?? ???public void Draw(HexTerrian type)? ?? ???{? ?? ?? ?? ?? ? terrianType = type;? ?? ?? ?? ?? ? GetComponent<Renderer>().material = materials[(int)type];? ?? ???……}
復制代碼
將做好的3個地形材質拖到數組上,記得和枚舉一一對應。
先處理湖泊地形,湖泊和平原相比,只在于水平面將會比平原低一點:
?
………? ?? ???private readonly float deep = 5;//方塊厚度? ?? ???private readonly float waterLevel = 2;//水平面離地距離? ?? ???public int fractalTime = 3;//細化次數………? ?? ?? ?? ?? ? for (HexDirection dir = HexDirection.NE; dir <= HexDirection.NW; dir++)? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v1 = HexMetrics.corners[(int)dir];? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v2 = HexMetrics.corners[(int)dir + 1];? ?? ?? ?? ?? ?? ?? ?? ?switch (type)? ?? ?? ?? ?? ?? ?? ?? ?{? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case HexTerrian.Water:? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? if (dir == HexDirection.NE)? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?center -= waterLevel * Vector3.up;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? }? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? v1 -= waterLevel * Vector3.up;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? v2 -= waterLevel * Vector3.up;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? break;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case HexTerrian.Plain:? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? break;? ?? ?? ?? ?? ?? ?? ?? ?}? ?? ?? ?? ?? ?? ?? ?? ?AddTriangle(center, v1, v2, fractalTime);? ?? ?? ?? ?? ?? ?? ?? ?AddEdge(v1, v2, fractalTime);? ?? ?? ?? ?? ? }……
復制代碼
邊緣六個面繪制的時候也要記得短一些:
?
private void AddEdge(Vector3 v1, Vector3 v2, int time)? ?? ???{? ?? ?? ?? ?? ? if (time == 0)? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?v1 = Perturb(v1);? ?? ?? ?? ?? ?? ?? ?? ?v2 = Perturb(v2);? ?? ?? ?? ?? ?? ?? ?? ?float d = (terrianType == HexTerrian.Water) ? (deep - waterLevel) : deep;? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v3 = v1 + d * Vector3.down;? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v4 = v2 + d * Vector3.down;? ?? ?? ?? ?? ?? ?? ?? ?AddSquare(v1, v2, v3, v4);? ?? ?? ?? ?? ? }? ?? ?? ?? ?? ? ……? ?? ???}
復制代碼
接下來繪制高山,對于高山,先簡單處理一下:將頂點提高一些,然后細化一下。
?
……case HexTerrian.Mountain:if (dir == HexDirection.NE){? ?? ???center += 5f * Vector3.up;}break;……
復制代碼
然后測試一下地形效果,隨機地形在這篇文章就不做了,主要是把所有的地形都顯示出來,所以隨便寫一下吧:
? ?? ?? ?? ?? ? foreach (HexCell c in hexCells)? ?? ?? ?? ?? ? {? ?? ?? ?? ?? ?? ?? ?? ?int height = (c.Pos.x + c.Pos.y) % 3;? ?? ?? ?? ?? ?? ?? ?? ?c.Draw((HexTerrian)height);? ?? ?? ?? ?? ? }
復制代碼
于是,得到了封面上的效果圖:
?
與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
以上是生活随笔為你收集整理的从零开始做一个SLG游戏(二):用mesh实现简单的地形的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。