三消游戏算法图文详解

转载自:https://blog.csdn.net/sinat_39291423/article/details/78089828

之前小编查询发的资料小编本人也不太理解,所以这里又找了一个讲的个很详细的文章,整理过后发出来大家一起分享!

消除算法图文详解

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

获取图案相同的所有相连对象

// 填充相同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

/// <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类图

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

http://pan.baidu.com/s/1hrBfXdU 密码:6uqw

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Otsu算法是一种图像分割算法,用于将图像分成背景和前景两部分。它基于图像的灰度直方图,通过寻找一个阈值,将图像中的像素分为两个类别。 以下是Otsu算法的代码详解: ```python import numpy as np import cv2 def otsu_threshold(image): # 将图像转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 计算灰度直方图 hist = cv2.calcHist([gray], [0], None, [256], [0, 256]) # 归一化直方图 hist_norm = hist.ravel() / hist.max() # 初始化类间方差和最佳阈值 best_threshold = 0 max_variance = 0 # 遍历所有可能的阈值 for threshold in range(256): # 计算背景和前景的像素数目 background_pixels = np.sum(hist_norm[:threshold]) foreground_pixels = np.sum(hist_norm[threshold:]) # 计算背景和前景的平均灰度值 background_mean = np.sum(np.arange(threshold) * hist_norm[:threshold]) / background_pixels foreground_mean = np.sum(np.arange(threshold, 256) * hist_norm[threshold:]) / foreground_pixels # 计算类间方差 variance = background_pixels * foreground_pixels * (background_mean - foreground_mean) ** 2 # 更新最大类间方差和最佳阈值 if variance > max_variance: max_variance = variance best_threshold = threshold # 应用最佳阈值进行二值化 _, binary = cv2.threshold(gray, best_threshold, 255, cv2.THRESH_BINARY) return binary # 读取图像 image = cv2.imread('image.jpg') # 应用Otsu算法进行图像分割 binary_image = otsu_threshold(image) # 显示结果 cv2.imshow('Original Image', image) cv2.imshow('Binary Image', binary_image) cv2.waitKey(0) cv2.destroyAllWindows() ``` 以上代码使用Python和OpenCV库实现了Otsu算法。首先,将彩色图像转换为灰度图像,然后计算灰度直方图并归一化。接下来,遍历所有可能的阈值,并计算背景和前景的像素数目以及平均灰度值。根据类间方差的计算公式,更新最大类间方差和最佳阈值。最后,应用最佳阈值进行二值化,得到分割后的二值图像。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值