QT练手小项目:飞机大战

目录

1.项目简介

2.创建项目

3.设置主场景导入资源

3.1配置文件

3.2主场景的初始化

3.3资源导入

4.地图滚动的实现

4.1创建地图文件和类

4.2添加地图类的成员函数和成员属性

5.创建我们的飞机

5.1创建我们的飞机文件和类

5.2添加成员函数和成员属性

6.子弹的绘制

6.1创建飞机子弹文件和类

6.2添加子弹成员函数和成员属性

6.3成员函数的实现

7.敌方飞机的绘制

7.1添加敌方飞机文件和类

7.2添加成员函数和成员属性

7.3成员函数的实现

8.boss类的制作

8.1 boss类文件的添加

8.2 添加成员函数和成员属性

8.3 成员函数的实现

9.boss子弹类的制作

10.碰撞检测

11.爆炸类的制作

11.1 创建爆炸文件和类

11.2 添加爆炸成员和成员属性

11.3 实现成员函数


1.项目简介

飞机大战相信大家都曾玩过,今天我们来制作一款我们自己的飞机大战,鼠标控制我们自己的飞机移动发射子弹,消灭上方的敌机和boss,我先把我们最终的效果给大家看一下

11

基础的功能基本都实现了,后续还可以加入更多的玩法进行升级,比如给飞机升级,添加关卡,以及不同的boss战等。

从视频中可以看到我们的一些需要实现的功能:

1.我们的地图背景是滚动的

2.飞机的绘制和控制移动

3.子弹的绘制和发射

4.敌机的绘制

5.碰撞检测,通过检测我们图片方框是否相交进行碰撞检测来判断子弹是否击中或者自己是否被击毁

6.坠毁的爆炸效果

2.创建项目

打开QT创建我们的项目,主窗口基类我们选择QWidget,我们的主场景我们取名mainsence,之后的游戏场景功能等实现都要在这个窗口进行,这里博主没有创建开始游戏的界面,直接就进入游戏,可以再创建一个主窗口游戏开始界面,通过点击开始按钮进入到游戏中。

3.设置主场景导入资源

我们需要先对游戏需要配置的资源,和界面大小,游戏图标进行设置

3.1配置文件

为了之后的程序设计方便我们先创建了一个配置数据头文件config.h来保存资源配置数据,方便后期进行修改,先将游戏窗口的大小进行设置,这里注意窗口大小取决于你选择的地图图片大小

/*****游戏配置信息******/
#define GAME_HEIGHT 768//游戏界面高度
#define GAME_WIDTH 512 //游戏界面宽度
#define GAME_TITLE "雷霆战机1.0" //游戏标题

3.2主场景的初始化

在我们的主场景类中添加一个初始化界面的成员函数initsence来初始化我们的游戏场景

void initscene();//场景初始化函数

(alt键+回车键可以快速在mainsence.cpp文件中进行该成员函数的定义)在cpp文件中我们进行该成员函数的实现

void mainsence::initscene()
{
    //固定游戏窗口大小
    setFixedSize(GAME_WIDTH,GAME_HEIGHT);

    //设置窗口标题
    setWindowTitle(GAME_TITLE);

    //加载图标
    setWindowIcon(QIcon(GAME_ICON));

    //定时器的设置
    m_Timer.setInterval(GAME_RATE);

    //敌机出场时间间隔初始化
    m_recorder=0;
    //boss出场时间初始化
    boss_recorder=0;
    //随机数种子
    srand((unsigned int)time(NULL));
    //加载游戏结束图片
    gameover.load(GAME_OVER);

}

随后在构造函数中我们调用该成员函数进行界面的初始化:

mainsence::mainsence(QWidget *parent)
    : QWidget(parent)
{
    initscene();//初始化场景
    playGame();//启动游戏
}

3.3资源导入

由于我们游戏中需要用到许多的图片和音频资源,所以我们需要对这些资源进行整理并放在我们的工程文件中。我们右键点击工程添加新文件选择下图中我们用箭头标注的选项

 过后会生成一个qrc文件,我们点击编辑将我们的资源全部添加进去,但如果资源过大,我们可以将qrc文件变成rcc二进制文件这会极大减少资源的所占的内存大小。

我们在资源所在文件夹打开cmd命令窗口:输入以下指令将qrc文件转换成rcc文件,这样一可以极大减少资源所占空间还可以防止我们游戏内的图片资源被人随意替换!!

随后我们需要在配置文件config.h中加入我们的rcc资源路径

