從零開始做一個SLG遊戲(三):用unity繪製圖形

遊資網發表於2019-10-11
從零開始做一個SLG遊戲(三):用unity繪製圖形

本文主要是使用mesh製作一些簡單的模型資源。

一般而言,模型的製作最好還是使用專業的軟體來做,但是製作一些簡單的模型,unity還是可以勝任的。

unity自帶的模型只有立方體,圓柱,球,膠囊,方塊等有限的幾個,所以稍微複雜一些的東西就不好做了,比如最常用到的圓錐,稜柱,稜臺等,十分困難。

所以首先要做的是,將一些常用的基本模型,實現出來。

首先要做的是寫一個模型基類:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshCollider))]
  5. public abstract class BasePoly : MonoBehaviour {
  6.         Mesh mesh;
  7.         private List<Vector3> vertices;
  8.         private List<int> triangles;
  9.         private List<Vector2> uvs;
  10.         private float length = 100f;
  11.         private void Awake()
  12.         {
  13.                 mesh = GetComponent<MeshCollider>().sharedMesh = GetComponent<MeshFilter>().mesh = new Mesh();
  14.                 mesh.name = "PolyMesh";
  15.                 vertices = new List<Vector3>();
  16.                 triangles = new List<int>();
  17.                 uvs = new List<Vector2>();
  18.         }
  19.        
  20.         // Update is called once per frame
  21.         void Update ()
  22.         {
  23.                 DrawMesh();
  24.         }
  25.         private void DrawMesh()
  26.         {
  27.                 Clear();
  28.                 Draw();
  29.                 UpdateMesh();
  30.         }
  31.         protected void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3)
  32.         {
  33.                 AddConer(v1);
  34.                 AddConer(v2);
  35.                 AddConer(v3);
  36.         }

  37.         protected void AddSquare(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
  38.         {
  39.                 AddTriangle(v1, v3, v2);
  40.                 AddTriangle(v2, v3, v4);
  41.         }

  42.         private void AddConer(Vector3 point)
  43.         {
  44.                 int count = vertices.Count;
  45.                 vertices.Add(point);
  46.                 triangles.Add(count);
  47.                 uvs.Add(new Vector2(point.x / (2 * length), point.z / (2 * length)));
  48.         }

  49.         private void UpdateMesh()
  50.         {
  51.                 mesh.vertices = vertices.ToArray();
  52.                 mesh.triangles = triangles.ToArray();
  53.                 mesh.uv = uvs.ToArray();
  54.                 mesh.RecalculateNormals();
  55.                 mesh.RecalculateBounds();
  56.         }

  57.         private void Clear()
  58.         {
  59.                 mesh.Clear();
  60.                 vertices.Clear();
  61.                 triangles.Clear();
  62.                 uvs.Clear();
  63.         }

  64.         public virtual void Draw()
  65.         {

  66.         }
  67. }
複製程式碼


具體實現的原理,在上一篇文章裡大部分都有講過。現在將之封裝了起來。

在子類中,重寫Draw()函式,呼叫下面兩個函式即可繪製各種想要的圖形。

AddTriangle(Vector3v1,Vector3 v2,Vector3 v3);

AddSquare(Vector3 v1,Vector3 v2,Vector3 v3,Vector3 v4);

首先實現一下正多邊形:

從零開始做一個SLG遊戲(三):用unity繪製圖形

正多邊形其實和前面六邊形的製作方式類似,不過我們需要手動計算出正多邊形的各個頂點。

將正n邊形的各個頂點和中心點連線,可以得到n個等腰三角形,而等腰三角形頂角的大小為(360°/n)。

所以,如果知道一個頂點p的座標,同時又知道原點座標(0,0)的話,那下一個頂點p'的座標可以通過p圍繞原點旋轉(360°/n)的角度獲得。

那麼我們複習一下高中的空間幾何知識:

一個點圍繞原點逆時針旋轉θ角的時候,我們可以通過旋轉變換來獲得旋轉後的座標。

旋轉變換的矩陣為:

從零開始做一個SLG遊戲(三):用unity繪製圖形

具體用法為:

對於(x,y)進行旋轉變換後,得到的(x',y')有

x'=x*cosθ-y*sinθ

y'=x*sinθ+y*cosθ

