作为一个即将步入游戏行业的新人,手写经典小游戏是必备技能哦。
由于录屏软件的问题,颜色和帧率与实际有所出入,不过不影响。
1 新建工程
建一个基类为QWidget的QT gui工程,实际过程中所有gui代码包括界面布局都是手巧的,所以其实不需要简历ui文件。
2 定义游戏数据结构
游戏场景和方块都用二维数组存储,有方块的存1,无方块的存0
场景数据
const int BLOCK_SIZE=25; //单个方块单元的边长 const int MARGIN=5; //场景边距 const int AREA_ROW=20; //场景行数 const int AREA_COL=12; //场景列数
图案数据
//定义图案代码和边界 //田字 int item1[4][4]= { {0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0} }; //右L int item2[4][4]= { {0,1,0,0}, {0,1,0,0}, {0,1,1,0}, {0,0,0,0} }; //左L int item3[4][4]= { {0,0,1,0}, {0,0,1,0}, {0,1,1,0}, {0,0,0,0} }; //右S int item4[4][4]= { {0,1,0,0}, {0,1,1,0}, {0,0,1,0}, {0,0,0,0} }; //左S int item5[4][4]= { {0,0,1,0}, {0,1,1,0}, {0,1,0,0}, {0,0,0,0} }; //山形 int item6[4][4]= { {0,0,0,0}, {0,0,1,0}, {0,1,1,1}, {0,0,0,0} }; //长条 int item7[4][4]= { {0,0,1,0}, {0,0,1,0}, {0,0,1,0}, {0,0,1,0} };
由于涉及到碰撞检测,所以要确定方块图案的上下左右边界
void Widget::GetBorder(int block[4][4],Border &border) { //计算上下左右边界 for(int i=0;i<4;i++) for(int j=0;j<4;j++) if(block[i][j]==1) { border.dbound=i; break; //直到计算到最后一行有1 } for(int i=3;i>=0;i--) for(int j=0;j<4;j++) if(block[i][j]==1) { border.ubound=i; break; } for(int j=0;j<4;j++) for(int i=0;i<4;i++) if(block[i][j]==1) { border.rbound=j; break; } for(int j=3;j>=0;j--) for(int i=0;i<4;i++) if(block[i][j]==1) { border.lbound=j; break; } // qDebug()<<cur_border.ubound<<cur_border.dbound<<cur_border.lbound<<cur_border.rbound; }
在Widget类里面定义好游戏场景和当前方块以及下一个方块的变量
int game_area[AREA_ROW][AREA_COL]; //场景区域,1表示活动的方块,2表示稳定的方块,0表示空 block_point block_pos; //当前方块坐标 int cur_block[4][4]; //当前方块形状 Border cur_border; //当前方块边界
游戏的场景是经过每一帧刷新实现界面变化的
void Widget::paintEvent(QPaintEvent *event) { QPainter painter(this); //画游戏场景边框 painter.setBrush(QBrush(Qt::white,Qt::SolidPattern)); painter.drawRect(MARGIN,MARGIN,AREA_COL*BLOCK_SIZE,AREA_ROW*BLOCK_SIZE); //画方块预告 painter.setBrush(QBrush(Qt::blue,Qt::SolidPattern)); for(int i=0;i<4;i++) for(int j=0;j<4;j++) if(next_block[i][j]==1) painter.drawRect(MARGIN*3+AREA_COL*BLOCK_SIZE+j*BLOCK_SIZE,MARGIN+i*BLOCK_SIZE,BLOCK_SIZE,BLOCK_SIZE); //绘制得分 painter.setPen(Qt::black); painter.setFont(QFont("Arial",14)); painter.drawText(QRect(MARGIN*3+AREA_COL*BLOCK_SIZE,MARGIN*2+4*BLOCK_SIZE,BLOCK_SIZE*4,BLOCK_SIZE*4),Qt::AlignCenter,"score: "+QString::number(score)); //绘制下落方块和稳定方块,注意方块边线的颜色是根据setPen来的,默认黑色 for(int i=0;i<AREA_ROW;i++) for(int j=0;j<AREA_COL;j++) { //绘制活动方块 if(game_area[i][j]==1) { painter.setBrush(QBrush(Qt::red,Qt::SolidPattern)); painter.drawRect(j*BLOCK_SIZE+MARGIN,i*BLOCK_SIZE+MARGIN,BLOCK_SIZE,BLOCK_SIZE); } //绘制稳定方块 else if(game_area[i][j]==2) { painter.setBrush(QBrush(Qt::green,Qt::SolidPattern)); painter.drawRect(j*BLOCK_SIZE+MARGIN,i*BLOCK_SIZE+MARGIN,BLOCK_SIZE,BLOCK_SIZE); } } }需要二个定时器,一个用于方块的自动下落,一个用于界面的刷新帧率
void Widget::timerEvent(QTimerEvent *event) { //方块下落 if(event->timerId()==game_timer) BlockMove(DOWN); //刷新画面 if(event->timerId()==paint_timer) update(); }键盘响应,上件旋转,左右下移动,空格键直接下落到底
void Widget::keyPressEvent(QKeyEvent *event) { switch(event->key()) { case Qt::Key_Up: BlockMove(UP); break; case Qt::Key_Down: BlockMove(DOWN); break; case Qt::Key_Left: BlockMove(LEFT); break; case Qt::Key_Right: BlockMove(RIGHT); break; case Qt::Key_Space: BlockMove(SPACE); break; default: break; } }
void Widget::BlockMove(Direction dir) { switch (dir) { case UP: if(IsCollide(block_pos.pos_x,block_pos.pos_y,UP)) break; //逆时针旋转90度 BlockRotate(cur_block); //防止旋转后bug,i和j从0到4重新设置方块 for(int i=0;i<4;i++) for(int j=0;j<4;j++) game_area[block_pos.pos_y+i][block_pos.pos_x+j]=cur_block[i][j]; //重新计算边界 GetBorder(cur_block,cur_border); break; case DOWN: //方块到达边界则不再移动 if(block_pos.pos_y+cur_border.dbound==AREA_ROW-1) { ConvertStable(block_pos.pos_x,block_pos.pos_y); ResetBlock(); break; } //碰撞检测,只计算上下左右边界,先尝试走一格,如果碰撞则稳定方块后跳出 if(IsCollide(block_pos.pos_x,block_pos.pos_y,DOWN)) { //只有最终不能下落才转成稳定方块 ConvertStable(block_pos.pos_x,block_pos.pos_y); ResetBlock(); break; } //恢复方块上场景 for(int j=cur_border.lbound;j<=cur_border.rbound;j++) game_area[block_pos.pos_y][block_pos.pos_x+j]=0; //没有碰撞则下落一格 block_pos.pos_y+=1; //方块下降一格,拷贝到场景,注意左右边界 for(int i=0;i<4;i++) //必须是0到4 for(int j=cur_border.lbound;j<=cur_border.rbound;j++) if(block_pos.pos_y+i<=AREA_ROW-1&&game_area[block_pos.pos_y+i][block_pos.pos_x+j]!=2) //注意场景数组不越界,而且不会擦出稳定的方块 game_area[block_pos.pos_y+i][block_pos.pos_x+j]=cur_block[i][j]; break; case LEFT: //到左边界或者碰撞不再往左 if(block_pos.pos_x+cur_border.lbound==0||IsCollide(block_pos.pos_x,block_pos.pos_y,LEFT)) break; //恢复方块右场景 for(int i=cur_border.ubound;i<=cur_border.dbound;i++) game_area[block_pos.pos_y+i][block_pos.pos_x+3]=0; block_pos.pos_x-=1; //方块左移一格,拷贝到场景 for(int i=cur_border.ubound;i<=cur_border.dbound;i++) for(int j=0;j<4;j++) if(block_pos.pos_x+j>=0&&game_area[block_pos.pos_y+i][block_pos.pos_x+j]!=2) //注意场景数组不越界 game_area[block_pos.pos_y+i][block_pos.pos_x+j]=cur_block[i][j]; break; case RIGHT: if(block_pos.pos_x+cur_border.rbound==AREA_COL-1||IsCollide(block_pos.pos_x,block_pos.pos_y,RIGHT)) break; //恢复方块左场景 for(int i=cur_border.ubound;i<=cur_border.dbound;i++) game_area[block_pos.pos_y+i][block_pos.pos_x]=0; block_pos.pos_x+=1; //方块右移一格,拷贝到场景 for(int i=cur_border.ubound;i<=cur_border.dbound;i++) for(int j=0;j<4;j++) if(block_pos.pos_x+j<=AREA_COL-1&&game_area[block_pos.pos_y+i][block_pos.pos_x+j]!=2) //注意场景数组不越界 game_area[block_pos.pos_y+i][block_pos.pos_x+j]=cur_block[i][j]; break; case SPACE: //一次到底 //一格一格下移,直到不能下移 while(block_pos.pos_y+cur_border.dbound<AREA_ROW-1&&!IsCollide(block_pos.pos_x,block_pos.pos_y,DOWN)) { //恢复方块上场景 for(int j=cur_border.lbound;j<=cur_border.rbound;j++) game_area[block_pos.pos_y][block_pos.pos_x+j]=0; //没有碰撞则下落一格 block_pos.pos_y+=1; //方块下降一格,拷贝到场景,注意左右边界 for(int i=0;i<4;i++) //必须是0到4 for(int j=cur_border.lbound;j<=cur_border.rbound;j++) if(block_pos.pos_y+i<=AREA_ROW-1&&game_area[block_pos.pos_y+i][block_pos.pos_x+j]!=2) //注意场景数组不越界,而且不会擦出稳定的方块 game_area[block_pos.pos_y+i][block_pos.pos_x+j]=cur_block[i][j]; } ConvertStable(block_pos.pos_x,block_pos.pos_y); ResetBlock(); break; default: break; } //处理消行,整个场景上面的行依次下移 int i=AREA_ROW-1; int line_count=0; //记消行数 while(i>=1) { bool is_line_full=true; for(int j=0;j<AREA_COL;j++) if(game_area[i][j]==0) { is_line_full=false; i--; break; } if(is_line_full) { for(int k=i;k>=1;k--) for(int j=0;j<AREA_COL;j++) game_area[k][j]=game_area[k-1][j]; line_count++;//每次增加消行的行数 } } score+=line_count*10; //得分 //判断游戏是否结束 for(int j=0;j<AREA_COL;j++) if(game_area[0][j]==2) //最顶端也有稳定方块 GameOver(); }每次方块稳定之后就把下一个方块拷贝到当前方块(产生新方块从顶上下落)
void Widget::ResetBlock() { //产生当前方块 block_cpy(cur_block,next_block); GetBorder(cur_block,cur_border); //产生下一个方块 int block_id=rand()%7; CreateBlock(next_block,block_id); //设置初始方块坐标,以方块左上角为锚点 block_point start_point; start_point.pos_x=AREA_COL/2-2; start_point.pos_y=0; block_pos=start_point; }
void Widget::InitGame() { for(int i=0;i<AREA_ROW;i++) for(int j=0;j<AREA_COL;j++) game_area[i][j]=0; speed_ms=800; refresh_ms=30; //初始化随机数种子 srand(time(0)); //分数清0 score=0; //开始游戏游戏开始,方块随机出现
void Widget::StartGame() { game_timer=startTimer(speed_ms); //开启游戏timer paint_timer=startTimer(refresh_ms); //开启界面刷新timer //产生初始下一个方块 int block_id=rand()%7; CreateBlock(next_block,block_id); ResetBlock(); //产生方块 }游戏结束,停止计时器
void Widget::GameOver() { //游戏结束停止计时器 killTimer(game_timer); killTimer(paint_timer); QMessageBox::information(this,"failed","game over"); }
原文地址:http://blog.csdn.net/u012234115/article/details/45966479