#define GAME_RES_PATH "./plane.rcc" //rcc文件路径
#define GAME_ICON ":/res/app_ico" //图标加载路径

 并且需要我们在主函数main.c中加载我们的rcc文件:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QResource::registerResource("./plane.rcc");
    mainsence w;
    w.show();

    return a.exec();
}

4.地图滚动的实现

4.1创建地图文件和类

点击我们的工程文件右键添加新文件,将我们的地图类取名为map

4.2添加地图类的成员函数和成员属性

我们要如何实现地图的滚动效果呢,首先我们需要两张一样的地图背景图,将他们拼接起来,将地图Y轴坐标进行改变实现滚动效果,就如下面这样背景图两张拼接起来,滚动起来不会有割裂感

 map.h

class Map
{
public:
    //构造函数
    Map();

    //地图滚动坐标计算
    void mapPosition();

    //地图图片对象
    QPixmap m_map1;
    QPixmap m_map2;

    //地图Y轴坐标
    int m_map1_posY;
    int m_map2_posY;

    //地图滚动速度
    int m_scroll_speed;

};

因为需要实现滚动效果所以我们需要一段时间对地图进行重新绘制,所以成员函数中我们设计了一个更新地图滚动坐标计算的函数,同时我们需要将地图的配置信息添加到config.h中

/*****地图配置路径******/
#define MAP_PATH ":/res/img_bg_level_1" //地图1路径
#define MAP_SCROLL_SPEED 2//地图滚动速度

我们在map.cpp文件中实现该成员函数:

#include "map.h"
#include "config.h"
Map::Map()
{
    //初始化加载地图对象
    m_map1.load(MAP_PATH);
    m_map2.load(MAP_PATH);

    //初始化Y轴坐标
    m_map1_posY=-GAME_HEIGHT;
    m_map2_posY=0;

    //地图滚动速度
    m_scroll_speed=MAP_SCROLL_SPEED;

}

void Map:: mapPosition()
{
    //处理第一张图片的滚动位置
    m_map1_posY+=MAP_SCROLL_SPEED;
    if(m_map1_posY>=0)
    {
        m_map1_posY=-GAME_HEIGHT;
    }

    //处理第二章图片的滚动位置
    m_map2_posY+=MAP_SCROLL_SPEED;
    if(m_map2_posY>=GAME_HEIGHT)
    {
        m_map2_posY=0;
    }

}

需要注意的是两张地图在滚动过程中如果第一张图的Y轴坐标为0,第二张图Y轴坐标已经越过游戏窗口界面的高度,那么我们需要对两张图的坐标进行重置,来达到循环播放滚动的效果。同时我们需要在主界面类中添加一个定时器对象来决定我们主窗口的刷新时间

QTimer m_Timer;//定时器1

并且我们需要重写绘图事件来绘制我们需要的地图资源

  void mainsence::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawPixmap(0,m_map.m_map1_posY,m_map.m_map1);
    painter.drawPixmap(0,m_map.m_map2_posY,m_map.m_map2);
}

mainscence.h

#ifndef MAINSENCE_H
#define MAINSENCE_H
#include <QTimer>
#include <QWidget>
#include <map.h>
#include <heroPlane.h>
#include <QMouseEvent>
#include <bullet.h>
#include <enemyplane.h>
#include <boss.h>
#include <bomb.h>
#include <QMediaPlayer>
class mainsence : public QWidget
{
    Q_OBJECT

public:
    mainsence(QWidget *parent = 0);
    ~mainsence();

    void initscene();//场景初始化函数

    void playGame();//启动游戏

    void updatePosition();//更新游戏资源坐标

    void paintEvent(QPaintEvent *);//绘制资源到屏幕

    void mouseMoveEvent(QMouseEvent*);//重写鼠标移动事件方便飞机移动

    void enemyToScene();//敌机小兵出场

    void collisionDetection();//碰撞检测

    void BossToScene();//BOSS出场

    void playmusic();//播放音乐



    bomb m_bombs[BOMB_NUM];//爆炸数组

    QTimer m_Timer;//定时器1
    Map m_map;//地图对象
    heroPlane m_hero;//飞机对象
    boss m_boss;//boss对象
    enemyPlane m_enemys[ENEMY_NUM];//敌机对象数组
    int m_recorder;//敌机出场间隔记录
    int boss_recorder;//boss出场记录时间
    QPixmap gameover;//结束游戏对象
   

};

#endif // MAINSENCE_H

加载了资源后我们可以在主界面类的成员函数playgame中来实现游戏的启动:当定时器信号达到我们就会进行一次资源的刷新,包括敌人的数量位置,我方的飞机子弹位置,是否有飞机坠毁等等