所以寫一個函式來實現旋轉變換:

  1. Vector3 RotationTranslate(Vector3 pos,float angle)

  2.         {

  3.                 Vector3 Pos = pos;

  4.                 float[,] transRect = {

  5.                         { Mathf.Cos(angle),-Mathf.Sin(angle)},

  6.                         { Mathf.Sin(angle),Mathf.Cos(angle)},

  7.                 };

  8.                 Pos.x = pos.x * transRect[0, 0] + pos.z * transRect[0, 1];

  9.                 Pos.z = pos.x * transRect[1, 0] + pos.z * transRect[1, 1];

  10.                 return Pos;

  11.         }
複製程式碼

angle為旋轉的角度。

正多邊形的各個點都有了,然後通過畫三角的方式,將正多邊形畫出來了。

程式碼如下:

  1. public class Polygon : BasePoly {
  2.         public int edgeCount = 3;
  3.         private float angle = 0f;

  4.         private List<Vector3> coners = new List<Vector3>();

  5.         public override void Draw()
  6.         {
  7.                 angle = 2f * Mathf.PI / count;
  8.                 Vector3 pos = new Vector3(1f, 0f, 0f);
  9.                 coners.Add(pos);
  10.                 for (int i = 0; i < count; i++)
  11.                 {
  12.                         pos = RotationTranslate(pos);
  13.                         coners.Add(pos);
  14.                 }
  15.                 for (int j = 0; j < count; j++)
  16.                 {
  17.                         AddTriangle(coners[j], Vector3.zero, coners[j + 1]);
  18.                 }
  19.                 coners.Clear();
  20.         }
  21.         Vector3 RotationTranslate(Vector3 pos)
  22.         {
  23.                 Vector3 Pos = pos;
  24.                 float[,] transRect = {
  25.                         { Mathf.Cos(angle),-Mathf.Sin(angle)},
  26.                         { Mathf.Sin(angle),Mathf.Cos(angle)},
  27.                 };
  28.                 Pos.x = pos.x * transRect[0, 0] + pos.z * transRect[0, 1];
  29.                 Pos.z = pos.x * transRect[1, 0] + pos.z * transRect[1, 1];
  30.                 return Pos;
  31.         }
  32. }
複製程式碼

實現後,會發現一個問題,那就是每幀都要繪製一次,會非常消耗功能,所以我們需要加一個函式,用於判斷是否需要再繪製一遍:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshCollider))]
  5. public abstract class BasePoly : MonoBehaviour {
  6. ……
  7.         private void DrawMesh()
  8.         {
  9.                 if (NeedDraw())
  10.                 {
  11.                         Clear();
  12.                         Draw();
  13.                         UpdateMesh();
  14.                 }
  15.         }
  16.         public virtual bool NeedDraw()
  17.         {
  18.                 return true;
  19.         }
  20. ……
  21. }
複製程式碼

然後再在子類中重寫NeedDraw()函式:

  1. public class Polygon : BasePoly {
  2.         public int edgeCount = 3;
  3.         private int count = 0;
  4.         private float angle = 0f;

  5.         ……
  6.         public override bool NeedDraw()
  7.         {
  8.                 if (edgeCount == count)
  9.                 {
  10.                         return false;
  11.                 }
  12.                 else if (edgeCount < 3)
  13.                 {
  14.                         return false;
  15.                 }
  16.                 else
  17.                 {
  18.                         count = edgeCount;
  19.                         angle = 2f * Mathf.PI / count;
  20.                         return true;
  21.                 }
  22.         }

  23.         public override void Draw()
  24.         {
  25.                 //angle = 2f * Mathf.PI / count;
  26.                 ……
  27.         }
  28.         ……
  29. }
複製程式碼

其他圖形也可以通過類似的方法,一一繪製出來,並通過自己的賦值進行微調。

比較麻煩的一點是,這麼實現,需要先讓工程執行起來,繪製的圖形才能顯示出來。幸運的是,這並不影響搭建場景。

接下來將介紹更多的基本圖形的畫法:

1.正稜錐

稜錐的畫法其實和前文製作六邊形網格的製作方法類似,將六邊形網格的中心點往上移動若干個單位,再加上底面,就是一個六稜錐了。

處理方式和前文的正多邊形相同:按角度一次分割三角形

