Qt planeGame day10

news2024/12/21 19:19:17

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的自定义update
			update(0);
			//重绘,父类的update
			QWidget::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的自定义update
			update(0);
			//重绘,父类的update
			QWidget::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_pos
	m_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的自定义update
			update(0);
			//重绘,父类的update
			QWidget::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的自定义update
			update(0);

			//重绘,父类的update
			QWidget::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;
	//大于等于窗口高度后重新置为0
	if (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;
	//大于等于窗口高度后重新置为0
	if (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的自定义update
			update(0);

			//重绘,父类的update
			QWidget::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_pos
	m_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;
	//大于等于窗口高度后重新置为0
	if (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

运行结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1121325.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系统报错“由于找不到vcomp140.dll无法继续执行代码”的解决方案

在我们日常使用电脑的过程中&#xff0c;可能会遇到一些错误提示&#xff0c;其中之一就是“找不到vcomp140.dll”。这个错误可能让许多用户感到困扰&#xff0c;因为它可能影响到我们的电脑使用。那么&#xff0c;vcomp140.dll是什么意思&#xff1f;当我们遇到这个问题时&…

学习笔记2——Nosql

学习笔记系列开头惯例发布一些寻亲消息 链接&#xff1a;https://baobeihuijia.com/bbhj/contents/3/194205.html 跟学链接 跟学视频链接&#xff1a;https://www.bilibili.com/video/BV1S54y1R7SB/?spm_id_from333.999.0.0 &#xff08;建议有java基础的同学学习或者一直…

发挥设计模式单例模式的力量:从技术到社会的转变

文章目录 &#x1f31f; 如何将设计模式单例模式运用到社会当中&#x1f34a; 什么是单例模式&#x1f34a; 单例模式在现实生活中的应用&#x1f389; 数据库连接池&#x1f389; 日志管理器&#x1f389; 系统配置中心 &#x1f34a; 如何将单例模式应用于社会中&#x1f389…

【Unity程序技巧】公共Update管理器

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

hugo-stack for github

静态博客框架jekyll、hexo和hugo三者之间的区别与差异 博客生成器? 全名为静态网站生成器&#xff0c; 可在任意拥有主机功能的环境下寄存(托管)可直接配合域名进行全球访问 劣势: 每次更新网页必须重新生成整个网站编译速度&#xff08;单位&#xff1a;秒&#xff09; Jek…

Win下Eclipse安装

eclipse官网https://www.eclipse.org/downloads/ 软件介绍&#xff1a; Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。Eclipse 是 Java 的集成开发环境&#xff08;IDE&#xff09;&#xff0c;当然 Eclipse 也可以作为其他开发语言的集成开发环境&#xff0c;如…

为什么使用KT6368A蓝牙芯片用app连接,基本都在5分钟左右后断开

为什么我们自己的板子用KT6368A蓝牙芯片&#xff0c;用app连接&#xff0c;基本都是在5分钟左右后 断掉或者断开&#xff0c;什么原因。参考的原理图&#xff0c;是官方的图纸如下 遇到这样的问题&#xff0c;根据我们的经验&#xff0c;大概率是硬件问题&#xff0c;分析方法如…

深度好文:How to get started in C++!

Datawhale干货 作者&#xff1a;zclll&#xff0c;推荐&#xff1a;卢雨畋&#xff0c;Datawhale成员 在大家的不懈催更下&#xff08;hhh)&#xff0c;这篇文章终于和大家见面了。对于程序设计、软件开发而言&#xff0c;我都只是一个入门水平而已&#xff08;也许C略多一点&…

whatsapp的越狱特征检测

一、检测点 1、检测目录 opendir打开bin是否成功 2、检测模块 解析macho的header头获取加载的模块&#xff0c;获取进程的所有模块&#xff0c;判断是否包含如下模块&#xff1a;substrate,watusi,zdumper 3、检测文件 判定文件或是应用是否的路径是否存在 /Applications/Cydia…

单链表的相关操作(精简版..在修改中)

目录 前言&#xff1a; 哨兵位&#xff1a; 链表的概念 链表的相关操作&#xff1a; 链表的创建&#xff1a; 打印链表&#xff1a; 申请新节点&#xff1a; 链表的尾插&#xff1a; &#xff01;&#xff01;&#xff01;对于传参中二级指针的解释&#xff1a; 链表的…

[C国演义] 第十六章

第十六章 等差数列的划分最长递增子序列 等差数列的划分 力扣链接 子数组 ⇒ dp[i]的含义: yinums[i] 为结尾的所有子数组中的 等差数列数组最多的个数子数组⇒ 状态转移方程: 根据最后一个元素的构成 初始化: 涉及到 i-1, i-2 ⇒ 所以要初始化dp[0] 和 dp[1] 都初始化为 0…

一次不成功的抓包过程

想搞Android app抓包&#xff0c;简单的方法&#xff0c;已经不起作用&#xff0c;打开charles代理工具&#xff0c;抓不到我的目标app任何请求&#xff0c;搞了两三天&#xff0c;也没成功。 我的目标APP里&#xff0c;经过apk反编译出来&#xff0c;看到有libflutter.so文件&…

AM@导数的应用@二阶导数的应用@函数的性态研究@函数图形的绘制

文章目录 概念称呼说明驻点极值和极值点最值极值点和最值比较曲线的凹凸性凹凸性判定定理&#x1f47a;例证明 凹凸性和单调性无必然关系拐点寻找拐点&#x1f47a; 函数图形的绘制例 概念 本文讨论导数的应用:利用导数研究函数的性态相关定理主要通过Lagrange中值定理进行推导…

首篇大模型压缩论文综述

首篇大模型压缩综述来啦&#xff01;&#xff01;&#xff01; 来自中国科学院和人民大学的研究者们深入探讨了基于LLM的模型压缩研究进展并发表了该领域的首篇综述《A Survey on Model Compression for Large Language Models》。 Abstract 大型语言模型&#xff08;LLMs&a…

文件操作 IO

文件(File) 狭义的文件: 指的是硬盘上的文件和目录 广义的文件: 泛指计算机中很多软硬件资源(操作系统中把很多硬件和软件资源抽象成了文件, 按照文件的方式同意管理) 本章内容只讨论狭义的文件 路径 绝对路径: 以c: , d: 盘符开头的路径相对路径: 以当前所在的目录为基准(…

【问题思考总结】如何求椭圆的切线?【过椭圆外一点】

问题 今天做2009年数一的真题&#xff0c;发现第17题求切线十分难顶&#xff0c;我用的方法是切线和椭圆方程联立&#xff0c;还有切线斜率和椭圆上一点和远点斜率相乘等于-1的方法。 思考 经过思考后&#xff0c;我认为之前的那个属于是高中方法&#xff08;还不完全是&…

Prometheus接入AlterManager配置邮件告警(基于K8S环境部署)

文章目录 一、配置AlterManager告警发送至邮箱二、Prometheus接入AlterManager配置三、部署PrometheusAlterManager(放到一个Pod中)四、测试告警 注意&#xff1a;请基于 PrometheusGrafana监控K8S集群(基于K8S环境部署)文章之上做本次实验。 一、配置AlterManager告警发送至邮…

手把手入门Node框架Egg.js

0.介绍 Egg.js 是一个面向企业级应用开发的 Node.js 框架&#xff0c;它建立在 Koa.js 之上&#xff0c;提供了一种更简单、灵活的开发方式。Egg.js 提供了一些默认约定和最佳实践&#xff0c;可以帮助开发者快速构建可靠、可扩展的应用程序。 基于 Koa.js&#xff1a;Egg.js …

spacy.load(“en_core_web_trf“)报错TypeError: issubclass() arg 1 must be a class

使用spacy时遇到的问题 写在最前面&#xff1a; 安装spacy和en_core_web_trf时需要保证二者版本一致 安装及查看对应spacy版本 安装 pip install spacy查看版本 import spacy spacy.__version__安装en_core_web_trf 直接安装&#xff08;如果可以的话&#xff09; pytho…

【论文阅读】以及部署BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework

BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework BEVFusion&#xff1a;一个简单而强大的LiDAR-相机融合框架 NeurIPS 2022 多模态传感器融合意味着信息互补、稳定&#xff0c;是自动驾驶感知的重要一环&#xff0c;本文注重工业落地&#xff0c;实际应用 融…