基于Qt的飞机小游戏实现
目录
前言
这是学习Qt时期做的一个小项目,现在看感觉有很多可优化的地方,放在这里给新人朋友们提供一个参考。
一、准备工作
无论什么项目,在正式写代码前应该梳理一下项目有哪些需要实现的基本功能,后期可扩展的功能,界面的基本布局,代码细分为哪些模块等等。总而言之,我们写程序必须具备迭代思维,为后期更新维护留空间。
我做的这个小游戏就类似于Flappy Bird,主要就是通过点击实现躲避障碍物。单一实现这一功能并不难,但游戏都讲究一个包装,所以我想还需要在界面上下点功夫。如下图所示,我把界面划分成了主菜单、游戏说明、游玩页面、结算页面四个部分:




二、基本功能实现
1.主菜单
①鼠标停留图标放大:
当鼠标悬浮在图标上方,图标变大,鼠标离开则图标复原。这一功能有更简单的实现方式(用QSS样式表设置伪状态),但当时我是重写鼠标事件判断鼠标坐标与图标是否重合来实现的(现在看来太过冗余=.=)。
void Widget::mouseMoveEvent(QMouseEvent *event)
{
int x=this->mapFromGlobal(QCursor().pos()).x();//获取鼠标全局坐标点
int y=this->mapFromGlobal(QCursor().pos()).y();
//qDebug() << "x:" << event->x() << "y:" << event->y();
scale1 = 0;
scale2 = 0;
scale3 = 0;
//判断鼠标位置是否在图标内
if(x > play_x && x <(play_x+pb_width) && y > play_y && y < (play_y+pb_height))
{
if(in_box)//标志位,true表示在图标内,默认状态为false
{
du->play();//播放音效
in_box = false;
}
//qDebug() << "box1:" << in_box;
scale1 = 1;
pb_scale();//调用缩放函数
}
else if(x > des_x && x <(des_x+pb_width) && y > des_y && y < (des_y+pb_height))
{
if(in_box)
{
du->play();
in_box = false;
}
scale2 = 1;
pb_scale();
}
else if(x > end_x && x <(end_x+pb_width) && y > end_y && y < (end_y+pb_height))
{
if(in_box)
{
du->play();
in_box = false;
}
scale3 = 1;
pb_scale();
}
else
{
if(!in_box)
{
in_box = true;
}
//qDebug() << "box2:" << in_box;
pb_scale();
}
}
void Widget::pb_scale()//缩放函数
{
ui->pb_play->setGeometry(play_x,play_y,pb_width,pb_height);
ui->pushButton_2->setGeometry(des_x,des_y,pb_width,pb_height);
ui->pb_close->setGeometry(end_x,end_y,pb_width,pb_height);
if(scale1)
{
//绘制图标放大后的位置及大小
ui->pb_play->setGeometry(play_x-5,play_y-5,pb_width+13,pb_height+8);
}
if(scale2)
{
ui->pushButton_2->setGeometry(des_x-5,des_y-5,pb_width+13,pb_height+8);
}
if(scale3)
{
ui->pb_close->setGeometry(end_x-5,end_y-5,pb_width+13,pb_height+8);
}
}
②音效开关:
添加背景音乐及互动时的音效。注意在pro文件中添加代码:
QT += multimedia
定义背景音乐bgm和音效du:
QMediaPlayer *bgm;
QSound *du;
du = new QSound(":/voice/du.wav",this);
bgm = new QMediaPlayer;
bgm->setMedia(QUrl("qrc:/voice/A Quest Unfolds.mp3"));//添加音频文件
bgm->setVolume(50);//设置音量
bgm->play();//开始播放
ui->voice_off->hide();//默认隐藏音效关闭按钮
void Widget::on_voice_on_clicked()//音效开->关
{
ui->voice_off->show();//show出音效关闭的按钮
bgm->setVolume(0);//设置音量为0
}
void Widget::on_voice_off_clicked()//音效关->开
{
ui->voice_off->hide();//隐藏音效关闭的按钮
bgm->setVolume(50);
}
2.核心玩法
①飞机:
飞机类,包含运动和撞机两个状态,初始化它的位置和速度(上升速度+重力),启用定时器,用绘图事件不断刷新飞机位置。
Plane::Plane(QObject *parent) : QObject(parent)
{
plane.load(":/image/plane3.png");
style = 0;
init();
connect(&timer_gravity,&QTimer::timeout,this,&Plane::Action);
}
void Plane::init()//初始化状态
{
gamestart = 1;//游戏开始标志位
pos = QPoint(50,150);//初始位置
UpSpeed = 0;//上升速度为0
gravity = 2;//重力为2
timer_gravity.start(30);//开启定时器
}
void Plane::draw(QPainter &painter)//绘制飞机位置
{
if(gamestart)
{
painter.translate(pos.x(), pos.y());
painter.translate(-pos.x(), -pos.y());
painter.drawPixmap(pos.x(), pos.y(), PLANE_WIDTH, PLANE_HEIGHT, plane);
}
}
void Plane::Up()//飞机上升
{
UpSpeed = -10;//给上升速度为10(QT坐标下,向上向左为负,向右向下为正)
}
void Plane::Crash()//撞机
{
gamestart = 0;//游戏结束
timer_gravity.stop();//定时器停止
}
void Plane::Action()//飞机运动
{
if(gamestart){
//qDebug() << "y:" << pos.y() << "x:" << pos.x();
if(UpSpeed<0)//判断飞机是否上升
{
pos.setY(pos.y()+(UpSpeed));//设置飞机y轴坐标
UpSpeed++;//上升速度逐渐减少,直到为0
}
else//飞机下坠
{
Speed = 0;
Speed += gravity;//重力加速度
pos.setY(pos.y()+(Speed));
}
}
}
②管道:
管道类,分为上下管道,提前刷新出新的管道并向左移动,营造飞机向前飞行的错觉,包含运动和撞飞机两个状态,同样,启用定时器,用绘图事件不断绘制管道位置。
Pipe::Pipe(QObject *parent) : QObject(parent)
{
texture_up[0].load(":/image/pipe_up.png");
texture_up[1].load(":/image/pipe_up.png");
texture_down[0].load(":/image/pipe_down.png");
texture_down[1].load(":/image/pipe_down.png");
//style = 0;
init();
//关联定时器,提前刷新出新的管道
connect(&timer_action, &QTimer::timeout, [=]()
{
for(int i = 0;i<3;i++)
{
pos[i].setX(pos[i].x()-1);
if(pos[i].x()+PIPE_WIDTH<0)
{
pos[i].setX(last->x()+PIPE_WIDTH+PIPES_WIDTH_SPACE);
pos[i].setY(getRandom(100-PIPE_HEIGHT, WINDOW_HEIGHT-100-PIPES_HEIGHT_SPACE-PIPE_HEIGHT-CLOUD_HEIGHT));
last = &pos[i];
}
}
});
}
void Pipe::run()
{
timer_action.start(10);//开启运动状态
}
void Pipe::stop()
{
timer_action.stop();//停止状态
}
void Pipe::draw(QPainter &painter)//绘制管道位置,不断向左移动
{
for(int i = 0;i<3;i++)
{
painter.drawPixmap(pos[i].x(), pos[i].y(), PIPE_WIDTH, PIPE_HEIGHT, texture_down[style]);
painter.drawPixmap(pos[i].x(), pos[i].y()+PIPE_HEIGHT+PIPES_HEIGHT_SPACE, PIPE_WIDTH, PIPE_HEIGHT, texture_up[style]);
}
}
void Pipe::init()//初始化管道位置
{
stop();
for(int i = 0;i<3;i++)
{
pos[i].setX(WINDOW_WIDTH+i*(PIPE_WIDTH+PIPES_WIDTH_SPACE));
pos[i].setY(getRandom(100-PIPE_HEIGHT, WINDOW_HEIGHT-100-PIPES_HEIGHT_SPACE-PIPE_HEIGHT-CLOUD_HEIGHT));
}
last = &pos[2];
}
int Pipe::getRandom(int min, int max)//随机函数
{
static QTime t = QTime::currentTime();
static int n = 0;
QTime T = QTime::currentTime();
int i = T.msecsTo(t);
qsrand(i+(n+=20));
if(n>100) n = 0;
int num = -(qrand()%(-min));
if(num > max) return getRandom(min, max);
else return num;
}
bool Pipe::Test(Plane &plane)//监测飞机与管道位置是否重合
{
int plane_x = plane.getPos().x();
int plane_y = plane.getPos().y();
for(int i = 0;i<3;i++)
{
int pipe_x = pos[i].x();
int pipe_y = pos[i].y();
if(plane_x+PLANE_WIDTH>pipe_x && plane_x<pipe_x+PIPE_WIDTH &&
(plane_y<pipe_y+PIPE_HEIGHT || plane_y+PLANE_HEIGHT>pipe_y+PIPE_HEIGHT+PIPES_HEIGHT_SPACE))
{
return false;
}
}
return true;
}
③游玩状态:
在游玩界面中创建飞机对象、管道对象,游戏开始后,使飞机、管道处于运动状态,并且通过定时器不断调用飞机和管道中的绘图事件,刷新位置。
3.其他
点击按钮实现界面跳转:在跳转时new出新界面并show出来,当前界面close掉;计分系统:可以判断飞机飞过管道没有撞机则积一分,我图省事直接通过时间来积分了;可扩展的附加功能:添加设置按钮更改飞行速度、排行榜功能记录历史积分;
注意:最后可以将项目打包为一个exe可执行文件,方便传输
资源文件
https://pan.baidu.com/s/1yTYOQ39V11wd8J5C62zIOg?pwd=7b72
提取码:7b72