從零開始做一個SLG遊戲(三):用unity繪製圖形

  1. List<Vector3> conors = new List<Vector3>();
  2.         public override void Draw()
  3.         {
  4.                 Vector3 pos = new Vector3(1f, 0f, 0f);
  5.                 conors.Add(pos);
  6.                 for (int i = 0; i < count; i++)
  7.                 {
  8.                         pos = RotationTranslate(pos);
  9.                         conors.Add(pos);
  10.                 }
  11.          }
複製程式碼


和多邊形一樣,先把點加進去,接下來和多邊形不同的是,我們需要畫2個三角形,如下圖:

從零開始做一個SLG遊戲(三):用unity繪製圖形

o為底面的中心點,o1為頂點,和前文一樣,底面中心的座標預設為(0,0,0),即Vector3.zero。

o1作為頂點,暫定高為1。於是o1的座標為(0,1,0)。(0,1,0)也可以表示為Vector3.up。

需要畫的三角形為底面的一部分(o,c<i>,c[i+1])以及側面(o1,c[i+1],c<i>)。

完整程式碼如下:

  1. List<Vector3> conors = new List<Vector3>();
  2.         public override void Draw()
  3.         {
  4.                 Vector3 pos = new Vector3(1f, 0f, 0f);
  5.                 conors.Add(pos);
  6.                 for (int i = 0; i < count; i++)
  7.                 {
  8.                         pos = RotationTranslate(pos);
  9.                         conors.Add(pos);
  10.                 }
  11.                 for (int k = 0; k < count; k++)
  12.                 {
  13.                         AddTriangle(Vector3.zero, conors[k], conors[k + 1]);
  14.                         AddTriangle(conors[k], Vector3.up, conors[k + 1]);
  15.                 }
  16.                 conors.Clear();
  17.         }
複製程式碼

2.正稜臺

正稜臺和正稜錐一樣,也是分割成多個方向,單個方向如圖:

從零開始做一個SLG遊戲(三):用unity繪製圖形

需要畫的是(o1,c1[i+1],c1<i>)(o,c<i>,c[i+1])兩個三角形底,以及(c<i>,c[1+1],c1<i>,c1[i+1])這個矩形的側面。

上下兩個三角形的底是相似的,並且對應的邊也是相互平行的。

o點座標為Vector3.zero,即(0,0,0),o1的座標為Vector3.up,即(0,1,0)。

然後定義兩個三角形的縮放比例為zoom的話

通過空間向量的換算,很容易得出:c1<i>=c<i>*zoom+Vector3.up

所以我們定義兩個List用於存放上下底的點,並用一個函式來新增點:

  1. List<Vector3> conors = new List<Vector3>();//下底的點
  2.         List<Vector3> _conors = new List<Vector3>();//上底的點
  3.         public float zoom = 1f;
  4.         void AddConors(Vector3 pos)
  5.         {
  6.                 conors.Add(pos);
  7.                 _conors.Add(pos * zoom + Vector3.up);
  8.         }
複製程式碼


接下來的就很簡單了,點加好後,把三個面加上去就行了,函式都封裝好了的:

  1. public override void Draw()
  2.         {
  3.                 angle = 2f * Mathf.PI / count;
  4.                 Vector3 pos = new Vector3(1f, 0f, 0f);
  5.                 AddConors(pos);
  6.                 for (int i = 0; i < count; i++)
  7.                 {
  8.                         pos = RotationTranslate(pos);
  9.                         AddConors(pos);
  10.                 }
  11.                 for (int k = 0; k < count; k++)
  12.                 {
  13.                         AddTriangle(Vector3.zero, conors[k], conors[k + 1]);
  14.                         AddTriangle(_conors[k], Vector3.up, _conors[k + 1]);
  15.                         AddSquare(conors[k], conors[k + 1], _conors[k], _conors[k + 1]);
  16.                 }

  17.                 conors.Clear();
  18.                 _conors.Clear();
  19.         }
複製程式碼

有了自制的稜臺、稜柱,以及unity自帶的方塊,球,膠囊,圓柱之類的物體,就可以在unity中自己搭建各種需要的場景了。

這樣做出來的場景物體,用於最終成品可能不達標,但是用於演示已經夠了。這最重要的是,暫時不用去學建模軟體了。

從零開始做一個SLG遊戲(三):用unity繪製圖形

相關閱讀:

從零開始做一個SLG遊戲(一):六邊形網格
從零開始做一個SLG遊戲(二):用mesh實現簡單的地形

作者:觀復
專欄地址:
https://zhuanlan.zhihu.com/p/44918148

相關文章