最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

300行代碼實(shí)現(xiàn)Minecraft(我的世界)大地圖生成

2017-08-24 17:51 作者:皮皮關(guān)做游戲  | 我要投稿

一直以來很多人都比較好奇,《我的世界》里的大地圖是如何隨機(jī)生成且還具有無限大小的,那么這一期教程,我就以最簡(jiǎn)化的代碼(300行左右)在Unity引擎中實(shí)現(xiàn)這一機(jī)制。

實(shí)現(xiàn)結(jié)果如下:


運(yùn)行后,隨機(jī)生成角色周圍的地形,且隨著角色的位置變化,動(dòng)態(tài)加載。


在實(shí)現(xiàn)之前呢,我們可以先來簡(jiǎn)單分析一下這個(gè)需求:

我的世界的地圖元素可以分為4個(gè)層次

World->Chunk->Block->Face

下面分別來解釋一下這4個(gè)層次。

1.Face: 正方體的一個(gè)面

2.Block: 6個(gè)面組成的一個(gè)正方體

3.Chunk: N個(gè)正方體組成的一個(gè)地圖塊

4.World: 多個(gè)地圖塊組成的世界,就是“我的世界”啦。

我們可以看到這4個(gè)層次,其實(shí)有點(diǎn)類似俄羅斯套娃對(duì)吧,一層包含一層。

我們要生成World,那么就是要在這些層次中,一層一層的去處理生成的邏輯, 在World里動(dòng)態(tài)加載Chunk, 在Chunk里生成Block, 在Block里生成Face。



OK ?大概的思路我們已經(jīng)說完了,接下來我們來拆解一下實(shí)現(xiàn)步驟

1.首先我們先實(shí)現(xiàn)Chunk的生成,內(nèi)部會(huì)包含 Block的生成,這里會(huì)用到simplex noise(一種Perlin噪聲的改進(jìn))

有關(guān)噪聲的知識(shí),如果讀者沒有接觸過,可以自行網(wǎng)上找找相關(guān)資料看看

這里推薦一篇(小姐姐寫的比較細(xì)致):http://blog.csdn.net/candycat1992/article/details/50346469

在這個(gè)部分我們會(huì)寫一個(gè)類Chunk.cs, ? (大約200行代碼)

2.接下來我們要通過玩家的位置信息來動(dòng)態(tài)加載Chunk

這個(gè)部分我們會(huì)寫一個(gè)類Player.cs ?(大約100行代碼)


Chunk生成

首先新建一個(gè)Unity工程后,導(dǎo)入一些資源,資源包在這里下載:pan.baidu.com/s/1hszPgw


接下來我們?cè)趫?chǎng)景中創(chuàng)建一個(gè)Cube

然后我們來創(chuàng)建一個(gè)Chunk類,并掛到這個(gè)Cube上。


打開剛才新建的Chunk.cs,我們來先聲明好Chunk類里需要用到的成員變量


public class Chunk : MonoBehaviour
{
??? //Block的類型
??? public enum BlockType
??? {
??????? //空
??????? None = 0,
??????? //泥土
??????? Dirt = 1,
??????? //草地
??????? Grass = 3,
??????? //碎石
??????? Gravel = 4,
??? }

??? //存儲(chǔ)著世界中所有的Chunk
??? public static List<Chunk> chunks = new List<Chunk>();

??? //每個(gè)Chunk的長(zhǎng)寬Size
??? public static int width = 30;
??? //每個(gè)Chunk的高度
??? public static int height = 30;

??? //隨機(jī)種子
??? public int seed;

??? //最小生成高度
??? public float baseHeight = 10;

??? //噪音頻率(噪音采樣時(shí)會(huì)用到)
??? public float frequency = 0.025f;
??? //噪音振幅(噪音采樣時(shí)會(huì)用到)
??? public float amplitude = 1;

??? //存儲(chǔ)著此Chunk內(nèi)的所有Block信息
??? BlockType[,,] map;

??? //Chunk的網(wǎng)格
??? Mesh chunkMesh;

??? //噪音采樣時(shí)會(huì)用到的偏移
??? Vector3 offset0;
??? Vector3 offset1;
??? Vector3 offset2;

??? MeshRenderer meshRenderer;
??? MeshCollider meshCollider;
??? MeshFilter meshFilter;

}


