【Unity3D】Unity3D开发《我的世界》之五、创建无限地形(视频)
转载请注明出处: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一直跑,已经可以当成小小的跑酷游戏玩了~