void mainsence::playGame()
{
    //启动背景音乐

    m_Timer.start();//启动定时器
    //监听定时器
    connect(&m_Timer,&QTimer::timeout,[=](){
       //敌机出场
        enemyToScene();
        //boss出场
        BossToScene();
        //更新游戏中元素坐标
        updatePosition();
        //绘制到屏幕
        update();
        //碰撞检测
        collisionDetection();
    });

}

5.创建我们的飞机

5.1创建我们的飞机文件和类

还是一样我们需要在工程文件中添加新文件,也就是我们的飞机文件类,然后将我们的飞机配置资源添加到config.h头文件中,方便我们进行修改

/*****飞机配置资源*******/
#define HERO_PATH ":/res/hero_3"//飞机模型路径

5.2添加成员函数和成员属性

随后我们需要在我们的飞机类中添加我们的成员函数和成员属性,由于我们的飞机需要发射子弹,并且需要移动,需要一个参数来记录飞机当前状态,如果被击毁则将状态变为空闲,以免绘图事件在飞机坠毁后继续绘制

heroplane.h

class heroPlane
{
public:
    heroPlane();
    //发射子弹函数
    void shoot();
    //更改飞机位置的函数
    void setPosition(int x,int y);
    //飞机资源对象
    QPixmap m_Plane;
    //飞机坐标位置
    int m_x;
    int m_y;
    //飞机边框方便检测碰撞事件
    QRect m_Rect;
    //弹匣
    bullet m_bullets[BULLET_NUM];
    //发射冷却时间
    int m_recorder;


    //飞机状态
    bool m_Free;


};

在cpp文件中进行初始化和成员函数的实现

heroplane.cpp

#include "heroplane.h"
#include "config.h"
#include "bullet.h"
heroPlane::heroPlane()
{
    //加载飞机模型资源
    m_Plane.load(HERO_PATH);
    //初始化飞机初始位置
    m_x=(GAME_WIDTH-m_Plane.width())/2;
    m_y=GAME_HEIGHT-m_Plane.height();

    //初始化边框
    m_Rect.setWidth(m_Plane.width());
    m_Rect.setHeight(m_Plane.height());
    m_Rect.moveTo(m_x,m_y);

    //初始化飞机状态
    m_Free=false;//表示飞机正在运作,如果后面被击中则变成闲置状态

    m_recorder=0;
}
void heroPlane::shoot()
{
    //累加时间间隔记录变量
    m_recorder++;

    //如果记录的数字,未达到发射间隔时间则不发射子弹
    if(m_recorder<BULLET_INTERVAL)
    {
        return ;
    }
    //达到发射时间,重置时间以便下次发射子弹
    m_recorder=0;

    //发射子弹
    for(int i=0;i<BULLET_NUM;i++)
    {
        //如果该子弹为空闲状态则发射
        if(m_bullets[i].m_Free)
        {
            //将空闲状态改为假
            m_bullets[i].m_Free=false;
            //设置子弹坐标
            m_bullets[i].m_x=m_x+m_Rect.width()*0.5-40 ;
            m_bullets[i].m_y=m_y;
            break;
        }
    }


}
//更新飞机位置
void heroPlane::setPosition(int x, int y)
{
    m_x=x;
    m_y=y;
    m_Rect.moveTo(m_x,m_y);
}

随后我们也需要在绘图事件中添加绘制飞机:前提条件是飞机不处于闲置状态

void mainsence::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
//绘制飞机
    if(m_hero.m_Free==false)
    {
        painter.drawPixmap(m_hero.m_x,m_hero.m_y,m_hero.m_Plane);
    }
  //飞机被击毁游戏结束绘制游戏结束
    else
    {
        painter.drawPixmap(GAME_WIDTH/2-100,GAME_HEIGHT/2-20,gameover);
    }
}

当然我们需要用鼠标控制飞机的移动那么就需要我们对鼠标移动事件进行重写,并且我们的飞机不能飞出游戏窗口外所以还需要进行边界检测来将飞机移动的位置固定下来:

void mainsence::mouseMoveEvent(QMouseEvent *event)
{
    int x=event->x()-m_hero.m_Rect.width()/2;
    int y=event->y()-m_hero.m_Rect.height()/2;
    //边界检测以免飞机飞出边框
    if(x<=0)
    {
        x=0;
    }
    if(x>=GAME_WIDTH-m_hero.m_Rect.width())
    {

        x=GAME_WIDTH-m_hero.m_Rect.width();
    }
    if(y>=GAME_HEIGHT-m_hero.m_Rect.height())
    {
        y=GAME_HEIGHT-m_hero.m_Rect.height();
    }
    if(y<=0)
    {
        y=0;

    }
    //设置飞机当前位置
    m_hero.setPosition(x,y);

}

