作者 | 白家名

责编 | 王晓曼

出品 | CSDN博客

本文作者使用 QT 框架写了一个塔防游戏程序,该程序中实现了购买炮塔、炮塔升级、怪物按照设定路径移动、炮塔自动寻找范围内目标、朝目标怪物发射炮弹、爆炸效果、怪物走到家时我方生命值减少、方便添加关卡等功能。

运行效果:

这张截图中间的炮塔比较大,这是该炮塔多次升级后的结果。

炮塔升级后图片不会改变,但该炮塔的大小、位置、炮弹大小、攻击所产生的爆炸效果的大小、攻击力、攻击范围都会发生改变。

遗憾点

尽管我已经尽力地标准化这个程序了,但还是因为我对程序后面的步骤的认知不正确,以及由于各种各样的原因,还是遗留下来了很多的遗憾。

在写这个 demo 的后期,我意识到写这个程序已经花费了太长的时间,而且当时我被这个程序折磨的很是难受,为了节省时间尽快完工,在代码上我没有按照最标准的情况来写,且游戏内容也被我简化了许多。

使用 QT 框架写程序,一般好像都是使用各种控件堆起来,然后监听这些控件的信号。但我在程序中却使用了非常原始的办法,即判断鼠标的点击区域,这是因为我发现在connect函数里面,不论是将控件创建在栈区还是堆区都没有用(或许有解决办法,但我不会,从网上也没有找到)。这也就是说没有办法实现最基本的点击一个按钮创建其他按钮的功能,所以我还是用了最原始,也可能是最不标准的搞笑办法实现的相关功能。

方案选择

从 QT 的使用的来看,我可能只是一个十足的新手,但就程序而言,逻辑应该还是差不多的。

如果想要在代码中添加一个关卡,且使用预设的产生怪物方案的话,大概只需要在 mainwindow.cpp 中添加三十五行代码即可,这些代码的用途主要是用于监听进入关卡的按钮、设定怪物的初始位置和路径点、调用预设的产生怪物方案函数和添加新的地图数组。

其实这个添加关卡的方案是我当时想出来的三种方案中最次的一种,这种添加关卡的方案需要直接修改游戏界面类,也就是程序的主要代码,这应该是非常不好的。

而另外两种我设想的方案,读取文件中的内容构建关卡和将关卡相关代码写在开始界面的构造函数中。

第一种方案其实是最好的,但因为涉及到各种各样的文件操作,包括需要精确地移动文件指针、精确地读取到每个字符、字符串和数字之间的转化等,学习这些非常麻烦。并且当时我已经将获取关卡信息的方式设为了读取各个对象、数组的信息的方式,全部修改也非常麻烦,所以放弃。

后来,我想通过将关卡所需要的数据全部写在开始界面类构造函数的方式实现创建关卡,这种方式最起码可以将主程序和关卡信息分离,且原理很简单,就是向主游戏界面对象中传递几个参数即可。但这个时候,我遇到了一个非常致命的问题,这个问题是,在向connect传递lambda表达式做参数时,表达式内部始终没有办法使用函数外部的一个用于传递怪物路径信息的三级指针,而且还是编译失败,并且在我看来,程序中是不存在语法错误的。这个问题导致我花掉了整整一下午的时间都没有找到解决方案,遂放弃。

因为程序比较复杂,所以我为了完成这个程序大概花费了八天的时间之久。这让我认识到,在写复杂程序之时,基础非常重要。

编程步骤

1、编译环境:

Windows Qt 5.9.0 Qt Creator 4.3.0

2、思路:

将二维数组中防御塔空位的坐标保存到动态数组中,遍历这个数组并判断点击区域实现点击防御塔空位,其他的点击也类似。

怪物是根据一个路径点数组中的数据移动的,这个数组需要给出所有怪物的拐点,怪物一直向第一个路径点处移动。这里其实也可以写成像防御塔位置那样自动获取路径点的,这样的话就只需要人为设定怪物遇到交叉路口时的移动方向就好了,但是因为在我想到这个主意时已经写好这一部分了,所以也就懒得改了。

防御塔寻找目标怪物的规则:从后往前找范围内的怪物,如果目标怪物被删除或走出范围了,则重新找到范围内最后一个怪物,否则一直瞄准目标怪物。

该程序中防御塔发射子弹的位置始终处于防御塔的正中心,不会随防御塔的旋转而改变,只是子弹的运动方向会始终跟随目标怪物。另外,因为我不想写了,没有在程序中添加子弹图片的旋转,一律使用的正方形图片作为子弹图片。这两点也是该程序不足的地方。

下面展示程序的主要代码。

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_OBJECT
private: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_H

mainwindow.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)/*X40是两个参数,为X坐标和Y坐标*/), 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->GetAimsMonster()->GetHeight() >> 1)*/ - defei->GetUpLeftY() + 40,defei->GetAimsMonster()->GetX()/* + (defei->GetAimsMonster()->GetWidth() >> 1)*/ - 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(NULL);     //超过距离将目标怪物设为空}//防御塔子弹移动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(NULL);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(NULL);                        //将防御塔目标设为空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 = NULL;}//释放选择框类SelBoxdelete SelBox;SelBox = NULL;//释放防御塔父类指针数组DefeTowerVecfor (auto it = DefeTowerVec.begin(); it != DefeTowerVec.end(); it++){delete *it;*it = NULL;}//释放怪物数组MonsterVecfor (auto it = MonsterVec.begin(); it != MonsterVec.end(); it++){delete *it;*it = NULL;}//释放爆炸效果指针数组ExploEffectCoorfor (auto it = ExploEffectCoor.begin(); it != ExploEffectCoor.end(); it++){delete *it;*it = NULL;}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_H

