系列概述

      这套教程涵盖了Unity Mesh编程、模拟水算法(water simulations)、方块移动算法(marching-cubes)等等。这是一套比较有深度的教程,可能需要你了解一些Unity和C#相关的知识。

 

预备开始

        首先,我们先来创建一个空的项目,命名随意即可。

 

        然后创建在Assets下创建一个文件夹,命名为“Scripts”,并创建三个C#脚本,如下图所示:

 

mc指令区块java 我的世界区块生成指令_数据库

 

Chunk用于存储方块数据和创建网格,并且对网格进行渲染和碰撞;Block用于存放方块需要的信息;MeshData用于存储网格数据。

 

Chunk.cs脚本:

mc指令区块java 我的世界区块生成指令_数据块_02

 

        首先,我们要求该脚本必须包含上个组件:MeshFilter、MeshRenderer、MeshCollider。我们的数据块(就是由一堆方块组成的大方块~)需要这三个组件完成网格的创建和碰撞。

        

        然后,我们有三个变量。我们有一个Block类型的三维数组变量 blocks,Block类用于存放方块需要的信息,因此我们这个blocks变量就是用于存放一个数据块的方块的信息。

 

        chunkSize是一个静态变量,它用于表示我们的数据块各个方向的大小(就是长宽高的大小)。

 

        最后我们有一个bool类型的变量,用于标志该数据块是否在每帧结束后更新。

 

        其次有三个函数,分别为GetBlock、UpdateChunk、RenderMesh。GetBlock用于获取对应位置的方块;UpdateChunk用于更新数据块的网格数据,然后将更新的数据提供给RenderMesh去渲染。

 

MeshData.cs脚本:

mc指令区块java 我的世界区块生成指令_数据块_03

    

        由于这个脚本只是为了存储数据,因此不必继承自MonoBehaviour。        

 

        前三个变量(vertices、triangles、uv)是用于渲染网格用的,后两个用于网格碰撞(colVetices、colTriangles)。

 

 

Block.cs脚本:

mc指令区块java 我的世界区块生成指令_mc指令区块java_04

 

        同样的,Block脚本也不需要继承自MonoBehaviour,并且它将会是所有方块的基类。主要用于存储方块所需信息。

 

        BlockData函数用于生成该方块的网格信息。

 

        我们来设想一下,假如我们的数据块是有25个方块组成的,那么在相邻的方块之间,有一些面就不必渲染出来,浪费系统资源。因此,接下来让我们进行剔除多余面数的处理。

 

        首先,我们需要一个函数去判断两个方块是否相邻,如果相邻则对各个方向的面进行剔除处理。

 

        在那之前,让我们先定一个表示方向的枚举(Block脚本中):

mc指令区块java 我的世界区块生成指令_数据库_05

 

    然后让我们为Block脚本添加判断的函数:

mc指令区块java 我的世界区块生成指令_mc指令区块java_06

 

        因为Block脚本是所有方块类的基类,所有我们在Block脚本中对IsSolid函数没有进行判断处理,全部返回true。

 

     现在让我们开始写一些我们的BlockData函数,在这个函数中,我们会根据当前方块对应方向上相邻方块的面进行剔除处理。   

 

mc指令区块java 我的世界区块生成指令_数据库_07

mc指令区块java 我的世界区块生成指令_数据_08

 

上图中的注释也说得很清楚了。举个栗子:判断当前方块顶上相邻的方块是否有底面,如果有则当前方块就不制作顶面,如下图分析所示:

mc指令区块java 我的世界区块生成指令_数据_09

PS:这个,,,图画得有点Low,希望大家能看懂它的意思。

mc指令区块java 我的世界区块生成指令_数据_10

 

接下来就是添加需要绘制对应的面的函数了。

 

上:

mc指令区块java 我的世界区块生成指令_mc指令区块java_11

 

上面的注释也说得很清楚了,但是克森还是给你们秀一秀我的美术功底。

mc指令区块java 我的世界区块生成指令_mc指令区块java_12

mc指令区块java 我的世界区块生成指令_mc指令区块java_13

 

其它的面就不细讲,代码如下:

 

下:

mc指令区块java 我的世界区块生成指令_mc指令区块java_14

 

东:

mc指令区块java 我的世界区块生成指令_数据_15

 

北:

mc指令区块java 我的世界区块生成指令_数据_16

 

南:

mc指令区块java 我的世界区块生成指令_数据_17

 

西:

mc指令区块java 我的世界区块生成指令_数据块_18

 

添加点之后,我们还要把这些点组合成三角形,因此在函数的最后调用了MeshData里的AddQuadTriangles(),由名字可知道,该函数用于添加面片,因此我们要用这四个顶点组合成一个面片,让我们回到MeshData中添加该函数:

 

mc指令区块java 我的世界区块生成指令_数据库_19

 

再给大家上一次克森的美术作品,相信大家都能理解了吧。

 