6.子弹的绘制

6.1创建飞机子弹文件和类

首先和前面一样我们需要将子弹的文件类添加到工程文件中 ,随后将子弹的配置资源添加到config.h头文件中方便使用


/*****飞机子弹资源配置*******/
#define BULLET_PATH ":/res/bullet_5" //子弹图片路径
#define BULLET_SPEED 10 //子弹移动速度

#define BULLET_NUM 50 //子弹总数
#define BULLET_INTERVAL 20 //发射子弹间隔

6.2添加子弹成员函数和成员属性

bullet.h

#include <QPixmap>
#include <QRect>
class bullet
{
public:
    bullet();

    //子弹坐标
    void updatePosition();

    //子弹资源对象
    QPixmap m_Bullet;
    //子弹坐标
    int m_x;
    int m_y;
    //子弹速度
    int m_speed;
    //子弹是否闲置,飞出游戏界面就要回收
    bool m_Free;
    //子弹的边框方便进行碰撞检测
    QRect m_Rect;
};

6.3成员函数的实现

bullet.cpp

#include "bullet.h"
#include "map.h"
#include "config.h"
#include "QPixmap"

bullet::bullet()
{
    //加载子弹资源
    m_Bullet.load(BULLET_PATH);

    //子弹坐标
    m_x=GAME_WIDTH*0.5 - m_Bullet.width() *0.5;
    m_y=GAME_HEIGHT;

    //子弹状态
    m_Free=true;

    //子弹速度
    m_speed=BULLET_SPEED;

    //子弹边框
    m_Rect.setWidth(m_Bullet.width());
    m_Rect.setHeight(m_Bullet.height());
    m_Rect.moveTo(m_x,m_y);

}

void bullet::updatePosition()
{
    //判断子弹状态,子弹空闲不需要计算坐标
    if(m_Free)
    {
        return;
    }
    //子弹不处于空闲,子弹要向上移动
    m_y-=m_speed;
    m_Rect.moveTo(m_x,m_y);

    //子弹位置超出屏幕,需要把子弹状态变成空闲进行回收
    if(m_y<=-m_Rect.height())
        m_Free=true;

}
}

为了让子弹有顺序的发射我们给子弹添加了状态属性,当子弹发射间隔时间到了我们就将子弹组中空闲的子弹置为运行,然后利用绘图事件将其绘制到屏幕,并根据她坐标的实时变化来完成子弹的发射移动,当子弹飞出边框或者发生碰撞时我们就需要将其置为空闲状态,来达到子弹的循环使用,在飞机的类中heroplane.cpp我们已经实现了子弹发射的成员函数。

绘制子弹:

void mainsence::paintEvent(QPaintEvent *)
{
    QPainter painter(this);

//绘制子弹,只有在飞机是运行状态才需要绘制子弹
    if(m_hero.m_Free==false)
    {
        for(int i=0;i<BULLET_NUM;i++)
        {
            if(m_hero.m_bullets[i].m_Free==false)
            {
                painter.drawPixmap(m_hero.m_bullets[i].m_x,m_hero.m_bullets[i].m_y,m_hero.m_bullets[i].m_Bullet);
            }
        }
    }
}

7.敌方飞机的绘制

7.1添加敌方飞机文件和类

首先我们需要在工程文件中添加敌方飞机的类,并将配置资源放到config.h头文件中

/*****敌机配置资源*****/
#define ENEMY_PATH ":/res/img-plane_1" //敌机资源路径
#define ENEMY_SPEED 5 //敌机移动速度
#define ENEMY_NUM 30 //敌机数量
#define ENEMY_INTERVAL 30 //敌机出现间隔

7.2添加成员函数和成员属性

enemyplane.h

#include <QPixmap>
#include <QRect>
class enemyPlane
{
public:
    enemyPlane();

    //更新敌机坐标
    void updatePosition();
    //敌机对象
    QPixmap m_enemy;
    //敌机坐标位置
    int m_x;
    int m_y;
    //敌机的边框,便于碰撞检测
    QRect m_Rect;
    //状态
    bool m_Free;
    //敌机速度
    int m_speed;

};

同样我们需要实时获取敌机的坐标来完成敌机的绘制,以及敌机的状态,如果敌机与子弹发生碰撞则要将其状态设置为空闲,这样来达成我们敌机资源的循环使用

