Loading

洗牌算法学习笔记

洗牌算法

Quad vs Plane

  1. Quad:

    • 两个单位三角形组成的四边形
      image
  2. Plane

    • Plane是一个10行10列一共200个单位三角形组成的
      image

三角形越多也就意味着模型顶点越多,所制作的动画和模型也就越精细;同理,如果三角形少,那么模型顶点也就越少,所能制作的效果会很具有局限性。

这里只是制作地图上的单位瓦片,所以使用Quad即可。

Z-fighting 深度冲突

在项目中,你可以试着把一个白色立方体,放在一个红色立方体的内部,当它们的一个面足够接近时,会出现很奇怪的效果。

Z-fighting

原因是:2个模型足够接近时,系统不知道该渲染哪一个。

随机生成地图的制作

  1. 首先,我们需要创建一个瓦片预制体(让它来成为构成我们地图的一部分),还有地图的大小,为了方便管理,把它们统一放在一个父物体底下,还有瓦片与瓦片间生成的空隙
    [Header("瓦片预制体")]public GameObject tilePrefab;
    [Header("地图大小")]public Vector2 mapSize;
    [Header("瓦片生成的父物体")]public Transform mapHolder;
    [Header("瓦片与瓦片之间的空隙")][Range(0,1)]public float outlinePercent;
  1. 接着就是生成地图,这里构建的想法就是以地图中心点为原点,向四周进行扩充。
  /// <summary>
    /// 生成地图
    /// 以第一个点为原点,向四周排列组合
    /// </summary>
    private void GenerateMap()
    {
        for (int i = 0; i < mapSize.x; i++)
        {
            for (int j = 0; j < mapSize.y; j++)
            {
                //向左平移一半的长度,还要扣除瓦片一半的长度(单位长度为1),让原点为中心点
                Vector3 newPos = new Vector3(-mapSize.x / 2 + 0.5F + i, 0, -mapSize.x / 2 + 0.5F + j);
                //生成物体需要先旋转90度
                GameObject spawnTile = Instantiate(tilePrefab, newPos, Quaternion.Euler(90, 0, 0));
                spawnTile.transform.SetParent(mapHolder);
                spawnTile.transform.localScale *= (1 - outlinePercent);
                
                allTilesCoord.Add(new Coord(i,j));
            }
        }
    }
  1. 接着就是障碍物的生成,这里我们设置了一个存储瓦片坐标(x,y)的结构体进行存储,然后以位置的形式让障碍物进行生成。
/// <summary>
/// 坐标结构体
/// 存储瓦片的信息
/// </summary>
[System.Serializable]
public struct Coord
{
    public int x;
    public int y;

    public Coord(int x,int y)
    {
        this.x = x;
        this.y = y;
    }
}
  1. 在生成瓦片的最后进行随机障碍物生成(障碍物的预制体和障碍物的个数)
    ...
    [Header("障碍物")] public GameObject obsPrefab;
    [Header("生成障碍物的数量")] public int obsCount;
    public List<Coord> allTilesCoord = new List<Coord>();
    /// <summary>
    /// 生成地图
    /// 以第一个点为原点,向四周排列组合
    /// </summary>
    private void GenerateMap()
    {
        ...

        //生成障碍物
        for (int i = 0; i < obsCount; i++)
        {
            Coord randomCoord = allTilesCoord[UnityEngine.Random.Range(0, allTilesCoord.Count)];
            Vector3 newPos = new Vector3(-mapSize.x / 2 + 0.5F + randomCoord.x, 0, -mapSize.x / 2 + 0.5F + randomCoord.y);
            GameObject spawnObs = Instantiate(obsPrefab, newPos, Quaternion.identity);
            spawnObs.transform.SetParent(mapHolder);
            spawnObs.transform.localScale *= (1 - outlinePercent);
        }
    }

普通随机遇到的问题

通过上面的方法后,我们经过一番调试后,发现地图确实可以生成,障碍物也是,但是,当我们在6*6的地图中生成9个障碍物时,发现了一个问题。
image

障碍物的数量和我们设置的障碍物的数量并不一致,原因是有2个障碍物重复出现在了相同的地方;我们不可能通过调整地图大小来解决出错的概率,所以下面就要用到"洗牌算法"

洗牌算法介绍

基本思路:
比如说这有10张牌,我们随机抽取了一张,每次取值的范围就是Range(0,10)之间,这也就是导致我们随机到之前抽取过牌的元凶;
洗牌算法在于,我们第一次随机到一张卡牌后,会与集合中第一张牌的位置进行互换;互换完成后我们下一次遍历就只会在Range(1,10)之间进行随机抽取。以此来避免抽到相同的卡牌。

卡牌算法的实现

