打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
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类图

结束语

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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
并发策略-CAS算法
C# WPF如何关闭通过父窗口打开的所有子窗口
经典排序算法 – 插入排序Insertion sort
算法入门---二分查找算法
C#函数的参数传递方式1(值传递与地址传递)
本周算法:背包问题
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服