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閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,762評論 0 33
  • 回溯算法 回溯法:也稱為試探法,它并不考慮問題規模的大小,而是從問題的最明顯的最小規模開始逐步求解出可能的答案,并...
    fredal閱讀 13,704評論 0 89
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,719評論 18 399
  • 想了一整年的用輸出倒逼自己的輸入,讓自己的知識更加成體系化,網絡化。懶惰于我,終于在一年多的今天利用午休小憩來寫上...
    最早的時光_最好的時光閱讀 249評論 0 0
  • 廣泛持久的學習助我們認識這個世界,發現更多可能性;認真精專的學習讓我們變得不可替代,能以更好的姿態迎接未來。 ...
    蘭小魚的海底世界閱讀 358評論 0 0