7.3成员函数的实现

enemyplane.cpp

#include "enemyplane.h"
#include "config.h"
enemyPlane::enemyPlane()
{
    //加载敌机资源
    m_enemy.load(ENEMY_PATH);
    //敌机初始位置
    m_x=0;
    m_y=0;

    //敌机状态
    m_Free=true;

    //敌机速度
    m_speed=ENEMY_SPEED;
    //敌机边框
    m_Rect.setWidth(m_enemy.width());
    m_Rect.setHeight(m_enemy.height());
    m_Rect.moveTo(m_x,m_y);
}
//更新敌机坐标
void enemyPlane::updatePosition()
{
    //空闲状态的敌机不需要计算坐标
    if(m_Free)
        return ;
    m_y+=m_speed;
    m_Rect.moveTo(m_x,m_y);
    //如果超出屏幕 重置空闲状态
    if(m_y>=GAME_HEIGHT+m_Rect.height())
        m_Free=true;
}

敌机的出场也需要我们成员函数来实现所以我们需要在主界面类中进行敌机出场的函数实现来控制敌人的出现间隔:

//敌机出场
void mainsence::enemyToScene()
{
    m_recorder++;
    //未到出场时间
    if(m_recorder<ENEMY_INTERVAL)
        return ;
    //达到出场时间,重置记录
    m_recorder=0;
    for(int i=0;i<ENEMY_NUM;i++)
    {
        if(m_enemys[i].m_Free)
        {
            m_enemys[i].m_Free=false;
            m_enemys[i].m_x=rand()%(GAME_WIDTH-m_enemys[i].m_Rect.width());
            m_enemys[i].m_y=-m_enemys[i].m_Rect.height();
            break;
        }

    }

}

8.boss类的制作

8.1 boss类文件的添加

首先我们添加一个 boss类的文件到工程文件中,并将boss的配置资源加入到config.h头文件中

/******BOSS资源配置********/
#define BOSS_PATH ":/res/boss_1"  //boss飞机模型路径
#define BOSS_BULLET_PATH ":/res/bulletboss_2"  //boss子弹图片路径
#define BOSS_BULLET_NUM 100  //boss子弹数量
#define BOSS_BULLET_SPEED 2  //boss子弹速度
#define BOSS_INTERVAL 1000 //BOSS出现时间
#define BOSS_BULLET_INTERVAL 50 //boss发射子弹间隔

8.2 添加成员函数和成员属性

boss.h

#include "config.h"
#include <QPixmap>
#include <QRect>
#include <bossbullet.h>
class boss
{
public:
    boss();
    //发射子弹函数
    void shoot();
    //更改BOSS位置的函数
    //void setPosition(int x,int y);


    //BOSS资源对象
    QPixmap m_Bossplane;
    //BOSS坐标位置
    int m_x;
    int m_y;
    //BOSS边框方便检测碰撞事件
    QRect m_Rect;

    //boss子弹发射冷却时间
    int m_recorder;
    //boss状态
    bool m_Free;
    //boss血量
    int blood;
    //boss子弹弹匣
    bossbullet boss_bullets[BOSS_BULLET_NUM];
};

这里我设置了boss的坐标,状态,血量等,当血量为0时将boss状态设置为闲置等待下次出场,boss的位置我将其固定在了上方。

8.3 成员函数的实现

#include "boss.h"
#include "config.h"
#include <QDebug>
boss::boss()
{
    //加载BOSS模型资源
    m_Bossplane.load(BOSS_PATH);
    //初始化BOSS初始位置
    m_x=(GAME_WIDTH-m_Bossplane.width())/2;
    m_y=0;

    //初始化边框
    m_Rect.setWidth(m_Bossplane.width());
    m_Rect.setHeight(m_Bossplane.height());
   // m_Rect.moveTo(m_x,m_y);

    //初始化飞机状态
    m_Free=true;
    //初始化血量
    blood=100;

    m_recorder=0;

}

void boss::shoot()
{
    //累加时间间隔记录变量
    m_recorder++;

    //如果记录的数字,未达到发射间隔时间则不发射子弹
    if(m_recorder<BOSS_BULLET_INTERVAL)
    {
        return ;
    }
    //达到发射时间,重置时间以便下次发射子弹
    m_recorder=0;

    //发射子弹
    for(int i=0;i<BOSS_BULLET_NUM;i++)
    {
        //如果该子弹为空闲状态则发射
        if(boss_bullets[i].m_Free)
        {
           qDebug()<<"4";
            //将空闲状态改为假
           boss_bullets[i].m_Free=false;
            break;
        }
    }
}

