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
运行结果