C 实现植物大战僵尸(三)

打印 上一主题 下一主题

主题 803|帖子 803|积分 2409

C 实现植物大战僵尸(三)

十 实现豌豆子弹

原操持

这里的操持思路和原 UP 主思路差异比较大,摆列如下
原作中只要僵尸在出如今某条门路上,且存在豌豆射手,豌豆射手就会发射子弹,(这里是网页在线版的链接 4399 在线玩植物大战僵尸 H5 )
可以看到正常环境下,同一豌豆射手只有上次发射的子弹爆炸后才会发射下一颗(也就是豌豆射击是偶然间隔断的)
如果按照原 UP 主思路操持一个子弹类
  1. typedef struct Bullet {
  2.     int x;              //当前 X 轴坐标
  3.     int y;              //当前 Y 轴坐标
  4.     int frameId;        //当前图片帧编号
  5.     int speed;          //子弹移动的速度
  6.     bool used;          //是否在使用
  7. };
  8. //同一豌豆射手只有上次发射的子弹爆炸后才会发射下一颗
  9. Bullet bullets[GRASS_GRID_ROW * GRASS_GRID_COL];
  10. IMAGE peaNormal;
复制代码
在更新游戏数据(updateGame) 地方创建子弹和更新子弹数据
  1. void updateGame()
  2. {
  3.     for (int i = 0; i < GRASS_GRID_ROW; ++i)
  4.     {
  5.         for (int j = 0; j < GRASS_GRID_COL; ++j)
  6.         {
  7.             if (plants[i][j].type >= 0)
  8.             {
  9.                 if (imgPlant[plants[i][j].type][++plants[i][j].frameId] == NULL)
  10.                     plants[i][j].frameId = 0;
  11.             }
  12.         }
  13.     }
  14.     createSunshine();
  15.     updateSunshine();
  16.     createZombie();
  17.     updateZombie();
  18.     //创建子弹和更新子弹数据
  19.     createBullets();
  20.     updateBullets();
  21. }
复制代码
在 gameInit 中加载图片
  1. //加载子弹图片
  2. loadimage(&peaNormal, "res/bullets/PeaNormal/PeaNormal_0.png");
复制代码
在 updateWindow 中渲染子弹
  1. //渲染子弹
  2. for (int i = 0; i < GRASS_GRID_ROW * GRASS_GRID_COL; ++i)
  3. {
  4.     if (bullets[i].used)
  5.         putimagePNG(bullets[i].x, bullets[i].y, &peaNormal);
  6. }
复制代码
重点 接下来实现 createBullets() 和 updateBullets() 函数
  1. void createBullets()
  2. {
  3.     int peaX = 0, peaY = 0, pic_width = 0;
  4.     //遍历是否存在僵尸
  5.     for (int i = 0; i < MAX_ZOMBIE_NUM && zombies[i].used; ++i)
  6.     {
  7.         //printf("%s zombies i = %d  row = %d \n", __FUNCTION__ , i, zombies[i].row);
  8.         
  9.         //遍历当前行是否存在豌豆
  10.         for (int j = 0; j < GRASS_GRID_COL &&
  11.             plants[zombies[i].row][j].type == (int)PEA; ++j)
  12.         {
  13.             //printf("%s pea i = %d  j = %d \n", __FUNCTION__, zombies[i].row, j);
  14.             //找到一颗未使用的子弹
  15.             for (int k = 0; k < (GRASS_GRID_ROW * GRASS_GRID_COL)
  16.                 && !bullets[k].used; ++k)
  17.             {
  18.                 //printf("%s bullet k = %d \n", __FUNCTION__,k);
  19.                 //之前豌豆的 X Y 坐标
  20.                 peaX = GRASS_LEFT_MARGIN + j * GRASS_GRID_WIDTH + 5;
  21.                 peaY = GRASS_TOP_MARGIN + zombies[i].row * GRASS_GRID_HIGHT + 10;
  22.                 pic_width = imgPlant[0][0]->getwidth();
  23.                 //初始化子弹
  24.                 bullets[k].x = peaX + pic_width;
  25.                 bullets[k].y = peaY + 5;
  26.                 bullets[k].speed = 7;
  27.                 bullets[k].frameId = 0;
  28.                 bullets[k].used = true;
  29.                 break;
  30.             }
  31.         }
  32.     }
  33. }
  34. void updateBullets()
  35. {
  36.     for (int k = 0; k < (GRASS_GRID_ROW * GRASS_GRID_COL)
  37.         && bullets[k].used; ++k)
  38.     {
  39.         bullets[k].x += bullets[k].speed;
  40.         if (bullets[k].x >= WIN_WIDTH)
  41.         {
  42.             bullets[k].used = false;
  43.         }
  44.     }
  45. }
复制代码
上述 createBullets 函数存在问题点,因为没有判断该该豌豆是否已经发射了子弹(假设豌豆射击的时间隔断为,在子弹爆炸前不会再发射子弹),以是子弹会刹时用完
这时可以在 Plant 布局体中增长成员变量来记录,用 index 来记录子弹数组的下标,通过在上述第三层循环中增长 plants[zombies.row][j].index = k; 就可以判断 该豌豆是否已经发射了子弹
  1. typedef struct Plant
  2. {
  3.     int type;     //植物类型, -1 表示草地
  4.     int frameId;  //表示植物摆动帧
  5.     int index;    //新增: 记录下标,如果植物类型是豌豆,表示未发射子弹
  6. }Plant;
复制代码
发射的问题解决了,但在豌豆碰撞到僵尸时,是需要把上述豌豆的 index 重新置为 -1 (初始化时 memset 值)的,以是还需要在 Bullet 记录下豌豆的坐标,当在 updateBullets 函数,if (bullets[k].x >= WIN_WIDTH) 时,把 plants[currPeaX][currPeaY].index = -1
  1. typedef struct Bullet {
  2.         int currPeaX;                //新增 豌豆 X 坐标
  3.         int currPeaY;                //新增 豌豆 Y 坐标
  4.     int x;              //当前 X 轴坐标
  5.     int y;              //当前 Y 轴坐标
  6.     int frameId;        //当前图片帧编号
  7.     int speed;          //子弹移动的速度
  8.     bool used;          //是否在使用
  9. };
复制代码
固然新增其它数据数据,豌豆和子弹间利用新增布局体之间接洽也可以。但豌豆和子弹自己应该是附属关系,以是无论是新增布局体或是用上面新增成员变量的方式(在 Plant 布局体中加上专属于 Pea 的成员也很希奇),代码都会有一种割裂感(原本一个整体却被割开了)
现操持

