消消乐游戏原理(附部分代码)

在vs里看见一些方法上次修改是501天以前,乖乖,为了复试我也算是把陈谷子烂芝麻都翻出来读了。

一.存储

格子+格子哈希表[id],格子的属性:x,y,id,type

其中id=x*10+y

x=id/10

y=id%10

type代表格子属于什么类型,相同的3个相连可以消除

比如一个9*9的棋盘,x横轴,y是纵轴

8 18 28 38 48 58 68 78 88
7 17 27 37 47 57 67 77 87
6 16 26 36 46 56 66 76 86
5 15 25 35 45 55 65 75 85
4 14 24 34 44 54 64 74 84
3 13 23 33 43 53 63 73 83
2 12 22 32 42 52 62 72 82
1 11 21 31 41 51 61 71 81
0 10 20 30 40 50 60 70 80

为什么要这样设计呢?这样计算格子id是按照格子的坐标位置来的,x和y分别乘100就是格子在局部坐标系里的位置

二.生成一个没有三消情况的棋盘

生成写在二重循环里,每次加载一个格子都要进行一些判断↓

刷新的顺序是从下到上,从左到右,所以如果要判断有无可以直接消除的情况,只需看每个格子的左边和下边

所以我们检查格子的左边和左边的左边,以及下边和下边的下边,如果有两个重样的,记录下type,随机给type赋值的时候去掉

         
         
         
         
         
         

 

至此,一个没有三连的棋盘就生成了

三.点击,移动

给格子添加点击事件,记录如果玩家两次分别点了两个不一样的格子,那么

判断两个格子是不是紧挨着,否则无法移动

如果可以,则修改两者的目标位置为彼此,然后将其中一个格子添加到移动列表中,并在哈希中交换两个格子的数据信息

,最后,修改在格子管理器内的update中用于检查消除的key为true。

格子管理器update部分代码如下

    void Update()
    {
        if (RemoveKye) //目前仅需关注这个
        {
            if (IsMove.Count == 0)
            {
                RemoveGrid();
                RemoveKye = false;
            }
        }

        if (IsDownKey)//目前仅需关注这个
        {
            if (IsMove.Count == 0)
            {
                IsDownKey = false;
                ReSetNullGrid();
            }
        }

        if (PlayKey)
        {
            mTime -= Time.deltaTime* factor;
            if (mTime < 0)
            {
                totalSeconds++;
                mTime = 1;
                GameTime -= 1;
                TimeText.text = GameTime.ToString();
                timeCounter.UpdateCombo();
                TimeBar.size = GameTime / GameTimeAll;
            }
            if (totalSeconds==60 )
            {
                factor+=0.5f;
                totalSeconds = 0;
            }

            if (GameTime == 0)
            {
                Global.instance.SoundManager.PlayBGM("ScoreSubmit");
                PlayKey = false;
                ScoreUpdateTransform.gameObject.SetActive(true);
                ScoreUp.text = numScore.ToString();
                AverCombo.text = timeCounter.CalculateAverCombo().ToString("f2");
                ComboCommit.text = timeCounter.GetComboCommit();
                timeCounter.InitTimeCounter();

                foreach (int id in DicGrid.Keys)
                {
                    Destroy(DicGrid[id].transform.gameObject);
                }
                DicGrid.Clear();
            }
        }

    }

而检查消除的方法在移动列表为空时才会执行,所以,现在两个格子正在朝彼此的位置移动。

如何实现朝彼此移动?在格子的update中添加判断

void Update()
    {
        if (transform.localPosition.x == TragetPos.x && transform.localPosition.y == TragetPos.y)
        {
            if (GridController.Instance.IsMove.Contains(GridId))
            {
                GridController.Instance.IsMove.Remove(GridId);
            }
        }
        if (transform.localPosition.x != TragetPos.x)
        {
            if (transform.localPosition.x > TragetPos.x)
            {
                Vector2 pos = transform.localPosition;
                pos.x -= (Speed * Time.deltaTime);
                pos.x = pos.x < TragetPos.x ? TragetPos.x : pos.x;
                transform.localPosition = pos;
            }
            else
            {
                Vector2 pos = transform.localPosition;
                pos.x += (Speed * Time.deltaTime);
                pos.x = pos.x > TragetPos.x ? TragetPos.x : pos.x;
                transform.localPosition = pos;
            }
        }
        if (transform.localPosition.y != TragetPos.y)
        {
            if (transform.localPosition.y > TragetPos.y)
            {
                Vector2 pos = transform.localPosition;
                pos.y -= (Speed * Time.deltaTime);
                pos.y = pos.y < TragetPos.y ? TragetPos.y : pos.y;
                transform.localPosition = pos;
            }
            else
            {
                Vector2 pos = transform.localPosition;
                pos.y += (Speed * Time.deltaTime);
                pos.y = pos.y > TragetPos.y ? TragetPos.y : pos.y;
                transform.localPosition = pos;
            }
        }
    }

由此可见,当格子到达指定位置时,停止移动,移动列表也将会删除该格子

同时,格子管理器内的update方法检测到key为true同时移动列表为空,即所有格子到达目标位置,则开始判断棋盘中的三消情况

四.检查消除

遍历整个棋盘,判断每个格子的上面2个和右边两个是否三个相等,是就查重后添加到删除列表中(事实上上面也有些操作需要查重,基本操作不需提)

然后判断如果消除列表不为0,则顺着删除列表依次把格子删除,此时,三消完成