boss子弹的发射和我方飞机子弹发射逻辑相同,当发射冷却时间到便将其中一个空闲的子弹置为运作状态。

boss的绘制同样在绘图事件中进行,所以我们需要在主界面添加一个boss出场的成员函数BossTosence

void mainsence::BossToScene()
{
    boss_recorder++;
    //未到出场时间
    if(boss_recorder<BOSS_INTERVAL)
        return ;
    //达到出场时间
     boss_recorder=0;
     m_boss.m_Free=false;
     m_boss.blood=1000;


}

当出场时间达到则在绘图事件中进行绘制:

void mainsence::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
//绘制boss
    if(m_boss.m_Free==false)
       painter.drawPixmap(m_boss.m_x,m_boss.m_y,m_boss.m_Bossplane);
}

9.boss子弹类的制作

boss子弹类制作和我方飞机步骤相同

bossbullet.h

class bossbullet
{
public:
    bossbullet();
    //boss子弹坐标
    void updatePosition();

    //子弹资源对象
    QPixmap boss_Bullet;
    //子弹坐标
    int m_x;
    int m_y;
    //子弹速度
    int m_speed;
    //子弹是否闲置,飞出游戏界面就要回收
    bool m_Free;
    //子弹的边框方便进行碰撞检测
    QRect m_Rect;
};

bossbullet.cpp

#include "bossbullet.h"
#include "map.h"
#include "config.h"
#include "QPixmap"
#include "boss.h"
bossbullet::bossbullet()
{
    //加载子弹资源
    boss_Bullet.load(BOSS_BULLET_PATH);

    //子弹坐标
    m_x=GAME_WIDTH*0.5-boss_Bullet.width()*0.5 ;
    m_y=GAME_HEIGHT*0.5-60;

    //子弹状态
    m_Free=true;

    //子弹速度
    m_speed=BOSS_BULLET_SPEED;

    //子弹边框
    m_Rect.setWidth(boss_Bullet.width());
    m_Rect.setHeight(boss_Bullet.height());
    m_Rect.moveTo(m_x,m_y);
}

void bossbullet::updatePosition()
{

    //判断子弹状态,子弹空闲不需要计算坐标
    if(m_Free)
    {
        return;
    }
    //子弹不处于空闲,子弹要向下移动
    m_y+=m_speed;
    m_Rect.moveTo(m_x,m_y);

    //子弹位置超出屏幕,需要把子弹状态变成空闲进行回收
    if(m_y>=GAME_HEIGHT)
        m_Free=true;
}

同样在子弹状态为运作时我们需要在绘图事件中进行绘制:

void mainsence::paintEvent(QPaintEvent *)
{
 //绘制boss子弹
    if(m_boss.m_Free==false)
    {
        qDebug()<<"1";
        for(int i=0;i<BOSS_BULLET_NUM;i++)
        {
            //qDebug()<<m_boss.boss_bullets[i].m_Free;
            if(m_boss.boss_bullets[i].m_Free==false)
            {
                //qDebug()<<"3";
                painter.drawPixmap(m_boss.boss_bullets[i].m_x,m_boss.boss_bullets[i].m_y,m_boss.boss_bullets[i].boss_Bullet);
            }
        }
    }
}

10.碰撞检测

我们还需要在主界面类中实现我们的碰撞检测,主要是利用一个函数来判断子弹飞机边框是否相交

首先我们将运行状态的子弹和敌机都循环遍历出来,随后循环判断他们边框是否相交,相交则将子弹和敌机状态都变为空闲等待下一次出场