因此感觉这里需要的应该是豌豆射手布局体(用布局体嵌套方式),上述中的豌豆射击的时间隔断,可以把它界说为豌豆的射击速度
操持布局体如下
  1. /* 植物相关结构和变量 */
  2. typedef struct Plant // 植物结构体
  3. {
  4.     int type;     //植物类型, -1 表示草地
  5.     int frameId;  //表示植物摆动帧
  6. }Plant;
  7. Plant* plants[GRASS_GRID_ROW][GRASS_GRID_COL]; //注意这里改成了指针二维数组
  8. /* 草地结构体 */
  9. typedef struct Grass
  10. {
  11.     Plant plant;
  12. } Grass;
  13. /* 向日葵结构体 */
  14. typedef struct SunFlower
  15. {
  16.     Plant plant;
  17. } SunFlower;
  18. /* 豌豆射手相关结构和变量 */
  19. #define MAX_BULLET_NUM 1 //默认同一豌豆只有上次发射子弹爆炸后才发射下一颗, 也可更改
  20. IMAGE peaNormal;
  21. IMAGE peaNormalExplode;
  22. /* 子弹结构体 */
  23. typedef struct Bullet {
  24.     int x;              //当前 X 轴坐标
  25.     int y;              //当前 Y 轴坐标
  26.     int speed;          //子弹移动的速度
  27.     bool used;          //是否在使用
  28. }Bullet;
  29. #define DEFAULT_SHOOT_TIME -1
  30. #define MAX_TIME_INTERVAL 100
  31. /* 豌豆射手结构体 */
  32. typedef struct PeaShooter
  33. {
  34.     Plant plant;
  35.     int shootSpeed; //豌豆射击速度, 或者叫豌豆发射子弹的时间间隔, -1 表示可发射子弹
  36.     Bullet bullet[MAX_BULLET_NUM]; //子弹夹
  37. } PeaShooter;
