连连看
前言
连连看游戏规则:只要将相同的两张牌用三根以内的直线连在一起就可以消除,规则简单容易上手。游戏速度节奏快,画面清晰可爱,适合细心的玩家。
--连连看百度百科
三丨级丨狗文章C++是如何从代码到游戏的
中非常有趣的讲述了从代码到游戏的过程,在整体结构上,描述的非常棒粉丝留言是这样调侃的
传说中的先画个大体线条,然后填充亿点点细节,然后就简单的完成了
这不亚于告诉你飞机长啥样,然后让你去造飞机,要知道,就是那亿点点细节让很多人望而止步,逻辑之难,难于上青天。
没关系,本期带你看那亿点点细节
正文
游戏设计
「整体结构」
首先呢是红色包围的整个地图,外圈辅助,说白了就是一个10*10数组,外圈为0.
整体结构肯定都晓得,游戏设计就看怎么消除相同的图片 消除相同的图片分三种情况
0个转折点
就是点击的两张图片在一条直线上,并且中间没有障碍物才可以通行
1个转折点
点击的两张图片不在一条直线上,那么这两张图片可能存在的转折点有两个,在对角区域
2个转折点
这个就比较复杂,两个转折点可能的情况太多,只能枚举出来,然后循环判断
「有没有什么好点的办法」
我们可以先确定一个转折点,将这个转折点当成点击的图片,去和另一张图片匹配,这样我们就可以用判断「1个转折点」的方法+判断「0个转折点」的方法,来实现「两个转折点」复杂的情况,「这个时候只需要罗列出其中一个转折点可能出现的地方就行了」
在一个转折点的判断里面用的就是0个转折点的方法
游戏三部曲
InitGame(); //初始化游戏
while (1)
{
UpdateGame();//数据更新
DrawGame(); //绘制游戏
}
「Easyx制作游戏,三部曲整起」
初始化游戏
全局定义地图大小
#define MAP_ROW 10
#define MAP_COL 10
#define MAP_SIZE ( MAP_ROW - 2 ) * ( MAP_COL - 2 ) //地图大小
初始时给地图赋值
/*初始化变量*/
int tempMap[MAP_SIZE] = { 0 };
/*随机生成地图数据*/
for (int i = 0; i < MAP_SIZE / 2; i++)
{
tempMap[i] = rand() % g_randSize + 1; /*随机[1 - 10]*/
tempMap[MAP_SIZE / 2 + i] = tempMap[i];
}
/*打乱数据*/
for (int i = 0; i < MAP_SIZE; i++)
{
int index = rand() % MAP_SIZE;
if (index != i)
{
int temp = tempMap[i];
tempMap[i] = tempMap[index];
tempMap[index] = temp;
}
}
/*转成一个二维地图数组*/
int index = 0;
for (int i = 0; i < MAP_ROW; i++)
{
for (int j = 0; j < MAP_COL; j++)
{
if (i == 0 || i == MAP_ROW - 1 || j == 0 || j == MAP_COL - 1)
{
g_map[i][j] = 0;
}
else
{
g_map[i][j] = tempMap[index++];
}
}
}
「因为图片要两两配对,只能先用一维数组赋值,再转成二维数组储存」
绘制游戏
这里是将图片资源贴在地图指定的地方,不同的框架绘制的方法不一,但都大体相同,双缓冲处理闪屏问题,绘制的界面好看与否取决于GUI,这里比较low,暂且这样
BeginBatchDraw();
/*绘制方块*/
for (int i = 0; i < MAP_ROW; i++)
{
for (int j = 0; j < MAP_COL; j++)
{
putimage(j * 46, i * 56, 46, 56, &g_block, 0, 56, SRCCOPY);
}
}
/*绘制动物*/
for (int i = 1; i < MAP_ROW - 1; i++)
{
for (int j = 1; j < MAP_COL - 1; j++)
{
/*不等于0,绘制动物,等于0,什么都不绘制 动物图片大小为39 * 39 */
if (g_map[i][j] != 0)
{
putimage(j * 46 + 1.5, i * 56 + 6, 39, 39, &g_animal, 39, (g_map[i][j] - 1) * 39, SRCAND);
putimage(j * 46 + 1.5, i * 56 + 6, 39, 39, &g_animal, 0, (g_map[i][j] - 1) * 39, SRCPAINT);
}
}
}
setlinecolor(GREEN); //设置线条颜色
setlinestyle(PS_SOLID | PS_ENDCAP_FLAT, 3); //画线样式为宽度 3 像素的实线,端点为平坦的
if (g_clickIndex > 0)
{
rectangle(g_targetPos[0].X * 46 + 4, g_targetPos[0].Y * 56 + 4, g_targetPos[0].X * 46 + 36, g_targetPos[0].Y * 56 + 44);
}
EndBatchDraw();
数据更新
「在数据更新之前,先定义好两个位置变量存储鼠标点击位置」
int g_clickIndex = 0; //点击的下标
COORD g_targetPos[2] = { -1, -1 }; //点击的两个位置
数据更新时获取鼠标点击的位置转化为地图坐标
MOUSEMSG msg = GetMouseMsg();
int row = msg.y / 56;
int col = msg.x / 45;
switch (msg.uMsg) /*msg.uMsg得到一个鼠标消息*/
{
case WM_LBUTTONDOWN: /*鼠标左键按下*/
g_targetPos[g_clickIndex].X = col; /*下标:0,1,超出了就越界*/
g_targetPos[g_clickIndex].Y = row;
break;
这个时候你就拥有了玩家点击两张图片在地图上的位置
对图片进行消除
/*点击一张图片两下,不消除,直接返回*/
if (g_targetPos[0].X == g_targetPos[1].X && g_targetPos[0].Y == g_targetPos[1].Y)
return;
/*点击两张图片,判断鼠标两个坐标的位置值不相等,直接返回*/
if (g_map[g_targetPos[0].Y][g_targetPos[0].X] != g_map[g_targetPos[1].Y][g_targetPos[1].X])
{
return;
}
/*进行消除操作*/
if (HavePath(g_targetPos) == true)
{
for (int i = 0; i < 2; i++)
{
g_map[g_targetPos[i].Y][g_targetPos[i].X] = 0;
}
g_score -= 2;
}
g_targetPos[0]
和g_targetPos[1]
是玩家点击两张图片的位置,先判断「点击的是不是同一张图片」,再判断「点击的图片在地图中的值是否相同」,也就是玩家眼中图片是否相同
调用消除函数HavePath(g_targetPos)
「消除函数的原型我是这样定义的」
bool HavePath(COORD targetPos[]);
bool HavePathCorner0(COORD p1, COORD p2); //没有转折点
bool HavePathCorner1(COORD p1, COORD p2); //一个转折点
bool HavePathCorner2(COORD p1, COORD p2); //两个转折点
顺便把0个、1个、2个转折点的函数原型也列举出来
我们看下「消除函数」
bool HavePath(COORD targetPos[])
{
/*没有转折点*/
if (HavePathCorner0(targetPos[0], targetPos[1]) == true)
{
return true;
}
/*有一个转折点*/
if (HavePathCorner1(targetPos[0], targetPos[1]) == true)
{
return true;
}
/*有两个转折点*/
if (HavePathCorner2(targetPos[0], targetPos[1]) == true)
{
return true;
}
return false;
}
好像串起来了,数据更新里面把坐标传给HavePath函数
,HavePath函数
判断传入的坐标对应的图片到底有可不可以消除,可以消除就返回true
,不可以false
.然后分别讨论每个情况,是一个转折点还是几个转折点。
「看起来是不是变简单了」
我么接着往下看
0个转折点
bool HavePathCorner0(COORD p1, COORD p2)
{
/*判断两张图片是否在一条直线上,不是,直接返回false*/
if (p1.X != p2.X && p2.Y != p1.Y)
return false;
/*为什么求最大值,最小值
因为:给的参数,不清楚你的p1,p2的坐标谁最大*/
int min = 0, max = 0;
/*竖向:判断竖向是否都为空*/
if (p1.X == p2.X)
{
min = p1.Y < p2.Y ? p1.Y : p2.Y;
max = p1.Y > p2.Y ? p1.Y : p2.Y;
for (min++; min < max; min++)
{
if (g_map[min][p1.X] != 0)
return false;
}
}
/*横向:判断横向是否都为空*/
if (p1.Y == p2.Y)
{
min = p1.X < p2.X ? p1.X : p2.X;
max = p1.X > p2.X ? p1.X : p2.X;
for (min++; min < max; min++)
{
if (g_map[p1.Y][min] != 0)
return false;
}
}
return true;
}
看起来挺复杂的,其实就是「横向纵向判断」(你不晓得两个点是横向还是纵向),从某一点到某一点是否有障碍物,有返回false,没有继续判断,所有判断完成之后返回true,说明两点之间没有障碍物。
1个转折点
bool HavePathCorner1(COORD p1, COORD p2)
{
//找到转折点
COORD temp[2]; //两个目标是转折点
temp[0].X = p1.X;
temp[0].Y = p2.Y;
temp[1].X = p2.X;
temp[1].Y = p1.Y;
//判断第一个转折点是否连通
if (g_map[temp[0].Y][temp[0].X] == 0)
{
if (HavePathCorner0(p1, temp[0]) == true && HavePathCorner0(p2, temp[0]) == true)
{
line(p1.X * 46 + 23, p1.Y * 56 + 28, temp[0].X * 46 + 23, temp[0].Y * 56 + 28);
line(p2.X * 46 + 23, p2.Y * 56 + 28, temp[0].X * 46 + 23, temp[0].Y * 56 + 28);
return true;
}
}
//判断第二个转折点是否连通
if (g_map[temp[1].Y][temp[1].X] == 0)
{
if (HavePathCorner0(p1, temp[1]) == true && HavePathCorner0(p2, temp[1]) == true)
{
line(p1.X * 46 + 23, p1.Y * 56 + 28, temp[1].X * 46 + 23, temp[1].Y * 56 + 28);
line(p2.X * 46 + 23, p2.Y * 56 + 28, temp[1].X * 46 + 23, temp[1].Y * 56 + 28);
return true;
}
}
return false;
}
先把转折点(对角点)所在的地方定义好,然后先「判断转折点是否是障碍物」,不是的话就可以「调用0个转折点」判断转折点和图片位置是否连通
「好嘛,一个调用一个,一套一套的」
2个转折点
「两个转折」点是以「一个转折点」为基础构建「0个转折点」和「1个转折点」的方法,让程序变得简单,看下代码:
bool HavePathCorner2(COORD p1, COORD p2)
{
COORD temp;
//任取一目标,往上找
for (temp.Y = p1.Y - 1, temp.X = p1.X; temp.Y >= 0; temp.Y--)
{
//如果往上找直到找到不为空的就结束,或者找到顶了
if (g_map[temp.Y][temp.X] != 0)
break;
//可以判断temp点到p1点是一路为空的,只需要判断p2点和temp是否为只有一个转折点就可以
if (HavePathCorner1(p2, temp))
return true;
}
//任取一目标,往下找
for (temp.Y = p1.Y + 1, temp.X = p1.X; temp.Y < MAP_ROW; temp.Y++)
{
//如果往下找直到找到不为空的就结束,或者找到顶了
if (g_map[temp.Y][temp.X] != 0)
break;
//可以判断temp点到p1点是一路为空的,只需要判断p2点和temp是否为只有一个转折点就可以
if (HavePathCorner1(p2, temp))
return true;
}
//任取一目标,往左找
for (temp.X = p1.X - 1, temp.Y = p1.Y; temp.X >= 0; temp.X--)
{
//如果往左找直到找到不为空的就结束,或者找到顶了
if (g_map[temp.Y][temp.X] != 0)
break;
//可以判断temp点到p1点是一路为空的,只需要判断p2点和temp是否为只有一个转折点就可以
if (HavePathCorner1(p2, temp))
return true;
}
//任取一目标,往右找
for (temp.X = p1.X + 1, temp.Y = p1.Y; temp.X < MAP_COL; temp.X++)
{
//如果往下找直到找到不为空的就结束,或者找到顶了
if (g_map[temp.Y][temp.X] != 0)
break;
//可以判断temp点到p1点是一路为空的,只需要判断p2点和temp是否为只有一个转折点就可以
if (HavePathCorner1(p2, temp))
return true;
}
return false;
}
这个是看着复杂,其实就是上下左右四个方向都遍历一遍,for循环遍历,找可能存在的转折点,怎么判断?,将另「外一个图片点」和这个「转折点」做1个转折点判断
「就这样,大功告成,连连看游戏想想也不难嘛,都是一套接一套,思路搞清楚了,代码是事?」
游戏人性化
我点了两张图片,图片突然就没了,我怀疑图片消除不了,你这游戏有bug
为了防止这种图片突然就没了,完全丧失游戏体验,我加了个「矩形」,绘制在「选中的图片上」,在消除之前,会有直线提示。具体怎么绘制的,这个不涉及逻辑思维,可以看下源码自行体会。
- Linux下部署SSH登录时的二次身份验证环境记录(利用Google Authenticator)
- Linux下DNS简单部署(主从域名服务器)
- 本地yum源部署记录
- silverlight4:摄像头占用状态检测以及二种截屏方法
- Flash/Flex学习笔记(16):如何做自定义Loading加载其它swf
- 台胞也能发红包喽!小编手把手教你搞定微信支付!
- 获取可视区域高度赋值给div(解决document.body.clientHeight的返回值为0的问题)
- Docker管理工具-Swarm部署记录
- 聊一聊大数据的问题和缺陷
- Flash/Flex学习笔记(13):对象拖动(startDrag/stopDrag)
- 原来Silverlight 4中是可以玩UDP的!
- Flash/Flex学习笔记(12):FMS 3.5之如何做视频实时直播
- Flash/Flex学习笔记(11):如何检测摄像头是否被占用
- Flash/Flex学习笔记(10):FMS 3.5之Hello World!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- iOS NSTimer不走的问题
- 我对python中的super()机制的一点理解
- 【Python】使用Pygame做一个Flappy bird小游戏(五)
- 【动手学深度学习笔记】之自定义层
- Ubuntu18.04系统安装和必备软件安装指南
- 如何查看微信好友已撤回的消息?
- 【Python爬虫】写一个爬取中国天气网的终端版天气预报爬虫
- 如何制定企业级代码规范与检查
- 哈希表:可以拿数组当哈希表来用,但哈希值不要太大!
- 【Python】Scrapy爬虫入门(一)Scrapy的基本用法和爬取静态网站
- 【动手学深度学习笔记】之读取和存储
- 据说这才是看热搜的正确方法?
- Ubuntu18.04安装Anaconda3和VSCode指南
- 微信小程序里如何使用npm?小程序集成友盟举例
- 听说你的表情包不够用了?