如果不能消除,则先判断鼠标点的那两个格子是否都存在,若都存在,则交换两个格子的位置,再换回来。

五.下落,生成新的格子

先下落

遍历棋盘,从下到上,从左往右,找到每列第一个空位,标记下来,再把上面所有非空格都挪下来。

代码示范

    public void AllGridMoveDown()
    {
        for (int x = 0; x < 7; x++)
        {
            int NullGridPos = -1;
            for (int y = 0; y < 7; y++)
            {
                if (!DicGrid.ContainsKey(GetGridID(x, y)))
                {
                    if (NullGridPos == -1)
                    {
                        NullGridPos = y;

                    }
                }
                else if (NullGridPos != -1)
                {
                    Vector2 pos = DicGrid[GetGridID(x, y)].transform.localPosition;
                    pos.y = NullGridPos * 100;
                    DicGrid[GetGridID(x, y)].TragetPos = pos;
                    if (!IsMove.Contains(GetGridID(x, NullGridPos)))
                    {
                        IsMove.Add(GetGridID(x, NullGridPos));
                    }
                    GridUnit t = DicGrid[GetGridID(x, y)];
                    DicGrid.Remove(GetGridID(x, y));
                    DicGrid.Add(GetGridID(x, NullGridPos), t);
                    t.GridId = GetGridID(x, NullGridPos);
                    NullGridPos++;
                }
            }
        }
        IsDownKey = true;
    }

等待移动列表为空,也就是上面的格子完成下落动作后,然后生成新格子,很简单,遍历棋盘,找到空位,按照生成棋盘的方法随机产生新格子即可。

产生格子完成后,重新检查能不能有三消,如果有,重复上述过程,如果没有,则检查地图内有无可以消除的隐藏情况,如果没有能消除的(死棋),则重新生成棋盘

bool ReStart()
    {
        for (int x = 0; x < xl; x++)
        {
            for (int y = 0; y < yl; y++)
            {
                if (
                    //尖括号种类消除
                    Comparator(GetGridID(x, y), GetGridID(x - 1, y - 1), GetGridID(x - 1, y + 1), GetGridID(x-1,y))  
                    || Comparator(GetGridID(x, y), GetGridID(x + 1, y + 1), GetGridID(x - 1, y + 1),GetGridID(x,y+1))
                    || Comparator(GetGridID(x, y), GetGridID(x + 1, y - 1), GetGridID(x + 1, y + 1),GetGridID(x+1,y))
                    || Comparator(GetGridID(x, y), GetGridID(x - 1, y - 1), GetGridID(x - 1, y + 1),GetGridID(x-1,y))
                    //感叹号种类消除
                    || Comparator(GetGridID(x, y), GetGridID(x - 2, y), GetGridID(x - 3, y),GetGridID(x-1,y))
                    || Comparator(GetGridID(x, y), GetGridID(x + 2, y), GetGridID(x + 3, y),GetGridID(x+1,y))
                    || Comparator(GetGridID(x, y), GetGridID(x, y - 2), GetGridID(x, y - 3),GetGridID(x,y-1))
                    || Comparator(GetGridID(x, y), GetGridID(x, y + 2), GetGridID(x, y + 3),GetGridID(x,y+1))
                    //小拐弯种类
                    || Comparator(GetGridID(x, y), GetGridID(x - 1, y - 1), GetGridID(x - 1, y - 2),GetGridID(x-1,y))
                    || Comparator(GetGridID(x, y), GetGridID(x + 1, y - 1), GetGridID(x + 1, y - 2),GetGridID(x+1,y))

                    || Comparator(GetGridID(x, y), GetGridID(x - 1, y + 1), GetGridID(x - 1, y + 2),GetGridID(x-1,y))
                    || Comparator(GetGridID(x, y), GetGridID(x + 1, y + 1), GetGridID(x + 1, y + 2),GetGridID(x+1,y))

                    || Comparator(GetGridID(x, y), GetGridID(x - 1, y - 1), GetGridID(x - 2, y - 1),GetGridID(x,y-1))
                    || Comparator(GetGridID(x, y), GetGridID(x - 1, y + 1), GetGridID(x - 2, y + 1),GetGridID(x,y+1))

                    || Comparator(GetGridID(x, y), GetGridID(x + 1, y - 1), GetGridID(x + 2, y - 1),GetGridID(x,y-1))
                    || Comparator(GetGridID(x, y), GetGridID(x + 1, y + 1), GetGridID(x + 2, y + 1),GetGridID(x,y+1))
                    )
                {
                    return true;
                }
            }
        }

        return false;
    }

找个图画一下,大致就是把各种移动一下就能消除的情况都列出来判断,代码不怕麻烦。

 

想到的一些其他功能

 消消乐游戏原理(附部分代码)

1.空格子,空格子也是一种格子,只是无法移动且无法消除,在格子下落和三消判断的那里添加相关注意就可以了

2.提示道具,跟上面检查有没有可以消除的隐藏位置原理相同。

3.地图编辑器,消消乐这种关卡游戏肯定需要地图编辑器,要不一个一个关卡手改也怪麻烦的

将关卡的相关信息,比如尺寸,空格子位置,道具数量等,专门定义一个类的对象来保存,在编辑器内做相关修改,完事用json导出到文本就可以了。需要的时候按照文本名字作为关卡名再用json导回。

 

我几乎不玩消消乐类游戏,应该还有其他很多的花样,不过在弄懂原理之后,修改起来也就有的放矢了。