轉載請注明出處:http://www.cnblogs.com/shamoyuu/p/unity_minecraft_05.html
一、導入Unity3D自帶的第一人稱角色控制器
直接導入就行,我們用FPSController。
二、為Map添加創建Chunk和判斷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; } //生成Chunk public void CreateChunk(Vector3i pos) { if (spawningChunk) return; StartCoroutine(SpawnChunk(pos)); } private IEnumerator SpawnChunk(Vector3i pos) { spawningChunk = true; Instantiate(chunkPrefab, pos, Quaternion.identity); yield return null; spawningChunk = false; } //通過Chunk的坐標來判斷它是否存在 public bool ChunkExists(Vector3i worldPosition) { return this.ChunkExists(worldPosition.x, worldPosition.y, worldPosition.z); } //通過Chunk的坐標來判斷它是否存在 public bool ChunkExists(int x, int y, int z) { return chunks.ContainsKey(new Vector3i(x, y, z)); } } }
上上一章用來測試的Start方法也刪掉了,我們下面會通過玩家的位置來生成
三、添加草方塊
using System.Collections.Generic; using UnityEngine; /// <summary> /// 存儲所有的Block對象的信息 /// </summary> public class BlockList : MonoBehaviour { public static Dictionary<byte, Block> blocks = new Dictionary<byte, Block>(); void Awake() { Block dirt = new Block(1, "Dirt", 2, 31); blocks.Add(dirt.id, dirt); Block grass = new Block(2, "Grass", 3, 31, 0, 31, 2, 31); blocks.Add(grass.id, grass); } public static Block GetBlock(byte id) { return blocks.ContainsKey(id) ? blocks[id] : null; } }
四、修改Chunk
using Soultia.Util; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; 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>(); //所有的uv信息 private List<Vector2> uv = new List<Vector2>(); //uv貼圖每行每列的寬度(0~1),這里我的貼圖是32×32的,所以是1/32 public static float textureOffset = 1 / 32f; //讓UV稍微縮小一點,避免出現它旁邊的貼圖 public static float shrinkSize = 0.001f; //當前Chunk是否正在生成中 private bool isWorking = false; void Start() { position = new Vector3i(this.transform.position); if (Map.instance.ChunkExists(position)) { Debug.Log("此方塊已存在" + position); Destroy(this); } else { Map.instance.chunks.Add(position, this.gameObject); 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++) { if (y == Chunk.height - 1) { if (Random.Range(1, 5) == 1) { blocks[x, y, z] = 2; } } else { 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++) { //獲取當前坐標的Block對象 Block block = BlockList.GetBlock(this.blocks[x, y, z]); if (block == null) continue; if (IsBlockTransparent(x + 1, y, z)) { AddFrontFace(x, y, z, block); } if (IsBlockTransparent(x - 1, y, z)) { AddBackFace(x, y, z, block); } if (IsBlockTransparent(x, y, z + 1)) { AddRightFace(x, y, z, block); } if (IsBlockTransparent(x, y, z - 1)) { AddLeftFace(x, y, z, block); } if (IsBlockTransparent(x, y + 1, z)) { AddTopFace(x, y, z, block); } if (IsBlockTransparent(x, y - 1, z)) { AddBottomFace(x, y, z, block); } } } } //為點和index賦值 mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.uv = uv.ToArray(); //重新計算頂點和法線 mesh.RecalculateBounds(); mesh.RecalculateNormals(); //將生成好的面賦值給組件 this.GetComponent<MeshFilter>().mesh = mesh; this.GetComponent<MeshCollider>().sharedMesh = mesh; yield return null; isWorking = false; } //此坐標方塊是否透明,Chunk中的局部坐標 public bool IsBlockTransparent(int x, int y, int z) { if (x >= width || y >= height || z >= width || x < 0 || y < 0 || z < 0) { return true; } else { //如果當前方塊的id是0,那的確是透明的 return this.blocks[x, y, z] == 0; } } //前面 void AddFrontFace(int x, int y, int z, Block block) { //第一個三角面 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)); //添加UV坐標點,跟上面4個點循環的順序一致 uv.Add(new Vector2(block.textureFrontX * textureOffset, block.textureFrontY * textureOffset) + new Vector2(shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureFrontX * textureOffset + textureOffset, block.textureFrontY * textureOffset) + new Vector2(-shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureFrontX * textureOffset + textureOffset, block.textureFrontY * textureOffset + textureOffset) + new Vector2(-shrinkSize, -shrinkSize)); uv.Add(new Vector2(block.textureFrontX * textureOffset, block.textureFrontY * textureOffset + textureOffset) + new Vector2(shrinkSize, -shrinkSize)); } //背面 void AddBackFace(int x, int y, int z, Block block) { //第一個三角面 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)); //添加UV坐標點,跟上面4個點循環的順序一致 uv.Add(new Vector2(block.textureBackX * textureOffset, block.textureBackY * textureOffset) + new Vector2(shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureBackX * textureOffset + textureOffset, block.textureBackY * textureOffset) + new Vector2(-shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureBackX * textureOffset + textureOffset, block.textureBackY * textureOffset + textureOffset) + new Vector2(-shrinkSize, -shrinkSize)); uv.Add(new Vector2(block.textureBackX * textureOffset, block.textureBackY * textureOffset + textureOffset) + new Vector2(shrinkSize, -shrinkSize)); } //右面 void AddRightFace(int x, int y, int z, Block block) { //第一個三角面 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)); //添加UV坐標點,跟上面4個點循環的順序一致 uv.Add(new Vector2(block.textureRightX * textureOffset, block.textureRightY * textureOffset) + new Vector2(shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureRightX * textureOffset + textureOffset, block.textureRightY * textureOffset) + new Vector2(-shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureRightX * textureOffset + textureOffset, block.textureRightY * textureOffset + textureOffset) + new Vector2(-shrinkSize, -shrinkSize)); uv.Add(new Vector2(block.textureRightX * textureOffset, block.textureRightY * textureOffset + textureOffset) + new Vector2(shrinkSize, -shrinkSize)); } //左面 void AddLeftFace(int x, int y, int z, Block block) { //第一個三角面 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)); //添加UV坐標點,跟上面4個點循環的順序一致 uv.Add(new Vector2(block.textureLeftX * textureOffset, block.textureLeftY * textureOffset) + new Vector2(shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureLeftX * textureOffset + textureOffset, block.textureLeftY * textureOffset) + new Vector2(-shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureLeftX * textureOffset + textureOffset, block.textureLeftY * textureOffset + textureOffset) + new Vector2(-shrinkSize, -shrinkSize)); uv.Add(new Vector2(block.textureLeftX * textureOffset, block.textureLeftY * textureOffset + textureOffset) + new Vector2(shrinkSize, -shrinkSize)); } //上面 void AddTopFace(int x, int y, int z, Block block) { //第一個三角面 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)); //添加UV坐標點,跟上面4個點循環的順序一致 uv.Add(new Vector2(block.textureTopX * textureOffset, block.textureTopY * textureOffset) + new Vector2(shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureTopX * textureOffset + textureOffset, block.textureTopY * textureOffset) + new Vector2(-shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureTopX * textureOffset + textureOffset, block.textureTopY * textureOffset + textureOffset) + new Vector2(-shrinkSize, -shrinkSize)); uv.Add(new Vector2(block.textureTopX * textureOffset, block.textureTopY * textureOffset + textureOffset) + new Vector2(shrinkSize, -shrinkSize)); } //下面 void AddBottomFace(int x, int y, int z, Block block) { //第一個三角面 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)); //添加UV坐標點,跟上面4個點循環的順序一致 uv.Add(new Vector2(block.textureBottomX * textureOffset, block.textureBottomY * textureOffset) + new Vector2(shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureBottomX * textureOffset + textureOffset, block.textureBottomY * textureOffset) + new Vector2(-shrinkSize, shrinkSize)); uv.Add(new Vector2(block.textureBottomX * textureOffset + textureOffset, block.textureBottomY * textureOffset + textureOffset) + new Vector2(-shrinkSize, -shrinkSize)); uv.Add(new Vector2(block.textureBottomX * textureOffset, block.textureBottomY * textureOffset + textureOffset) + new Vector2(shrinkSize, -shrinkSize)); } } }
我們修改了Chunk的CreateMap方法,讓它在最頂部有一定幾率生成草方塊,便於我們直觀地看到無限地形的生成。
然后修改了Start方法,判斷了它是否存在,如果不存在,就把它添加到Map的chunks里,如果已經存在了,就銷毀它。
五、添加PlayerController
這個對象就是用來檢測玩家周圍一定范圍內的Chunk是否已經生成,如果沒有生成就會生成它。
using Soultia.Util; using Soultia.Voxel; using UnityEngine; public class PlayerController : MonoBehaviour { //視線范圍 public int viewRange = 30; void Update() { for (float x = transform.position.x - Chunk.width * 3; x < transform.position.x + Chunk.width * 3; x += Chunk.width) { for (float z = transform.position.z - Chunk.width * 3; z < transform.position.z + Chunk.width * 3; z += Chunk.width) { int xx = Chunk.width * Mathf.FloorToInt(x / Chunk.width); int zz = Chunk.width * Mathf.FloorToInt(z / Chunk.width); if (!Map.instance.ChunkExists(xx, 0, zz)) { Map.instance.CreateChunk(new Vector3i(xx, 0, zz)); } } } } }
然后把它拖給玩家
到這里就已經可以生成無限地形了,按住Shift一直跑,已經可以當成小小的跑酷游戲玩了~