复制代码
以是相称于之前涉及到 plants 的地方都需要修改,因此把整体代码调解了一下。重要调解点在于
① plants 在 gameInit 中把所有格子初始化为草地
② 莳植植物时,先开释对应草格子内存,然后把 plants 二维指针数组对应位置,指向初始化的植物
③ 在游戏竣事时候调用 destroyPlants 接口把申请的内存销毁并把 plants 成员置 NULL
④ shoot 为豌豆射手发射子弹接口,豌豆射手需要到达射击时间且子弹未利用状态时才气发射豌豆射击。updateBullets 更新子弹图片帧接口,和之前逻辑根本同等
改动位置较多,无法一一说明,贴当前项目全部代码如下,所有代码内容均有解释
  1. #include <stdio.h>
  2. #include <graphics.h> // 引用图形库头文件
  3. #include <time.h>
  4. #include <math.h>
  5. #include <mmsystem.h>
  6. #include <assert.h>
  7. #include <stdlib.h>
  8. #include "tools.h"
  9. #pragma commet(lib, "winmm.lib")
  10. /* 一些数据宏定义, 具体含义参见宏名称 */
  11. #define WIN_WIDTH 900 //窗口属性宽高宏定义
  12. #define WIN_HIGHT 600
  13. #define MAX_PICTURE_NUM 20 //动态植物图片属性宏定义
  14. #define PIC_LEFT_MARGIN 338
  15. #define PIC_WIDTH 65
  16. #define GRASS_LEFT_MARGIN 252  //草格子属性宏定义
  17. #define GRASS_TOP_MARGIN 82
  18. #define GRASS_GRID_ROW 5
  19. #define GRASS_GRID_COL 9
  20. #define GRASS_GRID_HIGHT 98
  21. #define GRASS_GRID_WIDTH 81
  22. #define UI_LEFT_MARGIN 474 //游戏菜单属性宏定义
  23. #define UI_TOP_MARGIN 75
  24. #define UI_WIDTH 300
  25. #define UI_HIGHT 140
  26. int currX = 0, currY = 0, currIndex = -1; //当前拖动植物的坐标和类型
  27. enum PLANT_CARDS { PEA, SUNFLOWER, PLANT_CNT }; //使用 PLANT_CNT 统计 PLANT 总数
  28. IMAGE imgBg; //背景图片
  29. IMAGE imgBar; //工具栏图片
  30. IMAGE imgCards[PLANT_CNT]; //植物卡片
  31. IMAGE* imgPlant[PLANT_CNT][MAX_PICTURE_NUM]; //动态植物素材 (也可使用二维数组, 但存在浪费空间问题)
  32. /* 植物相关结构和变量 */
  33. typedef struct Plant // 植物结构体
  34. {
  35.     int type;     //植物类型, -1 表示草地
  36.     int frameId;  //表示植物摆动帧
  37. }Plant;
  38. Plant* plants[GRASS_GRID_ROW][GRASS_GRID_COL];
  39. /* 草地结构体 */
  40. typedef struct Grass
  41. {
  42.     Plant plant;
  43. } Grass;
  44. /* 向日葵结构体 */
  45. typedef struct SunFlower
  46. {
  47.     Plant plant;
  48. } SunFlower;
  49. /* 豌豆射手相关结构和变量 */
  50. #define MAX_BULLET_NUM 1 //默认同一豌豆只有上次发射子弹爆炸后才发射下一颗, 也可更改
  51. IMAGE peaNormal;
  52. IMAGE peaNormalExplode;
  53. /* 子弹结构体 */
  54. typedef struct Bullet {
  55.     int x;              //当前 X 轴坐标
  56.     int y;              //当前 Y 轴坐标
  57.     int speed;          //子弹移动的速度
  58.     bool used;          //是否在使用
  59. }Bullet;
  60. #define DEFAULT_SHOOT_TIME -1
  61. #define MAX_TIME_INTERVAL 100
  62. /* 豌豆射手结构体 */
  63. typedef struct PeaShooter
  64. {
  65.     Plant plant;
  66.     int shootSpeed; //豌豆射击速度, 或者叫豌豆发射子弹的时间间隔, -1 表示可发射子弹
  67.     Bullet bullet[MAX_BULLET_NUM]; //子弹夹
  68. } PeaShooter;
  69. /* 阳光球相关结构和变量 */
  70. typedef struct SunShineBall
  71. {
  72.     int x;              //当前 X 轴坐标, 阳光球在飘落过程中 X 坐标不变
  73.     int y;              //当前 Y 轴坐标
  74.     int frameId;        //当前图片帧编号
  75.     int destination;    //飘落目标位置 Y 坐标
  76.     bool used;          //是否在使用
  77.     int timer;          //统计飘落目标位置后的帧次数
  78.     float xOffset;      //阳光球飞跃过程中每次 X 轴偏移量
  79.     float yOffset;      //阳光球飞跃过程中每次 Y 轴偏移量
  80. }SunShineBall;
  81. #define MAX_BALLS_NUM 10
  82. #define SUM_SHINE_PIC_NUM 29
  83. SunShineBall balls[MAX_BALLS_NUM];
  84. IMAGE imgSunShineBall[SUM_SHINE_PIC_NUM];
  85. int sunShineVal = 50;   //全局变量阳光值
  86. /* 僵尸相关结构和变量 */
  87. #define MAX_ZOMBIE_NUM 10
  88. #define MAX_ZOMBIE_PIC_NUM 22
  89. typedef struct Zombie {
  90.     int x;              //当前 X 轴坐标
  91.     int y;              //当前 Y 轴坐标
  92.     int frameId;        //当前图片帧编号
  93.     int speed;          //僵尸移动的速度
  94.     int row;            //僵尸所在行
  95.     bool used;          //是否在使用
  96. };
  97. Zombie zombies[MAX_ZOMBIE_NUM];
  98. IMAGE imgZombies[MAX_ZOMBIE_PIC_NUM];
  99. /* 判断文件是否存在接口 */
  100. bool fileExist(const char* name)
  101. {
  102.     FILE* file = NULL;
  103.     if (file = fopen(name,"r"))
  104.         fclose(file);
  105.     return file == NULL ? false : true;
  106. }
  107. /* 游戏初始化接口, 主要加载游戏图片至内存 */
  108. void gameInit()
  109. {
  110.     loadimage(&imgBg, "res/map0.jpg"); //加载背景图片
  111.     loadimage(&imgBar, "res/bar5.png");
  112.     char name[64];
  113.     memset(imgPlant, 0, sizeof(imgPlant)); //将二维指针数组内存空间置零
  114.     memset(balls, 0, sizeof(balls));
  115.     memset(zombies, 0, sizeof(zombies));
  116.     memset(plants, 0, sizeof(plants));
  117.     Grass* grassPtr = NULL;
  118.     for (int i = 0; i < GRASS_GRID_ROW; ++i) //将植物数组全初始化为草地
  119.     {
  120.         for (int j = 0; j < GRASS_GRID_COL; ++j)
  121.         {
  122.             grassPtr = (Grass*)calloc(1, sizeof(Grass));
  123.             assert(grassPtr);
  124.             plants[i][j] = (Plant*)grassPtr;
  125.             plants[i][j]->type = -1;
  126.         }
  127.     }
  128.     for (int i = 0; i < PLANT_CNT; ++i)
  129.     {
  130.         sprintf(name, "res/Cards/card_%d.png", i + 1); //获取植物卡片相对路径名称
  131.         loadimage(&imgCards[i], name);
  132.         for (int j = 0;i < MAX_PICTURE_NUM; ++j)
  133.         {
  134.             sprintf(name, "res/Plants/%d/%d.png", i, j + 1); //获取动态植物素材相对路径名称
  135.             if (fileExist(name)) {
  136.                 imgPlant[i][j] = new IMAGE;
  137.                 loadimage(imgPlant[i][j], name);
  138.             }
  139.             else break;
  140.         }
  141.     }
  142.     for (int i = 0; i < SUM_SHINE_PIC_NUM; ++i) //加载阳光图片
  143.     {
  144.         sprintf(name, "res/sunshine/%d.png", i + 1);
  145.         loadimage(&imgSunShineBall[i], name);
  146.     }
  147.     for (int i = 0; i < MAX_ZOMBIE_PIC_NUM; ++i) //加载僵尸图片
  148.     {
  149.         sprintf(name, "res/zm/0/%d.png", i + 1);
  150.         loadimage(&imgZombies[i], name);
  151.     }
  152.     loadimage(&peaNormal, "res/bullets/PeaNormal/PeaNormal_0.png"); //加载子弹图片
  153.     srand(time(NULL)); //配置随机种子
  154.     initgraph(WIN_WIDTH, WIN_HIGHT, 1); //创建游戏图形窗口
  155.     LOGFONT f; //设置字体
  156.     gettextstyle(&f);
  157.     f.lfHeight = 30;
  158.     f.lfWidth = 15;
  159.     strcpy(f.lfFaceName, "Segoe UI Black");
  160.     f.lfQuality = ANTIALIASED_QUALITY; //抗锯齿化效果
  161.     settextstyle(&f);
  162.     setbkmode(TRANSPARENT); //设置背景透明
  163.     setcolor(BLACK); //设置字体颜色
  164. }
  165. /* 游戏更新窗口接口, 主要渲染游戏图片至输出窗口 */
  166. void updateWindow()
  167. {
  168.     BeginBatchDraw(); //使用双缓冲, 解决输出窗口闪屏
  169.     putimage(0, 0, &imgBg); //渲染背景图至窗口
  170.     putimagePNG(250, 0, &imgBar);
  171.     for (int i = 0;i < PLANT_CNT;++i) //渲染植物卡牌
  172.         putimage(PIC_LEFT_MARGIN + i * PIC_WIDTH, 6, &imgCards[i]);
  173.     for (int i = 0; i < GRASS_GRID_ROW; ++i) //渲染种植植物
  174.     {
  175.         for (int j = 0; j < GRASS_GRID_COL; ++j)
  176.         {
  177.             if (plants[i][j]->type >= 0)
  178.                 putimagePNG(GRASS_LEFT_MARGIN + j * GRASS_GRID_WIDTH + 5, //微调植物种植位置
  179.                             GRASS_TOP_MARGIN + i * GRASS_GRID_HIGHT + 10,
  180.                             imgPlant[plants[i][j]->type][plants[i][j]->frameId]);
  181.         }
  182.     }
  183.     for (int i = 0; i < MAX_BALLS_NUM; ++i) //渲染随机阳光
  184.     {
  185.         if (balls[i].used || balls[i].xOffset)
  186.             putimagePNG(balls[i].x, balls[i].y, &imgSunShineBall[balls[i].frameId]);
  187.     }
  188.     if (currIndex >= 0) //渲染当前拖动的植物
  189.     {
  190.         IMAGE* currImage = imgPlant[currIndex][0];
  191.         putimagePNG(currX - currImage->getwidth() / 2,
  192.             currY - currImage->getheight() / 2, currImage);
  193.     }
  194.     char scoreText[8]; //渲染阳光值
  195.     sprintf(scoreText, "%d", sunShineVal);
  196.     outtextxy(277, 67, scoreText);
  197.     for (int i = 0; i < MAX_ZOMBIE_NUM; ++i) //渲染僵尸
  198.     {
  199.         if (zombies[i].used)
  200.         {
  201.             IMAGE* img = &imgZombies[zombies[i].frameId];
  202.              putimagePNG(zombies[i].x, zombies[i].y + 30, img);
  203.         }
  204.     }
  205.     PeaShooter* peaShooter = NULL; //渲染子弹
  206.     for (int i = 0; i < GRASS_GRID_ROW; ++i)
  207.     {
  208.         for (int j = 0; j < GRASS_GRID_COL; ++j)
  209.         {
  210.             if (plants[i][j]->type == PEA)
  211.             {
  212.                 peaShooter = (PeaShooter*)plants[i][j];
  213.                 for (int k = 0; k < MAX_BULLET_NUM; ++k) //默认只有一发子弹, 但可调整
  214.                 {
  215.                     if (peaShooter->bullet[k].used)
  216.                         putimagePNG(peaShooter->bullet[k].x, peaShooter->bullet[k].y, &peaNormal);
  217.                 }
  218.             }           
  219.         }
  220.     }
  221.     EndBatchDraw(); //结束双缓冲
  222. }
  223. /* 收集随机阳光接口 */
  224. void collectSunShine(ExMessage* msg)
  225. {
  226.     IMAGE* imgSunShine = NULL;
  227.     for (int i = 0; i < MAX_BALLS_NUM; ++i) //遍历阳光球
  228.     {
  229.         if (balls[i].used) //阳光球在使用中
  230.         {
  231.             imgSunShine = &imgSunShineBall[balls[i].frameId]; //找到对应的阳光球图片
  232.             if (msg->x > balls[i].x && msg->x < balls[i].x + imgSunShine->getwidth()
  233.                 && msg->y > balls[i].y && msg->y < balls[i].y + imgSunShine->getheight()) //判断鼠标移动的位置是否处于当前阳光球的位置
  234.             {
  235.                 PlaySound("res/audio/sunshine.wav", NULL, SND_FILENAME | SND_ASYNC); //异步播放收集阳光球音效
  236.                 balls[i].used = false;  //将阳光球状态更改为未使用 (飞跃状态, 因为 xOffset 赋值了)
  237.                 const float angle = atan((float)(balls[i].y - 0) / (float)(balls[i].x - 262)); //使用正切函数
  238.                 balls[i].xOffset = 16 * cos(angle); //计算 X 轴偏移
  239.                 balls[i].yOffset = 16 * sin(angle); //计算 Y 轴偏移
  240.             }
  241.         }
  242.     }
  243. }
  244. /* 种植植物接口, 主要释放草格子内存, 二维指针数组对应位置,指向初始化的植物 */
  245. Plant* growPlants(Plant* plant, int type)
  246. {
  247.     assert(plant);
  248.     free((Grass*)plant); //释放该位置草格子内存
  249.     if (type == PEA) //根据类型初始化 PeaShooter
  250.     {
  251.         PeaShooter* peaShooter = (PeaShooter*)calloc(1, sizeof(PeaShooter)); //calloc 函数替代 malloc, 省略 memset
  252.         assert(peaShooter);
  253.         peaShooter->shootSpeed = DEFAULT_SHOOT_TIME; //豌豆射击速度, 或者叫豌豆发射子弹的时间间隔, -1 表示可发射子弹
  254.         peaShooter->bullet[0].speed = 10; //默认只使用了第一枚子弹, 可更改
  255.         return (Plant*)peaShooter;
  256.     }
  257.     else if (type == SUNFLOWER) //根据类型初始化 SunFlower
  258.     {
  259.         SunFlower* sunFlower = (SunFlower*)calloc(1, sizeof(SunFlower));
  260.         assert(sunFlower);
  261.         sunFlower->plant.type = 1;
  262.         return (Plant*)sunFlower;
  263.     }
  264. }
  265. /* 销毁植物接口, 主要释放草格子和种植植物的内存 */
  266. void destroyPlants()
  267. {
  268.     for (int i = 0; i < GRASS_GRID_ROW; ++i)
  269.     {
  270.         for (int j = 0; j < GRASS_GRID_COL; ++j)
  271.         {
  272.             if (plants[i][j]->type == PEA)
  273.                 free ((PeaShooter*)plants[i][j]);
  274.             else if (plants[i][j]->type == SUNFLOWER)
  275.                 free ((SunFlower*)plants[i][j]);
  276.             else
  277.                 free ((Grass*)plants[i][j]);
  278.         }
  279.     }
  280.     memset(plants, 0, sizeof(plants)); //将指针全部置 NULL
  281. }
  282. /* 用户点击接口, 主要监听鼠标事件并调用相应的函数 */
  283. void userClick()
  284. {
  285.     ExMessage msg; //创建消息体
  286.     static int status = 0; //种植植物必须先选中再拖动(拖动需先左键点击再拖动)
  287.     if (peekmessage(&msg)) //该函数用于获取一个消息,并立即返回
  288.     {
  289.         collectSunShine(&msg);
  290.         if (msg.message == WM_LBUTTONDOWN) //鼠标点击
  291.         {
  292.             if (msg.x > PIC_LEFT_MARGIN &&
  293.                 msg.x < PIC_LEFT_MARGIN + PLANT_CNT * PIC_WIDTH &&
  294.                 msg.y < 96)
  295.             {
  296.                 currX = msg.x, currY = msg.y;
  297.                 currIndex = (msg.x - PIC_LEFT_MARGIN) / PIC_WIDTH;
  298.                 status = 1;
  299.             }
  300.         }
  301.         else if (msg.message == WM_MOUSEMOVE && status == 1) //鼠标拖动
  302.         {
  303.             currX = msg.x, currY = msg.y; //记录当前拖动位置
  304.         }
  305.         else if (msg.message == WM_LBUTTONUP) //鼠标抬起
  306.         {
  307.             if (msg.x >= GRASS_LEFT_MARGIN &&
  308.                 msg.x <= GRASS_LEFT_MARGIN + GRASS_GRID_COL * GRASS_GRID_WIDTH &&
  309.                 msg.y >= GRASS_TOP_MARGIN &&
  310.                 msg.y <= GRASS_TOP_MARGIN + GRASS_GRID_ROW * GRASS_GRID_HIGHT) //当植物拖到至草地位置终止, 则种植植物
  311.             {
  312.                 int x = (msg.y - GRASS_TOP_MARGIN) / GRASS_GRID_HIGHT;  //计算第几行
  313.                 int y = (msg.x - GRASS_LEFT_MARGIN) / GRASS_GRID_WIDTH; //计算第几列
  314.                 if (plants[x][y]->type < 0 && status == 1) //未点击植物或当前位置已种植过植物,则不种植植物
  315.                     plants[x][y] = growPlants(plants[x][y], currIndex); //种植植物
  316.             }
  317.             status = 0, currIndex = -1; //停止拖动当前植物
  318.         }
  319.     }
  320. }
  321. /* 创建随机阳光球接口, 主要初始化随机阳光球 */
  322. void createSunshine()
  323. {
  324.     static int sunCallCnt = 0; //延缓函数调用次数并增加些随机性
  325.     static int randSunCallCnt = 400;
  326.     if (++sunCallCnt < randSunCallCnt) return;
  327.     randSunCallCnt = 200 + rand() % 200;
  328.     sunCallCnt = 0;
  329.     for (int i = 0; i < MAX_BALLS_NUM; ++i) //从阳光池中取一个可用阳光
  330.     {
  331.         if (!balls[i].used && balls[i].xOffset == 0) //找到一个未使用的阳光, 则进行初始化
  332.         {
  333.             balls[i].x = GRASS_LEFT_MARGIN + GRASS_GRID_WIDTH   //只允许阳光掉落在草地范围内(不允许左一格)
  334.                 + (rand() % GRASS_GRID_COL) * GRASS_GRID_WIDTH; //因为左一格的位置可能在上方阳光栏图片左边
  335.             balls[i].y = GRASS_TOP_MARGIN;
  336.             balls[i].frameId = 0;
  337.             balls[i].destination = GRASS_TOP_MARGIN
  338.                 +  GRASS_GRID_HIGHT + (rand() % (3 * GRASS_GRID_HIGHT)); //目标点在中间三行
  339.             balls[i].used = true;
  340.             balls[i].timer = 0;
  341.             balls[i].xOffset = 0;
  342.             balls[i].yOffset = 0;
  343.             break;
  344.         }
  345.     }
  346. }
  347. /* 更新随机阳光球接口, 主要更新随机阳光球的图片帧和处理飞跃状态时的 X Y 轴偏移 */
  348. void updateSunshine()
  349. {
  350.     for (int i = 0; i < MAX_BALLS_NUM; ++i)
  351.     {
  352.         if (balls[i].used)
  353.         {
  354.             if (balls[i].y < balls[i].destination)
  355.             {
  356.                 balls[i].y += 2; //每次移动两个像素
  357.                 balls[i].frameId = ++balls[i].frameId % SUM_SHINE_PIC_NUM; //修改当前图片帧编号, 并在到达 SUM_SHINE_PIC_NUM 时重置图片帧为 0
  358.             }
  359.             else //当阳光下落至目标位置时, 停止移动
  360.             {
  361.                 if (balls[i].timer < MAX_TIME_INTERVAL) ++balls[i].timer;
  362.                 else balls[i].used = false;
  363.             }
  364.         }
  365.         else if (balls[i].xOffset) //阳光球处于飞跃状态
  366.         {
  367.             if (balls[i].y > 0 && balls[i].x > 262)
  368.             {
  369.                 const float angle = atan((float)(balls[i].y - 0) / (float)(balls[i].x - 262)); //不断调整阳光球的位置坐标
  370.                 balls[i].xOffset = 16 * cos(angle);
  371.                 balls[i].yOffset = 16 * sin(angle);
  372.                 balls[i].x -= balls[i].xOffset;
  373.                 balls[i].y -= balls[i].yOffset;
  374.             }
  375.             else
  376.             {
  377.                 balls[i].xOffset = 0;  //阳光球飞至计分器位置, 则将 xOffset 置 0, 且加上 25 积分
  378.                 balls[i].yOffset = 0;
  379.                 sunShineVal += 25;
  380.             }
  381.         }
  382.     }
  383. }
  384. /* 创建僵尸接口, 主要用于初始化僵尸 */
  385. void createZombie()
  386. {
  387.     static int zombieCallCnt = 0; //延缓函数调用次数并增加些随机性
  388.     static int randZombieCallCnt = 500;
  389.     if (zombieCallCnt++ < randZombieCallCnt) return;
  390.     randZombieCallCnt = 300 + rand() % 200;
  391.     zombieCallCnt = 0;
  392.     for (int i = 0; i < MAX_ZOMBIE_NUM;  ++i) //找一个未在界面的僵尸初始化
  393.     {
  394.         if (!zombies[i].used)
  395.         {
  396.             zombies[i].row = rand() % GRASS_GRID_ROW; //僵尸出现在第几行(从 0 开始)
  397.             zombies[i].x = WIN_WIDTH;
  398.             zombies[i].y = zombies[i].row * GRASS_GRID_HIGHT; //出现在草地的任意一格上
  399.             zombies[i].frameId = 0;
  400.             zombies[i].speed = 1;  //僵尸的移动速度
  401.             zombies[i].used = true;
  402.             break; //结束循环
  403.         }
  404.     }
  405. }
  406. /* 更新僵尸接口, 主要用于处理僵尸图片帧, 实现僵尸行走 */
  407. void updateZombie()
  408. {
  409.     static int CallCnt = 0; //延缓函数调用次数
  410.     if (++CallCnt < 3) return;
  411.     CallCnt = 0;
  412.     for (int i = 0; i < MAX_ZOMBIE_NUM; ++i)
  413.     {
  414.         if (zombies[i].used)
  415.         {
  416.             zombies[i].x -= zombies[i].speed; //僵尸行走
  417.             zombies[i].frameId = ++zombies[i].frameId % MAX_ZOMBIE_PIC_NUM; //僵尸更换图片帧
  418.             if (zombies[i].x < 170) //目前先这样写待优化
  419.             {
  420.                 printf("GAME OVER !");
  421.                 MessageBox(NULL, "over", "over", 0);
  422.                 exit(0);
  423.             }
  424.         }
  425.     }
  426. }
  427. /* 更新植物图片帧接口, 主要用于实现植物摇摆 */
  428. void updatePlantsPic()
  429. {
  430.     for (int i = 0; i < GRASS_GRID_ROW; ++i) //遍历二维指针数组
  431.     {
  432.         for (int j = 0; j < GRASS_GRID_COL; ++j)
  433.         {
  434.             if (plants[i][j]->type >= 0 && //找到非草地的植物
  435.                 imgPlant[plants[i][j]->type][++plants[i][j]->frameId] == NULL) //将植物图片增加一, 判断是否到达图片帧末尾            
  436.                     plants[i][j]->frameId = 0; //重置图片帧为零
  437.         }
  438.     }
  439. }
  440. /* 豌豆射手发射子弹接口 */
  441. void shoot()
  442. {
  443.     PeaShooter* peaShooter = NULL;
  444.     int row = 0, peaX = 0, peaY = 0, pic_width = 0;
  445.     for (int i = 0; i < MAX_ZOMBIE_NUM; ++i) //遍历是否存在僵尸
  446.     {
  447.         if (zombies[i].used)
  448.         {
  449.             row = zombies[i].row;
  450.             for (int j = 0; j < GRASS_GRID_COL; ++j) //遍历当前行是否存在豌豆
  451.             {
  452.                 if (plants[row][j]->type == PEA)
  453.                 {
  454.                     peaShooter = (PeaShooter*)plants[row][j];
  455.                     if (peaShooter->shootSpeed++ == DEFAULT_SHOOT_TIME) //发射时机
  456.                     {
  457.                         for (int k = 0; k < MAX_BULLET_NUM; ++k) //从子弹夹里取一颗未使用的子弹(默认一颗)
  458.                         {
  459.                             if (!peaShooter->bullet[k].used) //该子弹未在使用中
  460.                             {
  461.                                 peaX = GRASS_LEFT_MARGIN + j * GRASS_GRID_WIDTH + 5; //之前豌豆的 X Y 坐标
  462.                                 peaY = GRASS_TOP_MARGIN + row * GRASS_GRID_HIGHT + 10;
  463.                                 pic_width = imgPlant[0][0]->getwidth();
  464.                                 peaShooter->bullet[k].x = peaX + pic_width; //初始化子弹
  465.                                 peaShooter->bullet[k].y = peaY + 5;
  466.                                 peaShooter->bullet[k].used = true;
  467.                                 break; //结束当前循环
  468.                             }
  469.                         }
  470.                     }
  471.                     else if (peaShooter->shootSpeed > MAX_TIME_INTERVAL) //不到发射时机
  472.                         peaShooter->shootSpeed = DEFAULT_SHOOT_TIME; //则将 timer 计时器增加 (默认一百帧)
  473.                 }
  474.             }
  475.         }
  476.     }
  477. }
  478. /* 更新子弹图片帧接口 */
  479. void updateBullets()
  480. {
  481.     PeaShooter* peaShooter = NULL;
  482.     for (int i = 0; i < GRASS_GRID_ROW; ++i)  //遍历植物二维指针数组
  483.     {
  484.         for (int j = 0; j < GRASS_GRID_COL; ++j)
  485.         {
  486.             if (plants[i][j]->type == PEA) //找到其中是豌豆的位置
  487.             {
  488.                 peaShooter = (PeaShooter*)plants[i][j];
  489.                 for (int k = 0; k < MAX_BULLET_NUM; ++k)
  490.                 {
  491.                     if (peaShooter->bullet[k].used) //找到在使用中的子弹
  492.                     {
  493.                         peaShooter->bullet[k].x += peaShooter->bullet[k].speed; //移动子弹位置
  494.                         if (peaShooter->bullet[k].x >= WIN_WIDTH) //如果到达窗口最右端
  495.                             peaShooter->bullet[k].used = false; //将子弹重置为未使用状态
  496.                     }
  497.                 }
  498.             }
  499.         }
  500.     }
  501. }
  502. /* 更新游戏属性的接口 */
  503. void updateGame()
  504. {
  505.     updatePlantsPic();
  506.     createSunshine();
  507.     updateSunshine();
  508.     createZombie();
  509.     updateZombie();
  510.     shoot();
  511.     updateBullets();
  512. }
  513. /* 游戏开始前的菜单界面 */
  514. void startUI()
  515. {
  516.     IMAGE imageBg, imgMenu1, imgMenu2;
  517.     loadimage(&imageBg, "res/menu.png");
  518.     loadimage(&imgMenu1, "res/menu1.png");
  519.     loadimage(&imgMenu2, "res/menu2.png");
  520.     bool mouseStatus = false; //0 表示鼠标未移动至开始游戏位置
  521.     while (1)
  522.     {
  523.         BeginBatchDraw(); //双缓冲解决闪屏
  524.         putimage(0, 0, &imageBg);
  525.         putimagePNG(UI_LEFT_MARGIN, UI_TOP_MARGIN, mouseStatus ? &imgMenu2 : &imgMenu1); //根据鼠标是否移动至游戏开始位置, 显示不同的图片
  526.         ExMessage msg;
  527.         if (peekmessage(&msg)) //监听鼠标事件
  528.         {
  529.             if (msg.x > UI_LEFT_MARGIN && msg.x < UI_LEFT_MARGIN + UI_WIDTH
  530.                 && msg.y > UI_TOP_MARGIN && msg.y < UI_TOP_MARGIN + UI_HIGHT) //当鼠标移动至开始游戏位置, 界面高亮
  531.             {
  532.                 putimagePNG(UI_LEFT_MARGIN, UI_TOP_MARGIN, &imgMenu2);
  533.                 mouseStatus = true; //表示鼠标移动至开始游戏位置, 如果一直不移动鼠标则一直高亮
  534.                 if (msg.message == WM_LBUTTONDOWN) //当鼠标点击时, 进入游戏
  535.                     return; //结束函数
  536.             }
  537.             else mouseStatus = false; //当鼠标未移动至开始游戏位置, 界面不高亮
  538.         }
  539.         EndBatchDraw();
  540.     }
  541. }
  542. /* 主函数 */
  543. int main()
  544. {
  545.     gameInit(); //不能把 startUI 放在 gameInit 前, gameInit 包含了创建游戏图形窗口
  546.     startUI();
  547.     updateWindow(); //窗口视图展示
  548.     int timer = 0; //用以计时 20 毫秒更新一次
  549.     while (1)
  550.     {
  551.         userClick(); //监听窗口鼠标事件
  552.         timer += getDelay();
  553.         if (timer > 20)
  554.         {
  555.             updateWindow(); //更新窗口视图
  556.             updateGame(); //更新游戏动画帧
  557.             timer = 0;
  558.         }
  559.     }
  560.     destroyPlants(); //释放内存
  561.     system("pause");
  562.     return 0;
  563. }
