通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

这篇具有很好参考价值的文章主要介绍了通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

内容简介

项目要求

项目实现

素材导入

核心思路

思路的转变:从main到mainwindow

如何让游戏动起来

如何设计一个物体类

如何从键盘输入操作

如何绘制图片 

如何初始化

项目源码


内容简介

该项目实现了基于Qt的FlappyBird动画游戏开发,我会从素材导入开始带大家熟悉Qt开发的全过程,该CrashCourse偏向于对C++有一定基础,并且了解Qt基本运作原理的同学

项目要求

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

该应用是一个飞鸟躲避障碍的游戏,刚开始除以待机状态按下space后飞鸟就开始不断下落,玩家需要通过按space让小鸟跳跃来保持高度穿过随机生成的障碍,右上角会记录小鸟的得分,即经过障碍的个数,随着游戏的进行,障碍速度变快,障碍密度增加,飞鸟在拍打翅膀和越过障碍时会有提示音,碰撞和游戏结束时也会有相应的音效,以提高反馈感,在游戏结束后,会弹出gameover的字样,再次按下space后游戏会重新开始,计数也会清零。

项目实现

素材导入

素材导入有两种方法:

1.直接以创建qt resource file导入

2.在导入后转化为二进制形式文件,第二种较复杂,因为每次添加新资源后都需要进行一遍二进制化操作,但相应的资源大小会被压缩很多,在资源文教较大时可考虑存为二进制形式

这里只讲第一种方法,至于二进制化可以参考这个博主的文章。

先从github,csdn等网站上找到flaggybird所需的资源放在同一文件夹里,然后移动该文件夹到项目所在目录

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

右键flappybird(项目名)点击添加新文件,选Qt,选Qt Resource File,取名字,

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

点击添加,添加前缀,删除前缀到只剩下/,然后点添加,添加文件,选中资源文件夹,拖动选取所有资源

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

这样就完成了资源的导入,需要使用时只要记住资源所在目录,然后宏定义替换掉即可

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

注意:如果出现以下情况

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

说明资源文件太大,要转化为二进制文件,也就是方法二,在上面有详细方法的链接,这里再粘贴一遍

至此就完成了素材的导入工作,在需要用到图片或音频的时候只需要通过宏定义就可以调用了,后面会给出具体宏定义的代码,在config.h的代码段里

config.h

#ifndef CONFIG_H
#define CONFIG_H
/********** 游戏配置数据 **********/
#define GAME_WIDTH 864 //宽度
#define GAME_HEIGHT 512 //高度
#define GAME_TITLE "FlaggyBird v1.0" //标题
#define GAME_RES_PATH "./bird.rcc" //rcc文件路径
#define GAME_ICON  ":/res/favicon.ico"
/********** 背景配置数据 **********/
#define STARTMAP_PATH ":/res/message.png"
#define MAP1_PATH ":/res/background-day.png"
#define MAP2_PATH ":/res/background-night.png"
#define GAME_RATE 10 //刷新间隔,帧率 单位毫秒
#define bgm ":/res/BGM.wav"
/********** 地基配置数据 **********/
#define BASE_PATH ":/res/base.png"
#define BASE_SPEED 1
#define BASE_Y 470
/********** 小鸟配置数据 **********/
#define BIRD_UP_PATH ":/res/bluebird-upflap.png"
#define BIRD_MID_PATH ":/res/bluebird-midflap.png"
#define BIRD_DOWN_PATH ":/res/bluebird-downflap.png"
#define BIRD_DOWN_SPEED 6
#define BIRD_UP_SPEED 60
#define BIRD_FLAP1_SOUND ":/res/swoosh.wav"
#define BIRD_FLAP2_SOUND ":/res/wing.wav"
#define BIRD_DEAD_SPEED 9
#define BIRD_DEAD_PATH ":/res/bluebird-dead.png"
#define BIRD_DROP_SOUND ":/res/die.wav"
#define BIRD_SCORE_SOUND ":/res/point.wav"
/********** 管道配置数据 **********/
#define TUBE_SPEED 1
#define TUBE_PATH ":/res/pipe-green.png"
#define TUBE_PATH2 ":/res/pipe-greendown.png"
#define TUBE_INTERVAL 220
#define TUBE_NUM 50
/********** 死亡配置数据 **********/
#define GAMEOVER_PATH ":/res/gameover.png"
#define CRASH_PATH ":/res/hit.wav"
/********** 得分配置数据 **********/
#define S_0 ":/res/0.png"
#define S_1 ":/res/1.png"
#define S_2 ":/res/2.png"
#define S_3 ":/res/3.png"
#define S_4 ":/res/4.png"
#define S_5 ":/res/5.png"
#define S_6 ":/res/6.png"
#define S_7 ":/res/7.png"
#define S_8 ":/res/8.png"
#define S_9 ":/res/9.png"

#endif // CONFIG_H

核心思路

思路的转变:从main到mainwindow

通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏

大多数大一没有接触过c++项目的小伙伴可能依然习惯于上课所学的int main(),然后在main函数里进行对象的调用并实现程序主体内容,但这里更推荐在mainwindow里实现,因为有些时候一个游戏会需要不同窗口,比如我室友的坦克大战游戏,需要一个单独的完结动画窗口,main函数通常用来调用不同的窗口,而不是每个窗口里的具体内容,因此虽然本程序是单窗口的,但更推荐在mainwindow.h里include其它类的.h并创建其它类的对象,在mainwindow.cpp里实现游戏代码的主体内容,main里只创建mainwindow的对象w,然后w.show()显示

