1. 程式人生 > >三消遊戲演算法圖文詳解

三消遊戲演算法圖文詳解

之前小編查詢發的資料小編本人也不太理解,所以這裡又找了一個講的個很詳細的文章,整理過後發出來大家一起分享!

消除演算法圖文詳解

三消演算法首要實現的就是找到所有三個或三個以上的可消除物件,但直接找到這些物件是不太現實的,所以我們要將需求拆分。可不可以先獲取所有圖案相連的物件,進而在獲取三消物件,這個演算法也是眾多三消遊戲的一致實現。

獲取圖案相同的所有相連物件

獲取圖案相同的所有相連物件

// 填充相同Item列表
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]); } } }

獲取圖案相同的物件,一定要以一個物件為基準,這樣才能夠知道以誰為中心,以這個中心為核心橫向及縱向的檢測,檢測到三個及以上的物件,那說明是可以消除的物件。

以檢測點為中心橫向縱向檢測

以檢測點為中心橫向縱向檢測

// 填充待消除列表
public void FillBoomList(Item current)
{
    //計數器
int rowCount = 0; int columnCount = 0; //臨時列表 List rowTempList = new List (); List columnTempList = new List (); //橫向縱向檢測 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 tempBoomList = new List (); //轉移到臨時列表 tempBoomList.AddRange (boomList); //開啟處理BoomList的協程 StartCoroutine (ManipulateBoomList (tempBoomList)); }

當然也有特殊情況,在遊戲開始時,如沒有設定任何阻止同色的演算法,即有可能出現這種狀況,我們就要也採用一些演算法去防止Bug出現。

跳躍同行同列Bug

跳躍同行同列Bug

/// <summary>
/// 檢測兩個Item之間是否有間隙(圖案不一致)
/// </summary>
/// <param name="isHorizontal">是否是橫向</param>
/// <param name="begin">檢測起點</param>
/// <param name="end">監測終點</param>
/// <returns></returns>
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 dropQueue = new Queue();
            //逐行檢測
            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().
                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 newItemQueue = new Queue();
            for (int j = 0; j < tableRow; j++)
            {
                if (allItems[j, i] == null)
                {
                    //生成一個Item
                    GameObject current = (GameObject)Instantiate(Resources.
                    Load(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();
                //隨機數
                int random = Random.Range(0, randomSprites.Length);
                //修改指令碼中的圖片
                currentItem.currentSpr = randomSprites[random];
                //修改真實圖片
                currentItem.currentImg.sprite = randomSprites[random];
                //獲取要移動的行數
                int r = tableRow - count + k;
                //移動
                currentItem.GetComponent().ItemMove(r, i, allPos[r, i]);
            }
        }
        yield break;
    }

當然如果兩個圖片交換後,無法消除要還原回原來位置

這裡寫程式碼片    /// <summary>
    /// Item交換
    /// </summary>
    /// <param name="dir">The exchange</param>
    /// <returns>Dir</returns>
    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().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().ItemMove(target.itemRow,
            target.itemColumn, target.transform.position);
            target.GetComponent().ItemMove(tempRow,
            tempColumn, myItem.transform.position);
            //延遲
            yield return new WaitForSeconds(0.2f);
            //操作完畢
            GameController.instance.isOperation = false;
        }
    }

專案實踐

這裡寫圖片描述

專案實踐

核心UML類圖

核心UML類圖

結束語
當然這個專案是最基礎版,只有簡單的消除操作,如果加上道具特效,演算法會更多,以後在慢慢琢磨品鑑。最後奉上原始碼,這個專案下落及生成新物件的延遲時間還沒有細調,調好後玩起來比較流暢。