前言
这个小游戏是我本人在大一小学期时所做的项目,难度不大,但是需要考虑很多游戏参数的问题, 比如画面更新的速度要设定为16ms,就是60帧;游戏背景画面其实不动,防止玩家眩晕,而是底下的管道移动;实现小鸟扑闪翅膀的动作是通过三张图片叠加完成的。
需要打包的源码(文件夹打包)的话点击这个链接
游戏素材下载点击下面这个链接
论文参考点击下面链接(论文中的图片出现了加载问题后期会进行优化)
工程
按照这个构建文件即可
bestscore.h
#ifndef BESTSCORE_H
#define BESTSCORE_H
class bestscore
{
public:
bestscore();
int score;
//存储最高分
void save();
//读入最高分
void init();
};
#endif // SCORE_H
globalutils.h
#ifndef GLOBALUTILS_H
#define GLOBALUTILS_H
#include <QObject>
class GlobalUtils
{
public:
GlobalUtils();
//获取随机数
static int getRandomNum(uint seed, int limit);
};
#endif // GLOBALUTILS_H
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPainter>
#include <QDebug>
#include <QTimer>
#include <QVector>
#include <QKeyEvent>
#include <QLabel>
#include <QPushButton>
#include "pipechannel.h"
#include "globalutils.h"
#include <bestscore.h>
#include <qsound.h>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void paintEvent(QPaintEvent *event);
//可通过按下键盘实现小鸟上升
//void keyPressEvent(QKeyEvent *event);
void mousePressEvent(QMouseEvent * event);
//初始化
void init();
//初始化游戏相关
void initGame();
//初始化速度
void initSpeed();
//开始游戏
void startGame();
//停止游戏
void stopGame();
//绘制背景
void drawBack();
//绘制bird
void drawBird();
//绘制管道
void drawPipe();
//碰撞判定
bool isCrush();
public slots:
//循环绘制(定时器)
void loopPaint();
//游戏的循环
void slotStartGame();
private:
QPixmap background; //背景图
QPixmap ground; //地面
QPixmap bird1; //小鸟的三种状态
QPixmap bird2;
QPixmap bird3;
int birdNo = 1;
int birdNoMax = 15;
int birdY = 0; // 小鸟的位置Y坐标
int birdX = 0; // 小鸟的位置X坐标
QMatrix matrix;
enum GameStatus{STOPING = 1, RUNNING}; //游戏状态枚举
enum BirdStatus{DEFAULT, UP, DOWN}; //小鸟飞行状态枚举
GameStatus gameStatus = GameStatus::STOPING; //游戏状态 默认停止
BirdStatus birdStatus = BirdStatus::DOWN;
double birdDownSpeed = 0;
double birdUpSpeed = 2.0;
QTimer *timer = nullptr;
QVector<int> * v = nullptr;//地面
int groundSize = 0; //地面个数
int moveSpeed = 2; //移动速度
QVector<PipeChannel*> * pipeChannels = nullptr;//管道
int score = 0;
bestscore best;
QLabel* scoreLabel = nullptr; //记录分数
QLabel* scoreLabel1 = nullptr; //记录最高分数
QPushButton* startButton = nullptr;
};
#endif // MAINWINDOW_H
pipechannel.h
#ifndef PIPECHANNEL_H
#define PIPECHANNEL_H
#include <QObject>
#include <QPixmap>
#include <QImage>
#include <QPainter>
/**
* @brief The PipeChannel class
* 通道
*/
class PipeChannel : public QObject
{
Q_OBJECT
public:
bool isScore = false; //是否已经计分
//winHeight 窗口高度 h1 底下管道高度 x 位置横坐标 groundHeight 地面高度 parent
PipeChannel(int winHeight, int h1, int x, int groundHeight, QObject *parent = nullptr);
//初始化管道
void initPipe();
//绘制管道
void draw(QPainter &painter);
//setX 设置位置 X坐标
void setX(int x);
//getX 获取位置
int getX();
//getY1 获取管道1(底部管道)的y坐标
int getY1();
//getPiPe2Height 获取管道2的高度
int getPiPe2Height();
//获取管道宽度
int getPipeWidth();
//设置底下管道高度
void setH1(int h1);
~PipeChannel();
signals:
public slots:
private:
QImage pipeImage; // 管道图片
QImage imageMirror; //管道图像 镜像
int channelWidth = 250; //通道宽度
int h1 = 150; //底下管道的高度
int pipeWidth = 90; //管道宽度
QPixmap pipe1; //底下管道
QPixmap pipe2; //上面管道
int winHeight, //窗口高度
x, //位置横坐标
groundHeight; //地面高度
};
#endif // PIPECHANNEL_H
bestscore.cpp
#include<bestscore.h>
#include<qfile.h>
#include<qstring.h>
#include<qcoreapplication.h>
#include<qdebug.h>
//读入最高分
bestscore::bestscore()
{
QFile file(QCoreApplication::applicationDirPath().append("/score.txt"));
file.open(QFile::ReadOnly);
QByteArray ba = file.readAll();
file.close();
score = ba.toInt();
}
//存储最高分
void bestscore::save()
{
QFile file(QCoreApplication::applicationDirPath().append("/score.txt"));
file.open(QFile::WriteOnly|QFile::Text);
file.write(QString::number(score).toLocal8Bit());
file.close();
}
//初始化
void bestscore::init()
{
QFile file(QCoreApplication::applicationDirPath().append("/score.txt"));
file.open(QFile::ReadOnly);
QByteArray ba = file.readAll();
file.close();
score = ba.toInt();
}
globalutils.h
#include "globalutils.h"
GlobalUtils::GlobalUtils(){
}
//获取随机数 0 ~ 200之间
int GlobalUtils::getRandomNum(uint seed,int limit){
qsrand(seed);
return qrand()%limit;
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp
#include "mainwindow.h"
#include <QString>
#include <QDebug>
#include <QCoreApplication>
#define DIEPATH QCoreApplication::applicationDirPath().append("/res/hit.wav")
#define WINGPATH QCoreApplication::applicationDirPath().append("/res/wing.wav")
#define BGMPATH QCoreApplication::applicationDirPath().append("/res/bgm.wav")
#define POINTPATH QCoreApplication::applicationDirPath().append("/res/point.wav")
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent)
{
init();
initGame();
}
MainWindow::~MainWindow()
{
delete pipeChannels;
}
void MainWindow::paintEvent(QPaintEvent *)
{
//qDebug() << "paintEvent";
drawBack();
drawPipe();
drawBird();
}
void MainWindow::mousePressEvent(QMouseEvent * )
{
// qDebug() << "click";
//QSound::play(WINGPATH);//鸟上升时扑打翅膀的声音
birdStatus = BirdStatus::UP;
initSpeed();
}
//初始化
void MainWindow::init()
{
QSound::play(BGMPATH);
//this->move(400, 300);
this->setFixedSize(480, 640);
qDebug() << "init ";
ground = QPixmap( QCoreApplication::applicationDirPath().append("/res/ground.png")); // 使用绝对路径
if(!ground){
qDebug() << "ground is null" << endl;
}
ground = ground.scaled(20,80); // 缩放
int groundHeight = ground.height();
background = QPixmap( QCoreApplication::applicationDirPath().append("/res/background.png"));
background = background.scaled(this->width(), this->height() - groundHeight); //缩放
groundSize = this->width()/ground.width() + 1; //防止除不尽有余数,所以加1
v = new QVector<int>(groundSize);
for(int i = 0; i < groundSize; i++){
int x = i * ground.width();
(*v)[i] = x;
}
QPixmap bird( QCoreApplication::applicationDirPath().append("/res/bird.png"));
bird1 = bird.copy(0, 0, 92, 64);
bird1 = bird1.scaled(60, 42);
bird2 = bird.copy(92, 0, 92, 64);
bird2 = bird2.scaled(60, 42);
bird3 = bird.copy(92*2, 0, 92, 64);
bird3 = bird3.scaled(60, 42);
scoreLabel = new QLabel(this);
scoreLabel->setGeometry(width()/2, 0, 50, 50);
QFont font ( "Microsoft YaHei", 20, 75); //第一个属性是字体(微软雅黑),第二个是大小,第三个是加粗(权重是75)
scoreLabel->setFont(font);
QPalette pa;
pa.setColor(QPalette::WindowText,Qt::white);
scoreLabel->setPalette(pa);
this->score = 0;
scoreLabel->setText(QString::number(this->score));
//最高分数设置
scoreLabel1 = new QLabel(this);
scoreLabel1->setGeometry(0, this->height() - 50, 250, 50);
scoreLabel1->setFont(font);
scoreLabel1->setPalette(pa);
scoreLabel1->setText("best score " + QString::number(best.score));
startButton = new QPushButton(this);
font = QFont( "Microsoft YaHei", 25, 75); //第一个属性是字体(微软雅黑),第二个是大小,第三个是加粗(权重是75)
startButton->setFont(font);
startButton->setPalette(pa);
startButton->setText(QString("start"));
startButton->setGeometry(width()/2 -50, height()/2 -30, 100, 60);
// startButton->hide();
timer = new QTimer(this);
//事件
connect(timer,SIGNAL(timeout()),this,SLOT(loopPaint()));
connect(startButton,SIGNAL(clicked()),this,SLOT(slotStartGame()));
}
//初始化游戏相关
void MainWindow::initGame()
{
best.init();
birdX = this->width() / 3;
birdY = this->height() / 2 - bird1.height();
this->score = 0;
scoreLabel->setText(QString::number(this->score));
scoreLabel1->setText("best score " + QString::number(best.score));
uint seed_x = static_cast<uint>(clock());
int l = GlobalUtils::getRandomNum(seed_x,200); //获取随机数
if(pipeChannels == nullptr){
pipeChannels = new QVector<PipeChannel*>(2); //创建两个通道
}else {
delete (*pipeChannels)[0];
delete (*pipeChannels)[1];
}
(*pipeChannels)[0] = new PipeChannel(height(), 100+l, width()+100, ground.height(), this);//窗口高度 底下管道高度 x位置横坐标 地面高度 objet parent
(*pipeChannels)[1] = new PipeChannel(height(), 150 +l, 2 * width() + l, ground.height(), this);
}
//初始化速度
void MainWindow::initSpeed()
{
birdDownSpeed = 2.0;
birdUpSpeed = 5.0;
}
//开始游戏
void MainWindow::startGame()
{
QSound::play(POINTPATH);
startButton->hide();
initGame();
initSpeed();
gameStatus = GameStatus::RUNNING;
birdStatus = BirdStatus::DOWN;
timer->start(16);
}
//停止游戏
void MainWindow::stopGame()
{
timer->stop();
gameStatus = GameStatus::STOPING;
startButton->show();
if(score > best.score)
{
best.score = score;
best.save();
}
}
//绘制背景
void MainWindow::drawBack()
{
QPainter painter(this);
int height = ground.height();
int pos;
// 绘制背景图
painter.drawPixmap(0,0, background);
// 绘制地面
for(int i = 0; i < groundSize; i++)
{
pos = (*v)[i];
painter.drawPixmap(pos,this->height() - height, ground);//绘制一个地面图像
// 改变坐标 移动起来
pos -= moveSpeed;
if(pos <= -ground.width()){
pos = (groundSize-1) * ground.width();
}
(*v)[i] = pos;
}
}
//绘制bird
void MainWindow::drawBird()
{
QPainter painter(this);
QPixmap bird = bird1;
if(gameStatus == GameStatus::RUNNING){
//绘制哪一个 (动画效果)
if(birdNo < birdNoMax/3){
bird = bird1;
}else if(birdNo < birdNoMax/3 * 2){
bird = bird2;
}else{
bird = bird3;
}
birdNo++;
if(birdNo > birdNoMax){
birdNo = 1;
}
}
// 小鸟下降
if(birdStatus == BirdStatus::DOWN){
birdY += birdDownSpeed;
birdDownSpeed+=0.1;
//matrix.rotate(1); //下降的同时旋转
//bird = bird.transformed(matrix, Qt::SmoothTransformation);
}
// 判断是否碰撞
if(isCrush()){
QSound::play(DIEPATH);
stopGame();
}
// 上升
if(birdStatus == BirdStatus::UP){
birdY -= birdUpSpeed;
birdUpSpeed -= 0.2;
//matrix.rotate(-1);
//bird = bird.transformed(matrix, Qt::SmoothTransformation);
if(birdUpSpeed <= 0.0){
birdStatus = BirdStatus::DOWN;
// qDebug() << "down";
initSpeed();
}
}
painter.drawPixmap(birdX, birdY, bird);
}
//绘制管道
void MainWindow::drawPipe()
{
QPainter painter(this);
uint seed_x = static_cast<uint>(clock());
int l = GlobalUtils::getRandomNum(seed_x,200); //获取随机数
int otherX = 0; //另一个通道的位置, 只有两个管道
for(int i = 0; i < pipeChannels->size(); i++)
{
PipeChannel * pipeChannel = (*pipeChannels)[i];
if(i ==0)
{
otherX = (*pipeChannels)[1]->getX();
}else {
otherX = (*pipeChannels)[0]->getX();
}
pipeChannel->draw(painter);
int x = pipeChannel->getX();
if(x + pipeChannel->getPipeWidth() < birdX && !pipeChannel->isScore)
{
QSound::play(POINTPATH);
this->score++; //增加得分
scoreLabel->setText(QString::number(this->score));
pipeChannel->isScore = true;
}
x -= moveSpeed; // 改变坐标移动起来
if(x <= -pipeChannel->getPipeWidth())// 移出窗口了
{
x = otherX + width() / 2 + 2 * l; //重新设置位置
pipeChannel->setH1(50+l); //重新设置高度
pipeChannel->initPipe();//重新初始化
}
pipeChannel->setX(x);
}
}
//碰撞检测
bool MainWindow::isCrush()
{
if (birdY >= this->height() - ground.height() - bird1.height()){ //是否碰撞地面
return true;
}
//if(birdY <= 0)return true;//是否碰撞天空
//是否碰撞管道
for(PipeChannel* c : *pipeChannels){
if(birdX + bird1.width() > c->getX() && birdX < c->getX() + c->getPipeWidth()
&& (birdY < c->getPiPe2Height() || birdY+bird1.height() > c->getY1())){
return true;
}
}
return false;
}
//循环绘制
void MainWindow::loopPaint(){
update();
}
//循环游戏
void MainWindow::slotStartGame(){
startGame();
}
pipechannel.cpp
#include "pipechannel.h"
#include <QCoreApplication>
//静态成员初始化
//winHeight 窗口高度 h1 底下管道高度 x 位置横坐标 groundHeight 地面高度 parent
PipeChannel::PipeChannel(int winHeight,int h1, int x, int groundHeight, QObject *parent) : QObject(parent)
{
this->h1 = h1;
this->x = x;
this->groundHeight = groundHeight;
this->winHeight = winHeight;
initPipe();
}
//初始化管道
void PipeChannel::initPipe()
{
pipeImage =QImage( QCoreApplication::applicationDirPath().append("/res/pipe.png"));
imageMirror = pipeImage.mirrored(false, true);
pipe1 = QPixmap::fromImage(pipeImage);
pipe2 = QPixmap::fromImage(imageMirror);
int h2 = getPiPe2Height();
pipe1 = pipe1.copy(0, 0, pipe1.width(), h1);
pipe1 = pipe1.scaled(pipeWidth, h1);
pipe2 = pipe2.copy(0, pipe2.height()-h2, pipe2.width(), h2);
pipe2 = pipe2.scaled(pipeWidth, h2);
isScore = false;
}
//绘制管道
void PipeChannel::draw(QPainter &painter){
painter.drawPixmap(x, getY1(), pipe1);//管道1(下面)
painter.drawPixmap(x, 0, pipe2);//管道2
}
//设置位置 X坐标
void PipeChannel::setX(int x)
{
this->x = x;
}
//getX 获取位置
int PipeChannel::getX()
{
return this->x;
}
//getY1 获取管道1(底部管道)的y坐标
int PipeChannel::getY1()
{
return winHeight - h1 - groundHeight;
}
//获取管道2的高度
int PipeChannel::getPiPe2Height()
{
return winHeight - channelWidth -h1;
}
//获取管道宽度
int PipeChannel::getPipeWidth()
{
return this->pipeWidth;
}
//设置高度
void PipeChannel::setH1(int h1)
{
this->h1 = h1;
}
PipeChannel::~PipeChannel()
{
}