消消乐游戏原理(附部分代码)
在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导回。
我几乎不玩消消乐类游戏,应该还有其他很多的花样,不过在弄懂原理之后,修改起来也就有的放矢了。