在学习完QT后,我尝试做了一下飞机大战这个小游戏。
首先是小游戏需要实现的功能:
1.滚动的背景
2.子弹的制作和射击
3.敌人的制作
4.爆炸效果
首先我们创建好项目后,我们开始创建新的头文件,用来配置所有的数据。这需要我们先把飞机大战需要的png图片素材先整理好,代码如下:
/********** 游戏配置数据 **********/
#define GAME_WIDTH 512 //宽度
#define GAME_HEIGHT 768 //高度
#define GAME_TITLE "飞机大战 sjn" //标题
#define GAME_RES_PATH":/resnew/app.ico"
#define GAME_ICON ":/res/app.ico" //图标路径
#define GAME_RATE 10 //单位毫秒
/********** 地图配置数据 **********/
#define MAP_PATH ":/resnew/img_bg_level_5.jpg" //地图图片路径
#define MAP_SCROLL_SPEED 2 //地图滚动速度
/********** 飞机配置数据 **********/
#define HERO_PATH ":/resnew/app.ico"
/********** 子弹配置数据 **********/
#define BULLET_PATH ":/resnew/bullet_1.png" //子弹图片路径
#define BULLET_SPEED 5 //子弹移动速度
#define BULLET_NUM 30 //弹匣中子弹总数
#define BULLET_INTERVAL 20 //发射子弹时间间隔
/********** 敌机配置数据 **********/
#define ENEMY_PATH ":/resnew/img-plane_1.png" //敌机资源图片
#define ENEMY_SPEED 5 //敌机移动速度
#define ENEMY_NUM 20 //敌机总数量
#define ENEMY_INTERVAL 30 //敌机出场时间间隔
/********** 爆炸配置数据 **********/
#define BOMB_PATH ":/resnew/bomb-1.png" //爆炸资源图片
#define BOMB_NUM 20 //爆炸数量
#define BOMB_MAX 7 //爆炸图片最大索引
#define BOMB_INTERVAL 20 //爆炸切图时间间隔
#define SOUND_BACKGROUND ":/res/bg.wav"
#define SOUND_BOMB ":/res/bomb.wav"
#endif // CONFIG_H
然后我们要开始写地图功能的编写
为了实现地图的滚动,也就是需要循环,这时可以调用游戏运行定时器,启动定时器,监听定时器信号实现游戏循环,看起来就是地图的滚动,代码如下
map.h
#ifndef MAP_H
#define MAP_H
#include
class Map
{
public:
//构造函数
Map();
//地图滚动坐标计算
void mapPosition();
public:
//地图图片对象
QPixmap m_map1;
QPixmap m_map2;
//地图Y轴坐标
int m_map1_posY;
int m_map2_posY;
//地图滚动幅度
int m_scroll_speed;
};
#endif // MAP_H
map.cpp:
#include "map.h"
#include "config.h"
Map::Map()
{
//初始化加载地图对象
m_map1.load(MAP_PATH);
m_map2.load(MAP_PATH);
//设置坐标
m_map1_posY = -GAME_HEIGHT;
m_map2_posY = 0;
//设置滚动速度
m_scroll_speed = MAP_SCROLL_SPEED;
}
void Map::mapPosition()
{
//处理第一张图片滚动
m_map1_posY += m_scroll_speed;
if(m_map1_posY >= 0)
{
m_map1_posY = -GAME_HEIGHT;
}
//处理第二张图片滚动
m_map2_posY += m_scroll_speed;
if(m_map2_posY >= GAME_HEIGHT)
{
m_map2_posY = 0;
}
}
启动定时器的代码如下:
1.在MainScene.h中添加新的成员函数以及成员对象
//启动游戏 用于启动定时器对象
void playGame();
//更新坐标
void updatePosition();
//绘图事件
void paintEvent(QPaintEvent *event);
在MainScene.cpp中实现成员函数,代码如下:
// 启动定时器
m_Timer.start();
//监听定时器的信号
connect(&m_Timer , &QTimer::timeout,[=](){
//敌机出场
enemyToScene();
//更新游戏中所有元素的坐标
updatePosition();
//游戏中的元素 绘制到屏幕中
update(); //再调用paintEvent函数
做好了这些,飞机大战游戏的大框架已经构建好了,之后我们来添加自己想要的素材和功能,
首先是我们自己操控的飞机,,步骤如下:
1.创建文件和类
2.添加成员函数和成员属性
3.实现成员函数
4.创建飞机对象并显示
5.可以拖拽
当我们创建好文件和类后,需要添加属性代码:
//发射子弹
void shoot();
//设置飞机位置
void setPosition(int x, int y);
public:
//飞机资源 对象
QPixmap m_Plane;
//飞机坐标
int m_X;
int m_Y;
//飞机的矩形边框
QRect m_Rect;
成员函数实现
因为上边config.h中,我展示的是所有代码,所以就不展示此处的飞机配置参数设置的代码了,
所以在heroplane.cpp中成员函数代码如下:
#include "heroplane.h"
#include "config.h"
HeroPlane::HeroPlane()
{
//加载飞机图片资源
m_Plane.load(HERO_PATH);
//初始化坐标
m_X = GAME_WIDTH * 0.5 - m_Plane.width() *0.5;
m_Y = GAME_HEIGHT - m_Plane.height() - 100;
//矩形边框 碰撞检测用
m_Rect.setWidth(m_Plane.width());
m_Rect.setHeight(m_Plane.height());
m_Rect.moveTo(m_X,m_Y);
//初始化间隔记录变量
m_recorder = 0;
}
void HeroPlane::setPosition(int x, int y)
{
m_X = x;
m_Y = y;
m_Rect.moveTo(m_X,m_Y);
}
加入素材,在MainScene.h中添加代码如下:
//创建飞机对象
HeroPlane m_hero;
MainScene.cpp中:
//绘制飞机
painter.drawPixmap(m_hero.m_X,m_hero.m_Y,m_hero.m_Plane);
最后我们编写拖拽飞机的指令
MainScene.h:
void mouseMoveEvent(QMouseEvent *);
MainScene.cpp:
void MainScene::mouseMoveEvent(QMouseEvent * event)
{
int x = event->x() -m_hero.m_Rect.width() * 0.5;
int y = event->y() -m_hero.m_Rect.height() *0.5;
//边界检测
if(x <= 0 )
{
x = 0;
}
if(x >= GAME_WIDTH - m_hero.m_Rect.width())
{
x = GAME_WIDTH - m_hero.m_Rect.width();
}
if(y <= 0)
{
y = 0;
}
if(y >= GAME_HEIGHT - m_hero.m_Rect.height())
{
y = GAME_HEIGHT - m_hero.m_Rect.height();
}
m_hero.setPosition(x,y);
}
然后就是子弹的功能,创建好子弹的文件和类后,我们需要在bullet.h中添加代码如下:
#ifndef BULLET_H
#define BULLET_H
#include "config.h"
#include
class Bullet
{
public:
Bullet();
//更新子弹坐标
void updatePosition();
public:
//子弹资源对象
QPixmap m_Bullet;
//子弹坐标
int m_X;
int m_Y;
//子弹移动速度
int m_Speed;
//子弹是否闲置
bool m_Free;
//子弹的矩形边框(用于碰撞检测)
QRect m_Rect;
};
#endif // BULLET_H
bullet.cpp中添加:
#include "bullet.h"
Bullet::Bullet()
{
//加载自动资源
m_Bullet.load(BULLET_PATH);
//子弹坐标初始化
m_X = GAME_WIDTH *0.5 - m_Bullet.width() * 0.5;
m_Y = GAME_HEIGHT;
//子弹空闲状态
m_Free = true;
//子弹速度
m_Speed = BULLET_SPEED;
//子弹矩形边框 (碰撞检测)
m_Rect.setWidth(m_Bullet.width());
m_Rect.setHeight(m_Bullet.height());
m_Rect.moveTo(m_X,m_Y);
}
void Bullet::updatePosition()
{
//如果子弹是空闲状态,不需要计算坐标
if(m_Free)
{
return;
}
//子弹向上移动
m_Y -=m_Speed;
m_Rect.moveTo(m_X,m_Y);
if(m_Y <= - m_Rect.height())
{
m_Free = true;
}
}
此时子弹的代码已经编写完成,我们需啊使子弹从飞机的位置发射出,这时我们在heroplane.cpp中添加代码:
void HeroPlane::shoot()
{
//累加事件间隔记录的变量
m_recorder++;
//如果记录数字 未达到发射间隔,直接return
if(m_recorder < BULLET_INTERVAL)
{
return;
}
m_recorder = 0;
//发射子弹
for(int i = 0 ; i < BULLET_NUM;i++)
{
//如果是空闲状态的子弹,发射子弹
if(m_bullets[i].m_Free)
{
m_bullets[i].m_Free = false;
m_bullets[i].m_X = m_X+m_Rect.width()*0.5 - 10;
m_bullets[i].m_Y = m_Y - 25 ;
break;
}
}
}
当然别忘了,在主场景中也实现添加子弹
MainScene.cpp:
//发射子弹
m_hero.shoot();
//计算子弹坐标
for(int i = 0 ;i < BULLET_NUM;i++)
{
//如果子弹状态为非空闲,计算发射位置
if(m_hero.m_bullets[i].m_Free == false)
{
m_hero.m_bullets[i].updatePosition();
}
}
然后我们开始敌人飞机的功能实现
首先我们需要创建好文件和类,然后在.h文件中添加成员函数,和成员属性
#ifndef ENEMYPLANE_H
#define ENEMYPLANE_H
#include
class EnemyPlane
{
public:
EnemyPlane();
//更新坐标
void updatePosition();
public:
//敌机资源对象
QPixmap m_enemy;
//位置
int m_X;
int m_Y;
//敌机的矩形边框(碰撞检测)
QRect m_Rect;
//状态
bool m_Free;
//速度
int m_Speed;
};
#endif // ENEMYPLANE_H
然后在.cpp中实现成员函数:
#include "enemyplane.h"
#include "config.h"
EnemyPlane::EnemyPlane()
{
//敌机资源加载
m_enemy.load(ENEMY_PATH);
//敌机位置
m_X = 0;
m_Y = 0;
//敌机状态
m_Free = true;
//敌机速度
m_Speed = ENEMY_SPEED;
//敌机矩形
m_Rect.setWidth(m_enemy.width());
m_Rect.setHeight(m_enemy.height());
m_Rect.moveTo(m_X,m_Y);
}
void EnemyPlane::updatePosition()
{
//空闲状态,不计算坐标
if(m_Free)
{
return;
}
m_Y += m_Speed;
m_Rect.moveTo(m_X,m_Y);
if(m_Y >= GAME_HEIGHT)
{
m_Free = true;
}
}
在MainScene.h中实行敌人飞机的出场
//敌机出场
void enemyToScene();
//敌机数组
EnemyPlane m_enemys[ENEMY_NUM];
//敌机出场间隔记录
int m_recorder;
void MainScene::enemyToScene()
{
//累加出场间隔
m_recorder++;
if(m_recorder < ENEMY_INTERVAL)
{
return;
}
m_recorder = 0;
for(int i = 0 ; i< ENEMY_NUM;i++)
{
if(m_enemys[i].m_Free)
{
//敌机空闲状态改为false
m_enemys[i].m_Free = false;
//设置坐标
m_enemys[i].m_X = rand() % (GAME_WIDTH - m_enemys[i].m_Rect.width());
m_enemys[i].m_Y = -m_enemys[i].m_Rect.height();
break;
}
}
}
碰撞效果实现:
void MainScene::collisionDetection()
{
//遍历所有非空闲的敌机
for(int i = 0 ;i < ENEMY_NUM;i++)
{
if(m_enemys[i].m_Free)
{
//空闲飞机 跳转下一次循环
continue;
}
//遍历所有 非空闲的子弹
for(int j = 0 ; j < BULLET_NUM;j++)
{
if(m_hero.m_bullets[j].m_Free)
{
//空闲子弹 跳转下一次循环
continue;
}
//如果子弹矩形框和敌机矩形框相交,发生碰撞,同时变为空闲状态即可
if(m_enemys[i].m_Rect.intersects(m_hero.m_bullets[j].m_Rect))
{
//播放音效
QSound::play(SOUND_BOMB);
m_enemys[i].m_Free = true;
m_hero.m_bullets[j].m_Free = true;
最后我们添加爆炸的效果,
bomb.h:
#ifndef BOMB_H
#define BOMB_H
#include "config.h"
#include
#include
class Bomb
{
public:
Bomb();
//更新信息(播放图片下标、播放间隔)
void updateInfo();
public:
//放爆炸资源数组
QVectorm_pixArr;
//爆炸位置
int m_X;
int m_Y;
//爆炸状态
bool m_Free;
//爆炸切图的时间间隔
int m_Recoder;
//爆炸时加载的图片下标
int m_index;
};
#endif // BOMB_H
bomb.cpp:
#include "bomb.h"
Bomb::Bomb()
{
//初始化爆炸图片数组
for(int i = 1 ;i <= BOMB_MAX ;i++)
{
QString str = QString(BOMB_PATH).arg(i);
m_pixArr.push_back(QPixmap(str));
}
//初始化坐标
m_X = 0;
m_Y = 0;
//初始化空闲状态
m_Free = true;
//当前播放图片下标
m_index = 0;
//爆炸间隔记录
m_Recoder = 0;
}
void Bomb::updateInfo()
{
//空闲状态
if(m_Free)
{
return;
}
m_Recoder++;
if(m_Recoder < BOMB_INTERVAL)
{
//记录爆炸间隔未到,直接return,不需要切图
return;
}
//重置记录
m_Recoder = 0;
//切换爆炸播放图片
m_index++;
//注:数组中的下标从0开始,最大是6
//如果计算的下标大于6,重置为0
if(m_index > BOMB_MAX-1)
{
m_index = 0;
m_Free = true;
}
}
MainScene.h中添加爆炸数组:
//爆炸数组
Bomb m_bombs[BOMB_NUM];
for(int k = 0 ; k < BOMB_NUM;k++)
{
if(m_bombs[k].m_Free)
{
//爆炸状态设置为非空闲
m_bombs[k].m_Free = false;
//更新坐标
m_bombs[k].m_X = m_enemys[i].m_X;
m_bombs[k].m_Y = m_enemys[i].m_Y;
break;
}
}
//计算爆炸播放的图片
for(int i = 0 ; i < BOMB_NUM;i++)
{
if(m_bombs[i].m_Free == false)
{
m_bombs[i].updateInfo();
}
}
//绘制爆炸图片
for(int i = 0 ; i < BOMB_NUM;i++)
{
if(m_bombs[i].m_Free == false)
{
painter.drawPixmap(m_bombs[i].m_X,m_bombs[i].m_Y,m_bombs[i].m_pixArr[m_bombs[i].m_index]);
}
}
至此,简易的飞机大战小游戏已经完成
最终结果展示:
未能实现功能:
其实还可以有更多的功能实现,比如做一个背景音乐,做一个闯关系统,血量,以及BOSS的挑战,
总结:
学习完QT后,听老师上课讲会发现可以听懂,但自己动手做项目,发现很多知识需要反复去看课堂回放才可以慢慢理解,不懂的知识夜学会了百度来解决问题,很多逻辑性的代码构建需要长时间理解。