自己用MFC写的第一个小游戏,应该写个文章来记录下来。
花了一周左右的时间终于写好了最终版本的俄罗斯方块,前一个版本的速度不是很快,而且代码量大,维护不方便,今天下午又花了一下午重构这些代码,使用了统一方法,使得代码量比原来少了许多,逻辑比以前也更清晰了,速度也快了些。音乐也加上去了,按钮也变成真正的按钮了。数据和显示分离,所以很多与显示无关的代码基本上都可以重用,很容易用qt写个linux版本的,有兴趣的可以去试试。这个是下载地址http://kuai.xunlei.com/d/LJWDATTHYVJQ。
之所以想到写这个小游戏,是在上周的图像处理课上面,又老师讲到的矩阵变换想到了小时候经常玩的俄罗斯方块的实现,花了一节课研究,大概知道了是怎么实现的了,主要还是一个2维数组,于是就决定花几天时间把它写起来,殊不知写的过程中,发送这样或那样的错误,究其原因还是根基没打好,开始写的一个方块类,信息不是太多,于是又重构,算是比较稳定的版本,然后就根据继续后续的开发工作,最后突然有点写不下去了,因为发现程序的逻辑太过繁琐,于是就上网搜了下,看了好多,本来是想看看他们是怎么写的,有各种版本的,不过我想到给容纳方块运动范围的数组加个虚拟边框,就重新回到自己的代码中来,不过没实现自己想要的结果,主要还是自己思路不够清晰,于是就继续原来的思路开始写,用了几个小时,差不多写出了一个完整版本。不过还是有点小bug,然后又回到找bug的过程,找bug比写代码蛋疼多了,于是就通过运行来发现各种错误,那些检测函数写了几个版本才稳定下来,然后把bug基本消除了,至少没有发现的bug。也给了一些人测试。然后加上了预览功能,用的预创建的方法,正在要创建的时候再赋值。如果输了就给个提示。这个算是稳定版本了。
第二天想到给程序加个背景,因为看起来实在是太单调了,所以慢慢实现了加背景的效果。开始的时候方块都没有上色,黑白的,测试起来也不方便,等到了稳定了就把颜色给加了上去,不过是单色的,于是又想到怎么画不同颜色的方块,想到了用一个二维数组来记录每一个方块的颜色,这样就实现了画不同颜色的方块,玩起来视觉上感觉也比较好,慢慢的,想到应该可以随时控制是否继续玩或者暂停,又加了这些功能,主要是对定时器的操作,比较简单。
那时候是用一个假按钮,上面写着开始,暂停,功能也实现了,随时暂停,随时继续,然后总感觉缺点啥,哦,声音,那天晚上不停地找呀,找了好久都没找到想要的声音,最后我在想,我玩那个百度应用的俄罗斯方块的时候,没网络的时候能继续玩,那它的声音肯定在我本地有缓存,翻了半天没找到任何wav或者mp3之类的音乐文件。最后我一个网页一个网页的打开,把目标锁定到那个swf文件。然后把那个wav文件给提取出来了。心里还是挺happy的。丫的,此时已是11点了。关机睡觉。
今天下午就想着,这处理按键的消息处理函数太过复杂,就想着重写以前的那些碰撞检测函数。对比那个方块类,写的时间花的比较少,主要还是测试,
之所以要重写,是因为我发现上个版本用的那改变形状的检测函数用的方法比较好,应该算是对比法了,于是就想着用这个方法实现其它的碰撞检测函数。少了许多函数调用,时间也自然少了些。然后按键消息处理函数比以前少了一半的代码量,而且实现起来容易,维护起来也方便不少,不需要在按键消息处理函数里面检测是否越界,因为用了那个虚拟边界,这样就实现了与方块活动范围的无关性,更有可复用性。检测没问题后,就把动态创建的按钮加了上去,因为mfc提供的CButton实在是在uglily。到此过程基本就算是最终的版本了。这个是运行截图
这里贴出部分代码。
方块类的数据结构和函数
#ifndef BLOCK_H
#define BLOCK_H
typedef struct blcokinfo {
int type; //方块的种类
int direction; //方块的方向
char block[4][4]; //方块所占用的矩阵范围
int cx; //方块所在4*4矩阵左上角距离边框14*10矩阵的横向距离
int cy; //...纵坐标
}BLOCKINFO;
class CBlock
{
private:
BLOCKINFO m_block;
public:
void InitBlock();
void SetBlock();
void GetBlock(BLOCKINFO &bl);
void ChangeBlock();
public:
CBlock& operator=(const CBlock &b);
int GetCx();
int GetCy();
int GetType();
int GetDirection();
};
#endif
这个是随机产生方块
void CBlock::SetBlock()
{
int i;
srand(time(NULL));
InitBlock();
m_block.type = rand() % 7 + 1; //随机产生1-7类型的方块
m_block.direction= 1;
m_block.cx = 3;
m_block.cy = 0;
//具体方块也不知道叫啥名字,用编号表示,具体是按照自己画的一个图纸来的
switch (m_block.type) { //对具体的初始方向为上的方块赋值,
case 1:
for (i = 0; i < 4; i++) {
m_block.block[0][i] = 1;
}
break;
case 2:
m_block.block[1][1] = 1;
for (i = 0; i < 3; i++) {
m_block.block[0][i] = 1;
}
break;
case 3:
for (i = 0; i < 2; i++) {
m_block.block[0][i+1] = 1;
m_block.block[1][i] = 1;
}
break;
case 4:
for (i = 0; i < 2; i++) {
m_block.block[0][i] = 1;
m_block.block[1][i+1] = 1;
}
break;
case 5:
for (i = 0; i < 2; i++) {
m_block.block[i][1] = 1;
m_block.block[i][2] = 1;
}
break;
case 6:
m_block.block[1][2] = 1;
for (i = 0; i < 3; i++) {
m_block.block[0][i] = 1;
}
break;
case 7:
m_block.block[1][0] = 1;
for (i = 0; i < 3; i++) {
m_block.block[0][i] = 1;
}
break;
}
}
其它几个函数也是类似的。
因为方块在4*4这个矩阵里面变换活动,这个是不变的,所以任何时候只要知道这个矩阵相对与那个大矩阵的横纵坐标就可以判断方块是具体某个地方了。
然后下面这个函数是CTetrisView类左方向的碰撞检测函数。
BOOL CTetrisView::CheckLeft()
{
char compareBlock[4][5];
int i, j;
for (i = 0; i < 4; i++) {//把14*10矩阵里方块活动范围记下来
for (j = -1; j < 4; j++) {//纵坐标向左平移一个单位
compareBlock[i][j+1] = m_region[m_nCy+i][m_nCx+j];
}
}
switch (m_bl.GetType()) {
case 1:
if (m_bl.GetDirection() == 2 || m_bl.GetDirection() == 4) {//改变后
for (i = 0; i < 4; i++) {
if (compareBlock[i][1] == 1) {
return TRUE;
}
}
return FALSE;
} else {
if (compareBlock[0][0] == 1) {//有障碍
return TRUE;
}
return FALSE;
}
break;
case 2:
if (m_bl.GetDirection() == 1) {
if (compareBlock[0][0] == 1) {
return TRUE;
}
if (compareBlock[1][1] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 2) {
if (compareBlock[0][1] == 1) {
return TRUE;
}
if (compareBlock[1][0] == 1) {
return TRUE;
}
if (compareBlock[2][1] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 3) {
if (compareBlock[0][1] == 1) {
return TRUE;
}
if (compareBlock[1][0] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 4) {
for (i = 0; i < 3; i++) {
if (compareBlock[i][1] == 1) {
return TRUE;
}
}
return FALSE;
}
break;
case 3:
if (m_bl.GetDirection() == 2 || m_bl.GetDirection() == 4) {
if (compareBlock[0][1] == 1) {
return TRUE;
}
if (compareBlock[1][1] == 1) {
return TRUE;
}
if (compareBlock[2][2] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 1 || m_bl.GetDirection() == 3) {
if (compareBlock[1][0] == 1) {
return TRUE;
}
if (compareBlock[0][1] == 1) {
return TRUE;
}
return FALSE;
}
break;
case 4:
if (m_bl.GetDirection() == 2 || m_bl.GetDirection() == 4) {
if (compareBlock[0][2] == 1) {
return TRUE;
}
if (compareBlock[1][1] == 1) {
return TRUE;
}
if (compareBlock[2][1] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 1 || m_bl.GetDirection() == 3) {
if (compareBlock[0][0] == 1) {
return TRUE;
}
if (compareBlock[1][1] == 1) {
return TRUE;
}
return FALSE;
}
break;
case 5:
if (compareBlock[0][1] == 1) {
return TRUE;
}
if (compareBlock[1][1] == 1) {
return TRUE;
}
return FALSE;
break;
case 6:
if (m_bl.GetDirection() == 1) {
if (compareBlock[0][0] == 1) {
return TRUE;
}
if (compareBlock[1][2] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 2){
if (compareBlock[0][1] == 1) {
return TRUE;
}
if (compareBlock[1][1] == 1) {
return TRUE;
}
if (compareBlock[2][0] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 3){
if (compareBlock[0][0] == 1) {
return TRUE;
}
if (compareBlock[1][0] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 4){
for (i = 0; i < 3; i++) {
if (compareBlock[i][1]) {
return TRUE;
}
}
return FALSE;
}
break;
case 7:
if (m_bl.GetDirection() == 1) {
if (compareBlock[0][0] == 1) {
return TRUE;
}
if (compareBlock[1][0] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 2){
if (compareBlock[0][0] == 1) {
return TRUE;
}
if (compareBlock[1][1] == 1) {
return TRUE;
}
if (compareBlock[2][1] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 3){
if (compareBlock[1][0] == 1) {
return TRUE;
}
if (compareBlock[0][2] == 1) {
return TRUE;
}
return FALSE;
} else if (m_bl.GetDirection() == 4){
for (i = 0; i < 3; i++) {
if (compareBlock[i][1] == 1) {
return TRUE;
}
}
return FALSE;
}
break;
}
return FALSE;
}
检测函数写好后就是一些程序逻辑处理的问题和绘图的问题,绘图的时候主要注意gdi
是稀缺资源,不用了要选回来,create的要delete,其它的我就不多说了。你可以看我的
源代码。要说的太多了,还不如你自己去实现了,每个人的想法都是不同的,但是殊途同归,
达到相同的效果,效率又高,这就够了。我把俄罗斯方块的源代码放在资源里面了,
需要的话可以去下载看看,另外实现qq那种增加难度的效果也是几十行代码的问题。
有兴趣的可以去试试。