总而言之,最好把mainwindow视作程序的主体部分而不是main

如何让游戏动起来

其实游戏的技术本质和动画一样,都类似于小时候看到的翻页连环画,在每一页小纸片上画上连续的动作内容,通过快速地翻动纸片,就可以看到连续的动画了

这里的纸片就是游戏里的帧(也许是连续地几帧,为了方便,这里粗略地把一次屏幕刷新叫做一帧),通过快速地屏幕刷新,欺骗我们的眼睛,使其可以看到连续的画面,唯一不同的是,游戏是需要根据玩家键盘和鼠标输入的指令,进行实时地反馈,调整下一帧里物体的动作或位置,因此,我们可以只需要考虑每一帧的时候,各个物体会做出什么操作(用QTimer类实现),以及玩家输入指令的时候,物体会做出什么操作(用keyPressEvent实现)

void MainWindow::initial() {
    //初始化窗口大小
    setFixedSize(GAME_WIDTH,GAME_HEIGHT);
    //设置窗口标题
    setWindowTitle(GAME_TITLE);
    //设置图标资源
    setWindowIcon(QIcon(GAME_ICON));
    //设置定时器帧率
    Timer.setInterval(GAME_RATE);
    //播放音乐
    if(gameover.state==0){
        QSound::play(bgm);
    }
    //抽象时钟
    count=0;
    //其它变量
    point=0;
    start=0;
    gameover.state=0;
    //重新开始
    recover();
    //开始游戏
    if(judge==0){
        PlayGame();
    }
}

这里的 Timer.setinterval(GAME_RATE),GAME_RATE是在config.h(宏定义汇总)里定义的屏幕每几帧刷新一次,其数值为10,这样Timer会每10帧发出一次名为timeout的信号

void MainWindow::PlayGame(){
    //启动定时器
        Timer.start();
        //监听定时器
        connect(&Timer,&QTimer::timeout,[=](){
           //更新游戏中元素的坐标
           updatePosition();
           //碰撞检测
           crash();
           //抽象计时器
           if(start==1&&gameover.state==0){
            count++;
           }
           //管道刷新器
           moretube();
           //刷新计分器
           score();
           //重新绘制图片
           update();
           //变速器
           speedup();
       });
   }

在PlayGame()函数被调用时即开始游戏时,会启动Timer开始按每10帧发出一次timeout的信号

再通过连接槽函数connect(connect函数在本程序不会多次用到,如果感兴趣可以查阅槽函数的相关内容,通俗理解为类似细胞释放的化学信号和另一个细胞上的受体就可以了,通过connect函数连接)将timeout信号和mainwindow连起来,这样每当mainwindow的对象收到timeout信号的时候,就会调用updatePosition()函数,也就是进行一次刷新(可通俗理解为连环画的翻页)

void MainWindow::updatePosition(){
    base.baseposition();
    if(start==1){
        if(gameover.state==1){
            bird.dead();
        }else{
            bird.birdposition();
        }
        for(int j=0;j<TUBE_NUM;j++){
            tube[j].updateposition();
        }
    }
}

 在mainwindow.cpp中自定义了updatePosition()函数功能如上,分别进行base的位置刷新,bird的位置刷新,以及tube的位置刷新,当然,刷新鸟位置的前提是,游戏开始了且游戏还没输,因此添加了两个判断语句来防止玩家没按空格开始游戏鸟就开始下坠,或者游戏已经结束鸟还没有播放死亡动画的情况,最后的tube,因为管道有多个,需要使用循环语句刷新所以管道的位置。

在理解如何让游戏动起来后,其实剩下的就只是轻松愉悦地具体实现每一帧(每一次刷新)各个物体所做的操作和键盘键入操作,这种翻页连环画让游戏动起来的方法同样适用于坦克大战,贪吃蛇等动态游戏中

如何设计一个物体类

游戏中的不同物体其实就是不同的类,通过定义类里的数据和函数,再在mainwindow.h中创建对象,就可以实现在mainwindow. cpp里

举个最简单的例子:实现地基

老师希望的地基是向后滚动的,并且鸟撞上后会死亡

base.h

#ifndef BASE_H
#define BASE_H
#include<QPixmap>
#include<QRect>

class Base
{
public:
    Base();
    //地基图像
    QPixmap base;
    //地基图像位置
    int base_X1;
    int base_X2;
    int base_X3;
    int base_X4;
    int speed;
    //地基矩形边框
    QRect base_rect;
    //地基位置更新
    void baseposition();
};

#endif // BASE_H

 base.cpp

#include "base.h"
#include "config.h"

Base::Base()
{
    //地基资源加载
    base.load(BASE_PATH);
    //地基位置
    base_X1=0;
    base_X2=288;
    base_X3=576;
    base_X4=864;
    //地基矩形
    base_rect.setWidth(3*base.width());
    base_rect.setHeight(base.height());
    base_rect.moveTo(0,BASE_Y);
    speed=BASE_SPEED;
}
//地基位置更新
void Base::baseposition(){
    base_X1-=speed;
    if(base_X1<=-288){
        base_X1=0;
    }
    base_X2-=speed;
    if(base_X2<=0){
        base_X2=288;
    }
    base_X3-=speed;
    if(base_X3<=288){
        base_X3=576;
    }
    base_X4-=speed;
    if(base_X4<=576){
        base_X4=864;
    }
}