接下來,我們往這個(gè)類中加一些初始化的函數(shù) 如下:


?? void Start ()
??? {
??????? //初始化時(shí)將自己加入chunks列表
??????? chunks.Add(this);

//獲取自身相關(guān)組件引用
meshRenderer = GetComponent<MeshRenderer>();
meshCollider = GetComponent<MeshCollider>();
meshFilter = GetComponent<MeshFilter>();

??????? //初始化地圖
??????? InitMap();
??? }

??? void InitMap()
??? {
??????? //初始化隨機(jī)種子
??????? Random.InitState(seed);
??????? offset0 = new Vector3(Random.value * 1000, Random.value * 1000, Random.value * 1000);
??????? offset1 = new Vector3(Random.value * 1000, Random.value * 1000, Random.value * 1000);
??????? offset2 = new Vector3(Random.value * 1000, Random.value * 1000, Random.value * 1000);

??????? //初始化Map
??????? map = new BlockType[width, height, width];

??????? //遍歷map,生成其中每個(gè)Block的信息
??????? for (int x = 0; x < width; x++)
??????? {
??????????? for (int y = 0; y < height; y++)
??????????? {
??????????????? for (int z = 0; z < width; z++)
??????????????? {
??????????????????? map[x, y, z] = GenerateBlockType(new Vector3(x, y, z) + transform.position);
??????????????? }
??????????? }
??????? }

??????? //根據(jù)生成的信息,Build出Chunk的網(wǎng)格
??????? BuildChunk();
??? }


在上面這段代碼中,我們需要注意兩個(gè)點(diǎn)

1.這里的map存的是Chunk內(nèi)每一個(gè)Block的信息

2.GenerateBlockType函數(shù)和BuildChunk函數(shù),我們還沒有實(shí)現(xiàn)

3.我們?cè)赟tart函數(shù)被調(diào)用時(shí),便將這個(gè)Chunk生成好了


在第二點(diǎn)中說的兩個(gè)函數(shù),便是我們接下來生成Chunk的兩個(gè)核心步驟

1.生成map信息(每個(gè)Block的類型,以及地形的高度信息)

2.構(gòu)建Chunk用來顯示的網(wǎng)格


那么我們接下來分別看看如何實(shí)現(xiàn)這兩步

1.GenerateBlockType


int GenerateHeight(Vector3 wPos)
??? {

??????? //讓隨機(jī)種子,振幅,頻率,應(yīng)用于我們的噪音采樣結(jié)果
??????? float x0 = (wPos.x + offset0.x) * frequency;
??????? float y0 = (wPos.y + offset0.y) * frequency;
??????? float z0 = (wPos.z + offset0.z) * frequency;

??????? float x1 = (wPos.x + offset1.x) * frequency * 2;
??????? float y1 = (wPos.y + offset1.y) * frequency * 2;
??????? float z1 = (wPos.z + offset1.z) * frequency * 2;

??????? float x2 = (wPos.x + offset2.x) * frequency / 4;
??????? float y2 = (wPos.y + offset2.y) * frequency / 4;
??????? float z2 = (wPos.z + offset2.z) * frequency / 4;

??????? float noise0 = Noise.Generate(x0, z0, y0) * amplitude;
??????? float noise1 = Noise.Generate(x1, z1, y1) * amplitude / 2;
??????? float noise2 = Noise.Generate(x2, z2, y2) * amplitude / 4;

??????? //在采樣結(jié)果上,疊加上baseHeight,限制隨機(jī)生成的高度下限
??????? return Mathf.FloorToInt(noise0 + noise1 + noise2 + baseHeight);
??? }

