基于QT框架的开发——飞机大战
概述
场景设置
创建Player
添加Enemy
添加Bullet
添加碰撞
添加Score
添加Health
游戏的结束与重启
添加音效
一.概述
由于笔者也是一个初学者,所以文章写的很拙劣,还请读者多多包含,这里呢,我推荐一个哔站up主关于飞机大战的学习教程,笔者也是跟着up主学习制作的,飞机大战教程
QT是一个跨平台的C++框架,主要是用于图形用户界面(GUI)的程序开发,当然也可以用于不带图形界面的程序开发;飞机大战是一款经典的射击类游戏,经常用于新手开发学习,通常包括以上几个部分,由于网上对于这一类游戏的教程比较丰富,这里我主要提供源代码为主。
首先,我是用的是QT最新版QT 6.7.2,使用是的模板是Qt Widget Application
中文名称为FighterGame,没有使用form的ui模板,使用的是qmake模板,至于默认的mainwindow文件,我也没有使用,(如下)这里文件名大家不要在意,主要是为了与我一样的小白展示的,同时,
我给出的代码顺序可能不是严格按照上面的目录顺序来给出的,不过,我写这个项目的的大体思路确实严格按照这个目录来写的
二场景设置
主窗口主要是创建在main.cpp文件中,
#include<QGraphicsScene> //创建场景的类
#include<QGraphicsView> //创建摄像机的类
#include<QIcon>
#include<QApplication>
#include"GameSetting.h"
#include"Player.h"
#include"score.h"
#include"health.h"
#include<QtMultimedia>
//#include "loginwindow.h"
#include <QWidget>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//kongzhi
//创建图标setWindowIcon是Application一个的静态的函数
a.setApplicationDisplayName("飞机大战 v1.0");
QApplication::setWindowIcon(QIcon(":/hero/imagesoundes/hero1.png"));//图标
//创建player
Player* player=new Player;
//创建场景
QGraphicsScene*scene=new QGraphicsScene; //使用指针来创建对象,是要放在对象上的,这里的对象指的是场景
//将player创建到主背景图上
scene->addItem(player);
//确定逻辑上的坐标,是照片正常的显示出来,
scene->setSceneRect(0,0,GameSetting::SceneWidth,GameSetting::SceneHeight);
scene->setBackgroundBrush(QImage(":/images/imagesoundes/mianimages.png"));//背景
//创建分数文字item
scene->addItem(&Score::getInstance());
//创建血量文字item
scene->addItem(&Health::getInstance());
scene->setStickyFocus(true);
QGraphicsView view(scene);
//创建窗口大小
view.setFixedSize(GameSetting::SceneWidth,GameSetting::SceneHeight);//使用摄像机,同时确定窗口的大小,setFixedSize就是为了固定窗口的大小
//去除滚轮条,第一个表示去除水平方向的,第二个表示去除竖直方向上的
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.show();//这里使用show函数是为了显示窗口
//添加背景音乐
QMediaPlayer bgMusic;
QAudioOutput audioOutput;
bgMusic.setAudioOutput(&audioOutput);
bgMusic.setSource(QUrl("qrc:/yinxia/SOUNDS/mian_sounds.wav"));//主题音效
bgMusic.play();
return a.exec();
}
当然这里面有些常量被设置成了全局变量,被放在了我自己创建的GameSetting头文件中,创建GameSetting的步骤如下,名称改为GameSetting。
GameSetting
类的一些全局常量展示如下:
三.创建Playey
单击项目名称右键,选择添加新文件,选择C++类,C++class,名称为player,勾选include QObject,以及Add_OBJECT,一直点击下一步就好了,QT会自动帮你生成player.h和player.cpp文件,代码块如下
player.h
#ifndef PLAYER_H
#define PLAYER_H
#include <QGraphicsPixmapItem>
#include <QObject>
#include<QSoundEffect>
class QGraphicsTextItem;
class Player : public QObject,public QGraphicsPixmapItem
{
Q_OBJECT
public:
Player(QGraphicsItem*parent=nullptr);
// QGraphicsItem interface
protected:
virtual void keyPressEvent(QKeyEvent *event) override;
private:
void enemySpawn();
void gameOver();
// QObject interface
protected:
virtual void timerEvent(QTimerEvent *event) override;
private:
bool playing=true;//确定游戏运行状态
QSoundEffect bulletSound;
QGraphicsTextItem*messageItem=nullptr;
};
#endif // PLAYER_H
player.cpp
#include"player.h"
#include<QPixmap>
#include"GameSetting.h"
#include<QKeyEvent>
#include"enemy.h"
#include<QGraphicsScene>
#include"bullet.h"
#include"health.h"
#include "qfontmetrics.h"
#include<QGraphicsTextItem>
#include<QFont>
#include"score.h"
using namespace GameSetting;//因为需要大量使用PlayerScale,所以先将这个命名空间打开
Player::Player(QGraphicsItem*parent):QGraphicsPixmapItem(parent)
{
bulletSound.setSource(QUrl("qrc:/yinxia/SOUNDS/bomb.wav"));//击杀音效
setPixmap(QPixmap(":/hero/imagesoundes/hero1.png"));
setScale(PlayerScale);
setPos(SceneWidth/2-boundingRect().width()*PlayerScale/2,SceneHeight-boundingRect().height()*PlayerScale);
//表示可以获取焦点
setFlag(QGraphicsItem::ItemIsFocusable);
setFocus();
startTimer(500);
}
//player的移动
void Player::keyPressEvent(QKeyEvent* event)
{
switch(event->key())
{
case Qt::Key_A:
if(pos().x()>0)
setPos(x()-PlayerMoveSpeed,y());
break;
case Qt::Key_D:
if(pos().x()<SceneWidth-boundingRect().height()*PlayerScale)
setPos(x()+PlayerMoveSpeed,y());
break;
case Qt::Key_S:
if(pos().y()<SceneHeight-boundingRect().width()*PlayerScale)
setPos(x(),y()+PlayerMoveSpeed);
break;
case Qt::Key_W:
if(pos().y()>0)
setPos(x(),y()-PlayerMoveSpeed);
break;
//用来控制bullet,确保bullet是在飞机的正中间的
case Qt::Key_H:
if(playing) return;
playing=true;
Health::getInstance().reset();
Score::getInstance().reset();
messageItem->hide();
break;
case Qt::Key_Space:
bulletSound.play();
Bullet*bullet=new Bullet;
int temp=x()+boundingRect().width()*PlayerScale/2;
temp-=bullet->boundingRect().width()*BulletScale/2;
bullet->setPos(temp,y());
scene()->addItem(bullet);
break;
}
}
void Player::enemySpawn()
{
//用来控制敌人的出现时间
Enemy*enemy=new Enemy;
scene()->addItem(enemy);
}
void Player::gameOver()
{
playing=false;//当玩家死亡时,所有的敌人都消失
for(auto item:scene()->items())
{
if(typeid(*item)==typeid(Enemy))
{
scene()->removeItem(item);
delete item;
}
}
if(!messageItem)
{
messageItem=new QGraphicsTextItem;
scene()->addItem(messageItem);
QString message("Game Over! 按H键重新开始!");
messageItem->setPlainText(message);
messageItem->setDefaultTextColor(Qt::black);
QFont font("curier New",GameSetting::FontSize*1.5,QFont::Bold);
messageItem->setFont(font);
QFontMetrics fm(font);
int msgWidth=fm.horizontalAdvance(message);
messageItem->setPos(GameSetting::SceneWidth/2-msgWidth/2,GameSetting::SceneHeight/2);
}else
messageItem->show();
}
void Player::timerEvent(QTimerEvent *)
{
if(playing)
{
enemySpawn();
}
if(Health::getInstance().getHealth()<=0)
{
gameOver();
}
}
四.添加Enemy
与Player的创建是一样的,这里我就不在做演示了,不过,在创建enemy这里,有一个非常重要的头文件,#include<QRandomGenerator>
,这是一个随机数生成器,顾名思义,用于随机生成敌人的,还需注意,由于地图不是滚动的,所以,我们用敌机的移动来实现类似于地图的滚动的,但实际上,敌机的移动并不是给予其速度,而是通过敌机的照片不断地删除,实时更新位置,在生成图片来实现的,同样的,玩家控制的player也是通过同样的方法来来实现的,这里,我不在做过多赘述;
enemy.h
#ifndef HEALTH_H
#define HEALTH_H
#include <QGraphicsTextItem>
#include <QObject>
#include"GameSetting.h"
class Health : public QGraphicsTextItem
{
Q_OBJECT
public:
Health(QGraphicsItem*parent=nullptr);
int getHealth(){return health;}
void decrease();
void reset();
static Health& getInstance()
{
static Health obj;
return obj;
}
private:
int health=GameSetting::HealthStar;
};
#endif // HEALTH_H
enemy.cpp
#include "enemy.h"
#include<QPixmap>
#include"GameSetting.h"
#include<QRandomGenerator>//随机数生成器,用来随机生成enemy
#include<QGraphicsScene>
#include"player.h"
#include"bullet.h"
#include"score.h"
#include"health.h"
Enemy::Enemy(QGraphicsItem*parent):QGraphicsPixmapItem(parent)
{
setPixmap(QPixmap(":/images/imagesoundes/img-plane_2.png"));
setScale(GameSetting::EnemyScale);
//随机生成敌人
int max=GameSetting::SceneWidth-boundingRect().width()*GameSetting::EnemyScale;
int randomNumber=QRandomGenerator::global()->bounded(1,max);
setPos(randomNumber,0);
//enemy出现的时间间隔
startTimer(GameSetting::EnemyTimer);
}
//为enemy添加移动
void Enemy::timerEvent(QTimerEvent *)
{
QList<QGraphicsItem *> itemList=collidingItems();
//添加碰撞
for(auto item:itemList)
{
if(typeid(*item)==typeid(Player))
{
//健康值需要decrease
Health::getInstance().decrease();
scene()->removeItem(this);
delete this;
return;
}
if(typeid(*item)==typeid(Bullet))
{
//需要加分
Score::getInstance().increase();
scene()->removeItem(item);
scene()->removeItem(this);
delete item;
delete this;
return;
}
}
//为enemy添加速度
setPos(x(),y()+GameSetting::EnemySpeed);
if(y()>GameSetting::SceneHeight)
{
scene()->removeItem(this);
delete this;
return;
}
}
五.添加bullet
子弹为bullet,bullet的创建方式与前面的player以及enemy一样,都是选择C++的类,即C++ class,不过,需要包含QGraphicsPixmapItem
,之前的player,enemy,也需要包含这个头文件,
bullet.h
如下:
#ifndef BULLET_H
#define BULLET_H
#include <QGraphicsPixmapItem>
#include <QObject>
class Bullet : public QObject,public QGraphicsPixmapItem
{
Q_OBJECT
public:
Bullet(QGraphicsItem*parent=nullptr);
// QObject interface
protected:
virtual void timerEvent(QTimerEvent *event) override;
};
#endif // BULLET_H
bullet.cpp
如下所示:
#include"GameSetting.h"
#include<QGraphicsScene>
Bullet::Bullet(QGraphicsItem *parent):QGraphicsPixmapItem(parent)
{
setPixmap(QPixmap(":/BulletScene/BulletScene/bomb46.png"));//子弹
//设置bullet的大小
this->setScale(GameSetting::BulletScale);
//设置bullet出现的时间间隔
startTimer(GameSetting::BulletTimer);
}
//bullet移动的速度
void Bullet::timerEvent(QTimerEvent *)
{
setPos(x(),y()-GameSetting::BulletSpeed);
//当bullet已经离开了这个mainscene时,将这个bullet销毁
if(y()+boundingRect().height()<0)
{
scene()->removeItem(this);
delete this;
}
}
六.添加Score
由于我主要展示代码的实现,详细的不在过多赘述,所以,碰撞部分的代码我是同bullet,player,enemy一起给出的,值得一提的是,在创建好的score.cpp
文件中,需要包含#include<QFont>
类,其主要是用于对字体的修改的,应为这里我的得分score以及血量health都是通过文字以及静态变量来实现的,而不是通过form的ui模板来实现的,
score.h
如下所示:
#ifndef SCORE_H
#define SCORE_H
#include <QGraphicsTextItem>
#include <QObject>
class Score : public QGraphicsTextItem
{
Q_OBJECT
public:
Score(QGraphicsItem *parent=nullptr);
void increase();
void reset();
static Score& getInstance()
{
static Score obj;
return obj;
}
private:
int score=0;
};
#endif // SCORE_H
score.cpp
代码如下所示:
#include "score.h"
#include<QFont>
#include"GameSetting.h"
Score::Score(QGraphicsItem *parent):QGraphicsTextItem{parent}
{
reset();
}
void Score::reset()
{
score=0;
setPlainText("分 数:"+QString::number(score));
setDefaultTextColor(Qt::green);
setFont(QFont("Courier New",GameSetting::FontSize,QFont::Bold));
}
void Score::increase()
{
++score;
setPlainText("分 数:"+QString::number(score));
}
七.添加health
具体步骤与六.添加score一样,所以,这里我不在做过多赘述,直接给出代码:
health.h
#ifndef HEALTH_H
#define HEALTH_H
#include <QGraphicsTextItem>
#include <QObject>
#include"GameSetting.h"
class Health : public QGraphicsTextItem
{
Q_OBJECT
public:
Health(QGraphicsItem*parent=nullptr);
int getHealth(){return health;}
void decrease();
void reset();
static Health& getInstance()
{
static Health obj;
return obj;
}
private:
int health=GameSetting::HealthStar;
};
#endif // HEALTH_H
health.cpp
如下所示:
#include "health.h"
#include<QFont>
#include"GameSetting.h"
Health::Health(QGraphicsItem*parent):QGraphicsTextItem(parent)
{
reset();
}
void Health::decrease()
{
--health;
setPlainText("血 量:"+QString::number(health));
}
void Health::reset()
{
health=GameSetting::HealthStar;
setPlainText("血 量:"+QString::number(health));
setDefaultTextColor(Qt::blue);
setFont(QFont("Courier",GameSetting::FontSize,QFont::Bold));
setPos(x(),GameSetting::FontSize*2);
}
至于最后两节的游戏结束与重启以及添加音效其实都放在了以上的文件中,并没有单独写成一个文件夹;