程序洞穴生成一(Procedural Cave Generation)

这边博客是我自己学习过程中写的第一篇文章,一方面是想与大家分享好的知识,另一方面是为了监督自己不断的学习,希望能给大家带来好的内容分享。

这篇文章的内容是搬运自Youtube上的Sebastian大佬的视频,这个Up主有很多的Unity、Shader、数学、算法等的视频教学,内容都很不错而且讲解非常的清楚,英语不错的小伙伴可以去Youtube上去学习,视频第一课的链接https://www.youtube.com/watch?v=v7yyZZjF1z4&list=PLFt_AvWsXl0eZgMK_DT5_biRkWXftAOf9。不太想看英文教程的下面我用文字的方式来分享。

首先定义出所需要的变量

    public int width;       //地图的宽度
    public int height;      //地图的高度

    public string seed;     //随机种子
    public bool useRandomSeed;      //是否使用随机中的Flag

    [Range(0, 100)]
    public int randomFillPercent;   //填充百分比

    int[,] map;     //地图的信息

变量的意义我也都写在上面了。

首先我们要做的就是生成随机的单个Cube通过不同颜色来表现的地图效果。然后我们定义一个GenerateMap的函数用来生成地图信息。初始化二维数组map。第一步生成纯随机无规律的地图信息,通过系统的Random函数来为map数组生成随机的0或者1这里1代表Wall(墙)。设置好宽度高度和填充百分比,此时生成的地图信息是随机,无规律的,如果1用黑色的块,0用白色的块来生成出来地图将会是这样的。

我这里width=128,height=72,RandomFillPercent = 45

这样的地图当然是没法用的,所以我们要给地图信息上规则,平滑地图信息。编写一个SmoothMap函数。平滑地图的原理是这样的,两层循环遍历二维数组map,然后判断一个地图信息点的周围的墙的数量,然后设定一个阈值,墙的数量大于这个阈值就将这个位置的地图信息设置为1(墙),如果小于这个阈值,那么就将地图设置为0(空),然后循环多次下来地图的效果就慢慢呈现出来了,视频作者循环的次数是5次,然后阈值是4,实现出来的效果如下图所示。

现在生成的地图是不是就有点像模像样了呢。

最后再讲一个如果生成显示在Scene视图中,直接贴代码,比较简单很好理解

    /// <summary>
    /// 绘制
    /// </summary>
    private void OnDrawGizmos()
    {
        if (map != null)
        {
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    Gizmos.color = (map[x, y] == 1) ? Color.black : Color.white;
                    Vector3 pos = new Vector3(-width / 2 + x + .5f, 0, -height / 2 + y + .5f);
                    Gizmos.DrawCube(pos, Vector3.one);
                }
            }
        }
    }

运行,然后调节Scene视图的视角是从上往下,然后设置成正交视角就能看到效果啦,视频作者还编写了一个鼠标点击就更换地图的随机生成,当然想随机生成还需要设置系统的随机种子。随机种子的生成是获取运行时间的字符串,然后拿到字符串的哈希码作为随机种子传进去,这样每次生成的地图就可以保证不重复了。

完整代码:

    public int width;       //地图的宽度
    public int height;      //地图的高度

    public string seed;     //随机种子
    public bool useRandomSeed;      //是否使用随机中的Flag

    [Range(0, 100)]
    public int randomFillPercent;   //填充百分比

    int[,] map;     //地图的信息


    private void Start()
    {
        GenerateMap();    
    }

    private void Update()
    {
        //测试点击鼠标生成新的地图
        if (Input.GetMouseButtonDown(0))
        {
            GenerateMap();
        }
    }

    /// <summary>
    /// 生成地图
    /// </summary>
    void GenerateMap()
    {
        map = new int[width, height];
        RandomFillMap();

        for (int i = 0; i < 5; i++)
        {
            SmoothMap();
        }
    }

    /// <summary>
    /// 随机填充地图
    /// </summary>
    void RandomFillMap()
    {
        if (useRandomSeed)
        {
            seed = Time.time.ToString();
        }

        System.Random pseudoRandom = new System.Random(seed.GetHashCode());
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                //将周围包括起来
                if (x == 0 || x == width - 1 || y == 0 || y == height - 1)
                {
                    map[x, y] = 1;
                }
                else
                {
                    map[x, y] = (pseudoRandom.Next(0, 100) < randomFillPercent) ? 1 : 0;
                }
            }
        }
    }

    /// <summary>
    /// 让地图变得平滑
    /// </summary>
    void SmoothMap()
    {
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                int neighbourWallTiles = GetSurroundingWallCount(x, y);

                ///这里的数字4和不等式的等于号是否要加,自己可以尝试,然后看运行的效果。
                if (neighbourWallTiles > 4)
                {
                    map[x, y] = 1;
                }
                else if (neighbourWallTiles < 4)
                {
                    map[x, y] = 0;
                }
            }
        }
    }

    /// <summary>
    /// 获取周围的墙的数量,(这里的墙指的就是Map【x,y】 = 0)
    /// </summary>
    /// <param name="gridX">坐标x</param>
    /// <param name="gridY">坐标y</param>
    /// <returns></returns>
    int GetSurroundingWallCount(int gridX, int gridY)
    {
        int wallCount = 0;
        for (int neighbourX = gridX - 1; neighbourX <= gridX + 1; neighbourX++)
        {
            for (int neighbourY = gridY - 1; neighbourY <= gridY + 1; neighbourY++)
            {
                if (neighbourX >= 0 && neighbourX < width && neighbourY >= 0 && neighbourY < height)
                {
                    if (neighbourX != gridX || neighbourY != gridY)
                    {
                        wallCount += map[neighbourX, neighbourY];
                    }
                }
                else
                {
                    wallCount++;
                }
            }
        }

        return wallCount;
    }

    /// <summary>
    /// 绘制
    /// </summary>
    private void OnDrawGizmos()
    {
        if (map != null)
        {
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    Gizmos.color = (map[x, y] == 1) ? Color.black : Color.white;
                    Vector3 pos = new Vector3(-width / 2 + x + .5f, 0, -height / 2 + y + .5f);
                    Gizmos.DrawCube(pos, Vector3.one);
                }
            }
        }
    }

第一部分不多也比较好理解,就先讲到这里了,想往后学习的可以去上面的视频链接找到视频作者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值