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
运行结果
相关文章:

Qt planeGame day10
Qt planeGame day10 Game基本框架 qt中没有现成的游戏框架可以用,我们需要自己搭框架首先创建一个QGame类作为框架,这个基本框架里面应该有如下功能:游戏初始化 void init(const QSize& siez,const QString& title);游戏反初始化(…...

贪吃蛇项目实践
游戏背景: 贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。 实现基本的功能: 贪吃蛇地图绘制 蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作) 蛇撞墙死亡 蛇撞⾃⾝死亡 计…...

【C++】哈希应用——海量数据面试题
哈希应用——海量数据面试题 一、位图应用1、给定100亿个整数,设计算法找到只出现一次的整数?2、给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?(1)用一个位图…...

CUDA学习笔记(五)GPU架构
本篇博文转载于https://www.cnblogs.com/1024incn/tag/CUDA/,仅用于学习。 GPU架构 SM(Streaming Multiprocessors)是GPU架构中非常重要的部分,GPU硬件的并行性就是由SM决定的。 以Fermi架构为例,其包含以下主要组成…...

逻辑漏洞详解
原理: 没有固定的概念,一般都是不符合常识的情况。比如任意用户注册,短信炸弹,占用资源,交易支付、密码修改、密码找回、越权修改、越权查询、突破限制。 根据实际业务逻辑进行比对,购物的可以根据数量&a…...

MySQL——八、MySQL索引视图
MySQL 一、视图1、什么是视图2、为什么需要视图3、视图的作用和优点4、创建视图5、视图使用规则6、修改视图7、删除视图 二、索引1、什么是索引2、索引优缺点3、索引分类4、索引的设计原则5、创建索引5.1 创建表是创建索引5.2 create index5.3 ALTER TABLE 6、删除索引7、MySQL…...
力扣100097. 合法分组的最少组数(哈希+贪心)
题目描述: 给你一个长度为 n 下标从 0 开始的整数数组 nums 。 我们想将下标进行分组,使得 [0, n - 1] 内所有下标 i 都 恰好 被分到其中一组。 如果以下条件成立,我们说这个分组方案是合法的: 对于每个组 g ,同一…...

uniapp map地图实现marker聚合点,并点击marker触发事件
1.uniapp官方文档说明 2.关键代码片段 // 仅调用初始化,才会触发 on.("markerClusterCreate", (e) > {})this._mapContext.initMarkerCluster({enableDefaultStyle: false, // 是否使用默认样式zoomOnClick: true, // 点击聚合的点,是否…...

【Mysql】Mysql中的B+树索引(六)
概述 从上一章节我们了解到InnoDB 的数据页都是由7个部分组成,然后各个数据页之间可以组成一个双向链表 ,而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表 ,每个数据页都会为存储在它里边儿的记录生成一个页目录 ÿ…...

【Dockerfile镜像实战】构建LNMP环境并运行Wordpress网站平台
这里写目录标题 一、项目背景和要求二、项目环境三、部署过程1)创建自定义网络2)部署NginxStep1 创建工作目录并上传相关软件包Step2 编写Dockerfile文件Step3 编写配置文件nginx.confStep4 创建nginx镜像Step5 运行容器 3)部署MysqlStep1 创…...

【工具】利用ffmpeg将网页中的.m3u8视频文件转化为.mp4格式
目录 0.环境 1.背景 2.前提 3.详细描述 1)在网站上找到你想下载的视频的.m3u8链接 2)打开命令行,用ffmpeg命令进行转化 3)过程&结果截图 0.环境 windows64 ffmpeg 1.背景 网页上有个.m3u8格式的视频文件,…...

Git简洁安装方式和使用方式【附安装包资源,Git基础操作,如拉取项目、上传代码、拉取代码】
文章目录 软件安装包安装步骤常用使用方式注意拉取项目上传代码或文件选择文件添加到本地Git存储库的缓存区将缓存区的更改提交到本地Git存储库,并设置提交信息将本地Git存储库的更新推送到远程Git仓库中上传示例拉取别人所上传的代码 常见问题上传代码失败…...

【29】c++设计模式——>策略模式
策略模式 C中的策略模式(Strategy Pattern)是一种行为型设计模式,它允许在运行时选择算法的行为。策略模式通过将算法封装成独立的类,并且使它们可以互相替换,从而使得算法的变化独立于使用算法的客户端。 策略模式通…...

2023Jenkins连接k8s
首先配置k8s config文件 1.方式获取k8s密钥 cat .kube/config 2.导出方式或者密钥 kubectl config view --raw > k8s-config-admin pipeline {agent {kubernetes {yaml apiVersion: v1kind: Podmetadata:labels:some-label: devopsspec:containers:- name: dockerimage: d…...

SpringBoot 入门 参数接收 必传参数 数组 集合 时间接收
接口声明 RestController //表示该类为请求处理类public class HttpDeal {RequestMapping("/login")//这个方法处理哪一个地址过来的请求public String hello(){return "返回给浏览器";}}接收参数 RequestMapping("/login")public String logi…...
【Qt之JSON文件】QJsonDocument、QJsonObject、QJsonArray等类介绍及使用
Qt之JSON相关类介绍 QJsonDocument常用函数枚举类型 QJsonDocument::DataValidation枚举类型 QJsonDocument::JsonFormat构造函数静态函数成员函数示例 QJsonObject常用函数构造函数:成员函数: QJsonObject 与 QVariantMap 相互转换 QJsonArray常用函数构…...
阿里云今年有双十一活动吗?不好说
阿里云今年有双十一活动吗?不好说,因为去年就没有。阿里云双11优惠活动是一项大型的促销活动,每年都有,但是去年没有双十一活动,不知道今年2023年阿里云是否有双11优惠活动。但是阿里云百科aliyunbaike.com猜想&#x…...

【驱动开发】创建设备节点、ioctl函数的使用
一、控制三盏灯的亮灭 头文件: #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t; #define PHY_LED1_ADDR 0X50006000 #def…...

Tomcat启动控制台乱码问题
修改Tomcat/conf/logging.properties...
学习周总结
http://t.csdnimg.cn/DKki2 http://t.csdnimg.cn/NvudJ 项目进度 做了大概的主界面,然后做了一个客户端和服务端的分离,实现了在客户端发送的信息,在服务端能收到;客户端和服务端的制作是我之前有写的一个http://t.csdnimg.cn/…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...

Qwen系列之Qwen3解读:最强开源模型的细节拆解
文章目录 1.1分钟快览2.模型架构2.1.Dense模型2.2.MoE模型 3.预训练阶段3.1.数据3.2.训练3.3.评估 4.后训练阶段S1: 长链思维冷启动S2: 推理强化学习S3: 思考模式融合S4: 通用强化学习 5.全家桶中的小模型训练评估评估数据集评估细节评估效果弱智评估和民间Arena 分析展望 如果…...

Modbus转Ethernet IP深度解析:磨粉设备效率跃升的底层技术密码
在建材矿粉磨系统中,开疆智能Modbus转Ethernet IP网关KJ-EIP-101的应用案例是一个重要的技术革新。这个转换过程涉及到两种主要的通信协议:Modbus和Ethernet IP。Modbus是一种串行通信协议,广泛应用于工业控制系统中。它简单、易于部署和维护…...