基于Qt的飞机小游戏实现

目录

前言

一、准备工作

二、基本功能实现

1.主菜单

 

2.核心玩法

3.其他

资源文件


前言

这是学习Qt时期做的一个小项目,现在看感觉有很多可优化的地方,放在这里给新人朋友们提供一个参考。


 

一、准备工作

        无论什么项目,在正式写代码前应该梳理一下项目有哪些需要实现的基本功能,后期可扩展的功能,界面的基本布局,代码细分为哪些模块等等。总而言之,我们写程序必须具备迭代思维,为后期更新维护留空间。

        我做的这个小游戏就类似于Flappy Bird,主要就是通过点击实现躲避障碍物。单一实现这一功能并不难,但游戏都讲究一个包装,所以我想还需要在界面上下点功夫。如下图所示,我把界面划分成了主菜单、游戏说明、游玩页面、结算页面四个部分:

                         3b8d2954596d4978880b03603903d8d0.png181dfae1b32a4be4b66164a9f0df9c84.png

                         bb0c855f77d94c9388d3d578c8dff6ab.pngd321af9f5d47495dae1d26be05da2cb2.png

 

二、基本功能实现

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