通过上面"卡牌算法"的思路,我们可以根据队列"先进先出"的特点进行完成。

  1. 首先,我们需要完成的是,卡片的互换,把随机得到的卡牌与数组中第一个卡片进行互换。最后并返回随机的数组。
    /// <summary>
    /// 对所有瓦片的位置重新排序后,返回瓦片集合
    /// </summary>
    /// <returns></returns>
    public static Coord[] ShuffleCoords(Coord[] _dateArray)
    {
        for (int i = 0; i < _dateArray.Length; i++)
        {
            int randomNum = Random.Range(i, _dateArray.Length);
            
            //Swap思想:AB互换
            //Coord temp = _dateArray[randomNum];
            // _dateArray[randomNum] = _dateArray[i];
            // _dateArray[i] = temp;
            (_dateArray[randomNum], _dateArray[i]) = (_dateArray[i], _dateArray[randomNum]);
        }

        return _dateArray;
    }
  1. 之后我们创建一个队列,把拿到的随机数组依次从头部取出,再从尾部放入;我们来重改一下我们之前的代码。
    ...
    private Queue<Coord> shuffledQueue;

    ...
    /// <summary>
    /// 生成地图
    /// 以第一个点为原点,向四周排列组合
    /// </summary>
    private void GenerateMap()
    {
        ...
        //生成障碍物
        shuffledQueue = new Queue<Coord>(Utilities.ShuffleCoords(allTilesCoord.ToArray()));
        for (int i = 0; i < obsCount; i++)
        {
            Coord randomCoord = GetRandomCoord();
            Vector3 newPos = new Vector3(-mapSize.x / 2 + 0.5F + randomCoord.x, 0.5F, -mapSize.x / 2 + 0.5F + randomCoord.y);
            GameObject spawnObs = Instantiate(obsPrefab, newPos, Quaternion.identity);
            spawnObs.transform.SetParent(mapHolder);
            spawnObs.transform.localScale *= (1 - outlinePercent);
        }
    }
        /// <summary>
    /// 移除第一个坐标并放入队列的最后
    /// 队列的先进先出的特性
    /// </summary>
    /// <returns></returns>
    private Coord GetRandomCoord()
    {
        Coord randomCoord = shuffledQueue.Dequeue();
        shuffledQueue.Enqueue(randomCoord);//将移除的元素放入队列的最后,保证队列的完整性。
        return randomCoord;
    }
...
  1. 最后我们在6*6的地图上生成33个障碍物,看看有没有什么问题。
    image

以上就是"卡牌算法"核心思路的全部实现了。

sharedMaterial

感觉障碍物的颜色有些单一,我们可以设置渐变色和随机高度,来增加障碍物的多样性。

  1. 设置渐变色的开始和结束的值,并设置随机高度。
    [Header("前场景颜色,和后场景颜色")] public Color foregroundColor, backgroundColor;
    [Header("随机高度")] public float minObsHeight, maxObsHeight;
  1. 再生成障碍物的地方添加颜色和高度。
    ...
    /// <summary>
    /// 生成地图
    /// 以第一个点为原点,向四周排列组合
    /// </summary>
    private void GenerateMap()
    {
        ...

        //生成障碍物
        shuffledQueue = new Queue<Coord>(Utilities.ShuffleCoords(allTilesCoord.ToArray()));
        for (int i = 0; i < obsCount; i++)
        {
            Coord randomCoord = GetRandomCoord();
            //随机高度
            float obsHeight = Mathf.Lerp(minObsHeight, maxObsHeight, UnityEngine.Random.Range(0F, 1F));
            Vector3 newPos = new Vector3(-mapSize.x / 2 + 0.5F + randomCoord.x, obsHeight / 2, -mapSize.x / 2 + 0.5F + randomCoord.y);
            GameObject spawnObs = Instantiate(obsPrefab, newPos, Quaternion.identity);
            spawnObs.transform.SetParent(mapHolder);
            spawnObs.transform.localScale = new Vector3(1 - outlinePercent,obsHeight,1 - outlinePercent);

            #region 渐变色

            MeshRenderer meshRenderer = spawnObs.GetComponent<MeshRenderer>();
            Material material = meshRenderer.material;
            //颜色渐变,传入Lerp时需要Y轴的百分比
            //因为Color.Lerp方法是0到1之间的一个数值
            float colorPercent = randomCoord.y / mapSize.y;
            material.color = Color.Lerp(foregroundColor,backgroundColor,colorPercent);
            meshRenderer.material = material;

            #endregion
        }
    }
    ...

接着我们调整一下,参数后,看看随机出来的地图如何。
image

优化为泛型

在日常项目中,类一般都是不确定的,所以我们一般使用泛型来保证每一种类型的卡牌都可以使用。

  /// <summary>
    /// 对所有瓦片的位置重新排序后,返回瓦片集合
    /// </summary>
    /// <returns></returns>
    public static T[] ShuffleCoords<T>(T[] _dateArray)
    {
        for (int i = 0; i < _dateArray.Length; i++)
        {
            int randomNum = Random.Range(i, _dateArray.Length);
            
            //Swap思想:AB互换
            //T temp = _dateArray[randomNum];
            // _dateArray[randomNum] = _dateArray[i];
            // _dateArray[i] = temp;
            (_dateArray[randomNum], _dateArray[i]) = (_dateArray[i], _dateArray[randomNum]);
        }

        return _dateArray;
    }
posted @ 2022-09-03 18:26  数学天才琪露诺  阅读(82)  评论(0编辑  收藏  举报