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{ } ; 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{ } ; 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 = 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 ( ) ; } update ( 0 ) ; 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的纯虚方法,这个类拥有设置坐标与加载图片的方法
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 ( ) ) ; void update ( ) override ; void render ( QPainter* painter) override ; private : QPixmap m_image; QVector2D m_pos;
} ;
# endif
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 ( ) ;
}
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 = 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 ( ) ; } update ( 0 ) ; 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 = { 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 : 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{ } ; 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)
{ Q_ASSERT_X ( ins == nullptr , "QGame" , "已经存在一个QGame实例" ) ; ins = this ;
}
QGame :: ~ QGame ( )
{ clean ( ) ;
}
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 = 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 ( ) ; } update ( 0 ) ; 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
# 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
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.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)
{ Q_ASSERT_X ( ins == nullptr , "QGame" , "已经存在一个QGame实例" ) ; ins = this ;
}
QGame :: ~ QGame ( )
{ clean ( ) ;
}
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 = 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 ( ) ; } update ( 0 ) ; QWidget :: update ( ) ; } ) ; 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; 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 ( ) ; virtual void update ( ) override ; virtual void render ( QPainter* painter) override ;
private : QPixmap m_pixmap; int yPos1, yPos2; int m_scrollSpeed = 2 ;
} ;
# endif
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; 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 : 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{ } ; 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)
{ Q_ASSERT_X ( ins == nullptr , "QGame" , "已经存在一个QGame实例" ) ; ins = this ;
}
QGame :: ~ QGame ( )
{ clean ( ) ;
}
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 = 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 ( ) ; } update ( 0 ) ; QWidget :: update ( ) ; } ) ; 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
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 ( ) ) ; void update ( ) override ; void render ( QPainter* painter) override ; private : QPixmap m_image; QVector2D m_pos; float m_speed = 3 ; QVector2D m_velocity; QRect m_collider{ } ;
} ;
# endif
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 = { 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.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.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 ( ) ; virtual void update ( ) override ; virtual void render ( QPainter* painter) override ;
private : QPixmap m_pixmap; int yPos1, yPos2; int m_scrollSpeed = 2 ;
} ;
# endif
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; 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
运行结果