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


  • 获取图案相同的所有相连对象
1 /// <summary>
 2     /// 填充相同Item列表
 3     /// </summary>
 4     public void FillSameItemsList(Item current)
 5     {
 6         //如果已存在,跳过
 7         if (sameItemsList.Contains (current))
 8             return;
 9         //添加到列表
10         sameItemsList.Add (current);
11         //上下左右的Item
12         Item[] tempItemList = new Item[]{
13             GetUpItem(current),GetDownItem(current),
14             GetLeftItem(current),GetRightItem(current)};
15         for (int i = 0; i < tempItemList.Length; i++) {
16             //如果Item不合法,跳过
17             if (tempItemList [i] == null)
18                 continue;
19             if (current.currentSpr == tempItemList [i].currentSpr) {
20                 FillSameItemsList (tempItemList[i]);
21             }
22         }
23     }



 

  • 获取图案相同的对象,一定要以一个对象为基准,这样才能够知道以谁为中心,以这个中心为核心横向及纵向的检测,检测到三个及以上的对象,那说明是可以消除的对象。

    以检测点为中心横向纵向检测
1 /// <summary>
 2 /// 填充待消除列表
 3 /// </summary>
 4 /// <param name="current">Current.</param>
 5 public void FillBoomList(Item current)
 6 {
 7     //计数器
 8     int rowCount = 0;
 9     int columnCount = 0;
10     //临时列表
11     List<Item> rowTempList = new List<Item> ();
12     List<Item> columnTempList = new List<Item> ();
13     ///横向纵向检测
14     foreach (var item in sameItemsList) {
15 
16         //如果在同一行
17         if (item.itemRow == current.itemRow) {
18             //判断该点与Curren中间有无间隙
19             bool rowCanBoom  = CheckItemsInterval(true,current,item);
20             if (rowCanBoom) {
21                 //计数
22                 rowCount++;
23                 //添加到行临时列表
24                 rowTempList.Add (item);
25             }
26         }
27         //如果在同一列
28         if (item.itemColumn == current.itemColumn) {
29             //判断该点与Curren中间有无间隙
30             bool columnCanBoom  = CheckItemsInterval(false,current,item);
31             if (columnCanBoom) {
32                 //计数
33                 columnCount++;
34                 //添加到列临时列表
35                 columnTempList.Add (item);
36             }
37         }
38     }
39     //横向消除
40     bool horizontalBoom = false;
41     //如果横向三个以上
42     if (rowCount > 2) {
43         //将临时列表中的Item全部放入BoomList
44         boomList.AddRange (rowTempList);
45         //横向消除
46         horizontalBoom = true;
47     }
48     //如果纵向三个以上
49     if (columnCount > 2) {
50         if (horizontalBoom) {
51             //剔除自己
52             boomList.Remove (current);
53         }
54         //将临时列表中的Item全部放入BoomList
55         boomList.AddRange (columnTempList);
56     }
57     //如果没有消除对象,返回
58     if (boomList.Count == 0)
59         return;
60     //创建临时的BoomList
61     List<Item> tempBoomList = new List<Item> ();
62     //转移到临时列表
63     tempBoomList.AddRange (boomList);
64     //开启处理BoomList的协程
65     StartCoroutine (ManipulateBoomList (tempBoomList));
66 }
  • 当然也有特殊情况,在游戏开始时,如没有设置任何阻止同色的算法,即有可能出现这种状况,我们就要也采用一些算法去防止Bug出现。
  • unity 算法面试 unity常用算法_游戏_02


  • 跳跃同行同列Bug



  • 1 /// <summary>
     2 /// 检测两个Item之间是否有间隙(图案不一致)
     3 /// </summary>
     4 /// <param name="isHorizontal">是否是横向.</param>
     5 /// <param name="begin">检测起点.</param>
     6 /// <param name="end">检测终点.</param>
     7 private bool CheckItemsInterval(bool isHorizontal,Item begin,Item end)
     8 {
     9  //获取图案
    10  Sprite spr = begin.currentSpr;
    11  //如果是横向
    12  if (isHorizontal) {
    13      //起点终点列号
    14      int beginIndex = begin.itemColumn;
    15      int endIndex = end.itemColumn;
    16      //如果起点在右,交换起点终点列号
    17      if (beginIndex > endIndex) {
    18          beginIndex = end.itemColumn;
    19          endIndex = begin.itemColumn;
    20      }
    21      //遍历中间的Item
    22      for (int i = beginIndex + 1; i < endIndex; i++) {
    23          //异常处理(中间未生成,标识为不合法)
    24          if (allItems [begin.itemRow, i] == null)
    25              return false;
    26          //如果中间有间隙(有图案不一致的)
    27          if (allItems [begin.itemRow, i].currentSpr != spr) {
    28              return false;
    29          }
    30      }
    31      return true;
    32  } else {
    33      //起点终点行号
    34      int beginIndex = begin.itemRow;
    35      int endIndex = end.itemRow;
    36      //如果起点在上,交换起点终点列号
    37      if (beginIndex > endIndex) {
    38          beginIndex = end.itemRow;
    39          endIndex = begin.itemRow;
    40      }
    41      //遍历中间的Item
    42      for (int i = beginIndex + 1; i < endIndex; i++) {
    43          //如果中间有间隙(有图案不一致的)
    44          if (allItems [i, begin.itemColumn].currentSpr != spr) {
    45              return false;
    46          }
    47      }
    48      return true;
    49  }
    50 }


  •  

  • 接下来就是消除处理了,采用一些动画之类,此处略过,我们来讲解下落算法。下落算法有很多,我们采用的是逐个入位法。

  • unity 算法面试 unity常用算法_unity 算法面试_03


  • 逐个入位法下落



  • 1 /// <summary>
     2 /// Items下落
     3 /// </summary>
     4 /// <returns>The drop.</returns>
     5 IEnumerator ItemsDrop()
     6 {
     7   isOperation = true;
     8   //逐列检测
     9   for (int i = 0; i < tableColumn; i++) {
    10       //计数器
    11       int count = 0;
    12       //下落队列
    13       Queue<Item> dropQueue = new Queue<Item> ();
    14       //逐行检测
    15       for (int j = 0; j < tableRow; j++) {
    16           if (allItems [j, i] != null) {
    17               //计数
    18               count++;
    19               //放入队列
    20               dropQueue.Enqueue(allItems [j, i]);
    21           }
    22       }
    23       //下落
    24       for (int k = 0; k < count; k++) {
    25           //获取要下落的Item
    26           Item current = dropQueue.Dequeue ();
    27           //修改全局数组(原位置情况)
    28           allItems[current.itemRow,current.itemColumn] = null;
    29           //修改Item的行数
    30           current.itemRow = k;
    31           //修改全局数组(填充新位置)
    32           allItems[current.itemRow,current.itemColumn] = current;
    33           //下落
    34           current.GetComponent<ItemOperation>().
    35               CurrentItemDrop(allPos[current.itemRow,current.itemColumn]);
    36       }
    37   }
    38 
    39   yield return new WaitForSeconds (0.2f);
    40 
    41   StartCoroutine (CreateNewItem());
    42   yield return new WaitForSeconds (0.2f);
    43   AllBoom ();
    44 }


  •  

  • 最后生成新的对象

  • 1 /// <summary>
     2 /// 生成新的Item
     3 /// </summary>
     4 /// <returns>The new item.</returns>
     5 public IEnumerator CreateNewItem()
     6 {
     7  isOperation = true;
     8  for (int i = 0; i < tableColumn; i++) {
     9      int count = 0;
    10      Queue<GameObject> newItemQueue = new Queue<GameObject> ();
    11      for (int j = 0; j < tableRow; j++) {
    12          if (allItems [j, i] == null) {
    13              //生成一个Item
    14              GameObject current = (GameObject)Instantiate(Resources.
    15                  Load<GameObject> (Util.ResourcesPrefab + Util.Item));
    16 //                        ObjectPool.instance.GetGameObject (Util.Item, transform);
    17              current.transform.parent = transform;
    18              current.transform.position = allPos [tableRow - 1, i];
    19              newItemQueue.Enqueue (current);
    20              count++;
    21          }
    22      }
    23      for (int k = 0; k < count; k++) {
    24          //获取Item组件
    25          Item currentItem = newItemQueue.Dequeue ().GetComponent<Item>();
    26          //随机数
    27          int random = Random.Range (0, randomSprites.Length);
    28          //修改脚本中的图片
    29          currentItem.currentSpr = randomSprites [random];
    30          //修改真实图片
    31          currentItem.currentImg.sprite = randomSprites [random];
    32          //获取要移动的行数
    33          int r = tableRow - count + k;
    34          //移动
    35          currentItem.GetComponent<ItemOperation> ().ItemMove (r,i,allPos[r,i]);
    36      }
    37  }
    38  yield break;
    39 }


  •  

  • 当然如果两个图片交换后,无法消除要还原回原来位置

  • 1 /// <summary>
     2 /// Item交换
     3 /// </summary>
     4 /// <returns>The exchange.</returns>
     5 /// <param name="dir">Dir.</param>
     6 IEnumerator ItemExchange(Vector2 dir)
     7 {
     8   //获取目标行列
     9   int targetRow = item.itemRow + System.Convert.ToInt32(dir.y);
    10   int targetColumn = item.itemColumn + System.Convert.ToInt32(dir.x);
    11   //检测合法
    12   bool isLagal = GameController.instance.CheckRCLegal (targetRow, targetColumn);
    13   if (!isLagal) {
    14       GameController.instance.isOperation = false;
    15       //不合法跳出
    16       yield break;
    17   }
    18   //获取目标
    19   Item target = GameController.instance.allItems [targetRow, targetColumn];
    20   //从全局列表中获取当前item,查看是否已经被消除,被消除后不能再交换
    21   Item myItem = GameController.instance.allItems [item.itemRow, item.itemColumn];
    22   if (!target || !myItem) {
    23       GameController.instance.isOperation = false;
    24       //Item已经被消除
    25       yield break;
    26   }
    27   //相互移动
    28   target.GetComponent<ItemOperation> ().ItemMove (item.itemRow, item.itemColumn, transform.position);
    29   ItemMove (targetRow, targetColumn, target.transform.position);
    30   //还原标志位
    31   bool reduction = false;
    32   //消除处理
    33   item.CheckAroundBoom();
    34   if (GameController.instance.boomList.Count == 0) {
    35       reduction = true;
    36   }
    37   target.CheckAroundBoom ();
    38   if (GameController.instance.boomList.Count != 0) {
    39       reduction = false;
    40   }
    41   //还原
    42   if (reduction) {
    43       //延迟
    44       yield return new WaitForSeconds (0.2f);
    45       //临时行列
    46       int tempRow, tempColumn;
    47       tempRow = myItem.itemRow;
    48       tempColumn = myItem.itemColumn;
    49       //移动
    50       myItem.GetComponent<ItemOperation> ().ItemMove (target.itemRow,
    51           target.itemColumn, target.transform.position);
    52       target.GetComponent<ItemOperation> ().ItemMove (tempRow,
    53           tempColumn, myItem.transform.position);
    54       //延迟
    55       yield return new WaitForSeconds (0.2f);
    56       //操作完毕
    57       GameController.instance.isOperation = false;
    58   } 
    59 }


  •  

  • 项目实践


    项目实践



    核心UML类图

结束语

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