mc指令区块java 我的世界区块生成指令_数据块_20

 

接下来,让我们创建一个新的脚本,命名为“BlockAir”,让它继承值Block类,如下所示:

 

mc指令区块java 我的世界区块生成指令_mc指令区块java_21

 

Okey,现在让我们回到Chunk脚本中,开始添加方块进行测试咯。添加如下代码:

 

mc指令区块java 我的世界区块生成指令_数据块_22

 

首先声明两个变量,一个为MeshFilter(网格过滤器)类型,另一个为MeshCollider(网格碰撞器)类型。分别用于存储和设置我们对应数据库上的组件属性。

 

首先通过GetComponent方法获取对应物体上的对应的组件,然后初始化了我们的数据块(blocks),我们的数据块是一个16*16*16大小的正方体,里面由一堆小方块组成。当前数据库的小方块类型为 BlockAir。

 

然后修改了该数据库blocks[3, 5, 2]的方块数据,修改为Block类型方块。

 

最后调用UpdateChunk函数进行数据的更新。

 

好的,接下来让我们完善我们的UpdateChunk函数:

mc指令区块java 我的世界区块生成指令_mc指令区块java_23

 

首先声明一个类型为MeshData的变量。然后循环遍历blocks进行数据的更新(就是调用每一个方块的BlockData函数,而BlockData函数则是用来处理方块的网格数据,例如剔除面等等)。

 

最后就是调用RenderMesh函数将更新好的网格数据传入,然后进行网格的渲染。

 

那么,接下来让我们完成我们的RenderMesh函数:

mc指令区块java 我的世界区块生成指令_mc指令区块java_24

 

这个函数很简单,就是先调用Clear函数清除上一次网格的数据,然后重新设置即可。

 

这一篇只是简单的介绍怎么生成数据块,还没涉及到贴图和碰撞,所以在RenderMesh函数里只是更新了网格数据。下一篇则教大家如何添加贴图到数据块上。

 

说那么多,先看看效果。

 

首先创建一个空物体,然后为该物体添加Chunk脚本。你将会发现如下效果:

mc指令区块java 我的世界区块生成指令_数据块_25

 

该物体便会自动添加脚本中需要的那三个组件。对了,记得将positio属性置为0,不然你很可能看不到我们的方块,之后启动游戏,你将会看到如下效果:

mc指令区块java 我的世界区块生成指令_数据库_26

 

为什么方块会跑那去了,为什么会是紫色的呢?因为该方块没有材质,所以是紫色的。因为我们设置了该方块的位置为(3,5,2),那我们是在哪里设置的呢,其实是在这个地方设置了,如下图所示:

mc指令区块java 我的世界区块生成指令_数据块_27

 

这个时候你可以创建一个Cube物体去比对一下就知道了,下图演示:

mc指令区块java 我的世界区块生成指令_数据库_28

 

看来是这样的没错。好,接下来克森带大家来走一走这个运行时候的步骤:

 

    1.首先我们先进行实例化数据块,也就是Chunk类里面的blocks变量。

mc指令区块java 我的世界区块生成指令_数据_29

    

    2.调用UpdateChunk更新网格数据,在UpdateChunk中又调用了各个方块的BlockData函数生成网格数据。

mc指令区块java 我的世界区块生成指令_数据块_30

 

    3.在BlockData中我们对当前方块根据检测相邻方块进行剔除面操作。

mc指令区块java 我的世界区块生成指令_数据块_31

(由于函数太大,所以只截一小部分)

 

    4.最后调用RenderMesh对网格数据进行更新。

mc指令区块java 我的世界区块生成指令_数据库_32

 

在这里,为什么我们只生成了一个方块呢。因为要想生成方块,就必须调用BlockData函数,而BlockAir的BlockData函数里我们只做了一个返回,并没有生成网格数据,

mc指令区块java 我的世界区块生成指令_数据库_33

 

因此只生成了一个方块,也就是我们在后面修改的那个位置为(3,5,2)的方块,因为在Block类里BlockData函数已经生成了网格数据。

mc指令区块java 我的世界区块生成指令_数据_34

 

为什么克森不直接将所有的方块实例化为Block类呢,原因是这样做会造成数组下标越界。大家还记得下面这个函数吗?

mc指令区块java 我的世界区块生成指令_数据块_35

 

假设当前方块的y为16,这y+1便会越界。对于这个处理,后续的文章中会有介绍。敬请关注吧。

 

忘记说最后一点了,对于为什么会生成一个方块呢。原因就是在下图判断中,如果返回为false则制作该方块对应的面,然后我们的BlockAir的IsSolid函数返回的就是false,因此我们的方块就出来了。

mc指令区块java 我的世界区块生成指令_数据库_36

mc指令区块java 我的世界区块生成指令_数据块_37

 

好吧,克森的文章总是又臭又长,其实理解我的人都知道我是为了照顾更多的人。