MTK平台俄罗斯方块小游戏
游戏评审
我二月份来到现在这个公司,现在转眼间过去了三个月,今天我也要做游戏评审了,评审通过了,我也就能正式转正了。这三个月好像从头开始学习一样。从MTK平台的搭建、程序的编译、Source Insight编写和浏览代码、vs调试代码(打函数断点、监视、跟踪)、使用SVN从公司服务器拉去代码、Beyond Compare比较代码的修改、C语言宏开关的使用、MTK的资源使用、以及程序和资源的独立。然后用一个月开发了一个小游戏,这个游戏于其说是自己写的,倒不如说是拼凑和修改的。
游戏的代码逻辑主要参考的是这篇文章——C++【EasyX】俄罗斯方块
游戏的MTK菜单框架menu-cui以及菜单处理函数。以及定时器参考的本地电脑中2019年的前辈写的程序写出来的。
游戏的存储,本来是要用nvram写的,但是本人比较内向,也不主动问,然后玩自己移植游戏的时候发现有人写过俄罗斯方块了,而且源码也可以用,也能调试。不过他用的不是nv而是用的是类似于在文件夹中写个txt这种方法来存储文件的。并且游戏声音的播放也是来源于这里
本人的作用就是拆分组装,以及修改一项bug,让程序正常运行。
游戏效果的展示
现在用一张我制作的动图展示我的游戏效果:
上面两个蓝色的方块,左边的是记录游戏分数的方块,右边的是记录游戏时间的方块。游戏得分的规则比较简单,消一行得一分。然后一个是游戏的主屏幕,一个是显示下一个方块的屏幕。 这个屏幕里面的方块显示不是很正。还需要画时间调整。下面两个按键是绘制出来,并且绑定函数来达到按键的效果。
游戏按键,中间键,是控制方块顺时针旋转的,向上的按键是方块硬着陆,向下的按键是加快方块下落速度。
游戏菜单中,有排行榜,继续游戏,新的游戏、声音等
游戏介绍
我制作的游戏主要做的工作是以下六点加上游戏背景的制作(画方格、填充颜色)、游戏按键的绑定。
游戏流程图
游戏初始化的过程大致如下:进入游戏屏幕,初始化全局变量,读取本地游戏文件、根据本地文件得到是否要继续上次的游戏的标志位的情况,绘制分数、绘制时间、绘制左按键、绘制右按键,绑定按键函数,开始游戏和时间两个定时器。
//进入游戏
void enter_my_tetris_screen()
{
S32 x1, y1;
mmi_frm_scrn_enter(GRP_ID_MY_TETRIS_GROUP, SCR_ID_TETRIS_SCREEN_NEW_GAME, exit_my_tetris_screen, enter_my_tetris_screen, MMI_FRM_FULL_SCRN);
clear_screen();
Init_Set_Global_Var();
tetris_new_or_resume_flag = 1;
My_Tetris_ReadLog(&MY_Tetris_Log);
MY_Tetris_Log.new_or_resume_flag = tetris_new_or_resume_flag;
My_Tetris_WriteLog(&MY_Tetris_Log);
tetris_draw_time();
tetris_draw_score();
tetris_draw_left();
SetKeyHandler(tetris_draw_left, KEY_LSK, KEY_EVENT_DOWN);
tetris_draw_right();
SetKeyHandler(tetris_draw_right, KEY_RSK, KEY_EVENT_DOWN);
draw_game_timer();
draw_sec_timer();
gui_BLT_double_buffer(0, 0, UI_device_width -1, UI_device_height - 1);
if(game_status == STATUS_START || game_status == STATUS_PAUSE)
{
Tetris_show_start_popup_text();
}
tetris_SetKeyHandler();
}
游戏的设计
接下来讲解的是我的游戏的设计思路,基本生都来源于C++【EasyX】俄罗斯方块。他的主要问题是俄罗斯边界控制不好,就是刚开始左右移动方块会发生卡块的现象。我改善了一下。
俄罗斯方块的设计
俄罗斯方块类型 | 实际形状个数 |
---|---|
山型 | 4 |
L型 | 4 |
反L型 | 4 |
Z型 | 2 |
反Z型 | 2 |
I型 | 2 |
田型 | 1 |
从右到左
//记录作图形状的偏移量,总共有7种形状,每种形状有四个方向,每个形状用两位数记录基准坐标
struct Square squares[7] = {
{ 0, -2, 0, -1, 0, 0, 1, 0, 0, -1, 1, -1, 2, -1, 0, 0, 0, -2, 1, -2, 1, -1, 1, 0, 0, 0, 1, 0, 2, 0, 2, -1 }, // L 型
{ 0, 0, 1, 0, 1, -1, 1, -2, 0, -1, 0, 0, 1, 0, 2, 0, 0, -2, 0, -1, 0, 0, 1, -2, 0, -1, 1, -1, 2, -1, 2, 0 }, // L 型(反)
{ 0, -1, 0, 0, 1, -1, 1, 0, 0, -1, 0, 0, 1, -1, 1, 0, 0, -1, 0, 0, 1, -1, 1, 0, 0, -1, 0, 0, 1, -1, 1, 0 }, // 田 型
{ 0, 0, 1, -1, 1, 0, 2, 0, 1, -2, 1, -1, 1, 0, 2, -1, 0, -1, 1, -1, 1, 0, 2, -1, 0, -1, 1, -2, 1, -1, 1, 0 }, // 山 型
{ 0, -3, 0, -2, 0, -1, 0, 0, 0, -3, 1, -3, 2, -3, 3, -3, 0, -3, 0, -2, 0, -1, 0, 0, 0, -3, 1, -3, 2, -3, 3, -3 }, // | 型
{ 0, -1, 1, -1, 1, 0, 2, 0, 0, -1, 0, 0, 1, -2, 1, -1, 0, -1, 1, -1, 1, 0, 2, 0, 0, -1, 0, 0, 1, -2, 1, -1 }, // Z 型
{ 0, 0, 1, -1, 1, 0, 2, -1, 1, -2, 1, -1, 2, -1, 2, 0, 0, 0, 1, -1, 1, 0, 2, -1, 1, -2, 1, -1, 2, -1, 2, 0 } // Z 型(反)
};
7种类型的俄罗斯方块,每种方块有上下左右四种方向,一个俄罗斯方块有四个基本
方块组成每个方向只要知道基准方块的位置(x坐标和y坐标)即可绘制,所以数组元素
单位有:4 × 4 × 2 =32个元素
俄罗斯方块设计的最终结果:
初始方块的随机生成
在如上所述的设计中,一个俄罗斯方块的全部属性仅仅由以下三者共同唯一决定:
- 俄罗斯方块类型索引
s_idx
- 俄罗斯方块形状索引
d_idx
- 俄罗斯方块颜色索引
c_idx
因此,如果要随机生成一个方块,只需要令其
要确定当前俄罗斯方块和下一个俄罗斯方块,我们需要下面6个变量
当需要切换到下一个俄罗斯方块时,将下一个俄罗斯方块的全部索引赋值给当前俄罗斯方块,然后重新随机生成下一个俄罗斯方块。
now_c_idx = next_c_idx;
now_s_idx = next_s_idx;
now_d_idx = next_d_idx;
next_c_idx = rand() % 7;
next_s_idx = rand() % 7;
next_d_idx = rand() % 4;
游戏中的具体实现
/*****************************************************************************\
* Funcation: initDatasPerSquare
*
* Purpose : 初始化每次掉落的俄罗斯方块数据
*
* Explain : 初始化每次掉落的俄罗斯方块数据
\*****************************************************************************/
//每次方块的初始化
void initDatasPerSquare()
{
My_Tetris_ReadLog(&MY_Tetris_Log);
MY_Tetris_Log.now_mp_x = SQUARE_X_START;
MY_Tetris_Log.now_mp_y = SQUARE_Y_START;
MY_Tetris_Log.flag_next = 0;
MY_Tetris_Log.now_c_idx = MY_Tetris_Log.next_c_idx;//俄罗斯方块的颜色索引
MY_Tetris_Log.now_s_idx = MY_Tetris_Log.next_s_idx;//现在的俄罗斯方块类型索引
MY_Tetris_Log.now_d_idx = MY_Tetris_Log.next_d_idx;//俄罗斯方块形状索引,记录俄罗斯方块的朝向
MY_Tetris_Log.next_c_idx = rand() % 7;
MY_Tetris_Log.next_s_idx = rand() % 7;
MY_Tetris_Log.next_d_idx = rand() % 4;
if(MY_Tetris_Log.now_s_idx == 4 && MY_Tetris_Log.now_d_idx) MY_Tetris_Log.now_mp_y += 2;
My_Tetris_WriteLog(&MY_Tetris_Log);
speedUp = 0;
}
检查方块的是否可以移动
分别读取四个方块的左边,然后在遍历,看每个坐标是否左右和下面越界。返回一个布尔类型的变量。
/*****************************************************************************\
* Funcation: checkPut
*
* Purpose : 游戏块是否到达边界
*
* Explain : 游戏块是否到达边界
\*****************************************************************************/
MMI_BOOL checkPut(int mp_x, int mp_y, int dir_idx)
{
int i;
//存储四个方块坐标的数组
int sq_x[4];
int sq_y[4];
My_Tetris_ReadLog(&MY_Tetris_Log);
for (i = 0; i < 4; ++i)
{
sq_x[i] = mp_x + squares[now_s_idx].dir[dir_idx][i][0]; //现在方块坐标的x轴
sq_y[i] = mp_y + squares[now_s_idx].dir[dir_idx][i][1]; //现在方块坐标的y轴
}
// 【左右越界、下方越界、重复占格】
for (i = 0; i < 4; ++i)
{
if (sq_x[i] < 0 || sq_x[i] > SQUARE_X_NUM - 1 || sq_y[i] > SQUARE_Y_NUM - 1)
return MMI_FALSE;
if (sq_y[i] < 0) // 检查坐标合法性
continue;
if (MY_Tetris_Log.MAP[sq_x[i]][sq_y[i]])
return MMI_FALSE;
}
return MMI_TRUE;
}
方块下移
/*****************************************************************************\
* Funcation: moveDown
*
* Purpose : 游戏块向下
*
* Explain : 游戏块向下
\*****************************************************************************/
void moveDown()
{
My_Tetris_ReadLog(&MY_Tetris_Log);
// 尝试能否下移
if (checkPut(MY_Tetris_Log.now_mp_x, MY_Tetris_Log.now_mp_y + 1, MY_Tetris_Log.now_d_idx))
{
++MY_Tetris_Log.now_mp_y;
My_Tetris_WriteLog(&MY_Tetris_Log);
return;
}
// 不能下移则说明这块方块“触礁”了,执行以下操作
// 1、提示可以开始下一个方块
// 2、将方块记录到map地图中
// 3、判断是否可以消行
// 4、判断消行后游戏是否结束
MY_Tetris_Log.flag_next = 1;
My_Tetris_WriteLog(&MY_Tetris_Log);
recordSquareNow();
execClear();
if (checkOver())
{
flag_over = 1;
MY_Tetris_Log.flag_over = flag_over;
My_Tetris_WriteLog(&MY_Tetris_Log);
if(MY_Tetris_Log.game_status == STATUS_PLAYING)
My_Tetris_PlaySound((U8*)Tetris_Lose_Sound,sizeof(Tetris_Lose_Sound),FALSE);
}
}
就是每个方块y轴坐标加一个单位。
方块左右移
就是每个方块x轴坐标加加减一个单位。
方块旋转
/*****************************************************************************\
* Funcation: moveRotate
*
* Purpose : 游戏块旋转
*
* Explain : 游戏块旋转
\*****************************************************************************/
void moveRotate()
{
int i;
My_Tetris_ReadLog(&MY_Tetris_Log);
now_mp_x = MY_Tetris_Log.now_mp_x;
now_mp_y = MY_Tetris_Log.now_mp_y;
now_d_idx = MY_Tetris_Log.now_d_idx;
// 尝试剩余所有形状,可以旋转即调整旋转状态
for (i = 1; i <= 3; ++i)
if (checkPut(now_mp_x, now_mp_y, (now_d_idx + i) % 4))
{
now_d_idx = (now_d_idx + i) % 4;
MY_Tetris_Log.now_d_idx = now_d_idx;
My_Tetris_WriteLog(&MY_Tetris_Log);
break;
}
}
一个俄罗斯方块由dir的三维数组表示,右边的第一个维度代表方向,我们遍历剩下的三种形态,并判度是否可以旋转,然后进行旋转。
方块消行
/*****************************************************************************\
* Funcation: execClear
*
* Purpose : 删去成行游戏块并加分
*
* Explain : 删去成行游戏块并加分
\*****************************************************************************/
void execClear()
{
int i;
int j;
int cnt_j = SQUARE_Y_NUM;
My_Tetris_ReadLog(&MY_Tetris_Log);
memcpy(mp, MY_Tetris_Log.MAP, SQUARE_X_NUM * SQUARE_Y_NUM*sizeof(int));
memcpy(mp_c, MY_Tetris_Log.MAP_C, SQUARE_X_NUM * SQUARE_Y_NUM*sizeof(int));
memset(mp_tmp, 0, sizeof(mp_tmp));
memset(mp_c_tmp, 0, sizeof(mp_c_tmp));
//j代表地图中的第几行
//game_height/p 代表地图中的函数
for (j = SQUARE_Y_NUM; j >= 0; --j)
{
int cnt = 0;//cnt统计一行中的方块个数
//i代表地图中的第几列
for (i = 0; i < SQUARE_X_NUM; ++i)
if (mp[i][j])
++cnt;
if (cnt != SQUARE_X_NUM) //行不满时
{
for (i = 0; i < SQUARE_X_NUM; ++i)
{
mp_tmp[i][cnt_j] = mp[i][j];
mp_c_tmp[i][cnt_j] = mp_c[i][j];
//疑问:flag_over的值在这里改变了,当mp_tmp[8][14]=0到mp_tmp[9][14]=1时,flag_over的值从0变为1
MY_Tetris_Log.flag_over = 0;
}
--cnt_j;
}
else{
++tetris_player_score_count; //行满时,增加分数
MY_Tetris_Log.tetris_player_score_count = tetris_player_score_count;
My_Tetris_PlaySound((U8*)Tetris_Clear_Sound,sizeof(Tetris_Clear_Sound),FALSE);
}
My_Tetris_WriteLog(&MY_Tetris_Log);
}
//更新地图
for (j = 0; j < SQUARE_Y_NUM ; ++j)
for (i = 0; i < SQUARE_X_NUM; ++i)
{
MY_Tetris_Log.MAP[i][j] = mp_tmp[i][j];
MY_Tetris_Log.MAP_C[i][j] = mp_c_tmp[i][j];
}
My_Tetris_WriteLog(&MY_Tetris_Log);
}
从底部第一行开始遍历,累加一行中的方块个数,如果它满行了,进行销行处理。
销行的操作,主要是定义了一个cnt_j变量,和空的地图数组,一行地图循环遍历时,行不满的时候cnt_j上移一行,行满的时候cnt_j,不变,以达到销行的目的。
定时器
功能模块
本游戏主要有六个功能模块组成:
继续菜单隐藏了。
新游戏
游戏运行所涉及到的核心是:
- 对T卡中的数据文件进行读写的操作
- 游戏地图的绘制
- 菜单高亮函数的设置
- 游戏逻辑
- 功能按键的实现
游戏数据的保存
把游戏中所有要中途保存的数据写入一个结构体中,方便将数据批量写入T卡中。
//写入手机内存的数组,记录游戏数据
typedef struct Grades{
char name[20];
int score;
}Grades;
typedef struct Tetris_RECORD{
int MAP[SQUARE_X_NUM][SQUARE_Y_NUM];
color MAP_C[SQUARE_X_NUM][SQUARE_Y_NUM];
int level;
int time;
int new_or_resume_flag;
int now_mp_y;
int now_mp_x;
int now_s_idx;
int now_d_idx;
int now_c_idx;
int next_s_idx;
int next_d_idx;
int next_c_idx;
int flag_over;
int flag_next;
int CurElapse;
int tetris_player_score_count;
int game_status;
MMI_BOOL sound;
Grades Tetris_grades[6];
}Tetris_RECORD;
Tetris_RECORD MY_Tetris_Log;
写此结构体写入和读取T卡中的函数如下:
//写入到手机存储器成为log的方法
void My_Tetris_InitLog(){
memset(&MY_Tetris_Log,0,sizeof(MY_Tetris_Log));
MY_Tetris_Log.sound = MMI_TRUE;
MY_Tetris_Log.new_or_resume_flag = 0;
MY_Tetris_Log.level = 1;
}
void My_Tetris_WriteLog(){
S32 size;
FS_HANDLE fd ;
fd= FS_Open(L"D:\\MY_Tetris_Log.txt",FS_CREATE);
if(fd >= 0){
FS_Write(fd, (void*)&MY_Tetris_Log, sizeof(MY_Tetris_Log), (UINT*)&size);
FS_Close(fd);
}
}
void My_Tetris_ReadLog(){
S32 size;
FS_HANDLE fd ;
fd= FS_Open(L"D:\\MY_Tetris_Log.txt",FS_READ_ONLY);
if(fd >= 0 ){
if(FS_GetFileSize(fd, (UINT *)&size)==FS_NO_ERROR){
FS_Read( fd,(void*)&MY_Tetris_Log,sizeof(MY_Tetris_Log),(UINT*)&size );
}
FS_Close(fd);
}
else{
My_Tetris_InitLog();
My_Tetris_WriteLog();
}
}
游戏地图的绘制
游戏中的地图绘制,是将数组中的元素以绘制方块的形式绘制到屏幕上,数组元素便是绘制矩形框的位置。
一个数组储存位置,一个数组储存颜色。
菜单高亮函数
屏幕结构以及相应的处理函数
游戏逻辑
游戏详细的流程图如下:
功能按键的实现
//俄罗斯方块按键注册
void tetris_SetKeyHandler(){
SetKeyHandler(Press_key_enter, KEY_ENTER, KEY_EVENT_DOWN);
SetKeyHandler(Press_key_enter, KEY_5, KEY_EVENT_DOWN);
SetKeyHandler(press_moveDown, KEY_8, KEY_EVENT_DOWN);
SetKeyHandler(press_moveDown, KEY_DOWN_ARROW, KEY_EVENT_DOWN);
SetKeyHandler(moveLeft, KEY_4, KEY_EVENT_DOWN);
SetKeyHandler(moveLeft, KEY_LEFT_ARROW, KEY_EVENT_DOWN);
SetKeyHandler(moveRight, KEY_6, KEY_EVENT_DOWN);
SetKeyHandler(moveRight, KEY_RIGHT_ARROW, KEY_EVENT_DOWN);
//SetKeyHandler(tetris_reset_timer, KEY_2, KEY_EVENT_DOWN);
SetKeyHandler(moveDown_Fast, KEY_UP_ARROW, KEY_EVENT_DOWN);
SetKeyHandler(moveDown_Fast, KEY_2, KEY_EVENT_DOWN);
SetKeyHandler(highlight_tetris_option, KEY_LSK, KEY_EVENT_UP);
SetKeyHandler(close_tetris_screen, KEY_RSK, KEY_EVENT_UP);
}
多功能按键的注册可以设置一个游戏状态变量,让后用swich语句来调整。
继续游戏
-
读取My_Tetris_WriteLog(&MY_Tetris_Log),读取MY_Tetris_Log
-
判断MY_Tetris_Log 的new_or_resume_flag是否为真
-
如果为真则隐藏Resume菜单,为假则显示Resume菜单
排行榜
排行榜功能主要包括数据的获取和界面的实现。
- 数据获取通过My_Tetris_ReadLog这个函数来读取T卡中数据文件中保存的数据
- 界面实现是通过ShowCategory153Screen模板来实现
帮助菜单
此菜单主要是对游戏的按键的说明,以及游戏得分的规则,按右键可返回至游戏列表界面。使用模板ShowCategory74Screen实现的。
声音
从其它游戏中移植过来 声音的二进制文件
//一行被清除时播放的音乐
#define TETRIS_MID_CLEAR_LEN 306 //513
const U8 Tetris_Clear_Sound[TETRIS_MID_CLEAR_LEN] = {
0x4D,0x54,0x68,0x64,0x00,0x00,0x00,0x06,0x00,0x01,0x00,0x04,0x03,0xC0,0x4D,0x54,0x72,0x6B,0x00,0x00,
0x00,0x50,0x00,0xFF,0x03,0x08,0x75,0x6E,0x74,0x69,0x74,0x6C,0x65,0x64,0x00,0xFF,0x02,0x1B,0x43,0x6F,
0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x3F,0x32,0x30,0x30,0x33,0x20,0x62,0x79,0x20,0x47,0x61,0x6D,
0x65,0x6C,0x6F,0x66,0x74,0x00,0xFF,0x01,0x08,0x4D,0x61,0x74,0x68,0x69,0x65,0x75,0x0A,0x00,0xFF,0x58,
0x04,0x04,0x02,0x18,0x08,0x00,0xFF,0x59,0x02,0x00,0x00,0x00,0xFF,0x51,0x03,0x07,0xA1,0x20,0x00,0xFF,
0x2F,0x00,0x4D,0x54,0x72,0x6B,0x00,0x00,0x00,0x72,0x00,0xFF,0x03,0x07,0x54,0x72,0x61,0x63,0x6B,0x20,
0x31,0x00,0xC0,0x11,0x00,0xB0,0x07,0x7F,0x2D,0x65,0x00,0x01,0x64,0x00,0x01,0x06,0x04,0x01,0x26,0x58,
0x36,0xE0,0x67,0x06,0x08,0x90,0x38,0x7D,0x00,0x41,0x6B,0x15,0xE0,0x47,0x07,0x10,0x7E,0x08,0x10,0x49,
0x0A,0x10,0x6B,0x0B,0x10,0x35,0x0D,0x10,0x35,0x0F,0x10,0x06,0x11,0x10,0x09,0x13,0x10,0x69,0x15,0x10,
0x05,0x1A,0x10,0x1C,0x21,0x10,0x0D,0x27,0x10,0x70,0x2D,0x10,0x03,0x36,0x10,0x3E,0x3E,0x10,0x66,0x49,
0x10,0x2D,0x55,0x10,0x1F,0x61,0x10,0x0B,0x6D,0x06,0x90,0x41,0x00,0x00,0x38,0x00,0x0A,0xE0,0x2E,0x76,
0x00,0xFF,0x2F,0x00,0x4D,0x54,0x72,0x6B,0x00,0x00,0x00,0x2B,0x00,0xFF,0x03,0x07,0x54,0x72,0x61,0x63,
0x6B,0x20,0x32,0x00,0xC1,0x76,0x00,0x91,0x3B,0x57,0x1E,0x3B,0x00,0x0F,0x3D,0x63,0x1A,0x3D,0x00,0x02,
0x3F,0x69,0x1B,0x3F,0x00,0x04,0x42,0x7B,0x1E,0x42,0x00,0x00,0xFF,0x2F,0x00,0x4D,0x54,0x72,0x6B,0x00,
0x00,0x00,0x17,0x00,0xFF,0x03,0x07,0x54,0x72,0x61,0x63,0x6B,0x20,0x33,0x6E,0x99,0x26,0x64,0x82,0x3B,
0x26,0x00,0x00,0xFF,0x2F,0x00
};
//游戏进行时的背景音乐
//游戏失败音乐
不足
本次制作的游戏还存在很多的不足。
- 游戏界面设计的还是比较难看。
这个是手机中原本存在的俄罗斯方块的设计界面,相比较之下我的游戏界面还比较丑陋。 - 游戏不太顺畅,由于定时器设置的不好,控制方块左右移动,有种延迟的感觉。
- 读写T卡的频率很高。