作为一个即将步入游戏行业的新人,手写经典小游戏是必备技能哦。
由于录屏软件的问题,颜色和帧率与实际有所出入,不过不影响。
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()<
在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
需要二个定时器,一个用于方块的自动下落,一个用于界面的刷新帧率
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 方块移动、旋转、消行,出现下一个方块和结束逻辑
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=1)
{
bool is_line_full=true;
for(int j=0;j=1;k--)
for(int j=0;j
每次方块稳定之后就把下一个方块拷贝到当前方块(产生新方块从顶上下落)
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
游戏开始,方块随机出现
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");
}
源码下载
csdn:俄罗斯方块
github:俄罗斯方块