创建一个地基类,关键要考虑两个事情,一个是地基的位置,一个是地基的动态

地基位置:

地基是需要有实体的,因为需要判断鸟是否撞上了地基,所以在设置地基位置变量时,不仅要设定地基图像位置base_X还要设定地基实体矩阵的位置base_rect(这里矩阵变量利用了Qt提供的类QRect来简化程序,如果不清楚任何Qt提供函数和类的使用规则,都可以在软件的帮助里选索引,然后进行查找)

这里可能会有小伙伴疑问为什么有4个base_X以及为什么base_rect需要*3,照理说是只需要2个base_X(为了实现滚动)且base_rect不需要乘3的,这是因为找到的地基素材是竖屏游戏素材,不够宽,所以并排放置了3个来增加宽度(这样看起来是一个更长的地基)

地基动态:

地基的动态在之前翻页连环画方法中已经提到,只需要考虑具体每一帧地基会执行什么操作,在base.cpp里具体实现了地基的位置更新函数baseposition(),其实就是简单地让每个地基倒退speed的距离,当每个地基完全偏离开始位置时,恢复到原位重新开始,这样就可以实现循环往复且图像连续移动的感觉

(在做飞机大战时,背景图片需要向下移动,这时同理需要两个背景图片同时向下移动,当每个图片完全偏离开始位置时,重新开始,这样虽然有两个图片,但肉眼只能看到一个,形成了一种背景图片连续向下移动的感觉)

base.load(BASE_PATH):

这个是用来画图的,这里的base是一个QPixmap类型的变量,而不是base类,具体会在之后mainwindow里的painter函数使用到,可以暂时理解为一个图片变量,通过load就加载了图片,以及图片长度宽度等信息

至此,我们已经能够独立地创建一个物体类,bird类和tube类大同小异,关键是搞清楚他们的图片位置和实体矩阵位置,以及每一帧的操作

bird.h

#ifndef BIRD_H
#define BIRD_H
#include<QPixmap>
#include<QRect>
#include<QSound>

class Bird
{
public:
    Bird();
    //拍打翅膀
    void setposition(int x,int y);
    //按帧率修改小鸟位置和状态
    void birdposition();
    //小鸟死亡
    void dead();
public:
    //小鸟资源
    QPixmap birdmap;
    //小鸟坐标
    int m_x;
    int m_y;
    //小鸟矩形边框(碰撞判定)
    QRect rec;
    //小鸟动作
    int state;
    int i;
    int sound;
};

#endif // BIRD_H

bird.cpp

#include "bird.h"
#include"config.h"
Bird::Bird()
{
    birdmap.load(BIRD_UP_PATH);
    m_x=GAME_WIDTH *0.1;
    m_y=GAME_HEIGHT*0.5;
    rec.setWidth(birdmap.width());
    rec.setHeight(birdmap.height());
    rec.moveTo(m_x,m_y);
    state=0;
    i=0;
    sound=0;
}
void Bird::setposition(int x,int y){
    m_x=x;
    m_y=y;
    rec.moveTo(m_x,m_y);
}
void Bird::birdposition(){
    i++;
    if(i%3==0){
        m_y+=BIRD_DOWN_SPEED;
        state++;
    }
    switch(state%3)
    {
    case 0:
        birdmap.load(BIRD_UP_PATH);
        break;
    case 1:
        birdmap.load(BIRD_MID_PATH);
        break;
    case 2:
        birdmap.load(BIRD_DOWN_PATH);
        break;
    }
    rec.moveTo(m_x,m_y);
}
void Bird::dead(){
    birdmap.load(BIRD_DEAD_PATH);
    m_x-=TUBE_SPEED;
    m_y+=BIRD_DEAD_SPEED;
    if(sound==0){
    QSound::play(CRASH_PATH);
    QSound::play(BIRD_DROP_SOUND);
    sound=1;
    }
}

tube.h 

#ifndef TUBE_H
#define TUBE_H
#include"config.h"
#include<QPixmap>
#include<QRect>
#include<QtGlobal>
#include <cstdlib>

class Tube
{
public:
    Tube();
    void updateposition();//更新管道坐标
    QPixmap tube;//管道资源
    QPixmap rtube;//上管道资源
    QRect rec;//下管道实体矩形
    int m_x;//下管道坐标x
    int m_y;//下管道坐标y
    QRect rrec;//上管道实体矩形
    int m_rx;//上管道坐标
    int m_ry;//上管道坐标
    bool exist;
    int speed;
};

#endif // TUBE_H

tube.cpp

#include "tube.h"

Tube::Tube()
{
    tube.load(TUBE_PATH);//加载管道资源
    rec.setWidth(tube.width());//设置实体矩形宽高
    rec.setHeight(tube.height());

    rtube.load(TUBE_PATH2);//加载上管道资源
    rrec.setWidth(rtube.width());//设置上管道实体矩阵
    rrec.setHeight(rtube.height());

    m_y=( qrand() % (GAME_HEIGHT-100 - 180 + 1) ) + 160;//利用随机数生成初始y坐标
    m_x=GAME_WIDTH;//设置初始X坐标
    rec.moveTo(m_x,m_y);//设置初始矩形位置

    m_rx=GAME_WIDTH;
    m_ry=m_y-rtube.height()-180;
    rrec.moveTo(m_rx,m_ry);//设置上矩形初始位置

    speed=TUBE_SPEED;
    exist=false;
}
void Tube::updateposition(){
    if(exist==true){
        m_x-=speed;
        m_rx-=speed;
        rec.moveTo(m_x,m_y);
        rrec.moveTo(m_rx,m_ry);
    }
}

 (这段直接放代码了,因为都是flappybird的具体操作不太具有普适性,有时间会补上)

