作为一个即将步入游戏行业的新人,手写经典小游戏是必备技能哦。

预览

由于录屏软件的问题,颜色和帧率与实际有所出入,不过不影响。

步骤

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; //当前方块边界

3 添加渲染循环和计时器事件以及键盘监听

游戏的场景是经过每一帧刷新实现界面变化的

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;}
}

4 方块移动、旋转、消行,出现下一个方块和结束逻辑

  • 整个场景以左上角为坐标原点,方块图案以其左上角为局部坐标原点,所以可以用全局坐标x,y加上局部坐标j,i来进行定位
  • 每次移动或者旋转都要先假想着旋转或者移动一步之后与原有场景有没有碰撞或者越界,如果没有则动作,如果有则不再变化,活动方块(红色)转化成稳定方块(绿色)
  • 碰撞检测原理是判断原有场景和活动方块是否在某个点都不为0
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到4for(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到4for(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;
}

5 游戏控制逻辑
游戏初始化,初始化计时器间隔和随机化种子以及分数

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));//分数清0score=0;//开始游戏

游戏开始,方块随机出现

void Widget::StartGame()
{game_timer=startTimer(speed_ms); //开启游戏timerpaint_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");}

截图

  

源码下载

csdn:俄罗斯方块

github:俄罗斯方块

Qt小游戏开发:俄罗斯方块相关推荐

  1. pygame小游戏开发 - 俄罗斯方块

    版权声明:原创不易,本文禁止抄袭.转载,侵权必究! 目录 一.开发环境&需求分析 二.功能模块 三.游戏视频 四.源码下载 五.作者Info 一.开发环境&需求分析 开发环境:pyth ...

  2. Qt小游戏教程之贪吃蛇(带源码)

    #1.内容介绍 Qt小游戏开发系列将为大家带来几个用Qt设计可发的几款简单小游戏(贪吃蛇.俄罗斯方块.黑白棋.扫雷).今天将为大家带来的是贪吃蛇游戏的设计思路详解以及代码实现,本博客适用于有一定Qt基 ...

  3. 小游戏:俄罗斯方块(Qt 5.9.8)

    小游戏:俄罗斯方块 游戏介绍 过程 设计思路 实现过程 1.Block类 2.Tetris类 3.Box类 4.NextBox类 5.MainWindow类 6.main函数 结果 分析 遇到的问题 ...

  4. 【开发记录】微信小游戏开发入门——俄罗斯方块

    叨叨 我在前一阵子,打算做一个微信小游戏,当然是单机的,只是为了了解小游戏开发的过程,最终选择了俄罗斯方块这一经典小游戏作为demo,源代码已托管值github,当然,这个游戏demo对用不并不友好, ...

  5. 鹅厂内部干货|微信小游戏开发技术怎么应用?

    作者介绍:陈阳(Younger) 2011年加入腾讯,现就职于腾讯游戏增值服务部,负责AMS游戏营销平台,致力于研究和推动Web及大前端相关技术的发展. 一.微信小游戏--H5小游戏及微信小程序 微信 ...

  6. 微信小游戏开发技术与应用

    作者介绍:陈阳(Younger) 2011年加入腾讯,现就职于腾讯游戏增值服务部,负责AMS游戏营销平台,致力于研究和推动Web及大前端相关技术的发展. 一.微信小游戏--H5小游戏及微信小程序 微信 ...

  7. 微信小游戏开发实战教程系列开启

    **这是小蚂蚁游戏开发公众号原创的第35篇. 在写完了"人人都能做游戏"的新手系列教程后,我收到了不少反馈.有人告诉我,因为看了我的系列教程做出了自己人生的第一个小游戏.也有人告诉 ...

  8. 动态加载子节点_微信小游戏开发之场景切换和常驻节点传递数据

    主题 场景切换 场景间数据传递方式 小游戏全局背景音效 特别说明 CocosCreator微信小游戏开发系列文章,是我在逐步开发过程中,基于官方文档之上,记录一些重点内容,以及对官方文档中有些知识点的 ...

  9. 微信小游戏开发教程-游戏实现3

    微信小游戏开发教程-游戏实现3 对象池 由于游戏过程中会创建很多临时对象,这些对象很快又不再使用,垃圾回收器也能帮我们主动回收这部分垃圾,但是回收时间不可控制,同时增大了创建对象的开销,所以我们使用对 ...

  10. 微信小游戏开发教程-游戏实现2

    微信小游戏开发教程-游戏实现2 绘制地面 类似于绘制背景,读者自行完成代码.src/runtime/land.js 简易View系统 坐标布局对于复杂的页面来说维护相当困难,因此这里我们引入布局的概念 ...

最新文章

  1. CC攻击工具list
  2. souce insight中文出现乱码
  3. BlockChain:《Blockchain Gate》听课笔记——区块链的共识机制—简介、理解、畅谈
  4. mfc调取摄像头显示并截图_前摄后录,让行车安全再次提升:70迈智能后视镜后摄像头体验...
  5. 智能指针shared_ptr
  6. 关于JAVA中的synchronized,一段不错的解释...
  7. resource和autowired
  8. c段服务器维护,服务器 多c段
  9. DICOM通讯(ACSE->DIMSE->Worklist)
  10. [概率统计]商务与经济统计知识点总结 Part 1
  11. linux 翻录cd,Linux下一个CD翻录 创CUE 压缩flac攻略
  12. uniapp 即时通讯_uniapp中与webview的即时通讯
  13. 实现阿里云物联网平台设备信息到微信小程序分享过程
  14. Echarts引入省级地图(简便快捷,以浙江省为例)
  15. 用python计算符号函数一元定积分和不定积分
  16. 用python在前程无忧高效投递简历
  17. office2020与2016版的不同_office2016与2019有什么区别_office2016与2019的区别详细说明...
  18. bga bond焊盘 wire_封装模式: FC-BGA VS. WireBond ,谁是封装工艺中的真英雄?(图)
  19. 农村宅基地审批管理系统
  20. 使用原生JS封装一个Ajax

热门文章

  1. 常来长安——西安游记(我愿称之为博物馆七日游)
  2. 很强大的上网行为管理终端软件-云子可信-一键禁止访问视频网站
  3. 利用Rstudio对考试成绩进行数据分析
  4. 交换机工作原理和配置命令
  5. 柳永 天涯 青砚1989
  6. c语言程序设计教程答案王晓云,【单选题】华人图灵奖获得者是( ) A. 吴恩达 B. 王小云 C. 姚期智 D. 杨振宁...
  7. 利用Drawable绘制圆角图片和圆形图片
  8. 联系人管理系统 python版
  9. 你居然只知道蓝绿发布?今天教你全链路灰度~
  10. 启动计算机键盘没反应,如何解决电脑开机显示屏和键盘无反应