零基础游戏编程:完成重点的游戏逻辑,俄罗斯方块就差不多了
解决了游戏的显示问题之后,我们就可以大刀阔斧地进行游戏逻辑方面的编程了。完成游戏逻辑方面的内容后,这个游戏算是完成大部分了。俄罗斯方块的游戏逻辑不难,但是涉及的点比较多。我们不要慌,慢慢来。
一、添加游戏变量
什么是游戏变量呢?就是游戏各种状态的值,这些值一定要保存在变量中,否则的话,游戏是无法进行下一帧计算的。
上一节课中,我们已经把背景图片载入了,俄罗斯方块的叠放背景已经有了,这一节课的主要内容,是把俄罗斯方块这个主体添加到窗口中。
在第5节课中,我们已经把俄罗斯方块的原理粗略地说了一下,其中最关键的,是用二维数组来代表20×10的叠放空间,所以,第一个变量就是叠放空间的二维数组:
int block[20][10];//10X20的叠放空间
除了叠放空间之外,俄罗斯方块还需要慢慢下落的正常方块,这些方块是由4个小方块随机组合而成的。在第5节课中,我们本想把俄罗斯方块所有的19种方块情况都画成图片,这个方法是可以的,只不过有点麻烦,还有点费事,所以,我们想要通过计算的方式来绘制这19种情况。
但是,仔细一想之后发现,4个小方块的随机组合是一个比较复杂的数学问题,想要通过编程来解决,有点浪费时间了。如果是计算10个方块的随机组合,我们只能用算法来解决,因为10个方块的随机组合情况太多,要全部列举出来,工作量太大了。而4个方块的随机组合,只有19种情况,完全就是一个不大不小的工作量。干脆,我们把这19种情况抽象成数组来表示。
由于是4个方块的随机组合,这个组合后的方块,完全可以放置在一个4×4的空间中,所以,我们就用4×4的二维数组来表示每一个方块。数组中,有方块的地方是1,没方块的地方是0,这样,全部的19种情况也没有多少数据,比绘制19张图片的数据量小多了。具体如何表示一个方块呢?我们可以看下图的例子:
所以,第二个变量就是所有的方块类型:
shape FK[7];//总共种情况O I L J T S Z
shape是一个自己定义的变量类型,这种变量类型叫做结构。结构的格式和C++类的格式差不多,只不过是用struct这个关键字作为前缀。shape的定义如下:
typedef struct
{
revolve state[4];//旋转状态信息
} shape;
一般而言,struct和typedef是一起使用的。typedef的是把后面的内容重新定义一个名字,上面语句的意思,是把struct和后面{}括起来的内容重新取个名字,这个名字就叫shape。为什么要这样呢?因为struct是一个整体,也就是说,想要用这个自定义类型去声明变量,需要这样写:
struct
{
revolve state[4];
} 新变量名称;
由于有了typedef这个“取名”关键字,struct那一大串的内容,就可以用shape来代表了,再声明新变量的时候,就可以这样写了:
shape 新变量名称;
我们再捋一捋。shape代表的是俄罗斯大类的方块,比如S型方块或者是Z型方块。而一个S型方块,并非只有一种状态,它还可以旋转,每次旋转是90°,所以,每种形状都有4种状态,分别是0°、90°、180°和270°的状态。这样,shape中定义的state[4]这个数组的意思就能理解了。
然后,就是revolve的定义了。由于4种状态是最终的状态,所以,revolve就是一个方块固定形态下需要记录的内容:
typedef struct
{
int len;//该形状宽度
int h;//该形状高度
int s[4][4];//代表形状
} revolve;
第3行的定义应该能明白,一个4×4的数组来表示每一个固定的方块。原本这样就行了,但是,在游戏的帧计算中,我们需要知道每一个方块的长度和高度。方块的长度和高度是可以计算的,计算起来也不难,但是,这种简单的、不会变化的数学问题,建议不要计算。因为游戏的帧计算是非常宝贵的,不能把大量的CPU时间浪费在这些不需要计算的问题上。很多游戏看起来不大,却对配置要求很高,很可能就是这种无意义的数学计算太多了。我们可以直接记录的,就直接记录。所以,每一种固定状态下的方块,我们都事先记录好它的长度和高度。
而这个下路方块在叠放空间中是可以移动和旋转的,所以,我们必须记录这个下落方块的位置,这个位置,就以4×4空间的左上角(0,0)点为基准。这样,第三、第四的变量就是下落方块在叠放空间中的坐标:
int x;
int y; //当前在叠放空间中的位置
我们对比其他的俄罗斯方块游戏,发现,界面中一般有两个方块,一个是当前的下落方块,另一个是显示在信息栏中下一个要掉下来的方块。所以,我们需要把这两个方块都记录下来。看我们的shape结构,确定一个固定方块,需要知道方块的大类和当前旋转的情况,所以,这4个变量都是整数:
int FKType;//当前是什么形状
int FKRevo;//当前旋转状态
int NextFKType;//下一个形状
int NextFKRevo;//下一个旋转状态
除此之外,我们还有下面几个变量:
bool isdown;//当前块是否到底
int delay;
bool isRevo;//是否按住了旋转键
int Score;//分数
bool stop;//暂停
第一个是一个标记,如果下落方块到底了,它的值就是真;第三个也是标记,如果玩家一直按着旋转键不放,它的值就是真,这个变量的意义,是防止下落方块一个劲地旋转;第四个记录的是游戏分数,目的是让玩家有那么一点点的激情;第五个变量控制游戏是否暂停。
这里要说一下第二个变量delay。它的作用,是防止玩家控制的下落方块移动过快。由于计算机运行速度快,只要玩家一操作了控制键,下落方块就会响应,出现的情况就是,一按下左右方向键,下落方块因为移动太快,直接移动到了边上。为了防止这种情况发生,delay这个变量,是故意降低移动速度的。
二、初始化19种方块
shape是一个结构类型的数组,我们为了计算的方便,把每个方块在4个角度下的状态都记录了下来,记录的方法,就是上面图片中的方法。废话不多说,直接初始化。由于初始化的内容比较多,7个方块,4种旋转状态,总共是28份数据,还是单独写在一个函数里吧。
首先是Tetris类声明成员函数:
void Init(void);
然后在CreateWnd函数中添加Init函数进行方块的初始化:
bool Tetris::CreateWnd(HINSTANCE hIns, int nCmdShow)
{
//这里创建窗口
//先创建D3D9
CreateD3D9();
//初始化方块
Init();
SetWindowLong(mhWnd, GWL_USERDATA, (LONG)this);
return true;
}
然后,是Init函数的实现代码:
void Tetris::Init(void)
{
memset(FK,0,sizeof(FK));
//O字形四种旋转情况一样
FK[0].state[0].s[0][0] = 1;
FK[0].state[0].s[0][1] = 1;
FK[0].state[0].s[1][0] = 1;
FK[0].state[0].s[1][1] = 1;
FK[0].state[0].len = 2;
FK[0].state[0].h = 2;
FK[0].state[1].s[0][0] = 1;
FK[0].state[1].s[0][1] = 1;
FK[0].state[1].s[1][0] = 1;
FK[0].state[1].s[1][1] = 1;
FK[0].state[1].len = 2;
FK[0].state[1].h = 2;
FK[0].state[2].s[0][0] = 1;
FK[0].state[2].s[0][1] = 1;
FK[0].state[2].s[1][0] = 1;
FK[0].state[2].s[1][1] = 1;
FK[0].state[2].len = 2;
FK[0].state[2].h = 2;
FK[0].state[3].s[0][0] = 1;
FK[0].state[3].s[0][1] = 1;
FK[0].state[3].s[1][0] = 1;
FK[0].state[3].s[1][1] = 1;
FK[0].state[3].len = 2;
FK[0].state[3].h = 2;
//I字形1、两种旋转情况一样,、两种旋转情况一样
FK[1].state[0].s[0][0] = 1;
FK[1].state[0].s[0][1] = 1;
FK[1].state[0].s[0][2] = 1;
FK[1].state[0].s[0][3] = 1;
FK[1].state[0].len = 4;
FK[1].state[0].h = 1;
FK[1].state[1].s[0][0] = 1;
FK[1].state[1].s[1][0] = 1;
FK[1].state[1].s[2][0] = 1;
FK[1].state[1].s[3][0] = 1;
FK[1].state[1].len = 1;
FK[1].state[1].h = 4;
FK[1].state[2].s[0][0] = 1;
FK[1].state[2].s[0][1] = 1;
FK[1].state[2].s[0][2] = 1;
FK[1].state[2].s[0][3] = 1;
FK[1].state[2].len = 4;
FK[1].state[2].h = 1;
FK[1].state[3].s[0][0] = 1;
FK[1].state[3].s[1][0] = 1;
FK[1].state[3].s[2][0] = 1;
FK[1].state[3].s[3][0] = 1;
FK[1].state[3].len = 1;
FK[1].state[3].h = 4;
//L字形四种情况都不一样
FK[2].state[0].s[0][0] = 1;
FK[2].state[0].s[1][0] = 1;
FK[2].state[0].s[2][0] = 1;
FK[2].state[0].s[2][1] = 1;
FK[2].state[0].len = 2;
FK[2].state[0].h = 3;
FK[2].state[1].s[0][0] = 1;
FK[2].state[1].s[0][1] = 1;
FK[2].state[1].s[0][2] = 1;
FK[2].state[1].s[1][0] = 1;
FK[2].state[1].len = 3;
FK[2].state[1].h = 2;
FK[2].state[2].s[0][0] = 1;
FK[2].state[2].s[0][1] = 1;
FK[2].state[2].s[1][1] = 1;
FK[2].state[2].s[2][1] = 1;
FK[2].state[2].len = 2;
FK[2].state[2].h = 3;
FK[2].state[3].s[0][2] = 1;
FK[2].state[3].s[1][0] = 1;
FK[2].state[3].s[1][1] = 1;
FK[2].state[3].s[1][2] = 1;
FK[2].state[3].len = 3;
FK[2].state[3].h = 2;
//J字形四种情况都不一样
FK[3].state[0].s[0][1] = 1;
FK[3].state[0].s[1][1] = 1;
FK[3].state[0].s[2][0] = 1;
FK[3].state[0].s[2][1] = 1;
FK[3].state[0].len = 2;
FK[3].state[0].h = 3;
FK[3].state[1].s[0][0] = 1;
FK[3].state[1].s[1][0] = 1;
FK[3].state[1].s[1][1] = 1;
FK[3].state[1].s[1][2] = 1;
FK[3].state[1].len = 3;
FK[3].state[1].h = 2;
FK[3].state[2].s[0][0] = 1;
FK[3].state[2].s[0][1] = 1;
FK[3].state[2].s[1][0] = 1;
FK[3].state[2].s[2][0] = 1;
FK[3].state[2].len = 2;
FK[3].state[2].h = 3;
FK[3].state[3].s[0][0] = 1;
FK[3].state[3].s[0][1] = 1;
FK[3].state[3].s[0][2] = 1;
FK[3].state[3].s[1][2] = 1;
FK[3].state[3].len = 3;
FK[3].state[3].h = 2;
//T字形四种情况都不一样
FK[4].state[0].s[0][1] = 1;
FK[4].state[0].s[1][0] = 1;
FK[4].state[0].s[1][1] = 1;
FK[4].state[0].s[1][2] = 1;
FK[4].state[0].len = 3;
FK[4].state[0].h = 2;
FK[4].state[1].s[0][0] = 1;
FK[4].state[1].s[1][0] = 1;
FK[4].state[1].s[1][1] = 1;
FK[4].state[1].s[2][0] = 1;
FK[4].state[1].len = 2;
FK[4].state[1].h = 3;
FK[4].state[2].s[0][0] = 1;
FK[4].state[2].s[0][1] = 1;
FK[4].state[2].s[0][2] = 1;
FK[4].state[2].s[1][1] = 1;
FK[4].state[2].len = 3;
FK[4].state[2].h = 2;
FK[4].state[3].s[0][1] = 1;
FK[4].state[3].s[1][0] = 1;
FK[4].state[3].s[1][1] = 1;
FK[4].state[3].s[2][1] = 1;
FK[4].state[3].len = 2;
FK[4].state[3].h = 3;
//S字形1、和、分别一样
FK[5].state[0].s[0][1] = 1;
FK[5].state[0].s[0][2] = 1;
FK[5].state[0].s[1][0] = 1;
FK[5].state[0].s[1][1] = 1;
FK[5].state[0].len = 3;
FK[5].state[0].h = 2;
FK[5].state[1].s[0][0] = 1;
FK[5].state[1].s[1][0] = 1;
FK[5].state[1].s[1][1] = 1;
FK[5].state[1].s[2][1] = 1;
FK[5].state[1].len = 2;
FK[5].state[1].h = 3;
FK[5].state[2].s[0][1] = 1;
FK[5].state[2].s[0][2] = 1;
FK[5].state[2].s[1][0] = 1;
FK[5].state[2].s[1][1] = 1;
FK[5].state[2].len = 3;
FK[5].state[2].h = 2;
FK[5].state[3].s[0][0] = 1;
FK[5].state[3].s[1][0] = 1;
FK[5].state[3].s[1][1] = 1;
FK[5].state[3].s[2][1] = 1;
FK[5].state[3].len = 2;
FK[5].state[3].h = 3;
//Z字形1、和、分别一样
FK[6].state[0].s[0][0] = 1;
FK[6].state[0].s[0][1] = 1;
FK[6].state[0].s[1][1] = 1;
FK[6].state[0].s[1][2] = 1;
FK[6].state[0].len = 3;
FK[6].state[0].h = 2;
FK[6].state[1].s[0][1] = 1;
FK[6].state[1].s[1][0] = 1;
FK[6].state[1].s[1][1] = 1;
FK[6].state[1].s[2][0] = 1;
FK[6].state[1].len = 2;
FK[6].state[1].h = 3;
FK[6].state[2].s[0][0] = 1;
FK[6].state[2].s[0][1] = 1;
FK[6].state[2].s[1][1] = 1;
FK[6].state[2].s[1][2] = 1;
FK[6].state[2].len = 3;
FK[6].state[2].h = 2;
FK[6].state[3].s[0][1] = 1;
FK[6].state[3].s[1][0] = 1;
FK[6].state[3].s[1][1] = 1;
FK[6].state[3].s[2][0] = 1;
FK[6].state[3].len = 2;
FK[6].state[3].h = 3;
//先生成下一个方块
NextFKType = Ran(7);
NextFKRevo = Ran(4);
//初始化设置该变量以便在游戏开始时生成下一个方块
isdown = true;
x = 4;
y = 0;
memset(block,0,sizeof(block));
delay = 0;
isRevo = false;
Score = 100;
stop = false;
}
举报/反馈