【Unity3D】Unity3D开发《我的世界》之六、创建地形(视频
+源码)
⼀、引⼊LibNoise
虽然Unity3D⾥也有⼀个Mathf.PerlinNoise,但是只能是2D的,这个可以⽣成3D的柏林噪⾳
github/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.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)
{
return1;
}
return0;
}
}
四、修改PlayerController,添加Y轴的Chunk⽣成
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 ansform.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()
{
yymcisWorking = 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.uv = uv.ToArray();