分享

Unity三消算法

 秋刀录 2020-09-14

前言

目前的游戏市场可谓日渐萧条,分分钟就逼死众多产品经理,三消游戏可谓一把温柔的弯刀,从女人这块获取到了一大片的市场,动不动就做个几百关,相互之间还有攀比,果然女人的钱还是更好赚一些的。当然,三消游戏确实有很大的优势,不浪费太多时间,不那么烧脑,简单有趣。但如果要做一款集成性很高的三消游戏,对于开发者而言也并不是那么简单,毕竟要用到很多算法,相比所谓的FPS、MMORPG有另一层次的深度。今天,就给大家简单分享一下三消中的核心算法,以及在Unity中的实现。

  • 消除算法图文详解

    • 三消算法首要实现的就是找到所有三个或三个以上的可消除对象,但直接找到这些对象是不太现实的,所以我们要将需求拆分。可不可以先获取所有图案相连的对象,进而在获取三消对象,这个算法也是众多三消游戏的一致实现。

      获取图案相同的所有相连对象
/// <summary>
    /// 填充相同Item列表
    /// </summary>
    public void FillSameItemsList(Item current)
    {
        //如果已存在,跳过
        if (sameItemsList.Contains (current))
            return;
        //添加到列表
        sameItemsList.Add (current);
        //上下左右的Item
        Item[] tempItemList = new Item[]{
            GetUpItem(current),GetDownItem(current),
            GetLeftItem(current),GetRightItem(current)};
        for (int i = 0; i < tempItemList.Length; i++) {
            //如果Item不合法,跳过
            if (tempItemList [i] == null)
                continue;
            if (current.currentSpr == tempItemList [i].currentSpr) {
                FillSameItemsList (tempItemList[i]);
            }
        }
    }
  • 获取图案相同的对象,一定要以一个对象为基准,这样才能够知道以谁为中心,以这个中心为核心横向及纵向的检测,检测到三个及以上的对象,那说明是可以消除的对象。

    以检测点为中心横向纵向检测
/// <summary>
    /// 填充待消除列表
    /// </summary>
    /// <param name="current">Current.</param>
    public void FillBoomList(Item current)
    {
        //计数器
        int rowCount = 0;
        int columnCount = 0;
        //临时列表
        List<Item> rowTempList = new List<Item> ();
        List<Item> columnTempList = new List<Item> ();
        ///横向纵向检测
        foreach (var item in sameItemsList) {

            //如果在同一行
            if (item.itemRow == current.itemRow) {
                //判断该点与Curren中间有无间隙
                bool rowCanBoom  = CheckItemsInterval(true,current,item);
                if (rowCanBoom) {
                    //计数
                    rowCount++;
                    //添加到行临时列表
                    rowTempList.Add (item);
                }
            }
            //如果在同一列
            if (item.itemColumn == current.itemColumn) {
                //判断该点与Curren中间有无间隙
                bool columnCanBoom  = CheckItemsInterval(false,current,item);
                if (columnCanBoom) {
                    //计数
                    columnCount++;
                    //添加到列临时列表
                    columnTempList.Add (item);
                }
            }
        }
        //横向消除
        bool horizontalBoom = false;
        //如果横向三个以上
        if (rowCount > 2) {
            //将临时列表中的Item全部放入BoomList
            boomList.AddRange (rowTempList);
            //横向消除
            horizontalBoom = true;
        }
        //如果纵向三个以上
        if (columnCount > 2) {
            if (horizontalBoom) {
                //剔除自己
                boomList.Remove (current);
            }
            //将临时列表中的Item全部放入BoomList
            boomList.AddRange (columnTempList);
        }
        //如果没有消除对象,返回
        if (boomList.Count == 0)
            return;
        //创建临时的BoomList
        List<Item> tempBoomList = new List<Item> ();
        //转移到临时列表
        tempBoomList.AddRange (boomList);
        //开启处理BoomList的协程
        StartCoroutine (ManipulateBoomList (tempBoomList));
    }
  • 当然也有特殊情况,在游戏开始时,如没有设置任何阻止同色的算法,即有可能出现这种状况,我们就要也采用一些算法去防止Bug出现。

    跳跃同行同列Bug
/// <summary>
    /// 检测两个Item之间是否有间隙(图案不一致)
    /// </summary>
    /// <param name="isHorizontal">是否是横向.</param>
    /// <param name="begin">检测起点.</param>
    /// <param name="end">检测终点.</param>
    private bool CheckItemsInterval(bool isHorizontal,Item begin,Item end)
    {
        //获取图案
        Sprite spr = begin.currentSpr;
        //如果是横向
        if (isHorizontal) {
            //起点终点列号
            int beginIndex = begin.itemColumn;
            int endIndex = end.itemColumn;
            //如果起点在右,交换起点终点列号
            if (beginIndex > endIndex) {
                beginIndex = end.itemColumn;
                endIndex = begin.itemColumn;
            }
            //遍历中间的Item
            for (int i = beginIndex + 1; i < endIndex; i++) {
                //异常处理(中间未生成,标识为不合法)
                if (allItems [begin.itemRow, i] == null)
                    return false;
                //如果中间有间隙(有图案不一致的)
                if (allItems [begin.itemRow, i].currentSpr != spr) {
                    return false;
                }
            }
            return true;
        } else {
            //起点终点行号
            int beginIndex = begin.itemRow;
            int endIndex = end.itemRow;
            //如果起点在上,交换起点终点列号
            if (beginIndex > endIndex) {
                beginIndex = end.itemRow;
                endIndex = begin.itemRow;
            }
            //遍历中间的Item
            for (int i = beginIndex + 1; i < endIndex; i++) {
                //如果中间有间隙(有图案不一致的)
                if (allItems [i, begin.itemColumn].currentSpr != spr) {
                    return false;
                }
            }
            return true;
        }
    }
  • 接下来就是消除处理了,采用一些动画之类,此处略过,我们来讲解下落算法。下落算法有很多,我们采用的是逐个入位法。

    逐个入位法下落
