轉載請注明出處:http://www.cnblogs.com/shamoyuu/p/unity_minecraft_03.html
一、引入int類型的Vector3
我們都知道Unity3D里Vector3的xyz都是float類型的,但是我們的每一個Block的坐標都應該是int類型,這樣在進行轉換和存儲的時候會有一定的消耗,所以我們先自己寫一個xyz都是int類型的Vector3i類,它擁有所有Vector3的屬性和方法,只是xyz都換成了int。
using UnityEngine; using System; namespace Soultia.Util { [Serializable] public struct Vector3i { public static readonly Vector3i zero = new Vector3i(0, 0, 0); public static readonly Vector3i one = new Vector3i(1, 1, 1); public static readonly Vector3i forward = new Vector3i(0, 0, 1); public static readonly Vector3i back = new Vector3i(0, 0, -1); public static readonly Vector3i up = new Vector3i(0, 1, 0); public static readonly Vector3i down = new Vector3i(0, -1, 0); public static readonly Vector3i left = new Vector3i(-1, 0, 0); public static readonly Vector3i right = new Vector3i(1, 0, 0); public static readonly Vector3i[] directions = new Vector3i[] { forward, back, right, left, up, down }; public int x, y, z; public Vector3i(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public Vector3i(int x, int y) { this.x = x; this.y = y; this.z = 0; } public Vector3i(Vector3 pos) { this.x = Mathf.FloorToInt(pos.x); this.y = Mathf.FloorToInt(pos.y); this.z = Mathf.FloorToInt(pos.z); } /////////////////////////////////////////////////////////////// public static Vector3i Mul(Vector3i a, Vector3i b) { return new Vector3i(a.x * b.x, a.y * b.y, a.z * b.z); } public static Vector3i Div(Vector3i a, Vector3i b) { return new Vector3i(a.x / b.x, a.y / b.y, a.z / b.z); } public static Vector3i Min(Vector3i a, Vector3i b) { return Process(a, b, Mathf.Min); } public static Vector3i Max(Vector3i a, Vector3i b) { return Process(a, b, Mathf.Max); } public static Vector3i Abs(Vector3i a) { return Process(a, Mathf.Abs); } public static Vector3i Floor(Vector3 v) { return v.ProcessTo3i(Mathf.FloorToInt); } public static Vector3i Ceil(Vector3 v) { return v.ProcessTo3i(Mathf.CeilToInt); } public static Vector3i Round(Vector3 v) { return v.ProcessTo3i(Mathf.RoundToInt); } public static Vector3i Process(Vector3i v, Func<int, int> func) { v.x = func(v.x); v.y = func(v.y); v.z = func(v.z); return v; } public static Vector3i Process(Vector3i a, Vector3i b, Func<int, int, int> func) { a.x = func(a.x, b.x); a.y = func(a.y, b.y); a.z = func(a.z, b.z); return a; } //////////////////////////////////////////////////////// public static Vector3i operator -(Vector3i a) { return new Vector3i(-a.x, -a.y, -a.z); } public static Vector3i operator -(Vector3i a, Vector3i b) { return new Vector3i(a.x - b.x, a.y - b.y, a.z - b.z); } public static Vector3i operator +(Vector3i a, Vector3i b) { return new Vector3i(a.x + b.x, a.y + b.y, a.z + b.z); } public static Vector3i operator *(Vector3i v, int factor) { return new Vector3i(v.x * factor, v.y * factor, v.z * factor); } public static Vector3i operator /(Vector3i v, int factor) { return new Vector3i(v.x / factor, v.y / factor, v.z / factor); } public static Vector3i operator *(Vector3i a, Vector3i b) { return Mul(a, b); } public static Vector3i operator /(Vector3i a, Vector3i b) { return Div(a, b); } //////////////////////////////////////////////////////// public static bool operator ==(Vector3i a, Vector3i b) { return a.x == b.x && a.y == b.y && a.z == b.z; } public static bool operator !=(Vector3i a, Vector3i b) { return a.x != b.x || a.y != b.y || a.z != b.z; } public static implicit operator Vector3(Vector3i v) { return new Vector3(v.x, v.y, v.z); } //////////////////////////////////////////////////////// public override bool Equals(object other) { if (other is Vector3i == false) return false; Vector3i vector = (Vector3i)other; return x == vector.x && y == vector.y && z == vector.z; } public override int GetHashCode() { return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2; } public override string ToString() { return string.Format("Vector3i({0} {1} {2})", x, y, z); } } }
以及Vector3的擴展工具類
using UnityEngine; using System.Collections; using System; namespace Soultia.Util { public static class Vector3Utils { public static Vector3 Mul(this Vector3 a, Vector3 b) { a.x *= b.x; a.y *= b.y; a.z *= b.z; return a; } public static Vector3 Div(this Vector3 a, Vector3 b) { a.x /= b.x; a.y /= b.y; a.z /= b.z; return a; } public static Vector3 Process(this Vector3 v, Func<float, float> func) { v.x = func(v.x); v.y = func(v.y); v.z = func(v.z); return v; } public static Vector3i ProcessTo3i(this Vector3 v, Func<float, int> func) { Vector3i vi; vi.x = func(v.x); vi.y = func(v.y); vi.z = func(v.z); return vi; } } }
這兩個類如果感興趣的童鞋可以看一下具體都是怎么寫的,不理解也沒事,只要把它當成Vector3來用就好。
需要注意的是,雖然Unity3D 2017版已經加入了int類型的Vector類Vector3Int,但是過於簡陋,我們還是自己寫了。
二、Chunk類
using Soultia.Util; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Soultia.Voxel { [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] [RequireComponent(typeof(MeshCollider))] public class Chunk : MonoBehaviour { public static int width = 16; public static int height = 16; public byte[,,] blocks; public Vector3i position; private Mesh mesh; //面需要的點 private List<Vector3> vertices = new List<Vector3>(); //生成三邊面時用到的vertices的index private List<int> triangles = new List<int>(); //當前Chunk是否正在生成中 private bool isWorking = false; void Start() { position = new Vector3i(this.transform.position); if (Map.instance.chunks.ContainsKey(position)) { Destroy(this); } else { this.name = "(" + position.x + "," + position.y + "," + position.z + ")"; StartFunction(); } } void StartFunction() { mesh = new Mesh(); mesh.name = "Chunk"; StartCoroutine(CreateMap()); } IEnumerator CreateMap() { while (isWorking) { yield return null; } isWorking = true; blocks = new byte[width, height, width]; for (int x = 0; x < Chunk.width; x++) { for (int y = 0; y < Chunk.height; y++) { for (int z = 0; z < Chunk.width; z++) { blocks[x, y, z] = 1; } } } StartCoroutine(CreateMesh()); } IEnumerator CreateMesh() { vertices.Clear(); triangles.Clear(); //把所有面的點和面的索引添加進去 for (int x = 0; x < Chunk.width; x++) { for (int y = 0; y < Chunk.height; y++) { for (int z = 0; z < Chunk.width; z++) { if (IsBlockTransparent(x + 1, y, z)) { AddFrontFace(x, y, z); } if (IsBlockTransparent(x - 1, y, z)) { AddBackFace(x, y, z); } if (IsBlockTransparent(x, y, z + 1)) { AddRightFace(x, y, z); } if (IsBlockTransparent(x, y, z - 1)) { AddLeftFace(x, y, z); } if (IsBlockTransparent(x, y + 1, z)) { AddTopFace(x, y, z); } if (IsBlockTransparent(x, y - 1, z)) { AddBottomFace(x, y, z); } } } } //為點和index賦值 mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); //重新計算頂點和法線 mesh.RecalculateBounds(); mesh.RecalculateNormals(); //將生成好的面賦值給組件 this.GetComponent<MeshFilter>().mesh = mesh; this.GetComponent<MeshCollider>().sharedMesh = mesh; yield return null; isWorking = false; } public static bool IsBlockTransparent(int x, int y, int z) { if (x >= width || y >= height || z >= width || x < 0 || y < 0 || z < 0) { return true; } return false; } //前面 void AddFrontFace(int x, int y, int z) { //第一個三角面 triangles.Add(0 + vertices.Count); triangles.Add(3 + vertices.Count); triangles.Add(2 + vertices.Count); //第二個三角面 triangles.Add(2 + vertices.Count); triangles.Add(1 + vertices.Count); triangles.Add(0 + vertices.Count); //添加4個點 vertices.Add(new Vector3(0 + x, 0 + y, 0 + z)); vertices.Add(new Vector3(0 + x, 0 + y, 1 + z)); vertices.Add(new Vector3(0 + x, 1 + y, 1 + z)); vertices.Add(new Vector3(0 + x, 1 + y, 0 + z)); } //背面 void AddBackFace(int x, int y, int z) { //第一個三角面 triangles.Add(0 + vertices.Count); triangles.Add(3 + vertices.Count); triangles.Add(2 + vertices.Count); //第二個三角面 triangles.Add(2 + vertices.Count); triangles.Add(1 + vertices.Count); triangles.Add(0 + vertices.Count); //添加4個點 vertices.Add(new Vector3(-1 + x, 0 + y, 1 + z)); vertices.Add(new Vector3(-1 + x, 0 + y, 0 + z)); vertices.Add(new Vector3(-1 + x, 1 + y, 0 + z)); vertices.Add(new Vector3(-1 + x, 1 + y, 1 + z)); } //右面 void AddRightFace(int x, int y, int z) { //第一個三角面 triangles.Add(0 + vertices.Count); triangles.Add(3 + vertices.Count); triangles.Add(2 + vertices.Count); //第二個三角面 triangles.Add(2 + vertices.Count); triangles.Add(1 + vertices.Count); triangles.Add(0 + vertices.Count); //添加4個點 vertices.Add(new Vector3(0 + x, 0 + y, 1 + z)); vertices.Add(new Vector3(-1 + x, 0 + y, 1 + z)); vertices.Add(new Vector3(-1 + x, 1 + y, 1 + z)); vertices.Add(new Vector3(0 + x, 1 + y, 1 + z)); } //左面 void AddLeftFace(int x, int y, int z) { //第一個三角面 triangles.Add(0 + vertices.Count); triangles.Add(3 + vertices.Count); triangles.Add(2 + vertices.Count); //第二個三角面 triangles.Add(2 + vertices.Count); triangles.Add(1 + vertices.Count); triangles.Add(0 + vertices.Count); //添加4個點 vertices.Add(new Vector3(-1 + x, 0 + y, 0 + z)); vertices.Add(new Vector3(0 + x, 0 + y, 0 + z)); vertices.Add(new Vector3(0 + x, 1 + y, 0 + z)); vertices.Add(new Vector3(-1 + x, 1 + y, 0 + z)); } //上面 void AddTopFace(int x, int y, int z) { //第一個三角面 triangles.Add(1 + vertices.Count); triangles.Add(0 + vertices.Count); triangles.Add(3 + vertices.Count); //第二個三角面 triangles.Add(3 + vertices.Count); triangles.Add(2 + vertices.Count); triangles.Add(1 + vertices.Count); //添加4個點 vertices.Add(new Vector3(0 + x, 1 + y, 0 + z)); vertices.Add(new Vector3(0 + x, 1 + y, 1 + z)); vertices.Add(new Vector3(-1 + x, 1 + y, 1 + z)); vertices.Add(new Vector3(-1 + x, 1 + y, 0 + z)); } //下面 void AddBottomFace(int x, int y, int z) { //第一個三角面 triangles.Add(1 + vertices.Count); triangles.Add(0 + vertices.Count); triangles.Add(3 + vertices.Count); //第二個三角面 triangles.Add(3 + vertices.Count); triangles.Add(2 + vertices.Count); triangles.Add(1 + vertices.Count); //添加4個點 vertices.Add(new Vector3(-1 + x, 0 + y, 0 + z)); vertices.Add(new Vector3(-1 + x, 0 + y, 1 + z)); vertices.Add(new Vector3(0 + x, 0 + y, 1 + z)); vertices.Add(new Vector3(0 + x, 0 + y, 0 + z)); } } }
最上面我們新建了一個變量blocks用來存儲block的信息,為了節約內存,我們只存儲block的id。blocks是一個byte類型的數組, byte是無符號8位整數,值是0~255。(做軟件的時候我永遠都不會考慮一個數值應該存儲在什么類型的變量中,這就是做游戲跟做軟件的區別吧。做軟件就像是在寫語文作業,做游戲就像是在寫數學作業)
我們首先調用CreateMap生成了一個Chunk中Block的信息,這里雖然都設置了它們是id為1的方塊,但是並沒有用到,這個我們下一章再講。
然后就是用CreateMesh方法來創建我們的地形網格。
在創建之前,我們寫了一個簡單的方法IsBlockTransparent來判斷這個坐標的Block是不是透明的。因為如果是透明的,那跟它相鄰的面就需要繪制出來
比如我們在繪制前面這個面的時候
if (IsBlockTransparent(x + 1, y, z)) { AddFrontFace(x, y, z); }
我們先判斷了它x+1的位置上的方塊是否透明,才決定是否繪制它。
這里我們只是簡單地判斷了一下它的坐標,后面我們會寫更詳細的判斷。
三、Map類
我們的Block並不是存儲在Map對象中,而是存儲在Chunk里,Chunk對象存儲在Map中。
我們的Map對象只干兩件事
1、存儲Chunk
2、生成Chunk
using Soultia.Util; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Soultia.Voxel { public class Map : MonoBehaviour { public static Map instance; public static GameObject chunkPrefab; public Dictionary<Vector3i, GameObject> chunks = new Dictionary<Vector3i, GameObject>(); //當前是否正在生成Chunk private bool spawningChunk = false; void Awake() { instance = this; chunkPrefab = Resources.Load("Prefab/Chunk") as GameObject; } void Start() { StartCoroutine(SpawnChunk(new Vector3i(0, 0, 0))); } void Update() { } public IEnumerator SpawnChunk(Vector3i pos) { spawningChunk = true; Instantiate(chunkPrefab, pos, Quaternion.identity); yield return 0; spawningChunk = false; } } }
代碼很簡單,首先生成了一個Map對象的單例,然后
因為現在只是為了簡單地生成一個Chunk,所以SpawnChunk方法是寫在了Start里,我們下下一章繼續完善。