//碰撞检测
void mainsence::collisionDetection()
{
    //遍历出场敌机
    for(int i=0;i<ENEMY_NUM;i++)
    {
        //空闲飞机则执行下一次循环
        if(m_enemys[i].m_Free)
        {
            continue;
        }
        //如果飞机与敌机相撞则飞机坠毁,将状态设置为空闲
        if(m_enemys[i].m_Rect.intersects(m_hero.m_Rect))
        {
            m_hero.m_Free=true;
            for(int k=0;k<BOMB_NUM;k++)
            {
                if(m_bombs[k].m_Free)
                {
                    //播放爆炸音效

                    //空闲的爆炸,可以播放
                    m_bombs[k].m_Free=false;
                    //更新爆炸坐标
                    m_bombs[k].m_x=m_hero.m_x;
                    m_bombs[k].m_y=m_hero.m_y;
                    break;
                }

            }

        }
        //遍历所有非空闲子弹
        for(int j=0;j<BULLET_NUM;j++)
        {
            //空闲子弹则执行下一次循环
            if(m_hero.m_bullets[i].m_Free)
            {
                continue;
            }

            //如果子弹边框和敌机边框相交则说明发生碰撞,则将他们状态设置为空闲
            if(m_enemys[i].m_Rect.intersects(m_hero.m_bullets[j].m_Rect))
            {
                m_enemys[i].m_Free=true;
                m_hero.m_bullets[i].m_Free=true;

                //播放爆炸效果
                for(int k=0;k<BOMB_NUM;k++)
                {
                    if(m_bombs[k].m_Free)
                    {
                        //播放爆炸音效

                        //空闲的爆炸,可以播放
                        m_bombs[k].m_Free=false;
                        //更新爆炸坐标
                        m_bombs[k].m_x=m_enemys[i].m_x;
                        m_bombs[k].m_y=m_enemys[i].m_y;
                        break;
                    }

                }
            }
            //判断子弹边框和boss边框是否相交,相交则扣除boss血量直到为0,boss状态设置为闲置
           if(m_boss.m_Free==false)
           {
                if(m_hero.m_bullets[j].m_Rect.intersects(m_boss.m_Rect))
                {
                    m_boss.blood--;
                    //qDebug()<<m_boss.blood;
                    if(m_boss.blood==0)
                    {
                        m_boss.m_Free=true;
                        //播放爆炸效果
                        for(int k=0;k<BOMB_NUM;k++)
                        {
                            if(m_bombs[k].m_Free)
                            {

                                 qDebug()<<"bomb";
                                //空闲的爆炸,可以播放
                                m_bombs[k].m_Free=false;
                                //更新爆炸坐标
                                m_bombs[k].m_x=m_boss.m_x;
                                m_bombs[k].m_y=m_boss.m_y;
                                break;
                             }

                        }

                    }
                }
            }
        }


    }


}

11.爆炸类的制作

11.1 创建爆炸文件和类

首先我们需要创建一个爆炸类的文件,并将爆炸的资源配置加入到config.h头文件中:由于我们的爆炸效果是一个动态的过程,我这里使用了7张图片来实现这个动态过程,这里路径中我们用了特殊的写法,这样可以让我们的路径可以显示多张图片的路径

/*****爆炸资源配置*******/
#define BOMB_PATH ":/res/bomb_%1" //爆炸资源图片
#define BOMB_NUM 32 //爆炸数量
#define BOMB_MAX 7 //爆炸图片最大索引
#define BOMB_INTERVAL 10 //爆炸图片显示间隔

11.2 添加爆炸成员和成员属性

bomb.h

#include "config.h"
#include <QPixmap>
#include <QVector>
class bomb
{
public:
    bomb();

    //更新爆炸信息
    void updateInfo();

    //放置爆炸资源的数组
    QVector<QPixmap> m_pixArr;

    //爆炸坐标
    int m_x;
    int m_y;

    //爆炸状态
    bool m_Free;

    //爆炸切图的时间间隔
    int m_Recoder;

    //爆炸时加载的图片下标
    int m_index;

};

这里我们将7张图片放进一个容器中,类似数组,后面我们可以根据下标来取出我们需要的对应图片。更新爆炸信息的成员函数表示每一张图片播放间隔之后我们会切换到下一张图片进行播放,直到七张播放完毕,在碰撞检测中,只要检测到碰撞我们就会调用爆炸对象,循环遍历爆炸组中哪个对像在空闲状态,将该对象进行播放,完毕后将该爆炸对象置为空闲方便循环使用爆炸资源。

11.3 实现成员函数

bomb.cpp

#include "bomb.h"
#include "config.h"
bomb::bomb()
{
    //将所有的爆炸资源放入数组中
    for(int i=1;i<=BOMB_MAX;i++)
    {
        QString str=QString(BOMB_PATH).arg(i);

        m_pixArr.push_back(QPixmap(str));
    }
    //坐标
    m_x=0;
    m_y=0;

    //空闲状态
    m_Free=true;

    //当前播放图片下标
    m_index=0;

    //播放爆炸间隔记录
    m_Recoder=0;
}

void bomb::updateInfo()
{
    if(m_Free)
        return;
    m_Recoder++;
    //如果记录爆炸的实际未达到爆炸间隔,不需要切图,直接return
    if(m_Recoder<BOMB_INTERVAL)
        return;
    //重置记录
    m_Recoder=0;
    //切换爆炸播放的图片下标
    m_index++;
    if(m_index>BOMB_MAX-1)
    {
        m_index=0;
        m_Free=true;//播放爆炸效果完成
    }

}

