【Unity3D】Unity3D开发《我的世界》之六、创建地形(视频 + 源码)
转载请注明出处:http://www.cnblogs.com/shamoyuu/p/unity_minecraft_06.html
一、引入LibNoise
虽然Unity3D里也有一个Mathf.PerlinNoise,但是只能是2D的,这个可以生成3D的柏林噪音
https://github.com/ricardojmendez/LibNoise.Unity
二、创建GameManager对象
这个对象很简单,就是用来管理随机数种子
using System; using UnityEngine; public class GameManager : MonoBehaviour { public static int randomSeed; void Awake() { //让默认的随机数种子为当前的时间戳 TimeSpan timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); randomSeed = (int)timeSpan.TotalSeconds; } }
三、创建Terrain对象
这个对象就是负责生成地形的,我们这里只是简单地通过世界坐标来获取一个噪音的值,然后通过这个值判断这个坐标上是什么方块。
using LibNoise; using LibNoise.Generator; using Soultia.Util; using UnityEngine; public class Terrain : MonoBehaviour { //通过方块的世界坐标获取它的方块类型 public static byte GetTerrainBlock(Vector3i worldPosition) { //LibNoise噪音对象 Perlin noise = new LibNoise.Generator.Perlin(1f, 1f, 1f, 8, GameManager.randomSeed, QualityMode.High); //为随机数指定种子,这样每次随机的都是同样的值 Random.InitState(GameManager.randomSeed); //因为柏林噪音在(0,0)点是上下左右对称的,所以我们设置一个很远很远的地方作为新的(0,0)点 Vector3 offset = new Vector3(Random.value * 100000, Random.value * 100000, Random.value * 100000); float noiseX = Mathf.Abs((worldPosition.x + offset.x) / 20); float noiseY = Mathf.Abs((worldPosition.y + offset.y) / 20); float noiseZ = Mathf.Abs((worldPosition.z + offset.z) / 20); double noiseValue = noise.GetValue(noiseX, noiseY, noiseZ); noiseValue += (20 - worldPosition.y) / 15f; noiseValue /= worldPosition.y / 5f; if (noiseValue > 0.5f) { return 1; } return 0; } }
四、修改PlayerController,添加Y轴的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 y = transform.position.y - Chunk.height * 3; y < transform.position.y + Chunk.height * 3; y += Chunk.height) { //Y轴上是允许最大16个Chunk,方块高度最大是256 if (y <= Chunk.height * 16 && y > 0) { 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 yy = Chunk.height * Mathf.FloorToInt(y / Chunk.height); int zz = Chunk.width * Mathf.FloorToInt(z / Chunk.width); if (!Map.instance.ChunkExists(xx, yy, zz)) { Map.instance.CreateChunk(new Vector3i(xx, yy, zz)); } } } } } } }
五、修改Chunk对象
1.修改CreateMap方法来通过噪音生成地形
2.修改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>(); //所有的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是否正在生成中 public static bool isWorking = false; private bool isFinished = 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 Update() { if (isWorking == false && isFinished == false) { isFinished = true; StartFunction(); } } void StartFunction() { isWorking = true; mesh = new Mesh(); mesh.name = "Chunk"; StartCoroutine(CreateMap()); } IEnumerator CreateMap() { 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++) { byte blockid = Terrain.GetTerrainBlock(new Vector3i(x, y, z) + position); if (blockid == 1 && Terrain.GetTerrainBlock(new Vector3i(x, y + 1, z) + position) == 0) { blocks[x, y, z] = 2; } else { blocks[x, y, z] = Terrain.GetTerrainBlock(new Vector3i(x, y, z) + position); } } } } yield return null; 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)); } } }
源码下载地址
链接: https://pan.baidu.com/s/1o8uqNY6 密码: hgar
放置和破坏方块是比较简单的,算是留给读者的一个小作业吧,如果你理解了我这整个系列教程中每一个知识点的话。提示:改变Chunk中blocks的值,然后重新绘制。
至此,我们Unity3D开发《我的世界》的教程就到此结束了。
但是我们离《我的世界》还差得很远很远很远很远。。。。。
《我的世界》中的五大难点
1.方块构成地形
2.通过生态构成自然的地形
3.液体
4.光照
5.红石电路
我们只勉勉强强完成了1,其他4个每一个都是极大的挑战,如果读者感兴趣,可以自己试一下。
《我的世界》这么火,真的不无道理。
完结,散花