Unity三消算法

前言

目前的游戏市场可谓日渐萧条,分分钟就逼死众多产品经理,三消游戏可谓一把温柔的弯刀,从女人这块获取到了一大片的市场,动不动就做个几百关,相互之间还有攀比,果然女人的钱还是更好赚一些的。当然,三消游戏确实有很大的优势,不浪费太多时间,不那么烧脑,简单有趣。但如果要做一款集成性很高的三消游戏,对于开发者而言也并不是那么简单,毕竟要用到很多算法,相比所谓的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类图

结束语

当然这个项目是最基础版,只有简单的消除操作,如果加上道具特效,算法会更多,以后在慢慢琢磨品鉴。最后奉上源码,这个项目下落及生成新对象的延迟时间还没有细调,调好后玩起来比较流畅。(内附价值50美元的资源包喔😉😉😉)链接: https://pan.baidu.com/s/1gfqpDmz 密码: n9r9

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,662评论 0 33
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,508评论 0 89
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,295评论 18 399
  • 想了一整年的用输出倒逼自己的输入,让自己的知识更加成体系化,网络化。懒惰于我,终于在一年多的今天利用午休小憩来写上...
    最早的时光_最好的时光阅读 230评论 0 0
  • 广泛持久的学习助我们认识这个世界,发现更多可能性;认真精专的学习让我们变得不可替代,能以更好的姿态迎接未来。 ...
    兰小鱼的海底世界阅读 326评论 0 0