??? BlockType GenerateBlockType(Vector3 wPos)
??? {
??????? //y坐標(biāo)是否在Chunk內(nèi)
??????? if (wPos.y >= height)
??????? {
??????????? return BlockType.None;
??????? }

??????? //獲取當(dāng)前位置方塊隨機(jī)生成的高度值
??????? float genHeight = GenerateHeight(wPos);

??????? //當(dāng)前方塊位置高于隨機(jī)生成的高度值時(shí),當(dāng)前方塊類型為空
??????? if (wPos.y > genHeight)
??????? {
??????????? return BlockType.None;
??????? }
??????? //當(dāng)前方塊位置等于隨機(jī)生成的高度值時(shí),當(dāng)前方塊類型為草地
??????? else if (wPos.y == genHeight)
??????? {
??????????? return BlockType.Grass;
??????? }
??????? //當(dāng)前方塊位置小于隨機(jī)生成的高度值 且 大于 genHeight - 5時(shí),當(dāng)前方塊類型為泥土
??????? else if (wPos.y < genHeight && wPos.y > genHeight - 5)
??????? {
??????????? return BlockType.Dirt;
??????? }
??????? //其他情況,當(dāng)前方塊類型為碎石
??????? return BlockType.Gravel;
??? }


上面這兩個(gè)函數(shù)實(shí)現(xiàn)了生成Block信息的過程

在上面這段代碼中我們需要注意以下幾點(diǎn)

1.GenerateHeight用于通過噪音來隨機(jī)生成每個(gè)方塊的高度,這種隨機(jī)生成的方式相比其他方式更貼近我們想要的結(jié)果。普通的隨機(jī)數(shù)得到的值都是離散的,均勻分布的結(jié)果,而通過simplex noise得到的結(jié)果,會(huì)是連續(xù)的。這樣會(huì)獲得更加真實(shí),接近自然的效果。