如何从键盘输入操作

Qt自带一个叫Event的东西,叫事件 ,事件里的操作会在程序运行时被一直重复的执行(也就是实时反馈,无论何时按下键盘,总会响应),注意这里keypressEvent和QKeyEvent是大小写都不能改的,是Qt提供的库函数,第一行算是铁模板,除了mainwindow和e基本是没法修改的,在函数内部e可以理解为指向键盘的指针,通过->key()可以访问到当前键盘键入的内容

void MainWindow::keyPressEvent(QKeyEvent *e){
    int y=bird.m_y;
    if(e->key() == Qt::Key_Space){
        y-=BIRD_UP_SPEED;
        if(gameover.state==0){
            QSound::play(BIRD_FLAP2_SOUND);
        }
        if(start==0){
            start=1;
            QSound::play(BIRD_FLAP1_SOUND);
        }
    }
    bird.setposition(bird.m_x,y);
    if(gameover.state==1){
        if(e->key() == Qt::Key_Space){
            judge=1;
            initial();
        }
    }
}

如何绘制图片 

Qt提供了一个叫paintEvent的事件,和键盘事件一样,会在程序运行过程中不断被执行,所以可以利用paintEvent来实现实时更新游戏画面的效果,前两行代码除了mainwindow和painter基本也是铁模板,通过painter.drawPixmap(对象x位置,对象y位置,对象的图片)来绘图,其中对象的图片在对象的类里会用load函数提前存为图片变量,通过实时更新x和y的位置,就可以达到图像的移动

