Qt planeGame day10

时间: 2023-12-16 admin 维修知识

Qt planeGame day10

Qt planeGame day10

Qt planeGame day10

Game基本框架

  • qt中没有现成的游戏框架可以用,我们需要自己搭框架
  • 首先创建一个QGame类作为框架,这个基本框架里面应该有如下功能:
  • 游戏初始化
void init(const QSize& siez,const QString& title);
  • 游戏反初始化(清理)
void clean();
  • 更新游戏
void update(int);
  • 渲染游戏
void render(QPainter* painter);
  • 游戏是否在运行
bool isRunning() const;
  • 退出游戏
void quit();
  • 运行游戏
void runGame();
  • 设置游戏帧率
void setFps(qreal fps);
  • 一个游戏肯定要在一个主循环里面,在Qt中肯定不能使用死循环,就得使用定时器了,基本的变量
	//控制游戏进行的变量bool m_isRunning = false;//游戏的主循环QTimer* m_mainLoopTimerP{};//游戏帧率60帧qreal m_fps = 1000 / 60;
  • 基本框架思路:判断游戏是否运行中,运行中的话需要连接定时器处理游戏更新于绘图,然后开启定时器,因为qt的绘图只能在事件中完成,所以把渲染写在事件中即可,在定时器中去调用父类的更新画面
  • 基本框架

QGame.h

#ifndef QGAME_H_
#define QGAME_H_#include <QWidget>
#include <Qtimer>class QGame :public QWidget
{Q_OBJECT
public:QGame(QWidget* parent = nullptr);~QGame();//游戏初始化void init(const QSize& size, const QString& title);//游戏反初始化void clean();//更新游戏,站位符与父类的方法做区别void update(int);//渲染游戏void render(QPainter* painter);//游戏是否进行bool isRuning() const;//退出游戏void quit();//运行游戏void runGame();//设置游戏帧率void setFps(qreal fps);//获取游戏帧数的接口qreal fps() const { return m_fps; }
protected:void paintEvent(QPaintEvent* ev) override;
private://控制游戏进行的变量bool m_isRunning = false;//游戏的主循环QTimer* m_mainLoopTimer{};//游戏帧率60帧qreal m_fps = 1000 / 60;
};#endif

QGame.cpp

#include "QGame.h"
#include <QApplication>
#include <QPainter>
QGame::QGame(QWidget* parent) :QWidget(parent), m_mainLoopTimer(new QTimer)
{}
QGame::~QGame()
{//清理clean();
}
//游戏初始化
void QGame::init(const QSize& size, const QString& title)
{//设置窗口固定大小setFixedSize(size);//设置标题setWindowTitle(title);//因为上面会有各种资源的初始化,如果初始化失败,那么m_isRunning=false;游戏不继续运行了//初始化成功就为true m_isRunning = true;
}
//游戏反初始化
void QGame::clean()
{
}
//更新游戏
void QGame::update(int)
{
}
//渲染游戏
void QGame::render(QPainter* painter)
{
}
//游戏是否进行
bool QGame::isRuning() const
{return true;
}
//退出游戏
void QGame::quit()
{m_isRunning = false;
}
//运行游戏
void QGame::runGame()
{	//显示窗口show();//连接定时器m_mainLoopTimer->callOnTimeout([=](){//如果游戏没有在运行,那么就结束游戏if (!isRuning()){m_mainLoopTimer->stop();qApp->quit();}//更新游戏,QGame的自定义updateupdate(0);//重绘,父类的updateQWidget::update();//测试游戏是否在进行qDebug() << "游戏运行中";});//开始定时器,设置游戏开启的帧数m_mainLoopTimer->start(m_fps);
}
//设置游戏帧率
void QGame::setFps(qreal fps)
{m_fps = fps;
}//绘图事件
void QGame::paintEvent(QPaintEvent* ev)
{//开启画家QPainter painter(this);//渲染游戏render(&painter);
}

mian.cpp

#include <QApplication>
#include "QGame.h"int main(int argc,char* argv[])
{QApplication a(argc, argv);QGame game;game.init({ 600,600 }, "小瓜");game.runGame();return a.exec();
}
  • 运行结果,游戏是在主循环中,基本框架搭建完毕

构建精灵与实体类

实体类

  • 新建一个Entity空类,什么都不需要继承,这个类里面可以存放各种实体,我们统一称为精灵,每一个精灵都会有一种状态,例如是否死亡;所以我们还需要存在一个类别用与判断实体类型。
private:bool m_active = true;//实体是否是活动的int m_type = 0;//实体类型
  • 那么这个实体被精灵继承的时候,是需要更新释放渲染实体的,所以这个实体类一定要有虚析构与纯虚方法,不然子类可能释放不了造成内存泄漏