/// <summary>
    /// Items下落
    /// </summary>
    /// <returns>The drop.</returns>
    IEnumerator ItemsDrop()
    {
        isOperation = true;
        //逐列检测
        for (int i = 0; i < tableColumn; i++) {
            //计数器
            int count = 0;
            //下落队列
            Queue<Item> dropQueue = new Queue<Item> ();
            //逐行检测
            for (int j = 0; j < tableRow; j++) {
                if (allItems [j, i] != null) {
                    //计数
                    count++;
                    //放入队列
                    dropQueue.Enqueue(allItems [j, i]);
                }
            }
            //下落
            for (int k = 0; k < count; k++) {
                //获取要下落的Item
                Item current = dropQueue.Dequeue ();
                //修改全局数组(原位置情况)
                allItems[current.itemRow,current.itemColumn] = null;
                //修改Item的行数
                current.itemRow = k;
                //修改全局数组(填充新位置)
                allItems[current.itemRow,current.itemColumn] = current;
                //下落
                current.GetComponent<ItemOperation>().
                    CurrentItemDrop(allPos[current.itemRow,current.itemColumn]);
            }
        }

        yield return new WaitForSeconds (0.2f);

        StartCoroutine (CreateNewItem());
        yield return new WaitForSeconds (0.2f);
        AllBoom ();
    }
  • 最后生成新的对象

/// <summary>
    /// 生成新的Item
    /// </summary>
    /// <returns>The new item.</returns>
    public IEnumerator CreateNewItem()
    {
        isOperation = true;
        for (int i = 0; i < tableColumn; i++) {
            int count = 0;
            Queue<GameObject> newItemQueue = new Queue<GameObject> ();
            for (int j = 0; j < tableRow; j++) {
                if (allItems [j, i] == null) {
                    //生成一个Item
                    GameObject current = (GameObject)Instantiate(Resources.
                        Load<GameObject> (Util.ResourcesPrefab + Util.Item));
//                      ObjectPool.instance.GetGameObject (Util.Item, transform);
                    current.transform.parent = transform;
                    current.transform.position = allPos [tableRow - 1, i];
                    newItemQueue.Enqueue (current);
                    count++;
                }
            }
            for (int k = 0; k < count; k++) {
                //获取Item组件
                Item currentItem = newItemQueue.Dequeue ().GetComponent<Item>();
                //随机数
                int random = Random.Range (0, randomSprites.Length);
                //修改脚本中的图片
                currentItem.currentSpr = randomSprites [random];
                //修改真实图片
                currentItem.currentImg.sprite = randomSprites [random];
                //获取要移动的行数
                int r = tableRow - count + k;
                //移动
                currentItem.GetComponent<ItemOperation> ().ItemMove (r,i,allPos[r,i]);
            }
        }
        yield break;
    }
  • 当然如果两个图片交换后,无法消除要还原回原来位置

/// <summary>
    /// Item交换
    /// </summary>
    /// <returns>The exchange.</returns>
    /// <param name="dir">Dir.</param>
    IEnumerator ItemExchange(Vector2 dir)
    {
        //获取目标行列
        int targetRow = item.itemRow + System.Convert.ToInt32(dir.y);
        int targetColumn = item.itemColumn + System.Convert.ToInt32(dir.x);
        //检测合法
        bool isLagal = GameController.instance.CheckRCLegal (targetRow, targetColumn);
        if (!isLagal) {
            GameController.instance.isOperation = false;
            //不合法跳出
            yield break;
        }
        //获取目标
        Item target = GameController.instance.allItems [targetRow, targetColumn];
        //从全局列表中获取当前item,查看是否已经被消除,被消除后不能再交换
        Item myItem = GameController.instance.allItems [item.itemRow, item.itemColumn];
        if (!target || !myItem) {
            GameController.instance.isOperation = false;
            //Item已经被消除
            yield break;
        }
        //相互移动
        target.GetComponent<ItemOperation> ().ItemMove (item.itemRow, item.itemColumn, transform.position);
        ItemMove (targetRow, targetColumn, target.transform.position);
        //还原标志位
        bool reduction = false;
        //消除处理
        item.CheckAroundBoom();
        if (GameController.instance.boomList.Count == 0) {
            reduction = true;
        }
        target.CheckAroundBoom ();
        if (GameController.instance.boomList.Count != 0) {
            reduction = false;
        }
        //还原
        if (reduction) {
            //延迟
            yield return new WaitForSeconds (0.2f);
            //临时行列
            int tempRow, tempColumn;
            tempRow = myItem.itemRow;
            tempColumn = myItem.itemColumn;
            //移动
            myItem.GetComponent<ItemOperation> ().ItemMove (target.itemRow,
                target.itemColumn, target.transform.position);
            target.GetComponent<ItemOperation> ().ItemMove (tempRow,
                tempColumn, myItem.transform.position);
            //延迟
            yield return new WaitForSeconds (0.2f);
            //操作完毕
            GameController.instance.isOperation = false;
        } 
    }
  • 项目实践

    项目实践
    核心UML类图

结束语

当然这个项目是最基础版,只有简单的消除操作,如果加上道具特效,算法会更多,以后在慢慢琢磨品鉴。最后奉上源码,这个项目下落及生成新对象的延迟时间还没有细调,调好后玩起来比较流畅。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多