void MainWindow::paintEvent(QPaintEvent *){
    QPainter painter(this);
    //绘制地图
    if(point%20<10){
        painter.drawPixmap(0,map.map_posY,map.map1);
        painter.drawPixmap(288,map.map_posY,map.map1);
        painter.drawPixmap(576,map.map_posY,map.map1);
    }else{
        painter.drawPixmap(0,map.map_posY,map.map2);
        painter.drawPixmap(288,map.map_posY,map.map2);
        painter.drawPixmap(576,map.map_posY,map.map2);
    }
    if(start==0){
        painter.drawPixmap(330,90,map.startmap);
    }

    //绘制地基
    painter.drawPixmap(base.base_X1,BASE_Y,base.basemap);
    painter.drawPixmap(base.base_X2,BASE_Y,base.basemap);
    painter.drawPixmap(base.base_X3,BASE_Y,base.basemap);
    painter.drawPixmap(base.base_X4,BASE_Y,base.basemap);
    //绘制小鸟
    painter.drawPixmap(bird.m_x,bird.m_y,bird.birdmap);
    //绘制管道
    for(int j=0;j<TUBE_NUM;j++){
        if(tube[j].exist==true){
            painter.drawPixmap(tube[j].m_x,tube[j].m_y,tube[j].tube);
            painter.drawPixmap(tube[j].m_rx,tube[j].m_ry,tube[j].rtube);
        }
    }

如何初始化

至此,程序的大体就已经完成,我们设计出了各个类,他们都有各自的位置更新函数,把他们放在mainwindow的updatePosition函数里,这样每次刷新时各个物体的位置就会更新,同时,键盘事件和绘图事件也使我们能够实时改变物体位置并更新物体图像位置,形成连续的画面,但是如何启动呢,这需要initial函数来初始化,在其中调用playgame函数以启动时钟,时钟一旦启动,游戏也就开始了

void MainWindow::initial() {
    //初始化窗口大小
    setFixedSize(GAME_WIDTH,GAME_HEIGHT);
    //设置窗口标题
    setWindowTitle(GAME_TITLE);
    //设置图标资源
    setWindowIcon(QIcon(GAME_ICON));
    //设置定时器帧率
    Timer.setInterval(GAME_RATE);
    //播放音乐
    if(gameover.state==0){
        QSound::play(bgm);
    }
    //抽象时钟
    count=0;
    //其它变量
    point=0;
    start=0;
    gameover.state=0;
    //重新开始
    recover();
    //开始游戏
    if(judge==0){
        PlayGame();
    }
}
void MainWindow::PlayGame(){
    //启动定时器
        Timer.start();
        //监听定时器
        connect(&Timer,&QTimer::timeout,[=](){
           //更新游戏中元素的坐标
           updatePosition();
           //碰撞检测
           crash();
           //抽象计时器
           if(start==1&&gameover.state==0){
            count++;
           }
           //管道刷新器
           moretube();
           //刷新计分器
           score();
           //重新绘制图片
           update();
           //变速器
           speedup();
       });
   }

项目源码

flappybird.pro


#-------------------------------------------------
#
# Project created by QtCreator 2022-07-12T18:14:43
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = flappybird
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0


SOURCES += \
        main.cpp \
        mainwindow.cpp \
    map.cpp \
    base.cpp \
    bird.cpp \
    tube.cpp \
    gameover.cpp \
    score.cpp

HEADERS += \
        mainwindow.h \
    config.h \
    map.h \
    base.h \
    bird.h \
    tube.h \
    gameover.h \
    score.h

FORMS += \
        mainwindow.ui
QT += core gui multimedia
RC_FILE = exe_ico.rc

 config.h

#ifndef CONFIG_H
#define CONFIG_H
/********** 游戏配置数据 **********/
#define GAME_WIDTH 864 //宽度
#define GAME_HEIGHT 512 //高度
#define GAME_TITLE "FlaggyBird v1.0" //标题
#define GAME_RES_PATH "./bird.rcc" //rcc文件路径
#define GAME_ICON  ":/res/favicon.ico"
/********** 背景配置数据 **********/
#define STARTMAP_PATH ":/res/message.png"
#define MAP1_PATH ":/res/background-day.png"
#define MAP2_PATH ":/res/background-night.png"
#define GAME_RATE 10 //刷新间隔,帧率 单位毫秒
#define bgm ":/res/BGM.wav"
/********** 地基配置数据 **********/
#define BASE_PATH ":/res/base.png"
#define BASE_SPEED 1
#define BASE_Y 470
/********** 小鸟配置数据 **********/
#define BIRD_UP_PATH ":/res/bluebird-upflap.png"
#define BIRD_MID_PATH ":/res/bluebird-midflap.png"
#define BIRD_DOWN_PATH ":/res/bluebird-downflap.png"
#define BIRD_DOWN_SPEED 6
#define BIRD_UP_SPEED 60
#define BIRD_FLAP1_SOUND ":/res/swoosh.wav"
#define BIRD_FLAP2_SOUND ":/res/wing.wav"
#define BIRD_DEAD_SPEED 9
#define BIRD_DEAD_PATH ":/res/bluebird-dead.png"
#define BIRD_DROP_SOUND ":/res/die.wav"
#define BIRD_SCORE_SOUND ":/res/point.wav"
/********** 管道配置数据 **********/
#define TUBE_SPEED 1
#define TUBE_PATH ":/res/pipe-green.png"
#define TUBE_PATH2 ":/res/pipe-greendown.png"
#define TUBE_INTERVAL 220
#define TUBE_NUM 50
/********** 死亡配置数据 **********/
#define GAMEOVER_PATH ":/res/gameover.png"
#define CRASH_PATH ":/res/hit.wav"
/********** 得分配置数据 **********/
#define S_0 ":/res/0.png"
#define S_1 ":/res/1.png"
#define S_2 ":/res/2.png"
#define S_3 ":/res/3.png"
#define S_4 ":/res/4.png"
#define S_5 ":/res/5.png"
#define S_6 ":/res/6.png"
#define S_7 ":/res/7.png"
#define S_8 ":/res/8.png"
#define S_9 ":/res/9.png"

#endif // CONFIG_H

base.h

#ifndef BASE_H
#define BASE_H
#include<QPixmap>
#include<QRect>

class Base
{
public:
    Base();
    //地基图像
    QPixmap base;
    //地基图像位置
    int base_X1;
    int base_X2;
    int base_X3;
    int base_X4;
    int speed;
    //地基矩形边框
    QRect base_rect;
    //地基位置更新
    void baseposition();
};

#endif // BASE_H

base.cpp 

#include "base.h"
#include "config.h"

Base::Base()
{
    //地基资源加载
    base.load(BASE_PATH);
    //地基位置
    base_X1=0;
    base_X2=288;
    base_X3=576;
    base_X4=864;
    //地基矩形
    base_rect.setWidth(3*base.width());
    base_rect.setHeight(base.height());
    base_rect.moveTo(0,BASE_Y);
    speed=BASE_SPEED;
}
//地基位置更新
void Base::baseposition(){
    base_X1-=speed;
    if(base_X1<=-288){
        base_X1=0;
    }
    base_X2-=speed;
    if(base_X2<=0){
        base_X2=288;
    }
    base_X3-=speed;
    if(base_X3<=288){
        base_X3=576;
    }
    base_X4-=speed;
    if(base_X4<=576){
        base_X4=864;
    }
}

bird.h

#ifndef BIRD_H
#define BIRD_H
#include<QPixmap>
#include<QRect>
#include<QSound>

class Bird
{
public:
    Bird();
    //拍打翅膀
    void setposition(int x,int y);
    //按帧率修改小鸟位置和状态
    void birdposition();
    //小鸟死亡
    void dead();
public:
    //小鸟资源
    QPixmap birdmap;
    //小鸟坐标
    int m_x;
    int m_y;
    //小鸟矩形边框(碰撞判定)
    QRect rec;
    //小鸟动作
    int state;
    int i;
    int sound;
};

#endif // BIRD_H

 bird.cpp

#include "bird.h"
#include"config.h"
Bird::Bird()
{
    birdmap.load(BIRD_UP_PATH);
    m_x=GAME_WIDTH *0.1;
    m_y=GAME_HEIGHT*0.5;
    rec.setWidth(birdmap.width());
    rec.setHeight(birdmap.height());
    rec.moveTo(m_x,m_y);
    state=0;
    i=0;
    sound=0;
}
void Bird::setposition(int x,int y){
    m_x=x;
    m_y=y;
    rec.moveTo(m_x,m_y);
}
void Bird::birdposition(){
    i++;
    if(i%3==0){
        m_y+=BIRD_DOWN_SPEED;
        state++;
    }
    switch(state%3)
    {
    case 0:
        birdmap.load(BIRD_UP_PATH);
        break;
    case 1:
        birdmap.load(BIRD_MID_PATH);
        break;
    case 2:
        birdmap.load(BIRD_DOWN_PATH);
        break;
    }
    rec.moveTo(m_x,m_y);
}
void Bird::dead(){
    birdmap.load(BIRD_DEAD_PATH);
    m_x-=TUBE_SPEED;
    m_y+=BIRD_DEAD_SPEED;
    if(sound==0){
    QSound::play(CRASH_PATH);
    QSound::play(BIRD_DROP_SOUND);
    sound=1;
    }
}

 gameover.h

#ifndef GAMEOVER_H
#define GAMEOVER_H
#include"config.h"
#include<QPixmap>
#include<QSound>
class Gameover
{
public:
    Gameover();
    QPixmap gameovermap;
    int state;
};

#endif // GAMEOVER_H

 gameover.cpp

#include "gameover.h"

Gameover::Gameover()
{
    state=0;
    gameovermap.load(GAMEOVER_PATH);
 }

mainwindow.h 

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include"config.h"
#include <QIcon>
#include"map.h"
#include<QPaintEvent>
#include<QPainter>
#include<QTimer>
#include"base.h"
#include"bird.h"
#include"tube.h"
#include"gameover.h"
#include"score.h"
#include <QAbstractButton>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void initial();
    void paintEvent(QPaintEvent *);//绘图事件
    void updatePosition();//以帧率为周期更新坐标
    void keyPressEvent(QKeyEvent *);//键盘事件
    void crash();//碰撞检测
    void GameOver();//游戏结束
    void moretube();//管道刷新器
    void score();//得分
    void PlayGame();//启动游戏,开启定时器对象
    void recover();//回复初始位置
    void speedup();//变速器

public:
    Map map;//地图对象
    QTimer Timer;//定时器对象
    Base base;//地基对象
    Bird bird;//小鸟对象
    Gameover gameover;//结束对象
    Tube tube[60];//管道对象
    Score scored;//计分对象
    int count;//计时器
    int point;
    int start;
    int judge=0;
private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

 mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDebug>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    initial();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::initial() {
    //初始化窗口大小
    setFixedSize(GAME_WIDTH,GAME_HEIGHT);
    //设置窗口标题
    setWindowTitle(GAME_TITLE);
    //设置图标资源
    setWindowIcon(QIcon(GAME_ICON));
    //设置定时器帧率
    Timer.setInterval(GAME_RATE);
    //播放音乐
    if(gameover.state==0){
        QSound::play(bgm);
    }
    //抽象时钟
    count=0;
    //其它变量
    point=0;
    start=0;
    gameover.state=0;
    //重新开始
    recover();
    //开始游戏
    if(judge==0){
        PlayGame();
    }
}

void MainWindow::paintEvent(QPaintEvent *){
    QPainter painter(this);
    //绘制地图
    if(point%20<10){
        painter.drawPixmap(0,map.map_posY,map.map1);
        painter.drawPixmap(288,map.map_posY,map.map1);
        painter.drawPixmap(576,map.map_posY,map.map1);
    }else{
        painter.drawPixmap(0,map.map_posY,map.map2);
        painter.drawPixmap(288,map.map_posY,map.map2);
        painter.drawPixmap(576,map.map_posY,map.map2);
    }
    if(start==0){
        painter.drawPixmap(330,90,map.startmap);
    }

    //绘制地基
    painter.drawPixmap(base.base_X1,BASE_Y,base.base);
    painter.drawPixmap(base.base_X2,BASE_Y,base.base);
    painter.drawPixmap(base.base_X3,BASE_Y,base.base);
    painter.drawPixmap(base.base_X4,BASE_Y,base.base);
    //绘制小鸟
    painter.drawPixmap(bird.m_x,bird.m_y,bird.birdmap);
    //绘制管道
    for(int j=0;j<TUBE_NUM;j++){
        if(tube[j].exist==true){
            painter.drawPixmap(tube[j].m_x,tube[j].m_y,tube[j].tube);
            painter.drawPixmap(tube[j].m_rx,tube[j].m_ry,tube[j].rtube);
        }
    }
    //绘制得分
    switch(point%10){
    case 0:
        painter.drawPixmap(820,20,scored.S0);
        break;
    case 1:
        painter.drawPixmap(820,20,scored.S1);
        break;
    case 2:
        painter.drawPixmap(820,20,scored.S2);
        break;
    case 3:
        painter.drawPixmap(820,20,scored.S3);
        break;
    case 4:
        painter.drawPixmap(820,20,scored.S4);
        break;
    case 5:
        painter.drawPixmap(820,20,scored.S5);
        break;
    case 6:
        painter.drawPixmap(820,20,scored.S6);
        break;
    case 7:
        painter.drawPixmap(820,20,scored.S7);
        break;
    case 8:
        painter.drawPixmap(820,20,scored.S8);
        break;
    case 9:
        painter.drawPixmap(820,20,scored.S9);
        break;
    }
    switch(point/10){
    case 0:
        painter.drawPixmap(788,20,scored.S0);
        break;
    case 1:
        painter.drawPixmap(788,20,scored.S1);
        break;
    case 2:
        painter.drawPixmap(788,20,scored.S2);
        break;
    case 3:
        painter.drawPixmap(788,20,scored.S3);
        break;
    case 4:
        painter.drawPixmap(788,20,scored.S4);
        break;
    case 5:
        painter.drawPixmap(788,20,scored.S5);
        break;
    case 6:
        painter.drawPixmap(788,20,scored.S6);
        break;
    case 7:
        painter.drawPixmap(788,20,scored.S7);
        break;
    case 8:
        painter.drawPixmap(788,20,scored.S8);
        break;
    case 9:
        painter.drawPixmap(786,20,scored.S9);
        break;
    }
    //绘制gameover
    if(gameover.state==1){
        painter.drawPixmap(320,200,gameover.gameovermap);
    }
}

void MainWindow::PlayGame(){
    //启动定时器
        Timer.start();
        //监听定时器
        connect(&Timer,&QTimer::timeout,[=](){
           //更新游戏中元素的坐标
           updatePosition();
           //碰撞检测
           crash();
           //抽象计时器
           if(start==1&&gameover.state==0){
            count++;
           }
           //管道刷新器
           moretube();
           //刷新计分器
           score();
           //重新绘制图片
           update();
           //变速器
           speedup();
       });
   }

void MainWindow::updatePosition(){
    base.baseposition();
    if(start==1){
        if(gameover.state==1){
            bird.dead();
        }else{
            bird.birdposition();
        }
        for(int j=0;j<TUBE_NUM;j++){
            tube[j].updateposition();
        }
    }
}

void MainWindow::keyPressEvent(QKeyEvent *e){
    int y=bird.m_y;
    if(e->key() == Qt::Key_Space){
        y-=BIRD_UP_SPEED;
        if(gameover.state==0){
            QSound::play(BIRD_FLAP2_SOUND);
        }
        if(start==0){
            start=1;
            QSound::play(BIRD_FLAP1_SOUND);
        }
    }
    bird.setposition(bird.m_x,y);
    if(gameover.state==1){
        if(e->key() == Qt::Key_Space){
            judge=1;
            initial();
        }
    }
}

void MainWindow::crash(){
    if(bird.rec.intersects(base.base_rect)){
        gameover.state=1;
    }
    for(int j=0;j<TUBE_NUM;j++){
        if(bird.rec.intersects(tube[j].rec)){
            gameover.state=1;
        }
        if(bird.rec.intersects(tube[j].rrec)){
            gameover.state=1;
        }
    }
}
void MainWindow:: moretube(){
    int w=count/(TUBE_INTERVAL-point*4);
    tube[w].exist=true;
}
void MainWindow::score(){
     if(bird.m_x>=tube[point].m_x&&bird.m_x<=tube[point].m_x+tube[point].rec.width()&&gameover.state==0){
         point++;
         QSound::play(BIRD_SCORE_SOUND);
     }
}
void MainWindow::speedup(){
    for(int i=0;i<TUBE_NUM;i++){
        tube[i].speed=point*0.2+TUBE_SPEED ;
    }
    base.speed=point*0.2+TUBE_SPEED;
}

void MainWindow:: recover(){
    bird.birdmap.load(BIRD_UP_PATH);
    for(int i=0;i<TUBE_NUM;i++){
        tube[i].m_x=GAME_WIDTH;
        tube[i].m_rx=GAME_WIDTH;
        tube[i].rec.moveTo(tube[i].m_x,tube[i].m_y);
        tube[i].rrec.moveTo(tube[i].m_rx,tube[i].m_ry);
        tube[i].exist=false;
    }
    bird.m_x=GAME_WIDTH *0.1;
    bird.m_y=GAME_HEIGHT*0.5;
    bird.rec.moveTo(bird.m_x,bird.m_y);
    bird.sound=0;
}

map.h

#ifndef MAP_H
#define MAP_H
#include<QPixmap>

class Map
{
public:
    QPixmap map1;
    QPixmap map2;
    QPixmap startmap;
    int map_posY;
    Map();
};

#endif // MAP_H

 map.cpp

#include "map.h"
#include"config.h"

Map::Map()
{
    map1.load(MAP1_PATH);
    map2.load(MAP2_PATH);
    startmap.load(STARTMAP_PATH);
    map_posY=0;
}

 score.h

#ifndef SCORE_H
#define SCORE_H
#include<QPixmap>
#include"config.h"
class Score
{
public:
    Score();
    QPixmap S0;
    QPixmap S1;
    QPixmap S2;
    QPixmap S3;
    QPixmap S4;
    QPixmap S5;
    QPixmap S6;
    QPixmap S7;
    QPixmap S8;
    QPixmap S9;
};

#endif // SCORE_H

 score.cpp

#include "score.h"

Score::Score()
{
    S0.load(S_0);
    S1.load(S_1);
    S2.load(S_2);
    S3.load(S_3);
    S4.load(S_4);
    S5.load(S_5);
    S6.load(S_6);
    S7.load(S_7);
    S8.load(S_8);
    S9.load(S_9);
}

tube.h

#ifndef TUBE_H
#define TUBE_H
#include"config.h"
#include<QPixmap>
#include<QRect>
#include<QtGlobal>
#include <cstdlib>

class Tube
{
public:
    Tube();
    void updateposition();//更新管道坐标
    QPixmap tube;//管道资源
    QPixmap rtube;//上管道资源
    QRect rec;//下管道实体矩形
    int m_x;//下管道坐标x
    int m_y;//下管道坐标y
    QRect rrec;//上管道实体矩形
    int m_rx;//上管道坐标
    int m_ry;//上管道坐标
    bool exist;
    int speed;
};

#endif // TUBE_H

 tube.cpp文章来源地址https://www.toymoban.com/news/detail-408162.html

#include "tube.h"

Tube::Tube()
{
    tube.load(TUBE_PATH);//加载管道资源
    rec.setWidth(tube.width());//设置实体矩形宽高
    rec.setHeight(tube.height());

    rtube.load(TUBE_PATH2);//加载上管道资源
    rrec.setWidth(rtube.width());//设置上管道实体矩阵
    rrec.setHeight(rtube.height());

    m_y=( qrand() % (GAME_HEIGHT-100 - 180 + 1) ) + 160;//利用随机数生成初始y坐标
    m_x=GAME_WIDTH;//设置初始X坐标
    rec.moveTo(m_x,m_y);//设置初始矩形位置

    m_rx=GAME_WIDTH;
    m_ry=m_y-rtube.height()-180;
    rrec.moveTo(m_rx,m_ry);//设置上矩形初始位置

    speed=TUBE_SPEED;
    exist=false;
}
void Tube::updateposition(){
    if(exist==true){
        m_x-=speed;
        m_rx-=speed;
        rec.moveTo(m_x,m_y);
        rrec.moveTo(m_rx,m_ry);
    }
}

到了这里,关于通关大一编程实践,用C++基础和Qt实现FlappyBird小游戏的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言大一期末考试必会编程题

    C语言期末考试必会的编程题,掌握了保证你能不挂科 素数定义: 素数又称质数,是指一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数;比如1,2,3,5,7,9,是素数,4,6,8,10不是素数。 思路: 比如一个数5,判断它是不是素数,就用5依次除以2~4的每

    2024年02月05日
    浏览(47)
  • 大一上学期c语言期末----程序填空 及 编程题

    1、 运行结果:请用标准日期格式输入一个日期(YYYY-MM-DD): 若用户从键盘输入:2022-12-12回车 则输出结果为: 2022 年 12 月 12 日 2、 2、 完成下面程序,计算圆的面积,结果保留两位小数: 3、 s 100 =1-1/2+1/3-1/4+…+1/999-1/1000 4、 求 1!+3!+5!+……+n!的和 5、 方法:辗转法求最大公约

    2024年02月03日
    浏览(46)
  • 【如何入门编程】编程升级通关绝招

    入门编程可以通过以下几个步骤实现: 选择一门编程语言:选择一门你感兴趣的编程语言,例如Python、Java、C++等等。你可以通过学习资源、社区、工具等等来确定自己喜欢的编程语言。 学习编程基础:学习编程基础是编程入门的第一步,包括变量、数据类型、条件语句、循

    2024年02月07日
    浏览(28)
  • C++(Qt)与Python混合编程

    文章开头,请大家注意一点: 开始搭配环境之前,请大家完成对自己电脑位数的查看【32位/64位】 本篇博客适用于windows64位版本安装。当然了,32位的友友也可以对照着配置! 本篇教程所用环境如下: windows11(64位) Qt 6.4 Python 3.8.6 【由于本人已安装python版本为3.8.6,不想重

    2024年01月24日
    浏览(37)
  • C++网络编程 TCP套接字基础知识,利用TCP套接字实现客户端-服务端通信

    流式套接字编程针对TCP协议通信,即是面向对象的通信,分为服务端和客户端两部分。 1)加载套接字库( 使用函数WSAStartup() ),创建套接字( 使用socket() ) 2)绑定套接字到一个IP地址和一个端口上( 使用函数bind() ) 3)将套接字设置为监听模式等待连接请求( 使用函数

    2024年02月03日
    浏览(60)
  • C++ easyx大一期末作业利器

    紧跟着1.0版本的完成,又到了激动人心的C++期末作业了。(上学期是C语言的),这学期,我仅仅改了一点点上学期的期末作业,然后很轻松的水…啊不,完成了这次的期末作业。 所以,大家一定要注重复用!!! 很快乐。 这个改一改就是你的…啊不是,我是说基于框架进行开发

    2024年02月15日
    浏览(47)
  • Qt固件映像 Raspberry Pi 嵌入式C++(Qt)编程

    在我们的游戏中,我们有一个桨、一个球和三十块砖。 计时器用于创建游戏周期。 我们不处理角度,我们只是改变方向:上、下、左、右。 Qt5 库是为创建计算机应用程序而开发的。尽管如此,它也可以用来创建游戏。开发计算机游戏是了解有关 Qt5 的更多信息的好方法。

    2024年01月19日
    浏览(43)
  • 深入《C++ Core Guidelines解析》:提升C++编程实践的关键指南

    C++因为其学习易学难用,曲线比较大,劝退了不少人(入门即放弃)。从学会使用 C++ 到用好 C++ 需要经过多年持续不断的学习和实践。自 C++11 以来,标准委员会每三年一更新,如今的“modern C++”相较于之前的 C++98 来说变化相当大。C++ 在成长和变化,对于 C++ 程序员来说,确

    2024年02月09日
    浏览(47)
  • QT Quick之quick与C++混合编程

            Qt quick能够生成非常绚丽界面,但有其局限性的,对于一些业务逻辑和复杂算法,比如低阶的网络编程如 QTcpSocket ,多线程,又如 XML 文档处理类库 QXmlStreamReader / QXmlStreamWriter 等等,在 QML 中要么不可用,要么用起来不方便,所以就有了quick和C++混合编程的需求。

    2024年02月12日
    浏览(40)
  • [编程语言][C++][Qt]单独添加UI文件

    不知什么原因,Qt Creator并不是很完美很智能。当先写好界面类的头文件和源代码文件后,我们再添加用于可视化界面设计的UI文件时,会出现一些问题。 当使用CMake管理项目时,CMake会读取 CMakeLists.txt 文件来确定各种项目设置。需要把 MainWindow.ui 包含进项目时,在 CMakeLists.

    2024年02月07日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包