同时我们也需要根据爆炸对象状态来决定是否进行绘制,所以我们还需要在绘制事件中进行判断绘制:

void mainsence::paintEvent(QPaintEvent *)
{ 
//绘制爆炸
    for(int i=0;i<BOMB_NUM;i++)
    {
        if(m_bombs[i].m_Free==false)
        {
            painter.drawPixmap(m_bombs[i].m_x,m_bombs[i].m_y,m_bombs[i].m_pixArr[m_bombs[i].m_index]);
        }
    }
}

在游戏中我们需要随时更新界面的资源坐标所以在主界面类中我们还需要一个更新资源位置的函数来更新坐标,包括飞机子弹boss敌机等等资源:

void mainsence::updatePosition()
{
    //更新地图坐标
    m_map.mapPosition();

    //发射子弹
    m_hero.shoot();

   if(m_boss.m_Free==false)
    {  //boss发射子弹
       m_boss.shoot();
    }
    //计算所有非空闲状态子弹的当前坐标
    for(int i=0;i<BULLET_NUM;i++)
    {
        if(m_hero.m_bullets[i].m_Free==false)
        {
            m_hero.m_bullets[i].updatePosition();
        }
    }
    //计算boss所欲非空闲状态子弹的当前坐标
    for(int i=0;i<BOSS_BULLET_NUM;i++)
    {
        if(m_boss.boss_bullets[i].m_Free==false)
        {
            m_boss.boss_bullets[i].updatePosition();
        }
    }
    //更新敌机坐标
    for(int i=0;i<ENEMY_NUM;i++)
    {
        if(m_enemys[i].m_Free==false)
        {
            m_enemys[i].updatePosition();
        }
    }

    //计算爆炸的播放图片
    for(int i=0;i<BOMB_NUM;i++)
    {
        if(m_bombs[i].m_Free==false)
        {
            m_bombs[i].updateInfo();
        }
    }

}

到此我们的项目基本完成,但是还是比较简陋,后续可以进行更新升级更多的功能,其次在程序编写中,我意识到在必要的地方加上打印信息对后续的程序调试帮助巨大,可以让我们明白问题有可能出现在什么地方!!!希望大家在写程序过程中,多使用Debug来进行调试打印。

  • 13
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个简单的Qt飞机大战小游戏的代码示例: ```cpp #include <QApplication> #include <QGraphicsScene> #include <QGraphicsView> #include <QTimer> #include <QKeyEvent> // 飞机类 class Player : public QGraphicsRectItem { public: void keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Left) { setPos(x() - 10, y()); } else if (event->key() == Qt::Key_Right) { setPos(x() + 10, y()); } } }; // 子弹类 class Bullet : public QObject, public QGraphicsRectItem { Q_OBJECT public: Bullet() { QTimer *timer = new QTimer(); connect(timer, SIGNAL(timeout()), this, SLOT(move())); timer->start(50); } public slots: void move() { setPos(x(), y() - 10); if (pos().y() < 0) { scene()->removeItem(this); delete this; } } }; // 敌机类 class Enemy : public QObject, public QGraphicsRectItem { Q_OBJECT public: Enemy() { QTimer *timer = new QTimer(); connect(timer, SIGNAL(timeout()), this, SLOT(move())); timer->start(50); } public slots: void move() { setPos(x(), y() + 10); if (pos().y() > 600) { scene()->removeItem(this); delete this; } } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); // 创建场景和视图 QGraphicsScene scene; QGraphicsView view(&scene); view.setFixedSize(800, 600); view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view.show(); // 创建飞机并添加到场景中 Player player; player.setRect(0, 0, 100, 100); player.setPos(350, 500); scene.addItem(&player); player.setFlag(QGraphicsItem::ItemIsFocusable); player.setFocus(); // 按键事件处理 QObject::connect(&player, SIGNAL(keyPressEvent(QKeyEvent*)), &player, SLOT(keyPressEvent(QKeyEvent*))); // 创建敌机并添加到场景中 QTimer enemyTimer; QObject::connect(&enemyTimer, SIGNAL(timeout()), [&]() { Enemy *enemy = new Enemy(); enemy->setRect(0, 0, 100, 100); enemy->setPos(qrand() % 700, 0); scene.addItem(enemy); }); enemyTimer.start(1000); return a.exec(); } #include "main.moc" ``` 这只是一个简单的示例代码,实现了一个基本的飞机大战小游戏。你可以根据自己的需求进行进一步的修改和扩展。希望对你有所帮助!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值