<center><h1 >坦克大战</h1></center>
## 一、环境
- 语言:C++
- 编译器:gcc version 7.3.0
- 应用程序开发框架:Qt version 5.14.1
- 集成开发环境:Qt Creator 4.11.0 (Community)
## 二、游戏总体介绍
### 1.设计规则
该游戏总共有 35 关,每关有 20 个敌方坦克,玩家每关 3 条命,因为是在个人电脑上玩所以仅实现了单人游戏
击败 20 个敌人自动进入下一关,通过 35 关游戏胜利,当生命数为 0 或大本营被击中游戏失败
玩家坦克由键盘 W、S、A、D 键控制移动方向,J 键发射子弹,敌方坦克则自动控制
### 2.效果展示图
![](https://www.writebug.com/myres/static/uploads/2021/11/4/cc14d8ff59832ddf009abb16f7bb3414.writebug)
![](https://www.writebug.com/myres/static/uploads/2021/11/4/bc092d57d7a0fa7c77e481a51acf8f8b.writebug)
![](https://www.writebug.com/myres/static/uploads/2021/11/4/02b51e092b116894515f8c5bcfa6b46b.writebug)
![](https://www.writebug.com/myres/static/uploads/2021/11/4/82e2e2bdb311ffcb89510d931a5cdbe8.writebug)
### 3.UML 类图
![](https://www.writebug.com/myres/static/uploads/2021/11/4/d0d133e356cd502853a88374ac14b7ac.writebug)
### 4.运行流程图
![](https://www.writebug.com/myres/static/uploads/2021/11/4/6d61c0c64671960b97f40f75f425ce32.writebug)
## 三、全局参数的设置
### 1.定义全局参数
我们的全局参数主要是枚举类型方向,地图数组,图像的单位尺寸,窗口长和宽
用一个 config.h 头文件来设置这些全局参数,并由其他类进行 include
### 2.遇到的问题
按照上面的方式写好代码进行编译时发生了变量的重复定义报错(已经进行了宏定义防止头文件多次包含)
后来经过跟踪报错发现子弹类包含了一个 map 数组,而坦克类中也有 map 数组,两者分开是不会互相影响的,但把这两个对象放到 Widget 对象中就会发生冲突
### 3.解决方法
在 config.h 中指明变量为外部变量,把具体变量定义到 config.cpp 中
这样子弹类和坦克类所包含的同名变量都为同一个外部变量,且指向同一个地址
## 四、主要代码
### 1.初始化
游戏初始化时先加载资源文件,各定时器开始计时,创建玩家角色,和敌方坦克
我们规定地图上最多只能有四个敌方坦克,用 enemyNum 变量记录剩余敌人数量,用 cursor 来标记出生点的位置
用 enemies 列表来储存敌方坦克对象
```cpp
void Widget::init()
{
//加载地图
loadMap();
camp.load((rootdir+"pic\\camp0.gif").c_str());
camp = resizePic(camp,SIZE,SIZE);
QSound::play((rootdir+"sound\\start.wav").c_str());
//创建敌人
cursor=0;
enemyNum = 20;
for(int i=0;i<4;i++)
{
createEnemy();
}
//创建玩家
createPlayer();
life = 3;
// 开始游戏
start = 30;
timer1->start(120);
timer2->start(150);
timer3->start(1000);
timer4->start(60);
timer5->start(33);
}
void Widget::createPlayer()
{
role1.bullet.setActive(false);
role1.setDir(direct::up);
role1.ismove=false;
role1.rect.setRect(9*BASESIZE,24*BASESIZE,SIZE,SIZE);
}
void Widget::createEnemy()
{
if(enemyNum<4)
return;
Enemy enemy;
enemy.rect.setRect(cursor*SIZE,0,SIZE,SIZE);
enemies.push_back(enemy);
cursor+=6;
cursor%=18;
}
```
### 2.图像统一大小
使用 Qt 中 QPixmap 类的 scaled 函数来改变图像的尺寸
```cpp
QPixmap QPixmap::scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation) const
```
### 3.地图设置
游戏界面分为 26 行 26 列由二维字符数组 map 保存地图内容,其设定如下所示
| 数据内容 | 地图类型 |
| :------: | :------: |
| 0 | 空白 |
| 1 | 草地 |
| 2 | 冰块 |
| 3 | 砖块 |
| 4 | 海洋 |
| 5 | 钢铁 |
游戏初始化时从 map.dat 文件中读取数据,根据 map 来绘制游戏界面的地形
```cpp
void Widget::loadMap()
{
std::ifstream file;
try
{
file.open((QCoreApplication::applicationDirPath()+"\\res\\map.dat").toStdString().c_str(),std::ios::in|std::ios::binary);
if(!file)
{
throw "无法加载地图,请确保可执行文件所在目录的res目录下有map.dat文件,并且该游戏不放在中文目录下";
}
}
catch(const char *err)
{
QMessageBox msg(QMessageBox::Icon(QMessageBox::Icon::Critical),"ERROR",err);
msg.exec();
exit(-1);
}
file.seekg(sizeof(map)*(gate-1));
file.read(*map,sizeof(map));
file.close();
}
void Widget::drawMap()
{
for (int i=0;i<26;i++)
{
for(int j=0;j<26;j++)
{
if(map[i][j]=='3')
{
paint.drawPixmap(j*BASESIZE,i*BASESIZE,brick);
}
else if(map[i][j]=='5')
{
paint.drawPixmap(j*BASESIZE,i*BASESIZE,iron);
}
else if(map[i][j]=='1')
{
paint.drawPixmap(j*BASESIZE,i*BASESIZE,grass);
}
else if(map[i][j]=='4')
{
paint.drawPixmap(j*BASESIZE,i*BASESIZE,water);
}
else if(map[i][j]=='2')
{
paint.drawPixmap(j*BASESIZE,i*BASESIZE,ice);
}
}
}
paint.drawPixmap(campRect.x(),campRect.y(),camp);
}
```
![](https://www.writebug.com/myres/static/uploads/2021/11/4/163d582a236cc2b9e32678541e3c05b8.writebug)
### 3.窗口绘制
Qt 的 QWidget 类提供了绘制窗口的虚函数,我们直接重载就能实现窗口的绘制,当界面更新时自动调用该函数
界面更新函数:`update();` 由定时器定时调用
```cpp
void paintEvent(QPaintEvent *) override;
```
这里主要是在窗口内绘制图像,我们只要根据各对象的坐标、方向和对应的图像然后调用 QPainter 类的 drawPixmap 成员函数来在指定位置绘制指定图像
具体实现如下
```cpp
void Widget::paintEvent(QPaintEvent *)
{
paint.begin(this);
//画关卡过渡界面
if(0<start--)
{
drawStart();
paint.end();
return;
}
//转换坐标系统
paint.save();
paint.translate(SIZE,SIZE);
//画地图
drawMap();
//画玩家坦克
static bool state=true;
role1.display(paint,state);
state = !state;
//画玩家子弹
role1.bullet.display(paint);
if(role1.bullet.bump)
{
role1.bullet.showExplosion(paint);
}
// 画敌人
for(auto& enemy:enemies)
{
//画子弹
enemy.bullet.display(paint);
if(enemy.bullet.bump)
{
enemy.bullet.showExplosion(paint);
}
//画坦克
enemy.display(paint,state);
}
//重置坐标系统
paint.restore();
//画边框
drawFrame();
//画信息面板
drawPanel();
paint.end();
}
```
![](https://www.writebug.com/myres/static/uploads/2021/11/4/c3411e2fbe9404a38243e26393c40393.writebug)
### 4.播放游戏音效
可以直接使用 Qt 自带的 QSound 类的静态方法 play 播放音效
```cpp
[static] void QSound::play(const QString &filename)
```
### 5.按键检测
Qt 提供了按键检测的虚函数,同窗口绘制函数一样我们只需要重载它们就能实现其功能,只要按下键盘就会自动调用该函数,然后根据响应的按键来设置玩家坦克的方向和改变运动状态就行
```cpp
void keyPressEvent(QKeyEvent *) override;
void keyReleaseEv
神仙别闹
- 粉丝: 4276
- 资源: 7532
最新资源
- 基于springboot+vue的高校教师科研管理系统(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的公司日常考勤系统(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的课程作业管理系统(Java毕业设计,附源码,部署教程).zip
- Mybatis入门学习代码
- 基于springboot+vue的课程答疑系统2(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的课程答疑系统(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的华强北商城二手手机管理系统(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的海滨学院班级回忆录的设计与实现(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的美发门店管理系统(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的旅游管理系统的设计与实现(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的旅游网站的设计与实现(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的欢迪迈手机商城设计与开发(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的美容院管理系统(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的民宿在线预定平台2(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的民宿在线预定平台(Java毕业设计,附源码,部署教程).zip
- 基于springboot+vue的基于mvc的高校办公室行政事务管理系统设计与实现(Java毕业设计,附源码,部署教程).zip
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