俄罗斯方块(Tetris)是一款风靡全球的电视游戏机和掌上游戏机游戏,也是作者最喜欢的游戏之一,所以作者就用zengl开发了俄罗斯方块,该游戏脚 本在windows和linux平台下都可以运行,本节开始...
俄罗斯方块(Tetris)是一款风靡全球的电视游戏机和掌上游戏机游戏,也是作者最喜欢的游戏之一,所以作者就用zengl开发了俄罗斯方块,该游戏脚 本在windows和linux平台下都可以运行,本节开始将对俄罗斯方块的算法进行分析,而分析一套代码最好的方法就是从最初的版本开始研究,所以本节 就从v1第一个版本开始分析。
v1版本的下载地址为:http://pan.baidu.com/share/link?shareid=396056&uk=940392313 (此为百度盘的共享链接地址) 在该链接里,进入zengl俄罗斯方块共享文件夹,在该文件夹中有个version_1_tetris.rar的压缩包就是v1版本的代码包,里面有个版本1说明.txt文件,包含了该版本的相关说明:
该版本是俄罗斯方块的第一个测试版本,该版本并没有任何可玩的游戏环节,只是建立了一个初始的游戏脚本运行环境,运行俄罗斯方块版本1双击我运行.bat批处理文件,会得到一个S形状的俄罗斯方块,从顶部落到底部,到了底部后,游戏结束,可以按ESC键退出。如果要玩完整版,可以查看"zengl编程语言"栏目,其中的"zengl编程语言v1.0.5 编译,执行,源码调试一体化,开发俄罗斯方块"这篇文章里有俄罗斯方块正式发布版的下载地址。
如果是linux用户,在成功编译了zengl v1.0.5或更高版本的程序后,可以将version_1_tetris.rar里的version_1_tetris文件夹拷贝到zengl根目录 中,最后运行./zenglrun version_1_tetris/tetris.zl -n 即可,如果要调试脚本,可以加个-d参数(参数的含义请参考上面提到的zengl v1.0.5的文章,或者通过-h参数来查看帮助)。
首先来看下俄罗斯方块的坐标系(也是SDL游戏开发采用的坐标系):
因为俄罗斯方块是以小方块为单位的,主游戏区域也是以小方块为一格单位的,所以在游戏脚本中还引入了虚拟坐标的概念,这个虚拟坐标不是以像素为单位,而是以小方块格子数为单位:
所以在游戏脚本中,是对方块的虚拟坐标进行加减计算,然后再根据小方块的宽高值将虚拟坐标换算为实际的SDL游戏像素值。例如假设某方块的虚拟坐标为 (1,1) ,并且假设小方块为20像素宽和20像素高,那么方块在主游戏区域的实际像素值就是(20px,20px),再加上主游戏区域左上角的坐标值,假设为 (25px,25px),那么该方块在整个游戏窗口中的实际像素坐标就是(45px,45px),得到了该像素值后,最后调用SDL模块提供的脚本函数, 就可以将方块绘制到游戏窗口的正确位置了。
现在看下本版本的游戏脚本中涉及到的一些SDL模块函数:
1) sdlFillRect(dest surface,dest rect , array color_alpha); 第一个参数dest surface是要填充的目标表面的指针值,第二个参数dest rect为要填充的矩形区域,如果是0则说明要填充目标的所有区域,第三个参数array color_alpha为要设置的颜色数组。
例如在该版本的tetris.zl脚本的第82行:
sdlFillRect(screen,array(TetrisWidth * tetris.members[i].vx + gameMainBg.pos.x,
TetrisHeight * tetris.members[i].vy + gameMainBg.pos.y,
TetrisWidth , TetrisHeight),tetris.color); //利用方块的颜色,和方块的坐标,宽高值将屏幕的指定矩形区域填充颜色
2) sdlGetTicks(); 返回当前的时间戳,在本脚本中主要用于设置方块下落的定时器。
3) sdlInit(); 初始化SDL库
4) sdlCreateWin(width,height,bpp,flags,repeat[option]); option表示可选参数,第一个参数是窗口宽,第二个参数是窗口高,第三个参数是位深,即分辨率,第四个参数是SDL的视频模式的一些可选标志或者参 数。第五个可选参数是用来设置按键检测的重复间隔,SDL默认只检测一次按键,有了这个参数就可以开启重复检测按键。俄罗斯方块游戏中,只有在最后的版本中才用到了这个参数。
例如在脚本的第107行:
screen = sdlCreateWin(WinWidth,WinHeight,32,0); //创建窗口
5) sdlPollEvent(event); event参数是一个数组或类,用于存放SDL事件的类型等信息,event类型原型如下:
class clsMouseMotionEvt //SDL定义的鼠标移动事件C结构体对应的zengl类型
type; //SDL_MOUSEMOTION
which; //The mouse device index
state; //The current button state
x; y; //The X/Y coordinates of the mouse
xrel; //The relative motion in the X direction
yrel; //The relative motion in the Y direction
endcls
class clsMouseButtonEvt //SDL定义的鼠标点击事件C结构体对应的zengl类型
type; //SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP
which; //The mouse device index
state; //SDL_PRESSED or SDL_RELEASED
x; y; //The X/Y coordinates of the mouse at press time
button; //The mouse button index
endcls
class clsEvent //event对应的zengl类型
type; //事件类型,判断是按键事件,还是鼠标事件等。
keytype; //如果是按键,则keytype中存放了按键的类型
clsMouseMotionEvt motion; //如果是鼠标移动事件,则将移动的坐标等信息存放到motion中。
clsMouseButtonEvt button; //如果是鼠标点击事件,则将点击相关的信息存放到button中。
endcls
例如在脚本的第121行:
while(sdlPollEvent(event)!=NoEvent)
6) sdlDelay(millisecond); 第一个参数millisecond是要延时的毫秒数
例如在该版本tetris.zl脚本的第135行:
sdlDelay(10); //设置10毫秒延时。防止CPU持续占用资源
7) sdlShowScreen(); 将游戏绘制好的帧显示出来,因为SDL的绘制操作都是绘制在缓存中的,必须通过sdlShowScreen脚本函数内部调用SDL_Flip进行翻转,才能将缓存中的数据输出到显卡显示出来。
该版本的tetris.zl脚本的主要计算逻辑集中在myTetrisMove自定义函数中:
/*
计算运动中的方块的虚拟坐标,并判断是否下落到了主游戏区域的底部
*/
fun myTetrisMove()
global time,isneedDraw,tetris;
clsTetris tetris;
if(sdlGetTicks() - time > 1000) //每隔1000毫秒,就进行匀速自由落体运动。
if(tetris.members[tetris.bottomIndex].vy+1 >= 20) //主游戏区域是20格的高度,如果运动方块的正下方是底部,则返回。
return;
endif
for(i=0;i<4;i++) //循环将俄罗斯方块的每个小方块的vy虚拟纵坐标加一
tetris.members[i].vy++;
endfor
isneedDraw = TRUE; //因为方块下落了一格,所以设置isneedDraw为TRUE,表示需要绘制游戏主区域和运动方块
time = sdlGetTicks(); //重置定时器
endif
endfun
至于脚本中的其他代码都在重要的部分加了注释,可以结合注释和调试器进行分析,该版本的运行界面如下:
当到达最底部时,方块就不再运动了,此时可以按ESC键退出。
OK,下节介绍v2的版本,休息,休息一下 O(∩_∩)O~