monster.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

【END】

更多精彩推荐
☞中国第一代程序员潘爱民的 30 年程序人生
☞云上容器 ATT&CK 矩阵详解,阿里云助力企业容器化安全落地
☞秋名山老司机从上车到翻车的悲痛经历,带你深刻了解什么是 Spark on Hive!| 原力计划
☞Python 还能实现哪些 AI 游戏?附上代码一起来一把!
☞数据科学产业中哪些架构最热门?本文为你盘点了 5 款!
☞行业大神支招!5 个方法让你的数字资产免受黑客祸害!
你点的每个“在看”,我都认真当成了喜欢

QT 框架搭建,用最原始的方法实现简单的塔防游戏 | 原力计划相关推荐

  1. QT实现简单的塔防游戏

    QT实现简单的塔防游戏 该程序中实现了购买炮塔.炮塔升级.怪物按照设定路径移动.炮塔自动寻找范围内目标.朝目标怪物发射炮弹.爆炸效果.怪物走到家时我方生命值减少.方便添加关卡等功能. 另附重构版本代码 ...

  2. 基于QT多关卡的塔防游戏

    基于QT多关卡的塔防游戏 基于QT多关卡的塔防游戏 参考模板 核心实现思想 运行图片 代码 基于QT多关卡的塔防游戏 多关卡多怪兽,多防御塔与多子弹类型,对于界面无美化,主要实现其功能,Boss尺寸是 ...

  3. Qt版本-塔防游戏实现一

    这个游戏来源于一篇较早的国外作品,不过原作是以Cocos2D为基础实现的,链接见下: http://www.raywenderlich.com/37701/how-to-make-a-tower-de ...

  4. 最受欢迎 Top 12 Python 开源框架,你都用过吗?| 原力计划

    作者 | 学Python的阿勇 责编 | 夕颜 出品 | CSDN博客 今天给大家带来了12个在GitHub等开源网站中最受欢迎的Python开源框架.如果你正在学习python,那么这12个开源框架 ...

  5. 不为人知的 35 个 More Effective C++ 改善编程与设计的最佳方法 | 原力计划

    作者 | fengbingchun 责编 | 屠敏 出品 | CSDN 博客 Scott Meyers大师Effective三部曲:Effective C++.More Effective C++.E ...

  6. VS2019使用QT框架搭建记录

    最近在学习C++,开始使用的是Code::Blocks但是遇到些问题,然后转到VS平台了,在VS2019使用QT框架的搭建过程中遇到些问题,这里做下记录. 此过程中我走了很多弯路,看了试了很多其他的方 ...

  7. Qt之塔防游戏 c++(一)

    话不多说,我们直接进入正题吧. 这个阶段我们要完成如下功能: 1:图片的绘制 2:敌人运动轨迹的绘制 3:防御塔坑(可放置防御塔点)的绘制 4:鼠标点击事件,实现防御塔的出现 图片的绘制 首先我们在Q ...

  8. Qt版本-塔防游戏实现二

    上篇已经为敌人的出现做好准备了,现在是时候让敌人登场了: 4.敌人初步实现 这里出去3件套(尺寸可以直接用图片大小,我用的是静态常量,习惯而已) 其中m_active表示是否可以移动,只有当其为tru ...

  9. 拿来就能用!几步搭建一套简单直播系统 | 原力计划

    作者 | mind_programmonkey 责编 | 伍杏玲 出品 | CSDN博客 本次用Ngix+RTMP+FFmpeg搭建一个流媒体服务器,实现简单的直播效果. Nginx是一款轻量级的We ...

最新文章

  1. 《剑指offer》c++版本 18.删除链表的结点
  2. springMVC 过滤器与拦截器的执行顺序问题。springboot一样参考
  3. 多个iis的进程w3wp
  4. BugkuCTF-MISC题隐写3
  5. Intel® Nehalem/Westmere架构/微架构/流水线 (7) - 存储转发增强
  6. ②SpringBoot之Web综合开发
  7. composer 完整路径才能访问_Docker 漏洞:允许攻击者获得主机 root 访问权限
  8. 反编译工具Reflector下载(转)
  9. 手机怎么看php格式的视频教程,wmv格式用手机怎么看
  10. 使用J-Link打印日志——SEGGER Real-Time Transfer(RTT)工具的移植使用
  11. html怎么做qq空间主页,如何设计qq空间
  12. CC00230.CloudKubernetes——|KuberNetes细粒度权限控制.V14|——|Ratel.v02|k8s资源管理平台配置|
  13. 华大单片机HC32L130/HC32L136从机IIC通信
  14. 每天一篇论文 289/365Deep Reinforcement Learning for Robotic Pushing and Picking in Cluttered Environment
  15. Python_Task03:异常处理
  16. UIRefreshControl系统下拉刷新
  17. Linux 中三种引号(单引号、双引号、反引号)的区别
  18. 为什么大型网站前端用 PHP 后台逻辑用 JAVA?
  19. Blender建模模块:匕首类模型的建模思路
  20. c语言编程区分负号与减号,C语言程序设计第二章.ppt

热门文章

  1. webpack使用优化(基本篇
  2. C# 在 webBrowser 光标处插入 html代码 .
  3. 读书随笔:The Book of Why——CHAPTER 2:From Buccaneers to Guinea Pigs: The Genesis of Causal Inference
  4. win10让一个绿色软件开机启动
  5. Keras Datasets 国内下载镜像
  6. java script 调用c_用vs2008调试Javacscript
  7. vba遍历字符串_EXCEL 公式 遍历查找 查找字符串
  8. Windows下安装Semantic-Segmentation-Editor标注软件
  9. bag文件加载及可视化显示
  10. 字符串分割 异常 泛型 练习