作者 | 白家名责编 | 王晓曼出品 | CSDN博客本文作者使用 QT 框架写了一个塔防游戏程序,该程序中实现了购买炮塔、炮塔升级、怪物按照设定路径移动、炮塔自动寻找范围内目标、朝目标怪物发射炮弹、爆炸效果、怪物走到家时我方生命值减少、方便添加关卡等功能。
运行效果:这张截图中间的炮塔比较大,这是该炮塔多次升级后的结果。
炮塔升级后图片不会改变,但该炮塔的大小、位置、炮弹大小、攻击所产生的爆炸效果的大小、攻击力、攻击范围都会发生改变。
遗憾点尽管我已经尽力地标准化这个程序了,但还是因为我对程序后面的步骤的认知不正确,以及由于各种各样的原因,还是遗留下来了很多的遗憾。
在写这个 demo 的后期,我意识到写这个程序已经花费了太长的时间,而且当时我被这个程序折磨的很是难受,为了节省时间尽快完工,在代码上我没有按照最标准的情况来写,且游戏内容也被我简化了许多。
使用 QT 框架写程序,一般好像都是使用各种控件堆起来,然后监听这些控件的信号。
但我在程序中却使用了非常原始的办法,即判断鼠标的点击区域,这是因为我发现在connect函数里面,不论是将控件创建在栈区还是堆区都没有用(或许有解决办法,但我不会,从网上也没有找到)。
这也就是说没有办法实现最基本的点击一个按钮创建其他按钮的功能,所以我还是用了最原始,也可能是最不标准的搞笑办法实现的相关功能。
方案选择从 QT 的使用的来看,我可能只是一个十足的新手,但就程序而言,逻辑应该还是差不多的。
如果想要在代码中添加一个关卡,且使用预设的产生怪物方案的话,大概只需要在 mainwindow.cpp 中添加三十五行代码即可,这些代码的用途主要是用于监听进入关卡的按钮、设定怪物的初始位置和路径点、调用预设的产生怪物方案函数和添加新的地图数组。
其实这个添加关卡的方案是我当时想出来的三种方案中最次的一种,这种添加关卡的方案需要直接修改游戏界面类,也就是程序的主要代码,这应该是非常不好的。
而另外两种我设想的方案,读取文件中的内容构建关卡和将关卡相关代码写在开始界面的构造函数中。
第一种方案其实是最好的,但因为涉及到各种各样的文件操作,包括需要精确地移动文件指针、精确地读取到每个字符、字符串和数字之间的转化等,学习这些非常麻烦。
并且当时我已经将获取关卡信息的方式设为了读取各个对象、数组的信息的方式,全部修改也非常麻烦,所以放弃。
后来,我想通过将关卡所需要的数据全部写在开始界面类构造函数的方式实现创建关卡,这种方式最起码可以将主程序和关卡信息分离,且原理很简单,就是向主游戏界面对象中传递几个参数即可。
但这个时候,我遇到了一个非常致命的问题,这个问题是,在向connect传递lambda表达式做参数时,表达式内部始终没有办法使用函数外部的一个用于传递怪物路径信息的三级指针,而且还是编译失败,并且在我看来,程序中是不存在语法错误的。
这个问题导致我花掉了整整一下午的时间都没有找到解决方案,遂放弃。
因为程序比较复杂,所以我为了完成这个程序大概花费了八天的时间之久。
这让我认识到,在写复杂程序之时,基础非常重要。
编程步骤1、编译环境:Windows Qt 5.9.0 Qt Creator 4.3.02、思路:将二维数组中防御塔空位的坐标保存到动态数组中,遍历这个数组并判断点击区域实现点击防御塔空位,其他的点击也类似。
怪物是根据一个路径点数组中的数据移动的,这个数组需要给出所有怪物的拐点,怪物一直向第一个路径点处移动。
这里其实也可以写成像防御塔位置那样自动获取路径点的,这样的话就只需要人为设定怪物遇到交叉路口时的移动方向就好了,但是因为在我想到这个主意时已经写好这一部分了,所以也就懒得改了。
防御塔寻找目标怪物的规则:从后往前找范围内的怪物,如果目标怪物被删除或走出范围了,则重新找到范围内最后一个怪物,否则一直瞄准目标怪物。
该程序中防御塔发射子弹的位置始终处于防御塔的正中心,不会随防御塔的旋转而改变,只是子弹的运动方向会始终跟随目标怪物。
另外,因为我不想写了,没有在程序中添加子弹图片的旋转,一律使用的正方形图片作为子弹图片。
这两点也是该程序不足的地方。
下面展示程序的主要代码。
mainwindow.h 主要游戏界面类头文件:#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QWidget>#include <QPainter> //画家#include <QMouseEvent> //鼠标事件#include <Qtimer> //定时器#include "defensetowerpit.h" //防御塔坑类#include "selectionbox.h" //选择框类#include "defetowerparent.h" //防御塔父类#include "monster.h" //怪物类#include <QLabel>class MainWindow : public QWidget{// Q_OBJECTprivate:QVector<DefenseTowerPit*> TowerPitVec; //防御塔坑数组SelectionBox* SelBox = new SelectionBox("C:/Users/ASUS/Desktop/image/选择框.png"); //选择框类QVector<DefeTowerParent*> DefeTowerVec; //重要防御塔父类数组QVector<Monster*> MonsterVec; //怪物数组void paintEvent(QPaintEvent *); //绘图事件void mousePressEvent(QMouseEvent *); //鼠标点击事件// void mouseMoveEvent(QMouseEvent *); //鼠标移动事件void DrawMapArr(QPainter&); //用于画出地图函数void DrawSelectionBox(QPainter&); //用于画出选择框void DrawDefenseTower(QPainter&); //画出防御塔void DrawMonster(QPainter&); //画出怪物void DrawRangeAndUpgrade(QPainter&); //画出防御塔攻击范围和升级按钮void DrawExplosion(QPainter&); //画出爆炸效果int DisplayRangeX, DisplayRangeY; //用于记录显示范围的防御塔的坐标bool DisplayRange = false; //用于显示防御塔攻击范围struct ExploStr //爆炸效果结构{CoorStr coor; //记录爆炸效果的坐标int index = 1; //记录要显示的图片文件的序号int ExplRangeWidth; //爆炸效果宽高int ExplRangeHeight;//爆炸效果需要初始化坐标、效果宽高ExploStr(CoorStr fcoor, int width, int height) : coor(fcoor), ExplRangeWidth(width), ExplRangeHeight(height) {}};QVector<ExploStr*> ExploEffectCoor; //爆炸效果坐标数组int money = 1200; //记录金钱QLabel *moneylable = new QLabel(this); //显示金钱标签控件inline bool DeductionMoney(int); //判断金钱是否足够并刷新标签int life = 10; //生命数量int counter = 0; //用于产生怪物的计数器const int RewardMoney = 26; //每次击败怪物获得的金钱数量CoorStr *homecoor = new CoorStr(0, 0); //记录家的坐标,从地图中自动获取void IrodMonsProgDefa(CoorStr**, CoorStr**, CoorStr*, int*, QLabel*); //预设两条路产生怪物方案函数const int LevelNumber; //标识关卡bool DisplayAllRange = false; //标识是否显示所有防御塔的攻击范围public:MainWindow(int); //构造~MainWindow;};#endif //MAINWINDOW_Hmainwindow.cpp 主要游戏界面类函数实现:#include "mainwindow.h"#include <QDebug>#include "globalstruct.h" //选择框按钮全局结构#include <cmath> //数学#include "greenturret.h" //绿色防御塔类#include "fireturret.h" //火防御塔类#include "lightturret.h" //光炮防御塔类#include "bigturret.h" //大炮防御塔类#include <QPushButton>//鼠标点击区域宏#define MouClickRegion(X, Width, Y, Height) (ev->x >= (X) && ev->x <= (X) + (Width) && ev->y >= (Y) && ev->y <= (Y) + (Height))//计算两点之间距离宏#define DistBetPoints(X1, Y1, X2, Y2) abs(sqrt((((X1) - (X2)) * ((X1) - (X2))) + (((Y1) - (Y2)) * ((Y1) - (Y2)))))//用于方便通过格子确定路径点坐标#define X40(X, Y) ((X) - 1) * 40 + 10, ((Y) - 1) * 40 + 10//插入怪物 路径点数组名、怪物初始坐标、怪物编号#define InsterMonster(PathNum, StaCoorNum, MonsterId) MonsterVec.push_back(new Monster(pointarr[PathNum], PathLength[PathNum], X40(staco[StaCoorNum].x, staco[StaCoorNum].y), MonsterId));//判断金钱是否足够并刷新标签inline bool MainWindow::DeductionMoney(int money){if (this->money - money < 0) return true; //判断金钱是否足够this->money -= money; //扣除金钱moneylable->setText(QString("金钱:%1").arg(this->money)); //刷新标签文本return false;}//构造MainWindow::MainWindow(int LevelNumber) : LevelNumber(LevelNumber){//设置固定窗口大小setFixedSize(1040, 640);//设置标题setWindowTitle("游戏界面");//提示胜利标签QLabel *victorylable = new QLabel(this);victorylable->move(176, 180);victorylable->setFont(QFont("楷体", 110));victorylable->setText(QString("游戏胜利"));victorylable->hide;QTimer* timer2 = new QTimer(this); //用于插入怪物定时器timer2->start(2000);connect(timer2,&QTimer::timeout,[=]{//根据关卡编号确定执行怪物的路径方案switch (LevelNumber){case 0:{//设置路径点CoorStr* Waypointarr1 = {new CoorStr(X40(8, 6)), new CoorStr(X40(2, 6)), new CoorStr(X40(2, 13)), new CoorStr(X40(19, 13)), new CoorStr(X40(19, 9)), new CoorStr(homecoor->x, homecoor->y)};CoorStr* Waypointarr2 = {new CoorStr(X40(20, 5)), new CoorStr(X40(14, 5)), new CoorStr(X40(14, 9)), new CoorStr(X40(8, 9)), new CoorStr(X40(8, 6)), new CoorStr(X40(2, 6)),new CoorStr(X40(2, 13)), new CoorStr(X40(19, 13)), new CoorStr(X40(19, 9)), new CoorStr(homecoor->x, homecoor->y)}; //最后的路径点设为家//怪物的四个起始点CoorStr staco = {CoorStr(8, 0), CoorStr(20, 0), CoorStr(8, -1), CoorStr(20, -1)};//每条路径的结点个数int PathLength = {sizeof(Waypointarr1)/sizeof(CoorStr*), sizeof(Waypointarr1)/sizeof(CoorStr*)};IrodMonsProgDefa(Waypointarr1, Waypointarr2, staco, PathLength, victorylable); //使用预设的两条路产生怪物方案break;}case 1:{//设置路径点CoorStr* Waypointarr1 = {new CoorStr(X40(4, 8)), new CoorStr(X40(20, 8)), new CoorStr(X40(20, 13)), new CoorStr(X40(6, 13)), new CoorStr(homecoor->x, homecoor->y)};CoorStr* Waypointarr2 = {new CoorStr(X40(11, 8)), new CoorStr(X40(20, 8)), new CoorStr(X40(20, 13)), new CoorStr(X40(6, 13)), new CoorStr(homecoor->x, homecoor->y)};//怪物的四个起始点CoorStr staco = {CoorStr(4, 0), CoorStr(11, 0), CoorStr(4, -1), CoorStr(11, -1)};//每条路径的结点个数int PathLength = {sizeof(Waypointarr1)/sizeof(CoorStr*), sizeof(Waypointarr1)/sizeof(CoorStr*)};IrodMonsProgDefa(Waypointarr1, Waypointarr2, staco, PathLength, victorylable); //使用预设的两条路产生怪物方案break;}case 2:{//设置路径点CoorStr* Waypointarr1 = {new CoorStr(X40(12, 9)), new CoorStr(X40(8, 9)), new CoorStr(X40(8, 0)), new CoorStr(homecoor->x, homecoor->y)};CoorStr* Waypointarr2 = {new CoorStr(X40(12, 9)), new CoorStr(X40(16, 9)), new CoorStr(X40(16, 0)), new CoorStr(homecoor->x, homecoor->y)};//怪物的四个起始点CoorStr staco = {CoorStr(12, 16), CoorStr(12, 16), CoorStr(12, 17), CoorStr(12, 18)};//每条路径的结点个数int PathLength = {sizeof(Waypointarr1)/sizeof(CoorStr*), sizeof(Waypointarr1)/sizeof(CoorStr*)};IrodMonsProgDefa(Waypointarr1, Waypointarr2, staco, PathLength, victorylable); //使用预设的两条路的产生怪物方案break;}}});// setMouseTracking(true);//显示防御塔范围按钮QPushButton* disranpush = new QPushButton(this);disranpush->setStyleSheet("color:black");disranpush->setGeometry(20,160, 150, 45);disranpush->setFont(QFont("微软雅黑", 12));disranpush->setText("显示全部范围");connect(disranpush,&QPushButton::clicked,[=]{//通过改变标识令防御塔攻击范围显示或关闭if (DisplayAllRange){DisplayAllRange = false;disranpush->setText("显示全部范围");}else{DisplayAllRange = true;disranpush->setText("隐藏全部范围");};update;});//金钱标签moneylable->move(20, 40); //位置setStyleSheet("color:white"); //设置颜色moneylable->setFont(QFont("微软雅黑", 24)); //设置金钱标签属性moneylable->setText(QString("金钱:%1").arg(money)); //显示金钱信息//生命值标签QLabel *lifelable = new QLabel(this);lifelable->setGeometry(20, 100, 220, 40); //设置控件位置和大小lifelable->setFont(QFont("微软雅黑", 24));lifelable->setText(QString("生命:%1").arg(life));//定时器每时每刻执行防御塔父类数组的活动函数QTimer* timer = new QTimer(this);timer->start(120);connect(timer,&QTimer::timeout,[=]{//防御塔寻找目标怪物的规律:找到最后一个怪物作为目标,目标丢失后找再继续找最后一个目标for (auto defei : DefeTowerVec) //遍历防御塔{if (!defei->GetAimsMonster) //目标怪物为空时从后往前遍历怪物数组寻找目标怪物{for(int i = MonsterVec.size - 1; i >= 0; i--)//这里以防御塔中心店和怪物中心点判断if (DistBetPoints(defei->GetUpLeftX + 40, defei->GetUpLeftY + 40,MonsterVec.at(i)->GetX + (MonsterVec.at(i)->GetWidth >> 1),MonsterVec.at(i)->GetY + (MonsterVec.at(i)->GetHeight >> 1)) <= defei->GetRange){defei->SetAimsMonster(MonsterVec.at(i)); //设置防御塔的目标怪物break; //找到后跳出循环}}else //当前防御塔拥有目标且怪物在防御塔范围之内时时攻击怪物if (DistBetPoints(defei->GetUpLeftX + 40, defei->GetUpLeftY + 40,defei->GetAimsMonster->GetX + (defei->GetAimsMonster->GetWidth >> 1),defei->GetAimsMonster->GetY + (defei->GetAimsMonster->GetHeight >> 1)) <= defei->GetRange){//根据每个防御塔的目标怪物计算旋转角度defei->SetRotatAngle(atan2(defei->GetAimsMonster->GetY - defei->GetUpLeftY + 40,defei->GetAimsMonster->GetX - defei->GetUpLeftX) * 180 / 3.1415 );defei->InterBullet; //拥有目标时一直创建子弹}//每次循环都判断目标怪物距离防御塔的距离,写在上面可能会不太好if (defei->GetAimsMonster) //目标怪物不为空if (DistBetPoints(defei->GetUpLeftX + 40, defei->GetUpLeftY + 40,defei->GetAimsMonster->GetX + (defei->GetAimsMonster->GetWidth >> 1),defei->GetAimsMonster->GetY + (defei->GetAimsMonster->GetHeight >> 1)) > defei->GetRange)defei->SetAimsMonster; //超过距离将目标怪物设为空}//防御塔子弹移动for (auto defei : DefeTowerVec)defei->BulletMove;//怪物移动for (auto Moni = MonsterVec.begin; Moni != MonsterVec.end; Moni++)if((*Moni)->Move) //怪物走到终点{delete *Moni;MonsterVec.erase(Moni); //怪物走到终点则删除这个怪物life--; //我们的生命数量-1lifelable->setText(QString("生命:%1").arg(life));if (life <= 0) this->close; //生命值为0时关闭该窗口break;}//判断子弹击中怪物for (auto defei : DefeTowerVec) //防御塔{auto &tbullvec = defei->GetBulletVec; //临时存储子弹for (auto bullit = tbullvec.begin; bullit != tbullvec.end; bullit++) //子弹for (auto monit = MonsterVec.begin; monit != MonsterVec.end; monit++)//怪物if ((*bullit)->GetX + (defei->GetBulletWidth >> 1) >= (*monit)->GetX && (*bullit)->GetX <= (*monit)->GetX + (*monit)->GetWidth &&(*bullit)->GetY + (defei->GetBulletHeight >> 1) >= (*monit)->GetY && (*bullit)->GetY <= (*monit)->GetY + (*monit)->GetHeight){ //击中怪物时tbullvec.erase(bullit); //删除子弹(*monit)->SetHealth((*monit)->GetHealth - defei->GetAttack); //敌人血量 = 本身血量减去 当前炮塔攻击力//将击中的怪物中心的坐标插入到爆炸效果数组ExploEffectCoor.push_back(new ExploStr(CoorStr((*monit)->GetX + ((*monit)->GetWidth >> 1), (*monit)->GetY + ((*monit)->GetHeight >> 1)),defei->GetExplRangeWidth, defei->GetExplRangeHeight));if ((*monit)->GetHealth <= 0) //生命值为空时{//判断一下其他防御塔的目标怪物是否和当前防御塔消灭的怪物重复,如果重复,则将那一个防御塔的目标怪物也设为空for (auto defei2 : DefeTowerVec)if (defei2->GetAimsMonster == *monit)defei2->SetAimsMonster;MonsterVec.erase(monit); //删除怪物money += RewardMoney; //击败怪物增加金钱moneylable->setText(QString("金钱:%1").arg(money));//刷新标签}//这里不能将防御塔目标怪物设为空,因为防御塔的子弹攻击到的怪物不一定是该防御塔的目标怪物goto L1;}L1:;}//显示爆炸效果for (auto expli = ExploEffectCoor.begin; expli != ExploEffectCoor.end; expli++){if((*expli)->index >= 8) //爆炸效果显示完后将该效果从数组中删除{ExploEffectCoor.erase(expli);break;}(*expli)->index++; //将要显示的图片的序号++}update; //绘图});}//预设的两条路产生怪物方案void MainWindow::IrodMonsProgDefa(CoorStr** Waypointarr1, CoorStr** Waypointarr2, CoorStr* staco, int* PathLength, QLabel* victorylable){CoorStr** pointarr = {Waypointarr1, Waypointarr2};if(counter >= 1 && counter <= 12){//插入小恐龙*InsterMonster(0, 0, 1); //第几条路径、第几个起始点、怪物编号}else if(counter > 12 && counter <= 34){//在、两路插入小恐龙和鲨鱼InsterMonster(0, 0, 1);InsterMonster(1, 1, 2);}else if (counter > 34 && counter <= 35){//两路插入巨龙InsterMonster(0, 0, 3);InsterMonster(1, 1, 3);}else if (counter > 35 && counter <= 52){//两路插入狮子、狮子、小恐龙InsterMonster(0, 2, 4);InsterMonster(0, 0, 4);InsterMonster(1, 1, 1);}if(counter > 52 && counter <= 56){//插入巨龙InsterMonster(0, 0, 3);InsterMonster(1, 1, 3);}if (counter > 52 && counter <= 71){//插入鲨鱼、蜗牛、小恐龙、狮子InsterMonster(0, 2, 2);InsterMonster(0, 0, 5);InsterMonster(1, 3, 1);InsterMonster(1, 1, 4);}if (counter > 71 && MonsterVec.empty) //时间大于71且怪物数组为空时游戏胜利victorylable->show;counter++; //计数器+1update;}//根据数组画出地图函数//由绘图函数调用void MainWindow::DrawMapArr(QPainter& painter){//地图数组 第一关int Map_1[16][26] ={0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 6, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 1, 1, 6, 6, 0, 0, 0, 0, 0, 0, 3, 6, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 3, 6, 1, 1, 3, 6, 0, 0, 0, 0, 0, 0, 6, 6, 1, 1, 3, 6, 0, 0, 0,0, 0, 0, 0, 0, 6, 6, 1, 1, 6, 6, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 6, 6, 0, 0, 0,0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 3, 6, 0, 1, 1, 0, 0, 0, 0, 3, 6, 0, 0, 0, 0, 0,0, 1, 1, 0, 3, 6, 0, 1, 1, 0, 6, 6, 0, 1, 1, 0, 3, 6, 0, 6, 6, 0, 0, 0, 0, 0,0, 1, 1, 0, 6, 6, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 6, 6, 1, 1, 1, 1, 1, 1, 5, 1,0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,0, 1, 1, 0, 3, 6, 0, 0, 3, 6, 0, 0, 3, 6, 0, 0, 3, 6, 1, 1, 3, 6, 0, 0, 0, 0,0, 1, 1, 0, 6, 6, 0, 0, 6, 6, 0, 0, 6, 6, 0, 0, 6, 6, 1, 1, 6, 6, 0, 0, 0, 0,0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};//第二关int Map_2[16][26] ={0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 3, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 3, 6, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 6, 6, 1, 1, 0, 0, 3, 6, 0, 1, 1, 0, 0, 3, 6, 0, 0, 3, 6, 0, 0, 0, 0, 0, 0,0, 0, 0, 1, 1, 0, 0, 6, 6, 0, 1, 1, 0, 0, 6, 6, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0,0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 3, 6, 0, 0, 0, 0, 3, 6, 0, 0, 0, 0, 3, 6, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 6, 6, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 5, 1, 3, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 1, 1, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};int Map_3[16][26] ={0, 0, 0, 0, 0, 3, 6, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 6, 0, 0, 3, 6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 1, 1, 6, 6, 0, 0, 6, 6, 1, 1, 3, 6, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 3, 6, 0, 0, 1, 1, 6, 6, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 6, 6, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 6, 0, 0, 3, 6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 1, 1, 6, 6, 0, 0, 6, 6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 3, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};int Map[16][26]; //用于拷贝不同的关卡数组switch (LevelNumber){case 0:memcpy(Map, Map_1, sizeof(Map));break;case 1:memcpy(Map, Map_2, sizeof(Map));break;case 2:memcpy(Map, Map_3, sizeof(Map));break;default:break;}for (int j = 0; j < 16; j++)for (int i = 0; i < 26; i++){switch (Map[j][i]){case 0: painter.drawPixmap(i * 40, j * 40, 40, 40,QPixmap(":/image/GrassBlock.png"));break;case 1: painter.drawPixmap(i * 40, j * 40, 40, 40,QPixmap(":/image/GroundBlock.png"));break;case 3: painter.drawPixmap(i * 40, j * 40, 80, 80,QPixmap(":/image/StoneBrick.png"));//防御塔坑坐标初始化塔坑坐标,插入到塔坑对象数组TowerPitVec.push_back(new DefenseTowerPit(i * 40, j * 40));break;case 5: //家在循环中也输出地面painter.drawPixmap(i * 40, j * 40, 40, 40,QPixmap(":/image/GroundBlock.png"));homecoor->x = i * 40, homecoor->y = j * 40;break;}}painter.drawPixmap(homecoor->x, homecoor->y, 80, 80,QPixmap(":/image/home.png")); //最后画出家}//画出选择框void MainWindow::DrawSelectionBox(QPainter& painter){//显示选择框if (!SelBox->GetDisplay)return;//画出选择框painter.drawPixmap(SelBox->GetX, SelBox->GetY, SelBox->GetWidth, SelBox->GetHeight,QPixmap(SelBox->GetImgPath));//画出子按钮SubbutStr *ASubBut = SelBox->GetSelSubBut; //接收子按钮结构数组for (int i = 0; i < 4; i++)painter.drawPixmap(ASubBut[i].SubX, ASubBut[i].SubY, ASubBut[i].SubWidth, ASubBut[i].SubHeight,QPixmap(ASubBut[i].SubImgPath));painter.setPen(QPen(Qt::yellow, 6, Qt::SolidLine)); //设置画笔painter.drawRect(QRect(SelBox->GetX + 95, SelBox->GetY + 95, 80, 80));}//画出防御塔void MainWindow::DrawDefenseTower(QPainter& painter){//画出防御塔for (auto defei : DefeTowerVec) //遍历防御塔数组{//画出底座painter.drawPixmap(defei->GetUpLeftX, defei->GetUpLeftY, 80, 80, QPixmap(defei->GetbaseImgPath));//画出所有防御塔的攻击范围if(DisplayAllRange)painter.drawEllipse(QPoint(defei->GetUpLeftX + 40, defei->GetUpLeftY + 40), defei->GetRange, defei->GetRange);//画出所有防御塔子弹for (auto bulli : defei->GetBulletVec)painter.drawPixmap(bulli->coor.x, bulli->coor.y, defei->GetBulletWidth, defei->GetBulletHeight,QPixmap(defei->GetBulletPath));//画出防御塔painter.translate(defei->GetUpLeftX + 40, defei->GetUpLeftY + 40); //设置旋转中心painter.rotate(defei->GetRotatAngle); //旋转角度painter.translate(-(defei->GetUpLeftX + 40), -(defei->GetUpLeftY + 40)); //原点复位painter.drawPixmap(defei->GetX, defei->GetY, defei->GetWidth, defei->GetHeight, QPixmap(defei->GetDefImgPath));painter.resetTransform; //重置调整}}//画出怪物void MainWindow::DrawMonster(QPainter& painter){for (auto moni : MonsterVec)//画出怪物painter.drawPixmap(moni->GetX, moni->GetY, moni->GetWidth, moni->GetHeight, QPixmap(moni->GetImgPath));}//画出防御塔和升级按钮void MainWindow::DrawRangeAndUpgrade(QPainter& painter){//根据条件画出防御塔攻击范围和升级按钮for (auto defei : DefeTowerVec)if(defei->GetUpLeftX == DisplayRangeX && defei->GetUpLeftY == DisplayRangeY && DisplayRange){ //画出防御塔攻击范围painter.setPen(QPen(Qt::red)); //使用红色画出范围painter.drawEllipse(QPoint(DisplayRangeX + 40, DisplayRangeY + 40), defei->GetRange, defei->GetRange);painter.drawPixmap(DisplayRangeX + 10, DisplayRangeY - 80, 60, 60, QPixmap(":/image/UpgradeBut1.png"));}}//画出爆炸效果void MainWindow::DrawExplosion(QPainter& painter){//显示所有爆炸效果for (auto expli : ExploEffectCoor)painter.drawPixmap(expli->coor.x - (expli->ExplRangeWidth >> 1), expli->coor.y - (expli->ExplRangeHeight >> 1),expli->ExplRangeWidth, expli->ExplRangeHeight, QPixmap(QString(":/image/ExplosionEffect%1.png").arg(expli->index)));}//绘图事件void MainWindow::paintEvent(QPaintEvent *){QPainter painter(this); //创建画家类painter.setRenderHint(QPainter::Antialiasing); //设置抗锯齿DrawMapArr(painter); //画出地图DrawDefenseTower(painter); //画出防御塔和子弹DrawMonster(painter); //画出怪物DrawRangeAndUpgrade(painter);//画出攻击范围和升级按钮DrawExplosion(painter); //画出爆炸效果DrawSelectionBox(painter); //画出选择框}//鼠标点击事件void MainWindow::mousePressEvent(QMouseEvent *ev){if (ev->button != Qt::LeftButton)return;//判断升级按钮的点击if (DisplayRange){if (MouClickRegion(DisplayRangeX + 10, 60 , DisplayRangeY - 80, 60)){//设置防御塔宽高,攻击力,微调坐标for (auto defei : DefeTowerVec)if (defei->GetUpLeftX == DisplayRangeX && defei->GetUpLeftY == DisplayRangeY && DisplayRange){if (DeductionMoney(200)) return; //升级防御塔需要花费200defei->SetAttack(defei->GetAttack + 20); //每次升级防御塔攻击力+20defei->SetWidthHeight(defei->GetWidth + 12, defei->GetHeight + 6); //防御塔宽高增加defei->SetXY(defei->GetX - 6, defei->GetY - 3); //调整防御塔坐标defei->SetAimsMonster; //将防御塔目标设为空defei->SetRange += 14; //设置防御塔的攻击范围defei->SetExplRangeWidthHeight(defei->GetExplRangeWidth + 5, defei->GetExplRangeHeight + 5); //设置防御塔攻击怪物所产生的爆炸效果宽高defei->SetBulletWidthHeight(defei->GetBulletWidth + 5, defei->GetBulletHeight + 5); //设置子弹宽高break;}SelBox->SetDisplay(false); //取消显示新建防御塔框DisplayRange = false; //取消显示自己update;return;}}//判断选择框四个子按钮的点击SubbutStr *ASubBut = SelBox->GetSelSubBut;for (int i = 0; i < 4; i++)if (MouClickRegion(ASubBut[i].SubX, ASubBut[i].SubWidth, ASubBut[i].SubY, ASubBut[i].SubHeight) && SelBox->GetDisplay){SelBox->SetDisplay(false); //取消显示选择框//根据点击的不同的按钮,将防御塔子类插入到防御塔父类数组switch (i){case 0: //绿瓶if (DeductionMoney(100)) return; //扣除金钱DefeTowerVec.push_back(new GreenTurret(SelBox->GetX + 110, SelBox->GetY + 112, SelBox->GetX + 95, SelBox->GetY + 95, 72, 46));break;case 1: //火瓶if (DeductionMoney(160)) return;DefeTowerVec.push_back(new FireTurret(SelBox->GetX + 110, SelBox->GetY + 112, SelBox->GetX + 95, SelBox->GetY + 95, 72, 46));break;case 2: //光炮if (DeductionMoney(240)) return;DefeTowerVec.push_back(new LightTurret(SelBox->GetX + 110, SelBox->GetY + 112, SelBox->GetX + 95, SelBox->GetY + 95, 76, 50));break;case 3: //大炮if (DeductionMoney(400)) return;DefeTowerVec.push_back(new BigTurret(SelBox->GetX + 110, SelBox->GetY + 104, SelBox->GetX + 95, SelBox->GetY + 95, 80, 70));break;default:break;}update;return;}//遍历所有塔坑for (auto APit : TowerPitVec)//判断点击塔坑if (MouClickRegion(APit->GetX, APit->GetWidth, APit->GetY, APit->GetHeight)){DisplayRange = false; //降防御塔的升级选择显示关闭for (auto defei : DefeTowerVec) //遍历数组判断防御塔坐标和点击坑坐标重合则返回if(defei->GetUpLeftX == APit->GetX && defei->GetUpLeftY == APit->GetY){DisplayRangeX = defei->GetUpLeftX, DisplayRangeY = defei->GetUpLeftY; //记录要显示攻击范围的防御塔的坐标DisplayRange = true; //显示防御塔攻击范围return;}SelBox->CheckTower(APit->GetX, APit->GetY); //选中防御塔,选择框显示update;return;}DisplayRange = false; //取消显示防御塔攻击范围SelBox->SetDisplay(false); //取消显示选择框update;}//鼠标移动事件 测试炮台旋转//void MainWindow::mouseMoveEvent(QMouseEvent *ev)//{// for(auto defei : DefeTowerVec)// defei->SetRotatAngle(atan2(ev->y - defei->GetUpLeftY + 40, ev->x - defei->GetUpLeftX) * 180 / 3.1415); //计算炮塔旋转的度数// update;//}//析构释放内存MainWindow::~MainWindow{//释放防御塔坑指针数组TowerPitVecfor (auto it = TowerPitVec.begin; it != TowerPitVec.end; it++){delete *it;*it = ;}//释放选择框类SelBoxdelete SelBox;SelBox = ;//释放防御塔父类指针数组DefeTowerVecfor (auto it = DefeTowerVec.begin; it != DefeTowerVec.end; it++){delete *it;*it = ;}//释放怪物数组MonsterVecfor (auto it = MonsterVec.begin; it != MonsterVec.end; it++){delete *it;*it = ;}//释放爆炸效果指针数组ExploEffectCoorfor (auto it = ExploEffectCoor.begin; it != ExploEffectCoor.end; it++){delete *it;*it = ;}delete homecoor;}monster.h 怪物类头文件:#ifndef MONSTER_H#define MONSTER_H#include <QVector>#include <QString>#include "globalstruct.h" //坐标结构//怪物类class Monster{private:QVector<CoorStr*> Waypoint; //存储怪物路径点数组int mx, my; //怪物坐标int mwidth, mheight; //怪物宽高QString ImgPath; //怪物图片路径int id; //怪物编号int health; //怪物生命值int mspeed = 10; //怪物移动速度public://参数:路径点数组、路径点的个数、怪物初始坐标、怪物宽度、怪物图片路径Monster(CoorStr **pointarr, int arrlength, int x, int y, int fid); //构造bool Move; //怪物移动函数int GetX const; //获取横坐标int GetY const; //获取横坐标int GetWidth const; //获取宽int GetHeight const; //获取高QString GetImgPath const; //获取图片路径int GetId const; //获取编号int GetHealth const; //获取生命值void SetHealth(int); //设置生命值};#endif // MONSTER_Hmonster.cpp 怪物类函数实现:#include "monster.h"#include <QDebug>//怪物类函数实现Monster::Monster(CoorStr **pointarr, int arrlength, int x, int y, int fid) :mx(x), my(y), id(fid){for(int i = 0; i < arrlength; i++) //将传进来的数组插入到Waypoint动态数组Waypoint.push_back(pointarr[i]);//确定不同怪物的生命值switch (id){case 1: //小恐龙怪1health = 100; //生命值mwidth = 64, mheight = 64; //宽高ImgPath = "C:/Users/ASUS/Desktop/image/怪物1.png";break;case 2: //鱼怪2health = 120;mwidth = 86, mheight = 64;ImgPath = "C:/Users/ASUS/Desktop/image/怪物2.png";break;case 3: //飞龙怪3health = 650;mwidth = 160, mheight = 120;ImgPath = "C:/Users/ASUS/Desktop/image/怪物3.png";break;case 4: //狮子怪4health = 310;mwidth = 98, mheight = 70;ImgPath = "C:/Users/ASUS/Desktop/image/怪物4.png";break;case 5: //蜗牛怪5health = 200;mwidth = 90, mheight = 76;ImgPath = "C:/Users/ASUS/Desktop/image/怪物5.png";break;default:break;}}//怪物按设定路径点移动bool Monster::Move{if(Waypoint.empty)return true;//如果第一个路径点的y小于怪物原本的路径点,则怪物向下走if (Waypoint.at(0)->y > my) //下{my += mspeed;return false;}if (Waypoint.at(0)->x < mx) //左{mx -= mspeed;return false;}if (Waypoint.at(0)->x > mx) //右{mx += mspeed;return false;}if (Waypoint.at(0)->y < my) //上{my -= mspeed;return false;}//如果怪物的坐标和路径点坐标几乎重合,则删除这个路径点// if (Waypoint.at(0)->y >= my && Waypoint.at(0)->y <= my + 14 && Waypoint.at(0)->x >= mx && Waypoint.at(0)->x <= mx + 14)if (Waypoint.at(0)->y == my && Waypoint.at(0)->x == mx){delete Waypoint.begin; //释放坐标点内存Waypoint.erase(Waypoint.begin); //从数组中删除// return false;}return false;}int Monster::GetX const //获取横坐标{return mx;}int Monster::GetY const //获取横坐标{return my;}int Monster::GetWidth const //获取宽{return mwidth;}int Monster::GetHeight const //获取高{return mheight;}QString Monster::GetImgPath const //获取图片路径{return ImgPath;}int Monster::GetId const //获取编号{return id;}int Monster::GetHealth const //获取生命值{return health;}void Monster::SetHealth(int fhealth)//设置生命值{health = fhealth;}版权声明:本文为CSDN博主「白家名」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_46239972/article/details/106073498