[知乎转载]Unity实现Minecraft(一)(二)

转载自 Cat Nine
Unity实现Minecraft(二)——优化原理 - 知乎 (zhihu.com)(Unity实现Minecraft(一)——原理介绍 - 知乎)

最近专门研究了下程序化生成地形,其中最典型的莫过于Minecraft中的地形生成,于是打算自己实现一下。闲话少说,游戏引擎:(2021.2) 。

①先来简单介绍原理:利用一个二重循环生成一个网格地图,然后对于每一个网格的位置坐标生成一个噪声值(噪声值介于0和1之间),然后乘以一个自己定义的高度值Height,就会得到一个(0,Height)的值;然后再用一个循环从0到Height,每个高度放置方块即可。

注意:我们规定每一个单位的方块叫Block,后面也可以叫它体素(Voxel)。

②在Unity模拟一下过程:

using System.Collections.Generic;
using UnityEngine;
using System;

namespace Test
{
public class ProceduralTerrain : MonoBehaviour
{
[SerializeField] private float noiseScale = 0.05f;
[SerializeField] private int worldSize = 6;
[SerializeField] private int worldHeight = 10;
[SerializeField] private int waterHeight = 4;
//用于保存每个Block类型的颜色
private Dictionary<t_BlockType, Color> colorDic = new Dictionary<t_BlockType, Color>();

    <span class="k">private</span> <span class="k">void</span> <span class="n">Awake</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//将Block的材质和颜色一一对应

colorDic.Add(t_BlockType.Grass_Dirt, Color.green);//草方块对应绿色
colorDic.Add(t_BlockType.Dirt, Color.yellow);//泥土对应黄色
colorDic.Add(t_BlockType.Water, Color.blue);//水对应蓝色
colorDic.Add(t_BlockType.Sand, Color.gray);//沙对应灰色
}
private void Start()
{
GenerateTerrainData();

    <span class="p">}</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">t_BlockData</span><span class="p">&gt;</span> <span class="n">worldBlockData</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">t_BlockData</span><span class="p">&gt;();</span>
    <span class="c1">//生成地形所需数据,就是求出一个每个坐标的高度值

void GenerateTerrainData()
{
t_BlockType blockType;
for (int i = 0; i < worldSize; i++)
{
for (int j = 0; j < worldSize; j++)
{
float noiseValue = Mathf.PerlinNoise((transform.position.x + i) noiseScale, (transform.position.z + j) noiseScale);
int groundPosition = Mathf.RoundToInt(noiseValue * worldHeight);
for (int y = 0; y < worldHeight; y++)
{
blockType = t_BlockType.Dirt;
if (y > groundPosition)
{
if (y < waterHeight)
{
blockType = t_BlockType.Water;
}
else
{
blockType = t_BlockType.Air;
}

                    <span class="p">}</span>
                    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">y</span> <span class="p">==</span> <span class="n">groundPosition</span> <span class="p">&amp;&amp;</span> <span class="n">y</span> <span class="p">&lt;</span> <span class="n">waterHeight</span><span class="p">)</span>
                    <span class="p">{</span>
                        <span class="n">blockType</span> <span class="p">=</span> <span class="n">t_BlockType</span><span class="p">.</span><span class="n">Sand</span><span class="p">;</span>
                    <span class="p">}</span>
                    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">y</span> <span class="p">==</span> <span class="n">groundPosition</span><span class="p">)</span>
                    <span class="p">{</span>
                        <span class="n">blockType</span> <span class="p">=</span> <span class="n">t_BlockType</span><span class="p">.</span><span class="n">Grass_Dirt</span><span class="p">;</span>
                    <span class="p">}</span>

                    <span class="n">t_BlockData</span> <span class="n">temp</span> <span class="p">=</span> <span class="k">new</span> <span class="n">t_BlockData</span><span class="p">(</span><span class="k">new</span> <span class="n">Vector3</span><span class="p">(</span><span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">x</span> <span class="p">+</span> <span class="n">i</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">z</span> <span class="p">+</span> <span class="n">j</span><span class="p">),</span> <span class="n">blockType</span><span class="p">);</span>
                    <span class="n">worldBlockData</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">temp</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">//用Gizmos来模拟生成的地形

private void OnDrawGizmos()
{
if (worldBlockData.Count > 0)
{
foreach (var block in worldBlockData)
{
if (block.blockType == t_BlockType.Air)
{
continue;
}
Gizmos.color = colorDic[block.blockType];
Gizmos.DrawCube(block.blockPosition,new Vector3(1f,1f,1f));
}
}

    <span class="p">}</span>
<span class="p">}</span>

<span class="k">internal</span> <span class="k">class</span> <span class="nc">t_BlockData</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Vector3</span> <span class="n">blockPosition</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">t_BlockType</span> <span class="n">blockType</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">t_BlockData</span><span class="p">(</span><span class="n">Vector3</span> <span class="n">position</span><span class="p">,</span><span class="n">t_BlockType</span> <span class="n">type</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">blockPosition</span> <span class="p">=</span> <span class="n">position</span><span class="p">;</span>
        <span class="n">blockType</span> <span class="p">=</span> <span class="n">type</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="k">internal</span> <span class="k">enum</span> <span class="n">t_BlockType</span>
<span class="p">{</span>
    <span class="n">None</span><span class="p">,</span>
    <span class="n">Air</span><span class="p">,</span>
    <span class="n">Grass_Dirt</span><span class="p">,</span>
    <span class="n">Dirt</span><span class="p">,</span>
    <span class="n">Sand</span><span class="p">,</span>
    <span class="n">Water</span>
<span class="p">};</span>

}

③总结:这样就简单完成了一个模拟过程,但这样有一个很明显的问题,一旦我们的地形过大就会导致游戏帧率下降,影响游戏体验,所以我们就来讲讲真正的Minecraft地形实现是从哪些方面来进行优化。

接上一篇文章,我们讲述了Minecraft地形生成原理,并在Unity中简单实现了出来。但我们可以看出这样的地形存在很多不必要的Block,我们在Minecraft中根本不会看到那些地面以下的Block,也就是只渲染那些我们玩家能够看到的那些处于地面上的Block,像下面这张图一样:

当然,不渲染不代表它不存在,我们仍然需要保留其数据。也就我们需要维护两个List,一个存放每个Block的数据(包括位置信息,Block类型等),另一个就是存放真正需要被渲染的Block的实体。(为了保证游戏的拓展性,我们需要将数据和渲染相分离,这样也是为了游戏性能更好,详细说明后面也会讲到)对于那些不需要在开始被渲染的Block,我们只是将其数据保存,以便后序需要渲染它时才把它渲染出来。

细心的读者可能会发现,上面那张图片还有一个神奇之处:

地面上的Block也应该是一个完整的方块,为什么它的有些面没了。这是因为游戏为了性能的进一步提升,采用了面优化技术。就是将某些玩家永远看不见的面给剔除,这里简单介绍一下在游戏中如何实现面优化:我们知道在Unity中,一个面由2个三角面构成,这两个三角面又是4个顶点的组合按照左手螺旋的顺序来构成的。所以要想单独渲染一个面,我们就得提供顶点和三角形。所以我们实现Minecraft的地形时采用的动态Mesh生成,而不是把方块做成Prefab,然后实例化。针对不需要渲染的面,我们就不给它提供顶点或者三角面即可。(建议读者可以去了解下Unity中的Mesh)

下篇文章就正式开始制作我们的Minecraft了。

2 个赞

方块人狠狠赞啦!!