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出现。
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 }
-
接下来就是消除处理了,采用一些动画之类,此处略过,我们来讲解下落算法。下落算法有很多,我们采用的是逐个入位法。
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 }
- 当然也有特殊情况,在游戏开始时,如没有设置任何阻止同色的算法,即有可能出现这种状况,我们就要也采用一些算法去防止Bug出现。
- 项目实践
结束语
当然这个项目是最基础版,只有简单的消除操作,如果加上道具特效,算法会更多,以后在慢慢琢磨品鉴。最后奉上源码,这个项目下落及生成新对象的延迟时间还没有细调,调好后玩起来比较流畅。(内附价值50美元的资源包喔😉😉😉)链接: https://pan.baidu.com/s/1gfqpDmz 密码: n9r9