public:virtual ~Entity() {};//虚析构,当子类继承重写的时候就可以释放子类的内存virtual void update() = 0;//更新实体virtual void render(QPainter* painter);//渲染实体
  • 我们当前实体类中的方法可以设置状态的销毁与实体的类型,到时候由一个统一的管理类去进行管理
	//接口bool active()const { return m_active; }int type()const { return m_type; }//状态销毁void destory() { m_active = false; }//设置实体类型void setType(int type) { m_type = type; }
Entity.h
#ifndef ENTITY_H_
#define ENTITY_H_#include <QPainter>class Entity
{
public:virtual ~Entity() {};//虚析构,当子类继承重写的时候就可以释放子类的内存virtual void update() = 0;//更新实体virtual void render(QPainter* painter) = 0;//渲染实体//接口bool active()const { return m_active; }int type()const { return m_type; }//状态销毁void destroy() { m_active = false; }//设置实体类型void setType(int type) { m_type = type; }private:bool m_active = true;//精灵是否是活动的int m_type = 0;//精灵类型};
#endif // !ENTITY_H_

精灵类

  • 新建一个精灵类,这个类需要重写Entity的纯虚方法,这个类拥有设置坐标与加载图片的方法
private:QPixmap m_image;QVector2D m_pos;
  • 设置图片
//设置图片
void setPixmap(const QString& fileName, const QSize& size = QSize());
  • 设置坐标
//设置坐标
void setPos(float x, float y)
{m_pos = { x,y };
}
Sprite.h
#ifndef SPRITE_H_
#define SPRITE_H_#include "Entity.h"
#include <QVector2D>class Sprite :public Entity
{
public:Sprite() = default;Sprite(const QString& fileName, const QSize& size = QSize());//接口QVector2D getPos()const { return m_pos; }QPixmap getPixmap()const { return m_image; }//设置坐标void setPos(float x, float y){m_pos = { x,y };}//设置图片void setPixmap(const QString& fileName, const QSize& size = QSize());// 通过 Entity 继承void update() override;// 通过 Entity 继承void render(QPainter* painter) override;private:QPixmap m_image;QVector2D m_pos;
};
#endif // !SPRITE_H_
Sprite.cpp
#include "Sprite.h"Sprite::Sprite(const QString& fileName, const QSize& size)
{setPixmap(fileName, size);
}
//设置图片
void Sprite::setPixmap(const QString& fileName, const QSize& size)
{m_image.load(fileName);if (size.isValid()){//保持缩放	m_image.scaled(size, Qt::AspectRatioMode::KeepAspectRatio);}
}void Sprite::update()
{
}void Sprite::render(QPainter* painter)
{painter->drawPixmap(m_pos.toPoint(), m_image);
}
QGame.cpp
  • 在QGame.cpp中声明一个全局的精灵类,然后去初始化精灵
#include "QGame.h"
#include "Sprite.h"
#include <QApplication>
#include <QPainter>QGame::QGame(QWidget* parent) :QWidget(parent), m_mainLoopTimer(new QTimer)
{}
QGame::~QGame()
{//清理clean();
}//全局精灵类,注意这里必须使用指针类,如果是普通类对象会报错
//因为QT里面QApplication执行后是不允许对象还没有构造完的,指针是个不完整类就不会默认构造
Sprite* player;//游戏初始化
void QGame::init(const QSize& size, const QString& title)
{//设置窗口固定大小setFixedSize(size);//设置标题setWindowTitle(title);player = new Sprite;player->setPixmap(":/plane/Resource/images/hero1.png");//因为上面会有各种资源的初始化,如果初始化失败,那么m_isRunning=false;游戏不继续运行了//初始化成功就为true m_isRunning = true;
}
//游戏反初始化
void QGame::clean()
{
}
//更新游戏
void QGame::update(int)
{player->update();
}
//渲染游戏
void QGame::render(QPainter* painter)
{player->render(painter);
}
//游戏是否进行
bool QGame::isRuning() const
{return true;
}
//退出游戏
void QGame::quit()
{m_isRunning = false;
}
//运行游戏
void QGame::runGame()
{	//显示窗口show();//连接定时器m_mainLoopTimer->callOnTimeout([=](){//如果游戏没有在运行,那么就结束游戏if (!isRuning()){m_mainLoopTimer->stop();qApp->quit();}//更新游戏,QGame的自定义updateupdate(0);//重绘,父类的updateQWidget::update();//测试游戏是否在进行qDebug() << "游戏运行中";});//开始定时器,设置游戏开启的帧数m_mainLoopTimer->start(m_fps);
}
//设置游戏帧率
void QGame::setFps(qreal fps)
{m_fps = fps;
}//绘图事件
void QGame::paintEvent(QPaintEvent* ev)
{//开启画家QPainter painter(this);//渲染游戏render(&painter);
}
  • 运行结果

精灵移动

  • 毫无疑问,我们需要让精灵动起来,那肯定得使用事件去调用,采用两种方式去移动精灵,键盘事件和鼠标事件
  • 使用键盘事件的时候,我们需要知道一个知识点,我们采用分量概念去乘上速度来达到效果
Sprite.h中
public:Sprite() = default;
Sprite(const QString& fileName, const QSize& size = QSize());//接口
QVector2D getPos()const { return m_pos; }
QPixmap getPixmap()const { return m_image; }
QVector2D velocity() const{ return m_velocity; }
QVector2D& velocity() { return m_velocity; }
private:
//移动速度
float m_speed = 3;
//速度分量
QVector2D m_velocity;
-----------------------------------------------------------------
Sprite.cpp中
void Sprite::update()
{//获取一下坐标float x = m_pos.x();float y = m_pos.y();//通过分量去改变坐标的速度,移动就比较正常一点x += m_velocity.x() * m_speed;y += m_velocity.y() * m_speed;//将坐标给m_posm_pos = { x,y };
}

QGame.h

protected:void paintEvent(QPaintEvent* ev) override;void keyPressEvent(QKeyEvent* ev) override;void keyReleaseEvent(QKeyEvent* ev) override;void mouseMoveEvent(QMouseEvent* ev) override;

QGame.cpp

//捕获按键事件
void QGame::keyPressEvent(QKeyEvent* ev)
{switch (ev->key()){case Qt::Key_Up:player->velocity().setY(-1);break;case Qt::Key_Down:player->velocity().setY(1);break;case Qt::Key_Left:player->velocity().setX(-1);break;case Qt::Key_Right:player->velocity().setX(1);break;}
}void QGame::keyReleaseEvent(QKeyEvent* ev)
{switch (ev->key()){case Qt::Key_Up:case Qt::Key_Down:player->velocity().setY(0);break;case Qt::Key_Left:case Qt::Key_Right:player->velocity().setX(0);break;}
}void QGame::mouseMoveEvent(QMouseEvent* ev)
{//让鼠标居中到图片中间auto pos = player->sizeImage() / 2;player->setPos(ev->pos() - QPoint{ pos.width(),pos.height() });
}

子弹类,飞机类与单例设计模式

  • 构造两个类,一个子弹类一个飞机类,为了在这些类里面能使用QGame的实例,我们设计一个单例设计模式让QGame实例唯一存在

QGame.h

#ifndef QGAME_H_
#define QGAME_H_
#include <QWidget>
#include <Qtimer>
//宏定义一下这个单例
#define qGame QGame::instance()class QGame :public QWidget
{Q_OBJECT
public://单例设计模式,让QGame实例只允许存在一个static QGame* instance();QGame(QWidget* parent = nullptr);~QGame();//游戏初始化void init(const QSize& size, const QString& title);//游戏反初始化void clean();//更新游戏,站位符与父类的方法做区别void update(int);//渲染游戏void render(QPainter* painter);//游戏是否进行bool isRuning() const;//退出游戏void quit();//运行游戏void runGame();//设置游戏帧率void setFps(qreal fps);//获取游戏帧数的接口qreal fps() const { return m_fps; }
protected:void paintEvent(QPaintEvent* ev) override;void keyPressEvent(QKeyEvent* ev) override;void keyReleaseEvent(QKeyEvent* ev) override;void mouseMoveEvent(QMouseEvent* ev) override;
private://控制游戏进行的变量bool m_isRunning = false;//游戏的主循环QTimer* m_mainLoopTimer{};//游戏帧率60帧qreal m_fps = 1000 / 60;
};#endif

QGame.cpp

#include "QGame.h"
#include "Sprite.h"
#include <QApplication>
#include <QPainter>
#include <QKeyEvent>
#include <QMessageBox>//定义一个静态指针,指向唯一对象
static QGame* ins = nullptr;
QGame* QGame::instance()
{return ins;
}QGame::QGame(QWidget* parent) :QWidget(parent), m_mainLoopTimer(new QTimer)
{//保证不存在调用多个QGame实例Q_ASSERT_X(ins == nullptr, "QGame", "已经存在一个QGame实例");ins = this;
}
QGame::~QGame()
{//清理clean();
}//全局精灵类,注意这里必须使用指针类,如果是普通类对象会报错
//因为QT里面QApplication执行后是不允许对象还没有构造完的,指针是个不完整类就不会默认构造
Sprite* player;//游戏初始化
void QGame::init(const QSize& size, const QString& title)
{//设置窗口固定大小setFixedSize(size);//设置标题setWindowTitle(title);//开启鼠标自动追踪setMouseTracking(true);player = new Sprite;player->setPixmap(":/plane/Resource/images/hero1.png");//因为上面会有各种资源的初始化,如果初始化失败,那么m_isRunning=false;游戏不继续运行了//初始化成功就为true m_isRunning = true;
}
//游戏反初始化
void QGame::clean()
{
}
//更新游戏
void QGame::update(int)
{player->update();
}
//渲染游戏
void QGame::render(QPainter* painter)
{player->render(painter);
}
//游戏是否进行
bool QGame::isRuning() const
{return true;
}
//退出游戏
void QGame::quit()
{m_isRunning = false;
}
//运行游戏
void QGame::runGame()
{	//显示窗口show();//连接定时器m_mainLoopTimer->callOnTimeout([=](){//如果游戏没有在运行,那么就结束游戏if (!isRuning()){m_mainLoopTimer->stop();qApp->quit();}//更新游戏,QGame的自定义updateupdate(0);//重绘,父类的updateQWidget::update();//测试游戏是否在进行qDebug() << "游戏运行中";});//开始定时器,设置游戏开启的帧数m_mainLoopTimer->start(m_fps);
}
//设置游戏帧率
void QGame::setFps(qreal fps)
{m_fps = fps;
}//绘图事件
void QGame::paintEvent(QPaintEvent* ev)
{//开启画家QPainter painter(this);//渲染游戏render(&painter);
}//捕获按键事件
void QGame::keyPressEvent(QKeyEvent* ev)
{switch (ev->key()){case Qt::Key_Up:player->velocity().setY(-1);break;case Qt::Key_Down:player->velocity().setY(1);break;case Qt::Key_Left:player->velocity().setX(-1);break;case Qt::Key_Right:player->velocity().setX(1);break;}
}void QGame::keyReleaseEvent(QKeyEvent* ev)
{switch (ev->key()){case Qt::Key_Up:case Qt::Key_Down:player->velocity().setY(0);break;case Qt::Key_Left:case Qt::Key_Right:player->velocity().setX(0);break;}
}void QGame::mouseMoveEvent(QMouseEvent* ev)
{//让鼠标居中到图片中间auto pos = player->sizeImage() / 2;player->setPos(ev->pos() - QPoint{ pos.width(),pos.height() });
}

PlayerPlane.h

#ifndef PLAYERPLANE_H_
#define PLAYERPLANE_H_#include "Sprite.h"
#include "Bullet.h"
#include <array>
class PlayerPlane : public Sprite
{
public:PlayerPlane();//发射子弹bool emitBullet();
private:};
#endif // !PLAYERPLANE_H_
  • PlayerPlane.cpp
#include "PlayerPlane.h"PlayerPlane::PlayerPlane()
{}bool PlayerPlane::emitBullet()
{return false;
}

Bullte.h

#ifndef BULLET_H_
#define BULLET_H_#include "Sprite.h"
class Bullet :public Sprite
{
public://更新子弹出边界就得消失void update() override;
private:};
#endif // !BULLET_H_

Bullte.cpp

#include "Bullet.h"
#include "QGame.h"
void Bullet::update()
{//父类方法帮忙移动Sprite::update();//就可以调用游戏唯一的单例//如果子弹超出边界让子弹消失if (getPos().x() > qGame->width() || getPos().x() < 0 - sizeImage().width() ||getPos().y() > qGame->height() || getPos().y() < 0 - sizeImage().height()){destroy();}
}

精灵管理类

  • 创建一个EntityManager类来管理所有的实体与精灵,为这个类构造单例,然后使用链表去管理存储所有的实体与精灵。主游戏里面的所有实体与精灵就可以通过EntityManger这个单例去完成操作

EntityManager.h

#ifndef		ENTITYMANAGER_H_
#define		ENTITYMANAGER_H_#include"Sprite.h"
#include<QList>
#include<memory>
#include<QDebug>
class EntityManager
{
public://得到唯一的实例static EntityManager& instance(){static EntityManager ev;return ev;}//更新管理的所有实体与精灵void update(){for (auto& e : m_entities){e->update();}}//渲染void render(QPainter* painter){for (auto& e : m_entities){e->render(painter);}}//添加实体开个模版方便使用template<typename T = Entity>T* addEntity(T* e){m_entities.emplaceBack(e);return e;}//刷新,如果有实体要消除,就在这里销毁void refresh(){m_entities.removeIf([](Entity* e){if (!e->active()){//测试输出qDebug() << "destoryed" << e;//释放这个实体delete e;return true;}return false;});//测试输出qDebug() << m_entities.size();}private:QList<Entity*> m_entities;//构造函数私有化,设计单例EntityManager() {}
};
#endif

PlayerPlane.h

#ifndef PLAYERPLANE_H_
#define PLAYERPLANE_H_#include "Sprite.h"
#include "Bullet.h"
#include <array>
class PlayerPlane : public Sprite
{
public://继承父类的构造方法using Sprite::Sprite;PlayerPlane();//发射子弹bool emitBullet();
private:};
#endif // !PLAYERPLANE_H_

此时的PlayerPlane.cpp就可以处理发射子弹了

#include "PlayerPlane.h"
#include "EntityManager.h"
PlayerPlane::PlayerPlane()
{}bool PlayerPlane::emitBullet()
{Bullet* b = new Bullet;//添加子弹b->setPixmap(":/plane/Resource/images/bullet2.png");//设置子弹在主角上b->setPos(getPos() + QVector2D{ sizeImage().width() / 2.0f,0.0f });//子弹发出b->velocity().setY(-1);//添加进实体EntityManager::instance().addEntity(b);return false;
}

QGame.cpp

#include "QGame.h"
#include "Sprite.h"
#include "EntityManager.h"
#include "PlayerPlane.h"#include <QApplication>
#include <QPainter>
#include <QKeyEvent>
#include <QMessageBox>//定义一个静态指针,指向唯一对象
static QGame* ins = nullptr;
QGame* QGame::instance()
{return ins;
}QGame::QGame(QWidget* parent) :QWidget(parent), m_mainLoopTimer(new QTimer)
{//保证不存在调用多个QGame实例Q_ASSERT_X(ins == nullptr, "QGame", "已经存在一个QGame实例");ins = this;
}
QGame::~QGame()
{//清理clean();
}//全局精灵类,注意这里必须使用指针类,如果是普通类对象会报错
//因为QT里面QApplication执行后是不允许对象还没有构造完的,指针是个不完整类就不会默认构造
PlayerPlane* player;//游戏初始化
void QGame::init(const QSize& size, const QString& title)
{//设置窗口固定大小setFixedSize(size);//设置标题setWindowTitle(title);//开启鼠标自动追踪setMouseTracking(true);player = EntityManager::instance().addEntity(new PlayerPlane(":/plane/Resource/images/hero1.png"));//因为上面会有各种资源的初始化,如果初始化失败,那么m_isRunning=false;游戏不继续运行了//初始化成功就为true m_isRunning = true;
}
//游戏反初始化
void QGame::clean()
{
}
//更新游戏
void QGame::update(int)
{//刷新销毁所有不需要的实体精灵EntityManager::instance().refresh();//更新实体精灵信息EntityManager::instance().update();//发射子弹player->emitBullet();
}
//渲染游戏
void QGame::render(QPainter* painter)
{EntityManager::instance().render(painter);
}
//游戏是否进行
bool QGame::isRuning() const
{return true;
}
//退出游戏
void QGame::quit()
{m_isRunning = false;
}
//运行游戏
void QGame::runGame()
{	//显示窗口show();//连接定时器m_mainLoopTimer->callOnTimeout([=](){//如果游戏没有在运行,那么就结束游戏if (!isRuning()){m_mainLoopTimer->stop();qApp->quit();}//更新游戏,QGame的自定义updateupdate(0);//重绘,父类的updateQWidget::update();//测试游戏是否在进行//qDebug() << "游戏运行中";});//开始定时器,设置游戏开启的帧数m_mainLoopTimer->start(m_fps);
}
//设置游戏帧率
void QGame::setFps(qreal fps)
{m_fps = fps;
}//绘图事件
void QGame::paintEvent(QPaintEvent* ev)
{//开启画家QPainter painter(this);//渲染游戏render(&painter);
}//捕获按键事件
void QGame::keyPressEvent(QKeyEvent* ev)
{switch (ev->key()){case Qt::Key_Up:player->velocity().setY(-1);break;case Qt::Key_Down:player->velocity().setY(1);break;case Qt::Key_Left:player->velocity().setX(-1);break;case Qt::Key_Right:player->velocity().setX(1);break;}
}void QGame::keyReleaseEvent(QKeyEvent* ev)
{switch (ev->key()){case Qt::Key_Up:case Qt::Key_Down:player->velocity().setY(0);break;case Qt::Key_Left:case Qt::Key_Right:player->velocity().setX(0);break;}
}void QGame::mouseMoveEvent(QMouseEvent* ev)
{//让鼠标居中到图片中间auto pos = player->sizeImage() / 2;player->setPos(ev->pos() - QPoint{ pos.width(),pos.height() });
}

背景图滚动

  • 思路:因为沿着y轴移动,用两个变量来表示图片不同位置的y坐标,然后一个位置在窗口上,一个位置在窗口上方,让两个变量一直自增实现滚动像素,当窗口上的坐标大于窗口高度时,就把窗口上的y坐标重置为0,当窗口之上的坐标大于0时就把y坐标重置为一开始的窗口之上的坐标
Map::Map()
{//加载背景图m_pixmap.load(":/plane/Resource/images/background.png");//一个在窗口上面yPos1 = -m_pixmap.height();//一个在窗口上yPos2 = 0;
}
void Map::update()
{//实现滚动背景yPos1 += m_scrollSpeed;if (yPos1 >= 0){yPos1 = -m_pixmap.height();}yPos2 += m_scrollSpeed;//大于等于窗口高度后重新置为0if (yPos2 >= qGame->height()){yPos2 = 0;}
}
void Map::render(QPainter* painter)
{painter->drawPixmap(0, yPos1, m_pixmap);painter->drawPixmap(0, yPos2, m_pixmap);
}
  • 新建地图类继承实体类,然后去重写渲染与更新方法

Map.h

#ifndef MAP_H_
#define MAP_H_#include "Entity.h"
class Map :public Entity
{
public:Map();// 通过 Entity 继承virtual void update() override;virtual void render(QPainter* painter) override;
private:QPixmap m_pixmap;//用来实现滚动int yPos1,yPos2;int m_scrollSpeed = 2;
};
#endif // !MAP_H_

Map.cpp

#include "Map.h"
#include "QGame.h"
Map::Map()
{//加载背景图m_pixmap.load(":/plane/Resource/images/background.png");//一个在窗口上面yPos1 = -m_pixmap.height();//一个在窗口上yPos2 = 0;
}void Map::update()
{//实现滚动背景yPos1 += m_scrollSpeed;if (yPos1 >= 0){yPos1 = -m_pixmap.height();}yPos2 += m_scrollSpeed;//大于等于窗口高度后重新置为0if (yPos2 >= qGame->height()){yPos2 = 0;}
}void Map::render(QPainter* painter)
{painter->drawPixmap(0, yPos1, m_pixmap);painter->drawPixmap(0, yPos2, m_pixmap);
}

子弹与敌机碰撞

  • 新建一个类用来存放应该enum,enum里面存放不同类别标识,现在就需要在子弹,player,敌机生成的时候设置类别,方便后面进行碰撞判断,在EntityManager中提供类别识别方法,注意识别类型要是活动的,不然就没意义,在Sprite中构造矩阵变量,在update方法中添加矩阵的构造,采用矩阵碰撞方式去检测碰撞,最后在QGame.cpp中去完成敌机的生成与碰撞。
  • 基本完整框架如下:

main.cpp

#include <QApplication>
#include "QGame.h"int main(int argc,char* argv[])
{QApplication a(argc, argv);QGame game;game.init({ 480,852 }, "小瓜");game.runGame();return a.exec();
}

QGame.h

#ifndef QGAME_H_
#define QGAME_H_
#include <QWidget>
#include <Qtimer>
//宏定义一下这个单例
#define qGame QGame::instance()class QGame :public QWidget
{Q_OBJECT
public://单例设计模式,让QGame实例只允许存在一个static QGame* instance();QGame(QWidget* parent = nullptr);~QGame();//游戏初始化void init(const QSize& size, const QString& title);//游戏反初始化void clean();//更新游戏,站位符与父类的方法做区别void update(int);//渲染游戏void render(QPainter* painter);//游戏是否进行bool isRuning() const;//退出游戏void quit();//运行游戏void runGame();//设置游戏帧率void setFps(qreal fps);//获取游戏帧数的接口qreal fps() const { return m_fps; }
protected:void paintEvent(QPaintEvent* ev) override;void keyPressEvent(QKeyEvent* ev) override;void keyReleaseEvent(QKeyEvent* ev) override;void mouseMoveEvent(QMouseEvent* ev) override;
private://控制游戏进行的变量bool m_isRunning = false;//游戏的主循环QTimer* m_mainLoopTimer{};//游戏帧率60帧qreal m_fps = 1000 / 60;
};#endif

QGame.cpp

#include "QGame.h"
#include "Sprite.h"
#include "EntityManager.h"
#include "PlayerPlane.h"
#include "Map.h"#include <QApplication>
#include <QPainter>
#include <QKeyEvent>
#include <QMessageBox>
#include <QStringList>
//随机数头文件
#include <qrandom.h>//宏定义这个随机生成器函数
#define qRand(min,max) QRandomGenerator::global()->bounded(min, max)//定义一个静态指针,指向唯一对象
static QGame* ins = nullptr;
QGame* QGame::instance()
{return ins;
}QGame::QGame(QWidget* parent) :QWidget(parent), m_mainLoopTimer(new QTimer)
{//保证不存在调用多个QGame实例Q_ASSERT_X(ins == nullptr, "QGame", "已经存在一个QGame实例");ins = this;
}
QGame::~QGame()
{//清理clean();
}//全局精灵类,注意这里必须使用指针类,如果是普通类对象会报错
//因为QT里面QApplication执行后是不允许对象还没有构造完的,指针是个不完整类就不会默认构造
PlayerPlane* player;//游戏初始化
void QGame::init(const QSize& size, const QString& title)
{//设置窗口固定大小setFixedSize(size);//设置标题setWindowTitle(title);//开启鼠标自动追踪setMouseTracking(true);//初始化背景EntityManager::instance().addEntity(new Map);//初始化主角对象player = EntityManager::instance().addEntity(new PlayerPlane(":/plane/Resource/images/hero1.png"));player->setType(Player);//因为上面会有各种资源的初始化,如果初始化失败,那么m_isRunning=false;游戏不继续运行了//初始化成功就为true m_isRunning = true;
}
//游戏反初始化
void QGame::clean()
{
}
//更新游戏
void QGame::update(int)
{//刷新销毁所有不需要的实体精灵EntityManager::instance().refresh();//更新实体精灵信息EntityManager::instance().update();static int BulletVelocity = 0;//发射子弹的速率if (BulletVelocity % 10 == 0){//发射子弹player->emitBullet();}//出现敌机if (BulletVelocity % 60 == 0){QStringList efile = { ":/plane/Resource/images/enemy1.png",":/plane/Resource/images/enemy2.png" };auto enemy = new Sprite(efile[qRand(0,2)]);//设置敌机从上面往下enemy->velocity().setY(1);//设置随机出现的敌机位置enemy->setPos(qRand(0, width()), -50);//设置类别enemy->setType(Enemy);//添加到精灵管理类中EntityManager::instance().addEntity(enemy);}//获取子弹列表auto bullet_list = EntityManager::instance().getSpriteByType(bullet);//获取敌机列表auto enemy_list = EntityManager::instance().getSpriteByType(Enemy);//遍历,查看是否矩形碰撞for (auto& e : enemy_list){for (auto& b : bullet_list){//判断敌机是否包含子弹,如果包含就释放掉子弹和敌机if (e->collider().intersects(b->collider())){e->destroy();b->destroy();break;}}}BulletVelocity++;qDebug() <<"时间值:" << BulletVelocity;
}
//渲染游戏
void QGame::render(QPainter* painter)
{EntityManager::instance().render(painter);
}
//游戏是否进行
bool QGame::isRuning() const
{return true;
}
//退出游戏
void QGame::quit()
{m_isRunning = false;
}
//运行游戏
void QGame::runGame()
{	//显示窗口show();//连接定时器m_mainLoopTimer->callOnTimeout([=](){//如果游戏没有在运行,那么就结束游戏if (!isRuning()){m_mainLoopTimer->stop();qApp->quit();}//更新游戏,QGame的自定义updateupdate(0);//重绘,父类的updateQWidget::update();//测试游戏是否在进行//qDebug() << "游戏运行中";});//开始定时器,设置游戏开启的帧数m_mainLoopTimer->start(m_fps);
}
//设置游戏帧率
void QGame::setFps(qreal fps)
{m_fps = fps;
}//绘图事件
void QGame::paintEvent(QPaintEvent* ev)
{//开启画家QPainter painter(this);//渲染游戏render(&painter);
}//捕获按键事件
void QGame::keyPressEvent(QKeyEvent* ev)
{switch (ev->key()){case Qt::Key_Up:player->velocity().setY(-1);break;case Qt::Key_Down:player->velocity().setY(1);break;case Qt::Key_Left:player->velocity().setX(-1);break;case Qt::Key_Right:player->velocity().setX(1);break;}
}void QGame::keyReleaseEvent(QKeyEvent* ev)
{switch (ev->key()){case Qt::Key_Up:case Qt::Key_Down:player->velocity().setY(0);break;case Qt::Key_Left:case Qt::Key_Right:player->velocity().setX(0);break;}
}void QGame::mouseMoveEvent(QMouseEvent* ev)
{//让鼠标居中到图片中间auto pos = player->sizeImage() / 2;player->setPos(ev->pos() - QPoint{ pos.width(),pos.height() });
}

Entity.h

#ifndef ENTITY_H_
#define ENTITY_H_#include "Global.h"
#include <QPainter>class Entity
{
public:virtual ~Entity() {};//虚析构,当子类继承重写的时候就可以释放子类的内存virtual void update() = 0;//更新实体virtual void render(QPainter* painter) = 0;//渲染实体//接口bool active()const { return m_active; }int type()const { return m_type; }//状态销毁void destroy() { m_active = false; }//设置实体类型void setType(int type) { m_type = type; }
private:bool m_active = true;//实体是否是活动的int m_type = 0;//实体类型};
#endif // !ENTITY_H_

Sprite.h

#ifndef SPRITE_H_
#define SPRITE_H_#include "Entity.h"
#include <QVector2D>class Sprite :public Entity
{
public:Sprite() = default;Sprite(const QString& fileName, const QSize& size = QSize());//接口QVector2D getPos()const { return m_pos; }QPixmap getPixmap()const { return m_image; }QVector2D velocity() const{ return m_velocity; }QVector2D& velocity() { return m_velocity; }QRect collider()const { return m_collider; }//设置坐标void setPos(float x, float y){m_pos = { x,y };}void setPos(const QPointF& pos){m_pos = { (float)pos.x(),(float)pos.y() };}void setPos(const QVector2D& pos) {m_pos = pos; }//设置速度分量void setVelocity(float vx, float vy){m_velocity = { vx,vy };}//获取图片大小QSize sizeImage()const;//设置图片void setPixmap(const QString& fileName, const QSize& size = QSize());// 通过 Entity 继承void update() override;// 通过 Entity 继承void render(QPainter* painter) override;private:QPixmap m_image;QVector2D m_pos;//移动速度float m_speed = 3;//速度分量QVector2D m_velocity;//碰撞器QRect m_collider{};
};
#endif // !SPRITE_H_

Sprite.cpp

#include "Sprite.h"Sprite::Sprite(const QString& fileName, const QSize& size)
{setPixmap(fileName, size);
}//获取精灵图片大小
QSize Sprite::sizeImage() const
{if (m_image.isNull()){return QSize();}return m_image.size();
}
//设置图片
void Sprite::setPixmap(const QString& fileName, const QSize& size)
{m_image.load(fileName);if (size.isValid()){//保持缩放	m_image.scaled(size, Qt::AspectRatioMode::KeepAspectRatio);}
}void Sprite::update()
{//获取一下坐标float x = m_pos.x();float y = m_pos.y();//通过分量去改变坐标的速度,移动就比较正常一点x += m_velocity.x() * m_speed;y += m_velocity.y() * m_speed;//将坐标给m_posm_pos = { x,y };//设置矩形m_collider = QRect(m_pos.x(), m_pos.y(), m_image.width(), m_image.height());
}void Sprite::render(QPainter* painter)
{painter->drawPixmap(m_pos.toPoint(), m_image);
}

Bullet.h

#ifndef BULLET_H_
#define BULLET_H_#include "Sprite.h"
class Bullet :public Sprite
{
public://更新子弹出边界就得消失void update() override;
private:};
#endif // !BULLET_H_

Bullet.cpp

#include "Bullet.h"
#include "QGame.h"
void Bullet::update()
{//继承父类的方法帮忙移动Sprite::update();//就可以调用游戏唯一的单例//如果子弹超出边界让子弹消失if (getPos().x() > qGame->width() || getPos().x() < 0 - sizeImage().width() ||getPos().y() > qGame->height() || getPos().y() < 0 - sizeImage().height()){destroy();}
}

PlayerPlane.h

#ifndef PLAYERPLANE_H_
#define PLAYERPLANE_H_#include "Sprite.h"
#include "Bullet.h"
#include <array>
class PlayerPlane : public Sprite
{
public://继承父类的构造方法using Sprite::Sprite;PlayerPlane();//发射子弹bool emitBullet();
private:};
#endif // !PLAYERPLANE_H_

PlayerPlane.cpp

#include "PlayerPlane.h"
#include "EntityManager.h"
PlayerPlane::PlayerPlane()
{}bool PlayerPlane::emitBullet()
{Bullet* b = new Bullet;//添加子弹b->setPixmap(":/plane/Resource/images/bullet2.png");//设置子弹在主角上b->setPos(getPos() + QVector2D{ sizeImage().width() / 2.0f,0.0f });//子弹发出b->velocity().setY(-2);//设置类型b->setType(bullet);//添加进实体EntityManager::instance().addEntity(b);return false;
}

EntityManager.h

#ifndef		ENTITYMANAGER_H_
#define		ENTITYMANAGER_H_#include"Sprite.h"
#include<QList>
#include<memory>
#include<QDebug>
class EntityManager
{
public://得到唯一的实例static EntityManager& instance(){static EntityManager ev;return ev;}//更新管理的所有实体与精灵void update(){for (auto& e : m_entities){e->update();}}//渲染void render(QPainter* painter){for (auto& e : m_entities){e->render(painter);}}//添加实体开个模版方便使用template<typename T = Entity>T* addEntity(T* e){m_entities.emplaceBack(e);return e;}//刷新,如果有实体要消除,就在这里销毁void refresh(){m_entities.removeIf([](Entity* e){//不是活动的就释放if (!e->active()){//测试输出qDebug() << "destoryed" << e;//释放这个实体delete e;return true;}return false;});//测试输出qDebug() << m_entities.size();}//获取类别,好做碰撞QList<Sprite*> getSpriteByType(int type){QList<Sprite*> s;for (auto& e : m_entities){//要是活动的if(e->type()==type && e->active()){s.append(dynamic_cast<Sprite*>(e));}}return s;}
private:QList<Entity*> m_entities;//构造函数私有化,设计单例EntityManager() {}
};#endif

Map.h

#ifndef MAP_H_
#define MAP_H_#include "Entity.h"
class Map :public Entity
{
public:Map();// 通过 Entity 继承virtual void update() override;virtual void render(QPainter* painter) override;
private:QPixmap m_pixmap;//用来实现滚动int yPos1,yPos2;int m_scrollSpeed = 2;
};
#endif // !MAP_H_

Map.cpp

#include "Map.h"
#include "QGame.h"
Map::Map()
{//加载背景图m_pixmap.load(":/plane/Resource/images/background.png");//一个在窗口上面yPos1 = -m_pixmap.height();//一个在窗口上yPos2 = 0;
}void Map::update()
{//实现滚动背景yPos1 += m_scrollSpeed;if (yPos1 >= 0){yPos1 = -m_pixmap.height();}yPos2 += m_scrollSpeed;//大于等于窗口高度后重新置为0if (yPos2 >= qGame->height()){yPos2 = 0;}
}void Map::render(QPainter* painter)
{painter->drawPixmap(0, yPos1, m_pixmap);painter->drawPixmap(0, yPos2, m_pixmap);
}

Global.h

#ifndef GLOBAL_H_
#define GLOBAL_H_enum EntityType
{None,Player,Enemy,bullet
};#endif

运行结果