【Unity3D】Unity3D开发《我的世界》之三、创建一个Chunk
转载请注明出处: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里,我们下下一章继续完善。