复制代码
效果展示
僵尸在出如今某条门路上,且存在豌豆射手,豌豆射手就会发射子弹,还没实现子弹和僵尸碰撞功能

小记录
不能将 X 范例的值分配到 X范例的实体问题
imgPlant[j] = new IMAGE; 该行是 easyx 内部在 IMAGE 构造函数里加了一些初始化内容,以是没办法用 malloc 替代
C++ 中的 new 和 delete,通过父类指针开释子类对象,是通过虚函数表实现的,在照旧用上述 C 的方式比较好(雷同于用 C 实现面对对象代码)
不能把判断条件写入循环条件内部,除非是可以用以竣事循环的条件
十一 实现子弹和僵尸碰撞

子弹布局体新增成员变量
  1. /* 子弹结构体 */
  2. typedef struct Bullet {
  3.     int x;              //当前 X 轴坐标
  4.     int y;              //当前 Y 轴坐标
  5.     int speed;          //子弹移动的速度
  6.     int frameIndex;     //帧序号
  7.     bool blast;         //子弹是否爆炸
  8.     bool used;          //是否在使用
  9. } Bullet;
复制代码
游戏初始化接口 gameInit,加载子弹爆炸图片至内存
  1. loadimage(&peaExplode[PEA_EXPLODE_PIC_NUM - 1], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png"); //加载豌豆子弹爆炸图片
  2. for (int i = 1; i < PEA_EXPLODE_PIC_NUM; ++i)
  3. {
  4.         loadimage(&peaExplode[i - 1], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png",
  5.             peaExplode[PEA_EXPLODE_PIC_NUM - 1].getwidth() * 0.2 * i,
  6.             peaExplode[PEA_EXPLODE_PIC_NUM - 1].getheight() * 0.2 * i, true); //加载豌豆子弹爆炸缩小版图片
  7. }
复制代码
游戏更新窗口接口 updateWindow,渲染子弹爆炸图片至输出窗口
  1. PeaShooter* peaShooter = NULL; //渲染子弹
  2. for (int i = 0; i < GRASS_GRID_ROW; ++i)
  3. {
  4.     for (int j = 0; j < GRASS_GRID_COL; ++j)
  5.     {
  6.         if (plants[i][j]->type == PEA)
  7.         {
  8.             peaShooter = (PeaShooter*)plants[i][j];
  9.             for (int k = 0; k < MAX_BULLET_NUM; ++k) //默认只有一发子弹, 但可调整
  10.             {
  11.                 if (peaShooter->bullet[k].used)
  12.                 {
  13.                     if (peaShooter->bullet[k].blast)
  14.                         putimagePNG(peaShooter->bullet[k].x, peaShooter->bullet[k].y,
  15.                             &peaExplode[peaShooter->bullet[k].frameIndex]); //渲染子弹爆炸图片
  16.                     else putimagePNG(peaShooter->bullet[k].x, peaShooter->bullet[k].y, &peaNormal); //渲染子弹图片
  17.                 }
  18.             }
  19.         }           
  20.     }
  21. }
复制代码
豌豆射手发射子弹接口 shoot 中初始化新增成员
  1. void shoot()
  2. {
  3.     PeaShooter* peaShooter = NULL;
  4.     int row = 0, peaX = 0, peaY = 0, pic_width = 0;
  5.     for (int i = 0; i < MAX_ZOMBIE_NUM; ++i) //遍历是否存在僵尸
  6.     {
  7.         if (zombies[i].used)
  8.         {
  9.             row = zombies[i].row;
  10.             for (int j = 0; j < GRASS_GRID_COL; ++j) //遍历当前行是否存在豌豆
  11.             {
  12.                 if (plants[row][j]->type == PEA)
  13.                 {
  14.                     peaShooter = (PeaShooter*)plants[row][j];
  15.                     if (peaShooter->shootSpeed++ == DEFAULT_SHOOT_TIME) //发射时机
  16.                     {
  17.                         for (int k = 0; k < MAX_BULLET_NUM; ++k) //从子弹夹里取一颗未使用的子弹(默认一颗)
  18.                         {
  19.                             if (!peaShooter->bullet[k].used) //该子弹未在使用中
  20.                             {
  21.                                 peaX = GRASS_LEFT_MARGIN + j * GRASS_GRID_WIDTH + 5; //之前豌豆的 X Y 坐标
  22.                                 peaY = GRASS_TOP_MARGIN + row * GRASS_GRID_HIGHT + 10;
  23.                                 pic_width = imgPlant[0][0]->getwidth();
  24.                                 
  25.                                 peaShooter->bullet[k].x = peaX + pic_width; //初始化子弹
  26.                                 peaShooter->bullet[k].y = peaY + 5;
  27.                                 peaShooter->shootSpeed = DEFAULT_SHOOT_TIME; //豌豆射击速度, 或者叫豌豆发射子弹的时间间隔, -1 表示可发射子弹
  28.                                 peaShooter->bullet[k].speed = 10; //默认只使用了第一枚子弹, 可更改
  29.                                 peaShooter->bullet[k].frameIndex = 0;
  30.                                 peaShooter->bullet[k].blast = false;
  31.                                 peaShooter->bullet[k].used = true;
  32.                                 peaShooter->bullet[k].blast = false;
  33.                                 break; //结束当前循环
  34.                             }
  35.                         }
  36.                     }
  37.                     else if (peaShooter->shootSpeed > MAX_TIME_INTERVAL) //不到发射时机
  38.                         peaShooter->shootSpeed = DEFAULT_SHOOT_TIME; //则将 timer 计时器增加 (默认一百帧)
  39.                 }
  40.             }
  41.         }
  42.     }
  43. }
复制代码
updateBullets 更新子弹图片帧接口
  1. void updateBullets()
  2. {
  3.     PeaShooter* peaShooter = NULL;
  4.     for (int i = 0; i < GRASS_GRID_ROW; ++i)  //遍历植物二维指针数组
  5.     {
  6.         for (int j = 0; j < GRASS_GRID_COL; ++j)
  7.         {
  8.             if (plants[i][j]->type == PEA) //找到其中是豌豆的位置
  9.             {
  10.                 peaShooter = (PeaShooter*)plants[i][j];
  11.                 for (int k = 0; k < MAX_BULLET_NUM; ++k)
  12.                 {
  13.                     if (peaShooter->bullet[k].used) //找到在使用中的子弹
  14.                     {
  15.                         peaShooter->bullet[k].x += peaShooter->bullet[k].speed; //移动子弹位置
  16.                         if (peaShooter->bullet[k].x >= WIN_WIDTH) //如果到达窗口最右端
  17.                             peaShooter->bullet[k].used = false; //将子弹重置为未使用状态
  18.                     }
  19.                     if (peaShooter->bullet[k].blast && //找到爆炸的子弹
  20.                         ++peaShooter->bullet[k].frameIndex >= PEA_EXPLODE_PIC_NUM) //子弹爆炸完成
  21.                         peaShooter->bullet[k].used = false; //重置子弹状态
  22.                         
  23.                 }
  24.             }
  25.         }
  26.     }
  27. }
复制代码
重点 僵尸和子弹碰撞检测接口 collsionCheck
  1. void collsionCheck()
  2. {
  3.     PeaShooter* peaShooter = NULL;
  4.     int row = 0, peaX = 0, pic_width = 0;
  5.     for (int i = 0; i < MAX_ZOMBIE_NUM; ++i) //遍历是否存在僵尸
  6.     {
  7.         if (zombies[i].used && !zombies[i].isDead) //僵尸正在使用中, 且存活
  8.         {
  9.             row = zombies[i].row;
  10.             for (int j = 0; j < GRASS_GRID_COL; ++j) //遍历当前行是否存在豌豆
  11.             {
  12.                 if (plants[row][j]->type == PEA)
  13.                 {
  14.                     peaShooter = (PeaShooter*)plants[row][j]; //找到对应的豌豆   
  15.                     for (int k = 0; k < MAX_BULLET_NUM; ++k) //从子弹夹找到一颗在使用的子弹(默认一颗)
  16.                     {
  17.                         if (peaShooter->bullet[k].used && !peaShooter->bullet[k].blast) //该子弹在使用中
  18.                         {
  19.                             peaX = peaShooter->bullet[k].x;
  20.                             if (peaX > (zombies[i].x + 80) && peaX < (zombies[i].x + 110)) //子弹和僵尸碰撞
  21.                             {
  22.                                 zombies[i].blood -= 10; //扣除僵尸血量
  23.                                 peaShooter->bullet[k].blast = true; //子弹开始爆炸
  24.                                 peaShooter->bullet[k].speed = 0; //将子弹速度降为 0
  25.                                 if (zombies[i].blood <= 0)
  26.                                 {
  27.                                     zombies[i].isDead = true; //僵尸死亡
  28.                                     zombies[i].speed = 0;     //重置僵尸速度
  29.                                     zombies[i].frameId = 0;   //此时更换为僵尸死亡图片帧
  30.                                 }
  31.                             }
  32.                             break; //结束当前循环
  33.                         }
  34.                     }
  35.                 }
  36.             }
  37.         }
  38.     }
  39. }
复制代码
updateGame 中调用 collsionCheck
  1. /* 更新游戏属性的接口 */
  2. void updateGame()
  3. {
  4.     updatePlantsPic();
  5.     createSunshine();
  6.     updateSunshine();
  7.     createZombie();
  8.     updateZombie();
  9.     shoot();
  10.     updateBullets();
  11.     collsionCheck();
  12. }
复制代码
效果展示

十二 实现僵尸殒命

新增僵尸殒命相干变量和图片
  1. /* 僵尸相关结构和变量 */
  2. #define MAX_ZOMBIE_NUM 10
  3. #define MAX_ZOMBIE_DEAD_PIC_NUM 10
  4. #define MAX_ZOMBIE_PIC_NUM 22
  5. typedef struct Zombie {
  6.     int x;              //当前 X 轴坐标
  7.     int y;              //当前 Y 轴坐标
  8.     int frameId;        //当前图片帧编号
  9.     int speed;          //僵尸移动的速度
  10.     int row;            //僵尸所在行
  11.     int blood;          //默认僵尸血条为 100
  12.     bool isDead;        //僵尸是否死亡
  13.     bool used;          //是否在使用
  14. } Zombie;
  15. Zombie zombies[MAX_ZOMBIE_NUM];
  16. IMAGE imgZombies[MAX_ZOMBIE_PIC_NUM];
  17. IMAGE imgDeadZombies[MAX_ZOMBIE_DEAD_PIC_NUM];
复制代码
游戏初始化接口 gameInit,加载僵尸殒命图片至内存
  1. for (int i = 0; i < MAX_ZOMBIE_DEAD_PIC_NUM; ++i) //加载僵尸死亡图片
  2. {
  3.     sprintf(name, "res/zm_dead/%d.png", i + 1);
  4.     loadimage(&imgDeadZombies[i], name);
  5. }
复制代码
游戏更新窗口接口 updateWindow,渲染僵尸殒命图片至输出窗口
  1. for (int i = 0; i < MAX_ZOMBIE_NUM; ++i) //渲染僵尸
  2. {
  3.     if (zombies[i].used)
  4.     {
  5.         if (zombies[i].isDead) putimagePNG(zombies[i].x, zombies[i].y + 30, &imgDeadZombies[zombies[i].frameId]);
  6.         else putimagePNG(zombies[i].x, zombies[i].y + 30, &imgZombies[zombies[i].frameId]);
  7.     }
  8. }
复制代码
创建僵尸接口,初始化僵尸新增成员
  1. void createZombie()
  2. {
  3.     static int zombieCallCnt = 0; //延缓函数调用次数并增加些随机性
  4.     static int randZombieCallCnt = 500;
  5.     if (zombieCallCnt++ < randZombieCallCnt) return;
  6.     randZombieCallCnt = 300 + rand() % 200;
  7.     zombieCallCnt = 0;
  8.     for (int i = 0; i < MAX_ZOMBIE_NUM;  ++i) //找一个未在界面的僵尸初始化
  9.     {
  10.         if (!zombies[i].used)
  11.         {
  12.             zombies[i].row = rand() % GRASS_GRID_ROW; //僵尸出现在第几行(从 0 开始)
  13.             zombies[i].x = WIN_WIDTH;
  14.             zombies[i].y = zombies[i].row * GRASS_GRID_HIGHT; //出现在草地的任意一格上
  15.             zombies[i].frameId = 0;
  16.             zombies[i].speed = 1;  //僵尸的移动速度
  17.             zombies[i].blood = 100; //默认僵尸血条为 100
  18.             zombies[i].isDead = false; //僵尸存活
  19.             zombies[i].used = true;
  20.             break; //结束循环
  21.         }
  22.     }
  23. }
复制代码
更新僵尸接口,处置惩罚僵尸殒命图片帧
  1. void updateZombie()
  2. {
  3.     static int CallCnt = 0; //延缓函数调用次数
  4.     if (++CallCnt < 3) return;
  5.     CallCnt = 0;
  6.     for (int i = 0; i < MAX_ZOMBIE_NUM; ++i)
  7.     {
  8.         if (zombies[i].used)
  9.         {
  10.             if (zombies[i].isDead)
  11.             {
  12.                 if (++zombies[i].frameId >= MAX_ZOMBIE_DEAD_PIC_NUM) //僵尸死亡则更换死亡帧
  13.                     zombies[i].used = false; //重置僵尸状态
  14.             }
  15.             else
  16.             {
  17.                 zombies[i].x -= zombies[i].speed; //僵尸行走
  18.                 zombies[i].frameId = ++zombies[i].frameId % MAX_ZOMBIE_PIC_NUM; //僵尸更换图片帧
  19.             }
  20.             if (zombies[i].x < 170) //目前先这样写待优化
  21.             {
  22.                 printf("GAME OVER !");
  23.                 MessageBox(NULL, "over", "over", 0);
  24.                 exit(0);
  25.             }
  26.         }
  27.     }
  28. }
复制代码
僵尸和子弹碰撞检测接口 collsionCheck 同上,更新僵尸血量和状态
效果展示


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表