2. GenerateHeight中那些數(shù)字字面量,沒有特殊意義,就是經(jīng)驗(yàn)數(shù)值,為了生成結(jié)果能夠產(chǎn)生更多變化而已??梢宰约赫{(diào)整試試看。

3.GenerateHeight中對(duì)多個(gè)噪聲的生成結(jié)果進(jìn)行了疊加,這是為了混合出理想的結(jié)果,具體可以網(wǎng)上檢索查閱噪聲相關(guān)資料。

4.GenerateBlockType內(nèi),會(huì)利用在指定位置隨機(jī)生成的高度,來決定當(dāng)前Block的類型。最內(nèi)層是巖石,中間混雜著泥土,地表則是草地。


在我們有了地形元素的類型信息后,我們就可以來構(gòu)建Chunk的網(wǎng)格,以來顯示我們的Chunk了。

接下來我們實(shí)現(xiàn)BuildChunk函數(shù)


public void BuildChunk()
{
??? chunkMesh = new Mesh();
??? List<Vector3> verts = new List<Vector3>();
??? List<Vector2> uvs = new List<Vector2>();
??? List<int> tris = new List<int>();
???
??? //遍歷chunk, 生成其中的每一個(gè)Block
??? for (int x = 0; x < width; x++)
??? {
??????? for (int y = 0; y < height; y++)
??????? {
??????????? for (int z = 0; z < width; z++)
??????????? {
??????????????? BuildBlock(x, y, z, verts, uvs, tris);
??????????? }
??????? }
??? }
???????????????
??? chunkMesh.vertices = verts.ToArray();
??? chunkMesh.uv = uvs.ToArray();
??? chunkMesh.triangles = tris.ToArray();
??? chunkMesh.RecalculateBounds();
??? chunkMesh.RecalculateNormals();
???
??? meshFilter.mesh = chunkMesh;
??? meshCollider.sharedMesh = chunkMesh;
}


如上所示,BuildChunk函數(shù)內(nèi)部遍歷了Chunk內(nèi)的每一個(gè)Block,為其生成網(wǎng)格數(shù)據(jù),并在最后將生成的數(shù)據(jù)(頂點(diǎn),UV, ?索引)提交給了chunkMesh。


接下來我們實(shí)現(xiàn)BuildBlock函數(shù)

?

??? void BuildBlock(int x, int y, int z, List<Vector3> verts, List<Vector2> uvs, List<int> tris)
??? {
??????? if (map[x, y, z] == 0) return;

??????? BlockType typeid = map[x, y, z];

??????? //Left
??????? if (CheckNeedBuildFace(x - 1, y, z))
??????????? BuildFace(typeid, new Vector3(x, y, z), Vector3.up, Vector3.forward, false, verts, uvs, tris);
??????? //Right
??????? if (CheckNeedBuildFace(x + 1, y, z))
??????????? BuildFace(typeid, new Vector3(x + 1, y, z), Vector3.up, Vector3.forward, true, verts, uvs, tris);

??????? //Bottom
??????? if (CheckNeedBuildFace(x, y - 1, z))
??????????? BuildFace(typeid, new Vector3(x, y, z), Vector3.forward, Vector3.right, false, verts, uvs, tris);
??????? //Top
??????? if (CheckNeedBuildFace(x, y + 1, z))
??????????? BuildFace(typeid, new Vector3(x, y + 1, z), Vector3.forward, Vector3.right, true, verts, uvs, tris);

??????? //Back
??????? if (CheckNeedBuildFace(x, y, z - 1))
??????????? BuildFace(typeid, new Vector3(x, y, z), Vector3.up, Vector3.right, true, verts, uvs, tris);
??????? //Front
??????? if (CheckNeedBuildFace(x, y, z + 1))
??????????? BuildFace(typeid, new Vector3(x, y, z + 1), Vector3.up, Vector3.right, false, verts, uvs, tris);
??? }

??? bool CheckNeedBuildFace(int x, int y, int z)
??? {
??????? if (y < 0) return false;
??????? var type = GetBlockType(x, y, z);
??????? switch (type)
??????? {
??????????? case BlockType.None:
??????????????? return true;
??????????? default:
??????????????? return false;
??????? }
??? }

??? public BlockType GetBlockType(int x, int y, int z)
??? {
??????? if (y < 0 || y > height - 1)
??????? {
??????????? return 0;
??????? }

??????? //當(dāng)前位置是否在Chunk內(nèi)
??????? if ((x < 0) || (z < 0) || (x >= width) || (z >= width))
??????? {
??????????? var id = GenerateBlockType(new Vector3(x, y, z) + transform.position);
??????????? return id;
??????? }
??????? return map[x, y, z];
??? }


BuildBlock內(nèi),我們分別去構(gòu)建了一個(gè)Block中的每一個(gè)Face, 并通過CheckNeedBuildFace來確定,某一面Face是否需要顯示出來,如果不需要,那么就不用去構(gòu)建這面Face了。也就是說這個(gè)檢測(cè),會(huì)只把我們可以看到的面,顯示出來,如下圖這樣。

(不做面優(yōu)化)

(做了面優(yōu)化)


我們的角色在地形上時(shí),只能看到最外部的一層面,其實(shí)看不到內(nèi)部的方塊,所以這些看不到的方塊,就沒有必要浪費(fèi)計(jì)算資源了。也正是這個(gè)原因,我們不能直接用正方體去隨機(jī)生成,而是要像現(xiàn)在這樣,以Face為基本單位來生成。實(shí)現(xiàn)這個(gè)功能的函數(shù),便是CheckNeedBuildFace。


接下來讓我們完成Chunk部分的最后一步


void BuildFace(BlockType typeid, Vector3 corner, Vector3 up, Vector3 right, bool reversed, List<Vector3> verts, List<Vector2> uvs, List<int> tris)
{
??? int index = verts.Count; ? ? ? ?verts.Add (corner);
??? verts.Add (corner + up);
??? verts.Add (corner + up + right);
??? verts.Add (corner + right);
???
??? Vector2 uvWidth = new Vector2(0.25f, 0.25f);
??? Vector2 uvCorner = new Vector2(0.00f, 0.75f);

??? uvCorner.x += (float)(typeid - 1) / 4;
??? uvs.Add(uvCorner);
??? uvs.Add(new Vector2(uvCorner.x, uvCorner.y + uvWidth.y));
??? uvs.Add(new Vector2(uvCorner.x + uvWidth.x, uvCorner.y + uvWidth.y));
??? uvs.Add(new Vector2(uvCorner.x + uvWidth.x, uvCorner.y));
???
??? if (reversed)
??? {
??????? tris.Add(index + 0);
??????? tris.Add(index + 1);
??????? tris.Add(index + 2);
??????? tris.Add(index + 2);
??????? tris.Add(index + 3);
??????? tris.Add(index + 0);
??? }
??? else
??? {
??????? tris.Add(index + 1);
??????? tris.Add(index + 0);
??????? tris.Add(index + 2);
??????? tris.Add(index + 3);
??????? tris.Add(index + 2);
??????? tris.Add(index + 0);
??? }
}


這一步我們構(gòu)建了正方體其中一面的網(wǎng)格數(shù)據(jù),頂點(diǎn),UV, 索引。這一步實(shí)現(xiàn)完后, 如果我們將這個(gè)組件掛在我們最初創(chuàng)建的Cube上,并運(yùn)行,我們即會(huì)得到隨機(jī)生成的一個(gè)Chunk。如下圖所示:



2.在世界中動(dòng)態(tài)加載多個(gè)Chunk

在實(shí)現(xiàn)第二部分之前,我們先在Chunk類中再添加一個(gè)函數(shù)


??? public static Chunk GetChunk(Vector3 wPos)
??? { ? ? ? ?for (int i = 0; i < chunks.Count; i++)
??????? {
??????????? Vector3 tempPos = chunks[i].transform.position; ? ? ? ? ? ?//wPos是否超出了Chunk的XZ平面的范圍
??????????? if ((wPos.x < tempPos.x) || (wPos.z < tempPos.z) || (wPos.x >= tempPos.x + 20) || (wPos.z >= tempPos.z + 20))
??????????????? continue;

??????????? return chunks[i];
??????? }
??????? return null;
??? }


這個(gè)函數(shù)用于給定一個(gè)世界空間的位置,獲取這個(gè)指定位置所在的Chunk對(duì)象。其中遍歷了chunks列表,并找出對(duì)應(yīng)的chunk返回。這個(gè)函數(shù)我們將在后面的代碼中用到。


接下來由于動(dòng)態(tài)加載是根據(jù)玩家位置的變化來進(jìn)行的,所以我們首先添加一個(gè)Player類

新建一個(gè)C#代碼文件:Player.cs,并在其中添加如下代碼:


public class Player : MonoBehaviour
{
??? CharacterController cc;
??? public float speed = 20;
??? public float viewRange = 30;
??? public Chunk chunkPrefab;

??? private void Start()
??? {
??????? cc = GetComponent<CharacterController>();
??? }

??? void Update ()
??? {
??????? UpdateInput();
??????? UpdateWorld();
??? }

??? void UpdateInput()
??? {
??????? var h = Input.GetAxis("Horizontal");
??????? var v = Input.GetAxis("Vertical");

??????? var x = Input.GetAxis("Mouse X");
??????? var y = Input.GetAxis("Mouse Y");

??????? transform.rotation *= Quaternion.Euler(0f, x, 0f);
??????? transform.rotation *= Quaternion.Euler(-y, 0f, 0f);

??????? if (Input.GetButton("Jump"))
??????? {
??????????? cc.Move((transform.right * h + transform.forward * v + transform.up) * speed * Time.deltaTime);
??????? }
??????? else
??????? {
??????????? cc.SimpleMove(transform.right * h + transform.forward * v * speed);
??????? }
??? }
}


這段代碼中有幾點(diǎn)需要注意

1.UpdateWorld我們還沒有實(shí)現(xiàn),這個(gè)函數(shù)將用來動(dòng)態(tài)生成Chunk。

2.UpdateInput函數(shù)中,我們實(shí)現(xiàn)了一個(gè)最簡(jiǎn)單的處理玩家輸入的小模塊(但并不成熟,甚至都沒有做視角的限制,感興趣的可以自己加入更多的處理),其可以根據(jù)玩家的鼠標(biāo)和鍵盤的輸入來控制角色移動(dòng)和旋轉(zhuǎn)。

3.控制玩家移動(dòng)的處理,我們使用了Unity內(nèi)置的CharacterController組件,這個(gè)組件自身就又膠囊體碰撞盒。


在這一步中我們從Update函數(shù)中已經(jīng)看出一些端倪了。這里會(huì)每一幀先處理玩家的輸入,然后根據(jù)處理后的結(jié)果(更新后的玩家位置)來動(dòng)態(tài)加載Chunk。


接下來我們添加最后一個(gè)函數(shù)UpdateWorld


??? void UpdateWorld()
??? {
??????? for (float x = transform.position.x - viewRange; x < transform.position.x + viewRange; x += Chunk.width)
??????? {
??????????? for (float z = transform.position.z - viewRange; z < transform.position.z + viewRange; z += Chunk.width)
??????????? {
??????????????? Vector3 pos = new Vector3(x, 0, z);
??????????????? pos.x = Mathf.Floor(pos.x / (float)Chunk.width) * Chunk.width;
??????????????? pos.z = Mathf.Floor(pos.z / (float)Chunk.width) * Chunk.width; ? ? ? ? ? ? ? ?Chunk chunk = Chunk.GetChunk(pos);
??????????????? if (chunk != null) continue;

??????????????? chunk = (Chunk)Instantiate(chunkPrefab, pos, Quaternion.identity);
??????????? }
??????? }
??? }


這個(gè)函數(shù) 使用了我們剛才實(shí)現(xiàn)過的靜態(tài)函數(shù)Chunk.GetChunk,來獲取相應(yīng)位置的chunk, 如果沒有獲取到的話,那么就通過chunkPrefab在相應(yīng)位置生成一個(gè)新的chunk。 這個(gè)函數(shù)會(huì)通過這種方式來動(dòng)態(tài)加載自身周圍的chunk。 viewRange參數(shù)可以控制需要加載的范圍。


到這里代碼部分我們就全部實(shí)現(xiàn)完了。

接下來我們,添加一個(gè)角色對(duì)象,并在其上掛載一個(gè)CharacterController組件,以及我們的Player組件。

別忘了,還要加上相機(jī)哦。


然后是Chunk。



最后我們來看看我們的成果吧:


本期教程兩個(gè)文件,總計(jì)大約300余行代碼

本期教程工程源碼:https://github.com/meta-42/Minecraft-Unity


————————————————————————————————————

對(duì)游戲開發(fā)感興趣的同學(xué),歡迎圍觀我們:【皮皮關(guān)游戲開發(fā)教育】 ,會(huì)定期更新各種教程干貨,更有別具一格的線下小班教育~

我們的官網(wǎng)地址:http://www.levelpp.com/

我們的游戲開發(fā)技術(shù)交流群:610475807

我們的微信公眾號(hào):皮皮關(guān)

300行代碼實(shí)現(xiàn)Minecraft(我的世界)大地圖生成的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
清原| 西宁市| 邹平县| 永兴县| 三穗县| 塘沽区| 鲁山县| 蓬莱市| 封丘县| 阿图什市| 荣昌县| 丹寨县| 商丘市| 民县| 聂荣县| 新疆| 郑州市| 辛集市| 闵行区| 南丰县| 晋中市| 乌拉特后旗| 庐江县| 凤阳县| 富川| 东莞市| 五常市| 西盟| 卓资县| 丰镇市| 阿拉善左旗| 宁海县| 南木林县| 洮南市| 普格县| 宜川县| 措美县| 将乐县| 汤阴县| 甘洛县| 郁南县|