思考的救赎(二):三消游戏功能完善
来源:互联网 发布:端口映射和端口转发 编辑:程序博客网 时间:2024/06/06 00:00
一、引言
昨天我已经在上一篇博客里实现了三消游戏的初始化和消除功能,然而并没有实现一般三消游戏都有的重力降落、自动消除、随机补充的功能。这篇博客就是研究如何实现这些功能的。
让我们总结下我们将要实现的三消游戏的功能:
1. 初始化
2. 消除
3. 重力降落
4. 自动消除(重力降落后可能引起的消除操作)
5. 随机补充(补充消除格子)
6. 额外功能:重新开始、计算分数
在上一篇博客我们已经实现了第 1 步和第 2 步,现在我们开始接下来功能的实现吧。
ps:
想要了解上一篇博客内容的同学可以点击这里:
思考的救赎(一):三消游戏实现探索
想要获取本系列博客代码的同学可以点击这里:
wangying2016/ThreeClearGame
哈哈哈,先来看下我们的最终效果展示吧:
二、开始前的一点架构
我们要实现如上 Gif 图的效果,我们就要知道整个游戏的流程是什么样子的:
这里我特意花了一个流程图(T_T 虽然有点不雅观),但是已经可以非常详细的说明我们一次消除过程中,到底程序做了什么。
也就是说,我们在消除两个格子之后,重力降落、自动消除和随机补充这三个行为是同步进行的(也就是一个结束了下一个才能开始的)。
并且由于自动消除的行为发生,我们会不停的随机补充新的格子,然后又可能会发生自动消除行为,最后又会触发我们的随机补充行为…
直到我们的当前网格里,已经没有三消可能了,才会停止下来。
这个过程的模拟,是由 SOUI 的定时器实现的。
1. 当我们成功消除格子之后,我们设置消除格子为删除皮肤,然后刷新界面。
// 刷新网格void CMainDlg::RefreshNet(std::vector<std::vector<Grid>> vecNet){ // 删除计时器 KillTimer(TIMER_LAND); // 刷新网格 SOUI::STileView* pTileView = FindChildByName2<SOUI::STileView>(L"tv_net"); assert(pTileView); CNetAdapter* pAdapter = static_cast<CNetAdapter*>(pTileView->GetAdapter()); if (pAdapter != nullptr) { pAdapter->UpdateNet(vecNet); pAdapter->notifyDataSetChanged(); } // 设置分数 SetScore(); // 定时计算降落 SetTimer(TIMER_LAND, 700);}
2. 刷新界面的函数自动调用 SetTimer 函数 1s 后计时响应检查是否需要重力降落,如果是,则重力降落(每次只冒泡一个删除按钮)执行完后继续调用刷新界面的函数,直到所有的重力降落逻辑结束;当没有需要重力降落的格子了,就需要进行自动消除的逻辑,检查是否产生了自动消除操作,如果产生了自动消除操作,则调用随机补充函数,如果没有产生自动消除操作,我们就可以安心退出此逻辑了(证明当前界面已经没有三消可能了)。
以下是我们的计时器响应逻辑:
void CMainDlg::OnTimer(UINT_PTR idEvent){ // 降落计算 if (idEvent == TIMER_LAND) { // 清除计数器 KillTimer(TIMER_LAND); // 是否有需要降落的点 if (m_NetMatrix.LandOneGrid()) { RefreshNet(m_NetMatrix.GetNet()); } // 没有需要降落的点 else { // 是否产生了自动消除 // 如果没有产生自动消除,则意味着操作结束,可以开始随机补充了 if (!m_NetMatrix.AutoDelete()) { // 随机补充一行 m_NetMatrix.AddRandomGrid(); } } }}
三、重力降落
重力降落,即当我们消除了指定区域的格子之后,在空白区域上方的格子能够自动掉落下来。
这个功能归根结底,是类似于冒泡的一种移动操作:
我们要做的,就是将每一列上的空白按钮往上移动,直到在它上面没有其他的非空白按钮为止
而幸而有我们上述讨论的架构设计,我们只需要一次冒泡一个删除按钮即可(也就是说我们的重力降落函数会在定时器内多次调用,直到没有需要降落的格子了为止)
这里就是我们的重力降落的函数:
// 重力降落bool NetMatrix::LandOneGrid(){ // 标记是否发生了重力降落 bool bNeedLand = false; // 遍历每列 for (int col = 0; col < NET_COL_NUMBER; ++col) { // 从下开始遍历每行 for (int row = NET_ROW_NUMBER - 1; row >= 1; --row) { // 让删除点冒泡上去 if (m_vecNet[row][col].status == Grid_Delete && m_vecNet[row - 1][col].status != Grid_Delete) { // 标记发生了重力沉降 bNeedLand = true; // 依次挪动删除点上去 int count = row; do { std::swap(m_vecNet[count][col], m_vecNet[count - 1][col]); } while (--count >= 1 && m_vecNet[count - 1][col].status != Grid_Delete); break; } } } return bNeedLand;}
简单讲解下代码:
1. 首先,遍历每一列,这是最外层循环
2. 然后,在每一列中遍历每一行,从底部往上遍历
3. 最后,中间的 do while 循环依次将一个空白按钮往上移动
这里的 bNeedLand 变量是用来记录是否发生了重力降落事件的,如果没有发生重力降落事件,我们就认为重力降落部份结束。
四、自动消除
当我们重力降落完了之后,就有可能发生新的三消可能,此时我们就需要被动的消除这些格子。
在上一篇博客里,我们在实现用户主动消除格子的时候,定义了一个 GetCancelPoints 函数,这个函数可以根据一个操作点,将其四周与其图像相同的格子返回,而这里,我们也可以利用这个函数来返回我们想要的消除点。
以下是我们自动消除的代码:
// 被动产生消除(重力降落)bool NetMatrix::AutoDelete(){ // 记录有连续情况的点 std::vector<PosPoint> points; // 行检查是否有连续 3 个 for (int i = 0; i < NET_ROW_NUMBER; ++i) { for (int j = 0; j < NET_COL_NUMBER - 2; ++j) { if (m_vecNet[i][j].status == m_vecNet[i][j + 1].status && m_vecNet[i][j + 1].status == m_vecNet[i][j + 2].status && m_vecNet[i][j].status != Grid_Delete) { points.push_back(PosPoint(i, j)); j += 3; } } } // 列检查是否有连续 3 个 for (int j = 0; j < NET_COL_NUMBER; ++j) { for (int i = 0; i < NET_ROW_NUMBER - 2; ++i) { if (m_vecNet[i][j].status == m_vecNet[i + 1][j].status && m_vecNet[i + 1][j].status == m_vecNet[i + 2][j].status && m_vecNet[i][j].status != Grid_Delete) { points.push_back(PosPoint(i, j)); i += 3; } } } // 如果没有找到这样的点,则证明没有被动产生消除 if (points.size() == 0) return false; // 根据点获取消除点 std::set<PosPoint> cancelPoints; for (int i = 0; i < points.size(); ++i) { std::vector<PosPoint> tempPoints = GetCancelPoints(points[i], m_vecNet); cancelPoints.insert(tempPoints.begin(), tempPoints.end()); } // 计算分数 m_nScore += cancelPoints.size(); // 输出自动消除点的信息 SOUI::SStringW strAutoCancelPoints, strAutoCancelMsg; for (auto it = cancelPoints.begin(); it != cancelPoints.end(); ++it) { SOUI::SStringW strPoint; strPoint.Format(L"(%d, %d)", it->row, it->col); strAutoCancelPoints += strPoint; } strAutoCancelMsg.Format(L"执行自动消除:%s", strAutoCancelPoints); MyHelper::Instance()->WriteLog(strAutoCancelMsg); // 设置以上点为删除状态 for (auto it = cancelPoints.begin(); it != cancelPoints.end(); ++it) { m_vecNet[it->row][it->col].status = Grid_Delete; } m_event->RefreshNet(m_vecNet); return true;}
简单介绍下代码逻辑:
1. 首先获取产生了三消可能的点
2. 根据上述查找到的点,调用 GetCancelPoints 函数将其周围相同图像的点全部找出来
3. 输出消除消息
4. 计算分数(默认消除一个格子得 1 分 ^_^)
5. 设置消除点为删除皮肤
6. 刷新界面(这是非常重要的一步,除了刷新了界面,还隐含调用了定时器检查是否触发了重力降落,从而引起一系列循环逻辑)
五、随机补充
当我们完成了自动消除之后,并且已经保证没有再有能够重力降落和自动消除的点了之后,我们就需要随机补充格子了。
随机补充其实非常简单,我们只需要每次掉用的时候在第一行补充格子即可,然后调用刷新界面的函数,其就会自动调用重力降落的逻辑,从而又触发了:
刷新界面 -> 重力降落 -> 自动消除 -> 随机补充
的循环,直到,我们补充的格子无法被自动消除之后结束。
这是我们的随机补充的代码:
// 随机补充格子void NetMatrix::AddRandomGrid(){ // m_vecNet 已经成为了一个删除按钮全部在上面, // 图像按钮全部在下面的矩阵了,此时需要做的是: // 1. 在首行随机插入点 // 2. 执行一次沉降 // 遍历首行 for (int col = 0; col < NET_COL_NUMBER; ++col) { if (m_vecNet[0][col].status == Grid_Delete) { int random = MyHelper::Instance()->Random(4); switch (random) { case 0: m_vecNet[0][col].status = Grid_Star; break; case 1: m_vecNet[0][col].status = Grid_Heart; break; case 2: m_vecNet[0][col].status = Grid_Sword; break; case 3: m_vecNet[0][col].status = Grid_Shield; break; } } } m_event->RefreshNet(m_vecNet);}
代码逻辑非常简单,这里不作过多叙述了。
六、总结
至此,我们的三消游戏已经全部完成了:)
基本上的功能都有了,剩下的也就是优化下逻辑和代码,检查下用户的边界性操作之类的云云。
花了三天的时候,从一开始的构思、架构,到后面的初始化算法的实现、消除算法的时候,再到最后的重力降落、自动消除和随机补充的循环,都是我一个石头一个石头摸着走过来的。当最终看到了效果之后,开心的简直要哭出来!
这就是编程的乐趣!
这就是创造的乐趣!
这就是思考的魅力!
我爱编程,我爱创造,我爱思考!
明天还得继续!
To be Stronger!
- 思考的救赎(二):三消游戏功能完善
- 思考的救赎(一):三消游戏实现探索
- hexo(三)功能完善
- Cocos2d-x 3.2 lua飞机大战开发实例(三)道具的掉落,碰撞检测,声音,分数,爆炸效果,完善游戏的功能细节
- 三消游戏(二)
- 对以前扫雷游戏功能进一步完善的版本.
- 完善你的Blog功能(二) 制作自定义CSDNBLOG皮肤
- 完善你的Blog功能(一)
- 自动生成摘要(二):丰富和完善功能
- Go游戏服务器开发的一些思考(十七):IO游戏同步(三)
- [体感游戏]关于体感游戏的一些思考(三) --- 射击
- 评阅本科毕业设计的一些思考(待完善)
- Go游戏服务器开发的一些思考(十四):IO游戏同步(二)
- [体感游戏]关于体感游戏的一些思考(二) --- POV和基本场景
- 游戏服务器引擎开发笔记之三——游戏服务器引擎和逻辑的功能的相互调用(二)
- linux,打造自己的专属迷你小linux(二)完善小linux功能,使其丰满
- Android网络:开发浏览器(三)——功能完善之历史功能
- 功能最完善的uboot
- JAVA 反射详解以及基础使用
- 左值和右值的区别(以a++和++a为例)
- HDU 6214 Smallest Minimum Cut (最小割)
- PAT乙级-1001A+B和C (15)
- 一小时上手Numpy
- 思考的救赎(二):三消游戏功能完善
- Git可视化工具SourceTree的使用
- backbone学习第一天
- 3.EmBitz个性化设置
- Java练习
- nginx中文文档
- hotspot虚拟机中java对象的分配与布局和访问
- JSP三大指令
- OpenCV笔记