目录
内容简介
项目要求
项目实现
素材导入
核心思路
思路的转变:从main到mainwindow
如何让游戏动起来
如何设计一个物体类
如何从键盘输入操作
如何绘制图片
如何初始化
项目源码
内容简介
该项目实现了基于Qt的FlappyBird动画游戏开发,我会从素材导入开始带大家熟悉Qt开发的全过程,该CrashCourse偏向于对C++有一定基础,并且了解Qt基本运作原理的同学
项目要求
该应用是一个飞鸟躲避障碍的游戏,刚开始除以待机状态按下space后飞鸟就开始不断下落,玩家需要通过按space让小鸟跳跃来保持高度穿过随机生成的障碍,右上角会记录小鸟的得分,即经过障碍的个数,随着游戏的进行,障碍速度变快,障碍密度增加,飞鸟在拍打翅膀和越过障碍时会有提示音,碰撞和游戏结束时也会有相应的音效,以提高反馈感,在游戏结束后,会弹出gameover的字样,再次按下space后游戏会重新开始,计数也会清零。
项目实现
素材导入
素材导入有两种方法:
1.直接以创建qt resource file导入
2.在导入后转化为二进制形式文件,第二种较复杂,因为每次添加新资源后都需要进行一遍二进制化操作,但相应的资源大小会被压缩很多,在资源文教较大时可考虑存为二进制形式
这里只讲第一种方法,至于二进制化可以参考这个博主的文章。
先从github,csdn等网站上找到flaggybird所需的资源放在同一文件夹里,然后移动该文件夹到项目所在目录
右键flappybird(项目名)点击添加新文件,选Qt,选Qt Resource File,取名字,
点击添加,添加前缀,删除前缀到只剩下/,然后点添加,添加文件,选中资源文件夹,拖动选取所有资源
这样就完成了资源的导入,需要使用时只要记住资源所在目录,然后宏定义替换掉即可
注意:如果出现以下情况
说明资源文件太大,要转化为二进制文件,也就是方法二,在上面有详细方法的链接,这里再粘贴一遍
至此就完成了素材的导入工作,在需要用到图片或音频的时候只需要通过宏定义就可以调用了,后面会给出具体宏定义的代码,在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++项目的小伙伴可能依然习惯于上课所学的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文章来源:https://www.toymoban.com/news/detail-408162.html
#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模板网!