当前位置:   article > 正文

C语言课设:植物大战僵尸_easyx做植物大战僵尸

easyx做植物大战僵尸

文章是作者C语言项目的开发日志,主要介绍与该项目有关的函数,代码实现以及在开发过程中遇到的问题。IDE:VS2022

 资源包以及工具文件已上传,需要的朋友可自取。

C语言:植物大战僵尸库函数解析-CSDN博客

目录

 资源包以及工具文件已上传,需要的朋友可自取。

1.EasyX图形库下载(确保在开始之前已经安装)

1.2进入官网:EasyX Graphics Library for C++

1.3安装后打开文件,点下一步

1.4将EasyX安装到正在使用的编译器中,带点击安装即可

1.5为了确保安装成功,需要打开编译器创建新项目输入以下代码

2.源文件main.cpp

2.1判断文件能否打开:fileExist()

2.2游戏初始化:gameinit()

2.3渲染僵尸:drawzm()

2.4渲染阳光:drawsunshine()

2.5渲染卡牌:drawCard()

2.6渲染种植后的植物:drawPlantMap()

2.7渲染鼠标拖动的植物:drawPlantMove()

2.8渲染豌豆子弹:drawPea()

2.9显示左上角阳光数:outsunshine()

2.10渲染:updatewindow()

2.11收集阳光:collectsunshine()

2.12鼠标操作:userclick()

2.13创建阳光:creatsunshine()

2.14更新阳光参数:updatesunshine()

2.15创建僵尸:creatzomb()

2.16更新僵尸参数:updatezomb()

2.17发射豌豆:shoot()

2.18更新子弹参数:updatebullets()

2.19豌豆->僵尸:checkbullet()

2.20僵尸->植物:checkeat()

2.21碰撞检测:collisioncheck()

2.22更新植物参数:updatePlant()

2.23更改参数:updategame()

2.24起始菜单:startUI()

2.25画面巡视:viewScene()

2.26工具栏下降:barsDown()

2.27判定游戏结束:checkOver()

2.28主函数

3.头文件tools.h(工具文件)

4.源文件tools.cpp(工具文件)

5.源文件vector2.cpp(工具文件:实现贝塞尔曲线)

6.头文件vector2.h(工具文件:实现贝塞尔曲线)


1.EasyX图形库下载(确保在开始之前已经安装)

1.2进入官网:EasyX Graphics Library for C++

1.3安装后打开文件,点下一步

1.4将EasyX安装到正在使用的编译器中,带点击安装即可

注意:easyx只支持C++,所以有些编译器无法下载

1.5为了确保安装成功,需要打开编译器创建新项目输入以下代码

  1. #include <graphics.h> // 引用 EasyX 绘图库头文件
  2. #include <conio.h>
  3. int main(){
  4. initgraph(640, 480);
  5. circle(320, 240, 100);
  6. _getch();
  7. closegraph();
  8. return 0;
  9. }

运行结果证明安装成功

2.源文件main.cpp

  1. #include <stdio.h>
  2. #include <graphics.h>//easyx图形库头文件
  3. #include <math.h>
  4. #include "tools.h"//图片透明函数接口
  5. #include <time.h>
  6. #include <mmsystem.h>//播放音乐头文件
  7. #include "vector2.h"//实现产阳光抛物线
  8. #define WIN_WIDTH 900
  9. #define WIN_HIGHT 600
  10. #define ZM_MAX 10
  11. #define _CRT_SECURE_NO_WARNINGS
  12. #pragma warning(disable:4996)//忽略不安全函数
  13. #pragma comment(lib,"winmm.lib")//播放音乐并加载库
  14. //植物类型
  15. enum { PEA, SUN_FLOWER, PLANT_COUNTS };//方便添加植物卡牌
  16. //阳光的状态
  17. enum { SUNSHINE_DOWN, SUNSHINE_GROUND, SUNSHINE_COLLECT, SUNSHINE_PRODUCT };//下降,落地,收集,生产
  18. //游戏状态
  19. enum{GOING,WIN,FAIL};//进行,胜利,失败
  20. IMAGE imgBg;//背景图片
  21. IMAGE imgBar;//工具栏
  22. IMAGE imgCard[PLANT_COUNTS];//植物卡牌
  23. IMAGE* imgPLANT[PLANT_COUNTS][20];//实现植物晃动的二维指针数组,20是晃动牌组
  24. IMAGE imgsunshine[29];//存放阳光球图片帧
  25. IMAGE imgzombie[22];//僵尸行走的图片帧
  26. IMAGE imgBulletNormal;//子弹正常形态
  27. IMAGE imgballblast[4];//爆炸图片帧
  28. IMAGE imgzmDead[20];//僵尸死亡帧
  29. IMAGE imgzmEat[21];//僵尸吃植物帧
  30. IMAGE imgzmStand[11];//僵尸站立帧
  31. int curX, curY;//表示选中植物卡牌的位置
  32. int curPLANT;//表示选中植物的类型,0:没有选中,1:选中第一种植物
  33. int sunshine = 50;//初始阳光值
  34. int killcount;//已经杀掉的僵尸个数
  35. int zmCount1;//已经出现的僵尸个数
  36. int gameStatus;//游戏状态
  37. struct PLANT//植物相关的结构体
  38. {
  39. int type;//一块草坪种植的植物类型,0:没有种植,1:种植第一种植物
  40. int frameindex;//表示植物晃动时图片的序号
  41. int shootTimer;//发射计数器
  42. bool catched;//是否被僵尸捕获
  43. int deadtime;//死亡计数器
  44. int timer;//生产阳光的计时器
  45. int x, y;//植物的位置
  46. };
  47. struct PLANT map[3][9];//草坪有39
  48. struct sunshineBALL //阳光球结构体
  49. {
  50. int x, y;//阳光掉落时的坐标
  51. int frameINDEX;//阳光旋转的图片帧
  52. int destY;//阳光下落的最终坐标
  53. bool used;//判断阳光是否在使用
  54. int timer;//阳光在场上停留的时间
  55. float xoff;//阳光飞跃时x轴偏移量
  56. float yoff;//阳光飞跃时y轴偏移量
  57. float t;//贝塞尔曲线的时间点
  58. vector2 p1, p2, p3, p4;//贝塞尔曲线的起始点(p1,p4)和控制点(p2,p3)
  59. vector2 pCur;//当前时刻阳光球的位置
  60. float speed;
  61. int status;//当前状态
  62. };
  63. struct sunshineBALL balls[10];//10个阳光球
  64. int ballMax = sizeof(balls) / sizeof(balls[0]);//阳光数量
  65. struct zomb//僵尸结构体
  66. {
  67. int x, y;//僵尸的坐标
  68. int frameIndex;//图片帧
  69. bool used;//使用情况
  70. int speed;//行走速度
  71. int zmrow;//僵尸在第(zmrow+1)行
  72. int blood;//僵尸的血量
  73. bool dead;//死亡
  74. bool eating;//正在吃植物的状态
  75. };
  76. struct zomb zms[10];//10个僵尸
  77. struct bullet//豌豆射手的子弹
  78. {
  79. int x, y;
  80. int pearow;//子弹所处的行
  81. int speed;//速度
  82. bool used;//使用情况
  83. bool blast;//子弹爆炸情况
  84. int frameindex;//爆炸帧
  85. };
  86. struct bullet bullets[30];//30个子弹
  87. bool fileExist(char* name)//判断文件能否打开
  88. {
  89. FILE* fp = fopen(name, "r");//以“读”的形式打开文件
  90. //fopen函数能打开文件返回文件路径,打不开文件返回NULL,所以需要定义指针变量
  91. if (fp == NULL)
  92. return false;//表示文件打不开
  93. else
  94. fclose(fp);//关闭文件
  95. return true;//表示文件能打开
  96. }
  97. void gameInit()//初始化函数
  98. {
  99. //加载背景图片
  100. loadimage(&imgBg, "res/bg.jpg");
  101. loadimage(&imgBar, "res/bar5.png");
  102. killcount = 0;
  103. zmCount1 = 0;
  104. gameStatus = GOING;
  105. //对数组整体初始化
  106. memset(imgPLANT, 0, sizeof(imgPLANT));
  107. memset(map, 0, sizeof(map));
  108. memset(balls, 0, sizeof(balls));
  109. memset(zms, 0, sizeof(zms));
  110. memset(bullets, 0, sizeof(bullets));
  111. char plant_name[64];
  112. for (int i = 0; i < PLANT_COUNTS; i++)//初始化植物卡牌
  113. {
  114. sprintf_s(plant_name, sizeof(plant_name), "res/Cards/card_%d.png", i + 1);
  115. loadimage(&imgCard[i], plant_name);
  116. for (int j = 0; j < 20; j++)
  117. {
  118. //将植物晃动的图片格式化到数组plant_name中
  119. sprintf_s(plant_name, sizeof(plant_name), "res/zhiwu/%d/%d.png", i, j + 1);
  120. if (fileExist(plant_name))//判断文件是否存在
  121. {
  122. imgPLANT[i][j] = new IMAGE;//分配新的内存
  123. loadimage(imgPLANT[i][j], plant_name);//将图片加载到实现晃动的数组里
  124. }
  125. else//打不开跳出循环
  126. break;
  127. }
  128. }
  129. curPLANT = 0;//初始化
  130. for (int i = 0; i < 29; i++)//初始化阳光球
  131. {
  132. sprintf_s(plant_name, sizeof(plant_name), "res/sunshine/%d.png", i + 1);
  133. loadimage(&imgsunshine[i], plant_name);
  134. }
  135. srand(time(NULL));//配置随机种子
  136. initgraph(WIN_WIDTH, WIN_HIGHT, 1);//创建游戏窗口,显示图片
  137. LOGFONT f;//设置显示阳光的字体
  138. gettextstyle(&f);//获取字体的文本格式
  139. f.lfHeight = 30;//字体高度30
  140. f.lfWeight = 15;//设置字体的粗细
  141. strcpy(f.lfFaceName, "Segoe UI Black");//设置字体格式
  142. f.lfQuality = ANTIALIASED_QUALITY;//抗锯齿
  143. settextstyle(&f);
  144. setbkmode(TRANSPARENT);//设置字体背景透明
  145. setcolor(BLACK);//设置字体颜色
  146. for (int i = 0; i < 22; i++)//初始化僵尸
  147. {
  148. sprintf_s(plant_name, sizeof(plant_name), "res/zm/%d.png", i + 1);
  149. loadimage(&imgzombie[i], plant_name);
  150. }
  151. //初始化豌豆normal
  152. loadimage(&imgBulletNormal, "res/bullets/bullet_normal.png");
  153. //初始化豌豆blast
  154. //爆炸的实质是一张爆炸帧图片放大四次
  155. loadimage(&imgballblast[3], "res/bullets/bullet_blast.png");
  156. for (int i = 0; i < 3; i++)//放大次数(可修改)
  157. {
  158. float k = (i + 1) * 0.2;//0.2是放大倍数(可修改)
  159. loadimage(&imgballblast[i], "res/bullets/bullet_blast.png",
  160. imgballblast[3].getwidth() * k, imgballblast[3].getheight() * k, true);
  161. //true表示按照如上的大小放大该图片,false相反
  162. }
  163. //初始化僵尸dead
  164. for (int i = 0; i < 20; i++)
  165. {
  166. sprintf_s(plant_name, sizeof(plant_name), "res/zm_dead/%d.png", i + 1);
  167. loadimage(&imgzmDead[i], plant_name);
  168. }
  169. //初始化僵尸eat
  170. for (int i = 0; i < 21; i++)
  171. {
  172. sprintf_s(plant_name, sizeof(plant_name), "res/zm_eat/%d.png", i + 1);
  173. loadimage(&imgzmEat[i], plant_name);
  174. }
  175. //初始化僵尸stand
  176. for (int i = 0; i < 11; i++)
  177. {
  178. sprintf_s(plant_name, sizeof(plant_name), "res/zm_stand/%d.png", i + 1);
  179. loadimage(&imgzmStand[i], plant_name);
  180. }
  181. }
  182. void drawzm()//渲染僵尸
  183. {
  184. int zmMax = sizeof(zms) / sizeof(zms[0]);
  185. for (int i = 0; i < zmMax; i++)
  186. {
  187. if (zms[i].used)
  188. {
  189. //根据僵尸的情况而调用不同的数组
  190. IMAGE* img3 = NULL;
  191. if (zms[i].dead)
  192. img3 = imgzmDead;
  193. else if (zms[i].eating)
  194. img3 = imgzmEat;
  195. else
  196. img3 = imgzombie;
  197. img3 += zms[i].frameIndex;
  198. putimagePNG(zms[i].x, zms[i].y - img3->getheight(), img3);
  199. }
  200. }
  201. }
  202. void drawsunshine()//渲染阳光
  203. {
  204. for (int i = 0; i < ballMax; i++)
  205. {
  206. //if (balls[i].used || balls[i].xoff)
  207. if(balls[i].used)
  208. {
  209. IMAGE* img2 = &imgsunshine[balls[i].frameINDEX];
  210. //putimagePNG(balls[i].x, balls[i].y, img2);
  211. putimagePNG(balls[i].pCur.x, balls[i].pCur.y, img2);
  212. }
  213. }
  214. }
  215. void drawCard()//渲染卡牌
  216. {
  217. for (int i = 0; i < PLANT_COUNTS; i++)//加载植物卡牌
  218. {
  219. int x = 338 + i * 65;//将植物加载到卡槽里
  220. int y = 6;
  221. putimage(x, y, &imgCard[i]);
  222. }
  223. }
  224. void drawPlantMap()//渲染种植后的植物
  225. {
  226. for (int i = 0; i < 3; i++)//控制行,渲染种植后的植物
  227. {
  228. for (int j = 0; j < 9; j++)//控制列
  229. {
  230. if (map[i][j].type > 0)
  231. {
  232. //int x = 256 + j * 81;//植物种植的横坐标
  233. //int y = 179 + i * 102 + 14;//植物种植的纵坐标,位置可自行调整
  234. putimagePNG(map[i][j].x, map[i][j].y, imgPLANT[map[i][j].type - 1][map[i][j].frameindex]);
  235. }
  236. }
  237. }
  238. }
  239. void drawPlantMove()//渲染鼠标拖动的植物
  240. {
  241. if (curPLANT)//渲染拖动时的植物模型
  242. {
  243. IMAGE* img1 = imgPLANT[curPLANT - 1][0];
  244. //实现无背景贴图,让鼠标位于植物的正中间
  245. putimagePNG(curX - img1->getwidth() / 2, curY - img1->getheight() / 2, img1);//取植物晃动的第一张图片
  246. }
  247. }
  248. void drawPea()渲染豌豆
  249. {
  250. int bulletMax = sizeof(bullets) / sizeof(bullets[0]);
  251. for (int i = 0; i < bulletMax; i++)
  252. {
  253. if (bullets[i].used)
  254. {
  255. if (bullets[i].blast)//爆炸
  256. {
  257. IMAGE* img5 = &imgballblast[bullets[i].frameindex];
  258. putimagePNG(bullets[i].x, bullets[i].y, img5);
  259. }
  260. else//正常
  261. {
  262. putimagePNG(bullets[i].x, bullets[i].y, &imgBulletNormal);
  263. }
  264. }
  265. }
  266. }
  267. void outsunshine()//显示左上角阳光数
  268. {
  269. char scoreText[8];
  270. sprintf_s(scoreText, sizeof(scoreText), "%d", sunshine);//将阳光格式化为字符串并加载进数组
  271. outtextxy(279, 67, scoreText);//在指定位置输出字符串
  272. }
  273. void updateWindow()//渲染显示图片
  274. {
  275. BeginBatchDraw();//开始缓冲
  276. putimage(-112, 0, &imgBg);//从最左上角显示草坪
  277. putimagePNG(250, 0, &imgBar);//植物栏
  278. drawCard();//渲染卡牌
  279. drawPlantMap();//渲染种植后的植物
  280. drawPlantMove();//渲染拖动时的植物
  281. drawsunshine();//渲染阳光
  282. drawzm();//渲染僵尸
  283. drawPea();//渲染豌豆
  284. outsunshine();//显示左上角阳光数
  285. EndBatchDraw();//结束双缓冲
  286. }
  287. void collectsunshine(ExMessage* msg)//实现收集阳光
  288. {
  289. int width = imgsunshine[0].getwidth();//获取阳光球的宽
  290. int hight = imgsunshine[0].getheight();//获取阳光球的高
  291. for (int i = 0; i < ballMax; i++)
  292. {
  293. if (balls[i].used)//遍历每一个用过的阳光
  294. {
  295. //int x = balls[i].x;//此阳光球左上角的x坐标
  296. //int y = balls[i].y;//此阳光球左上角的y坐标
  297. int x = balls[i].pCur.x;
  298. int y = balls[i].pCur.y;
  299. if (msg->x > x && msg->x<x + width && msg->y>y && msg->y < y + hight)//判断鼠标位置与阳光球的位置
  300. {
  301. //选中范围近似为矩形
  302. //balls[i].used = false;//选中后消失
  303. balls[i].status = SUNSHINE_COLLECT;//收集状态
  304. PlaySound("res/sunshine.wav", NULL, SND_FILENAME | SND_ASYNC);
  305. //mciSendString("play res/sunshine.mp3", 0, 0, NULL);//音乐播放(有延迟)
  306. //贝塞尔曲线法渲染阳光球飞跃路线
  307. balls[i].p1 = balls[i].pCur;//起点:被收集时的位置
  308. balls[i].p4 = vector2( 262,0 );//终点
  309. balls[i].t = 0;
  310. float distance = dis(balls[i].p1 - balls[i].p2);//两点之间的距离
  311. float off = 8;//每次移动的像素(可修改)
  312. balls[i].speed = 1.0 / (distance / off);//一次移动完成
  313. break;
  314. //获取阳光球与目的地中心连线与水平方向的夹角
  315. //float destY = 0;
  316. //float destX = 262;//植物栏阳光收集位置的坐标
  317. //float angle = atan((y - destY) / (x - destX));//通过反正切函数获取夹角
  318. //balls[i].xoff = 40 * cos(angle);
  319. //balls[i].yoff = 40 * sin(angle);//4是指定的飞跃速度
  320. }
  321. }
  322. }
  323. }
  324. void userclick()//实现鼠标相关操作
  325. {
  326. ExMessage msg;//结构体变量
  327. static int status = 0;//判断状态
  328. if (peekmessage(&msg))//判断msg的范围内是否有信息
  329. {
  330. if (msg.message == WM_LBUTTONDOWN)//鼠标左键按下
  331. {
  332. if (msg.x > 338 && msg.x < 338 + 65 * PLANT_COUNTS && msg.y < 96)//选择植物
  333. {
  334. int index = (msg.x - 338) / 65;//判断植物卡牌,这是下标
  335. status = 1;//表示鼠标左键已经按下
  336. //printf("%d\n", index);//测试
  337. curPLANT = index + 1;//选择植物
  338. }
  339. else //收集阳光
  340. {
  341. collectsunshine(&msg);
  342. }
  343. }
  344. else if (msg.message == WM_MOUSEMOVE && status == 1)//鼠标移动
  345. {
  346. curX = msg.x;//鼠标移动时图片的x轴位置
  347. curY = msg.y;//鼠标移动时图片的y轴位置
  348. }
  349. else if (msg.message == WM_LBUTTONUP)//鼠标左键抬起
  350. {
  351. if (msg.x > 144 && msg.y > 179 && msg.y < 489)//草坪的范围
  352. {
  353. int row = (msg.y - 179) / 102;//植物种植的行,102是行宽
  354. int col = (msg.x - 144) / 81;//植物种植的列,81是列宽
  355. //printf("%d,%d\n", row, col);//测试
  356. if (map[row][col].type == 0)//草坪只有空状态才能种植
  357. {
  358. map[row][col].type = curPLANT;//表示row行col列的植物类型为curPLANT
  359. map[row][col].frameindex = 0;
  360. map[row][col].shootTimer = 0;
  361. map[row][col].x = 144 + col * 81;
  362. map[row][col].y = 179 + row * 102 + 14;
  363. }
  364. }
  365. status = 0;
  366. curPLANT = 0;//鼠标左键抬起植物种下
  367. }
  368. }
  369. }
  370. void creatsunshine()//选取阳光,阳光结构体成员的初始化
  371. {
  372. static int count = 0;
  373. static int fre = 200;
  374. count++;
  375. if (count >= fre)
  376. {
  377. fre = 100 + rand() % 200;//200399毫秒创建一个阳光
  378. count = 0;
  379. int i = 0;
  380. for (i = 0; i < 29 && balls[i].used; i++);//空循环查询可使用的阳光
  381. //used初始为0表示该阳光可以被使用,循环停止
  382. if (i >= 29)
  383. return;
  384. balls[i].used = true;//第i个阳光被使用
  385. balls[i].frameINDEX = 0;//动画帧
  386. balls[i].timer = 0;
  387. //balls[i].x = 260 + rand() % (900 - 260);//阳光球随机在场上刷新
  388. //balls[i].y = 60;//初始下落坐标
  389. //balls[i].destY = 200 + (rand() % 4 * 90);
  390. //balls[i].xoff = 0;
  391. //balls[i].yoff = 0;
  392. balls[i].status = SUNSHINE_DOWN;//下落状态
  393. balls[i].t = 0;
  394. balls[i].p1 = vector2( 260 + rand() % (900 - 148) ,60 );//起点
  395. balls[i].p4 = vector2( balls[i].p1.x,200 + (rand() % 4 * 90) );//终点
  396. int off = 2;
  397. float distance = balls[i].p4.y - balls[i].p1.y;
  398. balls[i].speed = 1.0 / (distance / off);
  399. }
  400. //向日葵生产阳光
  401. static int sunshinecount = 0;
  402. if (++sunshinecount > 2)
  403. {
  404. sunshinecount = 0;
  405. for (int i = 0; i < 3; i++)//遍历39
  406. {
  407. for (int j = 0; j < 9; j++)
  408. {
  409. if (map[i][j].type == SUN_FLOWER + 1)
  410. {
  411. map[i][j].timer++;
  412. if (map[i][j].timer > 100)//生产阳光的时间间隔
  413. {
  414. map[i][j].timer = 0;//归零
  415. int k;//找到第k个可用阳光
  416. for (k = 0; k < ballMax && balls[k].used; k++);
  417. if (k >= ballMax)
  418. return;
  419. balls[k].used = true;
  420. balls[k].p1 = vector2(map[i][j].x, map[i][j].y);//起点
  421. int w = (100 + rand() % 50) * (rand() % 2 ? 1 : -1);//阳光掉落位置与向日葵的距离(可在左也可在右)
  422. balls[k].p4 = vector2(map[i][j].x + w, map[i][j].y + imgPLANT[SUN_FLOWER][0]->getheight() - imgsunshine[0].getheight());
  423. balls[k].p2 = vector2(balls[k].p1.x + w * 0.3, balls[k].p1.y - 100);
  424. balls[k].p3 = vector2(balls[k].p1.x + w * 0.7, balls[k].p1.y - 100);
  425. balls[k].status = SUNSHINE_PRODUCT;
  426. balls[k].speed = 0.05;
  427. balls[k].t = 0;
  428. }
  429. }
  430. }
  431. }
  432. }
  433. }
  434. void updatesunshine()//更新阳光状态:使用——>未使用,动画帧再初始化
  435. {
  436. for (int i = 0; i < ballMax; i++)
  437. {
  438. if (balls[i].used)//如果阳光球处于使用状态,则更新
  439. {
  440. balls[i].frameINDEX = (balls[i].frameINDEX + 1) % 29;//防止动画帧越界
  441. if (balls[i].status == SUNSHINE_DOWN)//下落
  442. {
  443. struct sunshineBALL* sun = &balls[i];
  444. sun->t += sun->speed;
  445. sun->pCur = sun->p1 + sun->t * (sun->p4 - sun->p1);//直线运动
  446. if (sun->t >= 1)//到达终点
  447. {
  448. sun->status = SUNSHINE_GROUND;//更改状态
  449. sun->timer = 0;
  450. }
  451. }
  452. else if (balls[i].status == SUNSHINE_GROUND)//落地
  453. {
  454. balls[i].timer++;
  455. if (balls[i].timer > 100)
  456. {
  457. balls[i].used = false;
  458. balls[i].timer = 0;
  459. }
  460. }
  461. else if (balls[i].status == SUNSHINE_COLLECT)//收集
  462. {
  463. struct sunshineBALL* sun = &balls[i];
  464. sun->t += sun->speed;
  465. sun->pCur = sun->p1 + sun->t * (sun->p4 - sun->p1);//直线运动
  466. if (sun->t > 1)
  467. {
  468. sun->used = false;
  469. sunshine += 25;
  470. }
  471. }
  472. else if (balls[i].status == SUNSHINE_PRODUCT)//生产
  473. {
  474. struct sunshineBALL* sun = &balls[i];
  475. sun->t += sun->speed;
  476. //调用贝塞尔曲线函数
  477. sun->pCur = calcBezierPoint(sun->t, sun->p1, sun->p2, sun->p3, sun->p4);//曲线运动
  478. if (sun->t > 1)
  479. {
  480. sun->status = SUNSHINE_GROUND;
  481. sun->timer = 0;
  482. }
  483. }
  484. //if (balls[i].timer == 0)//下落过程中停留时间不变为0
  485. //{
  486. // balls[i].y += 3;//阳光下落
  487. //}
  488. //if (balls[i].y >= balls[i].destY)
  489. //{
  490. // balls[i].timer++;
  491. // if (balls[i].timer > 100)//停留100帧后消失
  492. // {
  493. // balls[i].used = false;//更新阳光的使用状态
  494. // }
  495. //}
  496. }
  497. //else if (balls[i].xoff)
  498. //{
  499. // float destY = 0;
  500. // float destX = 262;//植物栏阳光收集位置的坐标
  501. // float angle = atan((balls[i].y - destY) / (balls[i].x - destX));//通过反正切函数获取夹角
  502. // balls[i].xoff = 40 * cos(angle);
  503. // balls[i].yoff = 40 * sin(angle);//4是指定的飞跃速度
  504. // //每次飞跃时消除误差
  505. // balls[i].x -= balls[i].xoff;
  506. // balls[i].y -= balls[i].yoff;
  507. // if (balls[i].x < 262 || balls[i].y < 0)
  508. // {
  509. // balls[i].xoff = 0;
  510. // balls[i].yoff = 0;//偏移量归零
  511. // sunshine += 25;
  512. // balls[i].used = false;//到达目的地后模型消失
  513. // }
  514. //}
  515. }
  516. }
  517. void creatzomb()//创建僵尸
  518. {
  519. if (zmCount1 >= 10)
  520. return;
  521. int i;
  522. int zmMax = sizeof(zms) / sizeof(zms[0]);//僵尸的数量
  523. static int zmcount = 0;
  524. static int zmfre = 40;
  525. zmcount++;
  526. if (zmcount >= zmfre)
  527. {
  528. zmfre = 100 + rand() % 100;
  529. zmcount = 0;
  530. for (i = 0; i < zmMax && zms[i].used; i++);//查询可用的僵尸
  531. if (i < zmMax)
  532. {
  533. memset(&zms[i], 0, sizeof(zms[i]));
  534. zms[i].used = true;//表示该僵尸已经用过
  535. zms[i].x = WIN_WIDTH;//僵尸的横坐标
  536. zms[i].zmrow = rand() % 3;
  537. zms[i].y = 173 + (zms[i].zmrow + 1) * 100;//行高近似为100
  538. zms[i].speed = 1;//初始速度
  539. zms[i].blood = 150;//10个豌豆
  540. zms[i].dead = false;
  541. zmCount1++;
  542. }
  543. else
  544. {
  545. printf("创建僵尸失败\n");
  546. }
  547. }
  548. }
  549. void updatezomb()//更新僵尸的数据
  550. {
  551. int zmMax = sizeof(zms) / sizeof(zms[0]);//僵尸的数量
  552. static int count = 0;
  553. count++;
  554. if (count > 2)
  555. {
  556. count = 0;
  557. for (int i = 0; i < zmMax; i++)
  558. {
  559. if (zms[i].used)//被使用则更新
  560. {
  561. zms[i].x -= zms[i].speed;
  562. if (zms[i].x <= 28)
  563. {
  564. //printf("GAME OVER\n");
  565. //MessageBox(NULL, "over", "over", 0);//显示对话框
  566. //exit(0);//退出程序(待优化)
  567. gameStatus = FAIL;//游戏失败
  568. }
  569. }
  570. }
  571. }
  572. static int count2 = 0;
  573. count2++;
  574. if (count2 > 8)
  575. {
  576. count2 = 0;
  577. for (int i = 0; i < zmMax; i++)//更新僵尸的图片帧
  578. {
  579. if (zms[i].used)
  580. {
  581. if (zms[i].dead)//判断僵尸死亡
  582. {
  583. zms[i].frameIndex++;
  584. if (zms[i].frameIndex >= 20)
  585. {
  586. zms[i].used = false;
  587. killcount++;
  588. if (killcount == ZM_MAX)
  589. gameStatus = WIN;//游戏胜利
  590. }
  591. }
  592. else if (zms[i].eating)
  593. {
  594. zms[i].frameIndex = (zms[i].frameIndex + 1) % 21;
  595. }
  596. else
  597. {
  598. zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;//防止越界
  599. }
  600. }
  601. }
  602. }
  603. }
  604. void shoot()//发射豌豆
  605. {
  606. static int count6 = 0;
  607. if (++count6 < 5)
  608. return;
  609. count6 = 0;
  610. int lines[3] = { 0 };//表示3行草坪
  611. int zmMax = sizeof(zms) / sizeof(zms[0]);//僵尸数量
  612. int peaMax = sizeof(bullets) / sizeof(bullets[0]);
  613. int dangerX = WIN_WIDTH;//- imgzombie[0].getwidth();//发射豌豆的距离(可调节)
  614. for (int i = 0; i < zmMax; i++)
  615. {
  616. if (zms[i].used && zms[i].x < dangerX)//僵尸出现在草坪上
  617. {
  618. lines[zms[i].zmrow] = 1;//僵尸出现的行表示为1
  619. }
  620. }
  621. for (int i = 0; i < 3; i++)//遍历草坪
  622. {
  623. for (int j = 0; j < 9; j++)
  624. {
  625. if (map[i][j].type == PEA + 1 && lines[i])//植物类型是1代表豌豆射手,可以发射子弹
  626. {
  627. map[i][j].shootTimer++;
  628. if (map[i][j].shootTimer > 15)//调节豌豆发射的频率(可调节)
  629. {
  630. map[i][j].shootTimer = 0;
  631. int k;
  632. for (k = 0; k < peaMax && bullets[k].used; k++);//查找可用子弹
  633. if (k < peaMax)
  634. {
  635. bullets[k].used = true;
  636. bullets[k].pearow = i;//第i+1
  637. bullets[k].speed = 3;
  638. bullets[k].blast = false;//最开始没有发生爆炸
  639. bullets[k].frameindex = 0;
  640. int peax = 144 + j * 81;//植物种植的横坐标
  641. int peay = 179 + i * 102 + 14;//植物种植的纵坐标,位置可自行调整
  642. //发射位置是豌豆左上角坐标加上一个身位的嘴部,-10可自行调整,只是为了更接近原版
  643. bullets[k].x = peax + imgPLANT[map[i][j].type - 1][0]->getwidth() - 10;
  644. bullets[k].y = peay + 5;
  645. }
  646. }
  647. }
  648. }
  649. }
  650. }
  651. void updatebullets()//更新子弹的数据
  652. {
  653. int bulletMax = sizeof(bullets) / sizeof(bullets[0]);//子弹数目
  654. for (int i = 0; i < bulletMax; i++)
  655. {
  656. if (bullets[i].used)
  657. {
  658. bullets[i].x += bullets[i].speed;//更新x坐标
  659. if (bullets[i].x > WIN_WIDTH)
  660. {
  661. bullets[i].used = false;//对超出边界的豌豆回收
  662. }
  663. if (bullets[i].blast)//子弹符合爆炸条件
  664. {
  665. bullets[i].frameindex++;
  666. if (bullets[i].frameindex >= 4)
  667. {
  668. bullets[i].used = false;//爆炸结束
  669. }
  670. }
  671. }
  672. }
  673. }
  674. void checkbullet()//豌豆->僵尸
  675. {
  676. int bulletMax = sizeof(bullets) / sizeof(bullets[0]);
  677. int zombMax = sizeof(zms) / sizeof(zms[0]);
  678. for (int i = 0; i < bulletMax; i++)//遍历子弹
  679. {
  680. if (bullets[i].used == false || bullets[i].blast)
  681. continue;//如果子弹还未出现或已经爆炸则无需做爆炸检测
  682. for (int j = 0; j < zombMax; j++)//遍历僵尸
  683. {
  684. if (zms[j].used == false)
  685. continue;//僵尸还未出现无需检测
  686. int x1 = zms[j].x + 80;
  687. int x2 = zms[j].x + 110;//僵尸模型的范围
  688. int x = bullets[i].x;//子弹的坐标
  689. if (zms[j].dead == false && bullets[i].pearow == zms[j].zmrow && x > x1 && x < x2)//保证僵尸和子弹在同一行
  690. {
  691. //僵尸死亡不用做碰撞检测
  692. zms[j].blood -= 20;//20滴血
  693. bullets[i].blast = true;//子弹爆炸
  694. bullets[i].speed = 0;
  695. if (zms[j].blood <= 0)
  696. {
  697. zms[j].dead = true;//僵尸死亡
  698. zms[j].speed = 0;
  699. zms[j].frameIndex = 0;
  700. }
  701. break;
  702. }
  703. }
  704. }
  705. }
  706. void checkeat()//僵尸->植物
  707. {
  708. int zmcount = sizeof(zms) / sizeof(zms[0]);
  709. for (int i = 0; i < zmcount; i++)//遍历僵尸
  710. {
  711. if (zms[i].dead)
  712. continue;
  713. int samerow = zms[i].zmrow;
  714. for (int k = 0; k < 9; k++)//只遍历相同行的9个格子
  715. {
  716. if (map[samerow][k].type == 0)//该格子没有种植植物
  717. continue;
  718. int plantX = 144 + k * 81;//植物图片左上角的坐标
  719. int x1 = plantX + 10;//植物的左边界
  720. int x2 = plantX + 60;//植物的右边界
  721. int x3 = zms[i].x + 80;//僵尸的左边界
  722. if (x3 > x1 && x3 < x2)
  723. {
  724. if (map[samerow][k].catched)//植物被捕获
  725. {
  726. map[samerow][k].deadtime ++;
  727. if (map[samerow][k].deadtime > 100)
  728. {
  729. map[samerow][k].deadtime = 0;
  730. map[samerow][k].type = 0;
  731. zms[i].eating = false;
  732. zms[i].frameIndex = 0;
  733. zms[i].speed = 2;
  734. map[samerow][k].catched = false;
  735. }
  736. }
  737. else
  738. {
  739. map[samerow][k].catched = true;
  740. map[samerow][k].deadtime = 0;//死亡倒计时
  741. zms[i].eating = true;//僵尸开吃
  742. zms[i].speed = 0;
  743. zms[i].frameIndex = 0;
  744. }
  745. }
  746. }
  747. }
  748. }
  749. void collisioncheck()//碰撞检测
  750. {
  751. checkbullet();//子弹碰撞
  752. checkeat();//僵尸捕获植物
  753. }
  754. void updatePlant()//更新植物
  755. {
  756. static int count = 0;//频度控制
  757. if (++count > 2)
  758. {
  759. count = 0;
  760. for (int i = 0; i < 3; i++)
  761. {
  762. for (int j = 0; j < 9; j++)
  763. {
  764. if (map[i][j].type > 0)
  765. {
  766. map[i][j].frameindex++;//从第一张图片到最后一张图片
  767. //判断能否打开文件,打不开证明是最后一帧
  768. if (imgPLANT[map[i][j].type - 1][map[i][j].frameindex] == NULL)
  769. {
  770. map[i][j].frameindex = 0;//再初始化,从第一帧重新开始
  771. }
  772. }
  773. }
  774. }
  775. }
  776. }
  777. void updateGame()//在每次循环后更改相应的参数
  778. {
  779. updatePlant();//更新植物
  780. creatsunshine();//创建阳光
  781. updatesunshine();//更新阳光
  782. creatzomb();//创建僵尸
  783. updatezomb();//更新僵尸
  784. shoot();//发射豌豆
  785. updatebullets();//更新豌豆
  786. collisioncheck();//碰撞检测
  787. }
  788. void startUI()//起始菜单
  789. {
  790. mciSendString("play res/bg.mp3", 0, 0, NULL);
  791. IMAGE imgBG, imgMenu1, imgMenu2;
  792. loadimage(&imgBG, "res/menu.png");
  793. loadimage(&imgMenu1, "res/menu1.png");//选中选项卡
  794. loadimage(&imgMenu2, "res/menu2.png");//未选中选项卡
  795. int flag = 0;//表示是否选中,选中则为1
  796. while (1)
  797. {
  798. BeginBatchDraw();
  799. putimage(0, 0, &imgBG);
  800. putimagePNG(474, 75, flag ? &imgMenu2 : &imgMenu1);
  801. ExMessage msg;
  802. if (peekmessage(&msg))
  803. {
  804. if (msg.message == WM_LBUTTONDOWN &&
  805. msg.x > 474 && msg.x < 474 + 300 && msg.y>75 &&
  806. msg.y < 75 + 140)
  807. {
  808. flag = 1;//表示选中
  809. }
  810. else if (msg.message == WM_LBUTTONUP && flag)
  811. {
  812. EndBatchDraw();
  813. break;
  814. }
  815. }
  816. EndBatchDraw();
  817. }
  818. mciSendString("close res/bg.mp3", 0, 0, 0);
  819. }
  820. void viewScene()//视角移动
  821. {
  822. int xMax = WIN_WIDTH - imgBg.getwidth();//窗口宽-图片宽(坐标为负数)
  823. vector2 point[9] = { {550,80},{530,160},{630,170},{530,200},{515,270},
  824. {565,370},{605,340},{705,280},{690,340} };//阅览僵尸站位(可修改)
  825. int index[9];
  826. for (int j = 0; j < 9; j++)
  827. {
  828. index[j] = rand() % 11;//僵尸起始帧序号
  829. }
  830. int scenecount = 0;
  831. //画面巡视
  832. for (int i = 0; i >= xMax; i -= 2)
  833. {
  834. BeginBatchDraw();
  835. putimage(i, 0, &imgBg);
  836. scenecount++;
  837. for (int k = 0; k < 9; k++)//绘制僵尸站位
  838. {
  839. putimagePNG(point[k].x - xMax + i, point[k].y, &imgzmStand[index[k]]);
  840. if (scenecount >= 10)
  841. index[k] = (index[k] + 1) % 11;
  842. }
  843. if (scenecount >= 10)
  844. scenecount = 0;
  845. EndBatchDraw();
  846. Sleep(5);
  847. }
  848. //画面停留时间(可修改)
  849. for (int i = 0; i < 50; i++)
  850. {
  851. BeginBatchDraw();
  852. putimage(xMax, 0, &imgBg);
  853. for (int k = 0; k < 9; k++)
  854. {
  855. putimagePNG(point[k].x, point[k].y, &imgzmStand[index[k]]);
  856. index[k] = (index[k] + 1) % 11;
  857. }
  858. EndBatchDraw();
  859. Sleep(30);
  860. }
  861. //画面返回
  862. for (int k = xMax; k <= -112; k += 2)
  863. {
  864. BeginBatchDraw();
  865. putimage(k, 0, &imgBg);
  866. scenecount++;
  867. for (int i = 0; i < 9; i++)
  868. {
  869. putimagePNG(point[i].x - xMax + k, point[i].y, &imgzmStand[index[i]]);
  870. if (scenecount >= 10)
  871. index[i] = (index[i] + 1) % 11;
  872. }
  873. if (scenecount >= 10)
  874. scenecount = 0;
  875. EndBatchDraw();
  876. Sleep(5);
  877. }
  878. }
  879. void barsDown()//工具栏下降
  880. {
  881. int hight = imgBar.getheight();//获取工具栏的高
  882. for (int i = -hight; i <= 0; i++)
  883. {
  884. BeginBatchDraw();
  885. putimage(-112, 0, &imgBg);
  886. putimagePNG(250, i, &imgBar);
  887. for (int k = 0; k < PLANT_COUNTS; k++)
  888. {
  889. int x = 338 + k * 65;
  890. int y = 6 + i;
  891. putimage(x, y, &imgCard[k]);
  892. }
  893. EndBatchDraw();
  894. Sleep(10);
  895. }
  896. }
  897. bool checkOver()//游戏结束画面
  898. {
  899. bool ret = false;
  900. if (gameStatus == WIN)//游戏胜利
  901. {
  902. mciSendString("play res/win.mp3", 0, 0, NULL);
  903. Sleep(2000);
  904. mciSendString("close res/win.mp3", 0, 0, NULL);
  905. IMAGE* img7 = NULL;
  906. loadimage(img7, "res/gameWin.png");
  907. putimage(0, 0, img7);
  908. ret = true;
  909. }
  910. else if (gameStatus == FAIL)
  911. {
  912. mciSendString("play res/lose.mp3", 0, 0, NULL);
  913. Sleep(2000);
  914. mciSendString("close res/lose.mp3", 0, 0, NULL);
  915. IMAGE* img9 = NULL;
  916. loadimage(img9, "res/gameFail.png");
  917. putimage(0, 0, img9);
  918. ret = true;
  919. }
  920. return ret;
  921. }
  922. int main()
  923. {
  924. gameInit();
  925. startUI();
  926. viewScene();
  927. barsDown();
  928. int timer = 0;//实现帧等待
  929. bool flag = true;
  930. while (1)//循环退出条件:用户单击
  931. {
  932. userclick();
  933. timer += getDelay();//运行时间间隔
  934. if (timer > 10)//运行时间间隔大于10毫秒
  935. {
  936. flag = true;
  937. timer = 0;//再初始化
  938. }
  939. if (flag)
  940. {
  941. flag = false;
  942. updateWindow();
  943. updateGame();
  944. if (checkOver())
  945. break;
  946. }
  947. }
  948. system("pause");//暂停
  949. return 0;
  950. }

2.1判断文件能否打开:fileExist()

  1. bool fileExist(char* name)//判断文件能否打开
  2. {
  3. FILE* fp = fopen(name, "r");//以“读”的形式打开文件
  4. //fopen函数能打开文件返回文件路径,打不开文件返回NULL,所以需要定义指针变量
  5. if (fp == NULL)
  6. return false;//表示文件打不开
  7. else
  8. fclose(fp);//关闭文件
  9. return true;//表示文件能打开
  10. }

2.2游戏初始化:gameinit()

  1. void gameInit()//初始化函数
  2. {
  3. //加载背景图片
  4. loadimage(&imgBg, "res/bg.jpg");
  5. loadimage(&imgBar, "res/bar5.png");
  6. killcount = 0;
  7. zmCount1 = 0;
  8. gameStatus = GOING;
  9. //对数组整体初始化
  10. memset(imgPLANT, 0, sizeof(imgPLANT));
  11. memset(map, 0, sizeof(map));
  12. memset(balls, 0, sizeof(balls));
  13. memset(zms, 0, sizeof(zms));
  14. memset(bullets, 0, sizeof(bullets));
  15. char plant_name[64];
  16. for (int i = 0; i < PLANT_COUNTS; i++)//初始化植物卡牌
  17. {
  18. sprintf_s(plant_name, sizeof(plant_name), "res/Cards/card_%d.png", i + 1);
  19. loadimage(&imgCard[i], plant_name);
  20. for (int j = 0; j < 20; j++)
  21. {
  22. //将植物晃动的图片格式化到数组plant_name中
  23. sprintf_s(plant_name, sizeof(plant_name), "res/zhiwu/%d/%d.png", i, j + 1);
  24. if (fileExist(plant_name))//判断文件是否存在
  25. {
  26. imgPLANT[i][j] = new IMAGE;//分配新的内存
  27. loadimage(imgPLANT[i][j], plant_name);//将图片加载到实现晃动的数组里
  28. }
  29. else//打不开跳出循环
  30. break;
  31. }
  32. }
  33. curPLANT = 0;//初始化
  34. for (int i = 0; i < 29; i++)//初始化阳光球
  35. {
  36. sprintf_s(plant_name, sizeof(plant_name), "res/sunshine/%d.png", i + 1);
  37. loadimage(&imgsunshine[i], plant_name);
  38. }
  39. srand(time(NULL));//配置随机种子
  40. initgraph(WIN_WIDTH, WIN_HIGHT, 1);//创建游戏窗口,显示图片
  41. LOGFONT f;//设置显示阳光的字体
  42. gettextstyle(&f);//获取字体的文本格式
  43. f.lfHeight = 30;//字体高度30
  44. f.lfWeight = 15;//设置字体的粗细
  45. strcpy(f.lfFaceName, "Segoe UI Black");//设置字体格式
  46. f.lfQuality = ANTIALIASED_QUALITY;//抗锯齿
  47. settextstyle(&f);
  48. setbkmode(TRANSPARENT);//设置字体背景透明
  49. setcolor(BLACK);//设置字体颜色
  50. for (int i = 0; i < 22; i++)//初始化僵尸
  51. {
  52. sprintf_s(plant_name, sizeof(plant_name), "res/zm/%d.png", i + 1);
  53. loadimage(&imgzombie[i], plant_name);
  54. }
  55. //初始化豌豆normal
  56. loadimage(&imgBulletNormal, "res/bullets/bullet_normal.png");
  57. //初始化豌豆blast
  58. //爆炸的实质是一张爆炸帧图片放大四次
  59. loadimage(&imgballblast[3], "res/bullets/bullet_blast.png");
  60. for (int i = 0; i < 3; i++)//放大次数(可修改)
  61. {
  62. float k = (i + 1) * 0.2;//0.2是放大倍数(可修改)
  63. loadimage(&imgballblast[i], "res/bullets/bullet_blast.png",
  64. imgballblast[3].getwidth() * k, imgballblast[3].getheight() * k, true);
  65. //true表示按照如上的大小放大该图片,false相反
  66. }
  67. //初始化僵尸dead
  68. for (int i = 0; i < 20; i++)
  69. {
  70. sprintf_s(plant_name, sizeof(plant_name), "res/zm_dead/%d.png", i + 1);
  71. loadimage(&imgzmDead[i], plant_name);
  72. }
  73. //初始化僵尸eat
  74. for (int i = 0; i < 21; i++)
  75. {
  76. sprintf_s(plant_name, sizeof(plant_name), "res/zm_eat/%d.png", i + 1);
  77. loadimage(&imgzmEat[i], plant_name);
  78. }
  79. //初始化僵尸stand
  80. for (int i = 0; i < 11; i++)
  81. {
  82. sprintf_s(plant_name, sizeof(plant_name), "res/zm_stand/%d.png", i + 1);
  83. loadimage(&imgzmStand[i], plant_name);
  84. }
  85. }

2.3渲染僵尸:drawzm()

  1. void drawzm()//渲染僵尸
  2. {
  3. int zmMax = sizeof(zms) / sizeof(zms[0]);
  4. for (int i = 0; i < zmMax; i++)
  5. {
  6. if (zms[i].used)
  7. {
  8. //根据僵尸的情况而调用不同的数组
  9. IMAGE* img3 = NULL;
  10. if (zms[i].dead)
  11. img3 = imgzmDead;
  12. else if (zms[i].eating)
  13. img3 = imgzmEat;
  14. else
  15. img3 = imgzombie;
  16. img3 += zms[i].frameIndex;
  17. putimagePNG(zms[i].x, zms[i].y - img3->getheight(), img3);
  18. }
  19. }
  20. }

2.4渲染阳光:drawsunshine()

  1. void drawsunshine()//渲染阳光
  2. {
  3. for (int i = 0; i < ballMax; i++)
  4. {
  5. //if (balls[i].used || balls[i].xoff)
  6. if(balls[i].used)
  7. {
  8. IMAGE* img2 = &imgsunshine[balls[i].frameINDEX];
  9. //putimagePNG(balls[i].x, balls[i].y, img2);
  10. putimagePNG(balls[i].pCur.x, balls[i].pCur.y, img2);
  11. }
  12. }
  13. }

2.5渲染卡牌:drawCard()

  1. void drawCard()//渲染卡牌
  2. {
  3. for (int i = 0; i < PLANT_COUNTS; i++)//加载植物卡牌
  4. {
  5. int x = 338 + i * 65;//将植物加载到卡槽里
  6. int y = 6;
  7. putimage(x, y, &imgCard[i]);
  8. }
  9. }

2.6渲染种植后的植物:drawPlantMap()

  1. void drawPlantMap()//渲染种植后的植物
  2. {
  3. for (int i = 0; i < 3; i++)//控制行,渲染种植后的植物
  4. {
  5. for (int j = 0; j < 9; j++)//控制列
  6. {
  7. if (map[i][j].type > 0)
  8. {
  9. //int x = 256 + j * 81;//植物种植的横坐标
  10. //int y = 179 + i * 102 + 14;//植物种植的纵坐标,位置可自行调整
  11. putimagePNG(map[i][j].x, map[i][j].y, imgPLANT[map[i][j].type - 1][map[i][j].frameindex]);
  12. }
  13. }
  14. }
  15. }

2.7渲染鼠标拖动的植物:drawPlantMove()

  1. void drawPlantMove()//渲染鼠标拖动的植物
  2. {
  3. if (curPLANT)//渲染拖动时的植物模型
  4. {
  5. IMAGE* img1 = imgPLANT[curPLANT - 1][0];
  6. //实现无背景贴图,让鼠标位于植物的正中间
  7. putimagePNG(curX - img1->getwidth() / 2, curY - img1->getheight() / 2, img1);//取植物晃动的第一张图片
  8. }
  9. }

2.8渲染豌豆子弹:drawPea()

  1. void drawPea()渲染豌豆
  2. {
  3. int bulletMax = sizeof(bullets) / sizeof(bullets[0]);
  4. for (int i = 0; i < bulletMax; i++)
  5. {
  6. if (bullets[i].used)
  7. {
  8. if (bullets[i].blast)//爆炸
  9. {
  10. IMAGE* img5 = &imgballblast[bullets[i].frameindex];
  11. putimagePNG(bullets[i].x, bullets[i].y, img5);
  12. }
  13. else//正常
  14. {
  15. putimagePNG(bullets[i].x, bullets[i].y, &imgBulletNormal);
  16. }
  17. }
  18. }
  19. }

2.9显示左上角阳光数:outsunshine()

  1. void outsunshine()//显示左上角阳光数
  2. {
  3. char scoreText[8];
  4. sprintf_s(scoreText, sizeof(scoreText), "%d", sunshine);//将阳光格式化为字符串并加载进数组
  5. outtextxy(279, 67, scoreText);//在指定位置输出字符串
  6. }

2.10渲染:updatewindow()

  1. void updateWindow()//渲染显示图片
  2. {
  3. BeginBatchDraw();//开始缓冲
  4. putimage(-112, 0, &imgBg);//从最左上角显示草坪
  5. putimagePNG(250, 0, &imgBar);//植物栏
  6. drawCard();//渲染卡牌
  7. drawPlantMap();//渲染种植后的植物
  8. drawPlantMove();//渲染拖动时的植物
  9. drawsunshine();//渲染阳光
  10. drawzm();//渲染僵尸
  11. drawPea();//渲染豌豆
  12. outsunshine();//显示左上角阳光数
  13. EndBatchDraw();//结束双缓冲
  14. }

2.11收集阳光:collectsunshine()

  1. void collectsunshine(ExMessage* msg)//实现收集阳光
  2. {
  3. int width = imgsunshine[0].getwidth();//获取阳光球的宽
  4. int hight = imgsunshine[0].getheight();//获取阳光球的高
  5. for (int i = 0; i < ballMax; i++)
  6. {
  7. if (balls[i].used)//遍历每一个用过的阳光
  8. {
  9. //int x = balls[i].x;//此阳光球左上角的x坐标
  10. //int y = balls[i].y;//此阳光球左上角的y坐标
  11. int x = balls[i].pCur.x;
  12. int y = balls[i].pCur.y;
  13. if (msg->x > x && msg->x<x + width && msg->y>y && msg->y < y + hight)//判断鼠标位置与阳光球的位置
  14. {
  15. //选中范围近似为矩形
  16. //balls[i].used = false;//选中后消失
  17. balls[i].status = SUNSHINE_COLLECT;//收集状态
  18. PlaySound("res/sunshine.wav", NULL, SND_FILENAME | SND_ASYNC);
  19. //mciSendString("play res/sunshine.mp3", 0, 0, NULL);//音乐播放(有延迟)
  20. //贝塞尔曲线法渲染阳光球飞跃路线
  21. balls[i].p1 = balls[i].pCur;//起点:被收集时的位置
  22. balls[i].p4 = vector2( 262,0 );//终点
  23. balls[i].t = 0;
  24. float distance = dis(balls[i].p1 - balls[i].p2);//两点之间的距离
  25. float off = 8;//每次移动的像素(可修改)
  26. balls[i].speed = 1.0 / (distance / off);//一次移动完成
  27. break;
  28. //获取阳光球与目的地中心连线与水平方向的夹角
  29. //float destY = 0;
  30. //float destX = 262;//植物栏阳光收集位置的坐标
  31. //float angle = atan((y - destY) / (x - destX));//通过反正切函数获取夹角
  32. //balls[i].xoff = 40 * cos(angle);
  33. //balls[i].yoff = 40 * sin(angle);//4是指定的飞跃速度
  34. }
  35. }
  36. }
  37. }

2.12鼠标操作:userclick()

  1. void userclick()//实现鼠标相关操作
  2. {
  3. ExMessage msg;//结构体变量
  4. static int status = 0;//判断状态
  5. if (peekmessage(&msg))//判断msg的范围内是否有信息
  6. {
  7. if (msg.message == WM_LBUTTONDOWN)//鼠标左键按下
  8. {
  9. if (msg.x > 338 && msg.x < 338 + 65 * PLANT_COUNTS && msg.y < 96)//选择植物
  10. {
  11. int index = (msg.x - 338) / 65;//判断植物卡牌,这是下标
  12. status = 1;//表示鼠标左键已经按下
  13. //printf("%d\n", index);//测试
  14. curPLANT = index + 1;//选择植物
  15. }
  16. else //收集阳光
  17. {
  18. collectsunshine(&msg);
  19. }
  20. }
  21. else if (msg.message == WM_MOUSEMOVE && status == 1)//鼠标移动
  22. {
  23. curX = msg.x;//鼠标移动时图片的x轴位置
  24. curY = msg.y;//鼠标移动时图片的y轴位置
  25. }
  26. else if (msg.message == WM_LBUTTONUP)//鼠标左键抬起
  27. {
  28. if (msg.x > 144 && msg.y > 179 && msg.y < 489)//草坪的范围
  29. {
  30. int row = (msg.y - 179) / 102;//植物种植的行,102是行宽
  31. int col = (msg.x - 144) / 81;//植物种植的列,81是列宽
  32. //printf("%d,%d\n", row, col);//测试
  33. if (map[row][col].type == 0)//草坪只有空状态才能种植
  34. {
  35. map[row][col].type = curPLANT;//表示row行col列的植物类型为curPLANT
  36. map[row][col].frameindex = 0;
  37. map[row][col].shootTimer = 0;
  38. map[row][col].x = 144 + col * 81;
  39. map[row][col].y = 179 + row * 102 + 14;
  40. }
  41. }
  42. status = 0;
  43. curPLANT = 0;//鼠标左键抬起植物种下
  44. }
  45. }
  46. }

2.13创建阳光:creatsunshine()

  1. void creatsunshine()//选取阳光,阳光结构体成员的初始化
  2. {
  3. static int count = 0;
  4. static int fre = 200;
  5. count++;
  6. if (count >= fre)
  7. {
  8. fre = 100 + rand() % 200;//200399毫秒创建一个阳光
  9. count = 0;
  10. int i = 0;
  11. for (i = 0; i < 29 && balls[i].used; i++);//空循环查询可使用的阳光
  12. //used初始为0表示该阳光可以被使用,循环停止
  13. if (i >= 29)
  14. return;
  15. balls[i].used = true;//第i个阳光被使用
  16. balls[i].frameINDEX = 0;//动画帧
  17. balls[i].timer = 0;
  18. //balls[i].x = 260 + rand() % (900 - 260);//阳光球随机在场上刷新
  19. //balls[i].y = 60;//初始下落坐标
  20. //balls[i].destY = 200 + (rand() % 4 * 90);
  21. //balls[i].xoff = 0;
  22. //balls[i].yoff = 0;
  23. balls[i].status = SUNSHINE_DOWN;//下落状态
  24. balls[i].t = 0;
  25. balls[i].p1 = vector2( 260 + rand() % (900 - 148) ,60 );//起点
  26. balls[i].p4 = vector2( balls[i].p1.x,200 + (rand() % 4 * 90) );//终点
  27. int off = 2;
  28. float distance = balls[i].p4.y - balls[i].p1.y;
  29. balls[i].speed = 1.0 / (distance / off);
  30. }
  31. //向日葵生产阳光
  32. static int sunshinecount = 0;
  33. if (++sunshinecount > 2)
  34. {
  35. sunshinecount = 0;
  36. for (int i = 0; i < 3; i++)//遍历39
  37. {
  38. for (int j = 0; j < 9; j++)
  39. {
  40. if (map[i][j].type == SUN_FLOWER + 1)
  41. {
  42. map[i][j].timer++;
  43. if (map[i][j].timer > 100)//生产阳光的时间间隔
  44. {
  45. map[i][j].timer = 0;//归零
  46. int k;//找到第k个可用阳光
  47. for (k = 0; k < ballMax && balls[k].used; k++);
  48. if (k >= ballMax)
  49. return;
  50. balls[k].used = true;
  51. balls[k].p1 = vector2(map[i][j].x, map[i][j].y);//起点
  52. int w = (100 + rand() % 50) * (rand() % 2 ? 1 : -1);//阳光掉落位置与向日葵的距离(可在左也可在右)
  53. balls[k].p4 = vector2(map[i][j].x + w, map[i][j].y + imgPLANT[SUN_FLOWER][0]->getheight() - imgsunshine[0].getheight());
  54. balls[k].p2 = vector2(balls[k].p1.x + w * 0.3, balls[k].p1.y - 100);
  55. balls[k].p3 = vector2(balls[k].p1.x + w * 0.7, balls[k].p1.y - 100);
  56. balls[k].status = SUNSHINE_PRODUCT;
  57. balls[k].speed = 0.05;
  58. balls[k].t = 0;
  59. }
  60. }
  61. }
  62. }
  63. }
  64. }

2.14更新阳光参数:updatesunshine()

  1. void updatesunshine()//更新阳光状态:使用——>未使用,动画帧再初始化
  2. {
  3. for (int i = 0; i < ballMax; i++)
  4. {
  5. if (balls[i].used)//如果阳光球处于使用状态,则更新
  6. {
  7. balls[i].frameINDEX = (balls[i].frameINDEX + 1) % 29;//防止动画帧越界
  8. if (balls[i].status == SUNSHINE_DOWN)//下落
  9. {
  10. struct sunshineBALL* sun = &balls[i];
  11. sun->t += sun->speed;
  12. sun->pCur = sun->p1 + sun->t * (sun->p4 - sun->p1);//直线运动
  13. if (sun->t >= 1)//到达终点
  14. {
  15. sun->status = SUNSHINE_GROUND;//更改状态
  16. sun->timer = 0;
  17. }
  18. }
  19. else if (balls[i].status == SUNSHINE_GROUND)//落地
  20. {
  21. balls[i].timer++;
  22. if (balls[i].timer > 100)
  23. {
  24. balls[i].used = false;
  25. balls[i].timer = 0;
  26. }
  27. }
  28. else if (balls[i].status == SUNSHINE_COLLECT)//收集
  29. {
  30. struct sunshineBALL* sun = &balls[i];
  31. sun->t += sun->speed;
  32. sun->pCur = sun->p1 + sun->t * (sun->p4 - sun->p1);//直线运动
  33. if (sun->t > 1)
  34. {
  35. sun->used = false;
  36. sunshine += 25;
  37. }
  38. }
  39. else if (balls[i].status == SUNSHINE_PRODUCT)//生产
  40. {
  41. struct sunshineBALL* sun = &balls[i];
  42. sun->t += sun->speed;
  43. //调用贝塞尔曲线函数
  44. sun->pCur = calcBezierPoint(sun->t, sun->p1, sun->p2, sun->p3, sun->p4);//曲线运动
  45. if (sun->t > 1)
  46. {
  47. sun->status = SUNSHINE_GROUND;
  48. sun->timer = 0;
  49. }
  50. }
  51. //if (balls[i].timer == 0)//下落过程中停留时间不变为0
  52. //{
  53. // balls[i].y += 3;//阳光下落
  54. //}
  55. //if (balls[i].y >= balls[i].destY)
  56. //{
  57. // balls[i].timer++;
  58. // if (balls[i].timer > 100)//停留100帧后消失
  59. // {
  60. // balls[i].used = false;//更新阳光的使用状态
  61. // }
  62. //}
  63. }
  64. //else if (balls[i].xoff)
  65. //{
  66. // float destY = 0;
  67. // float destX = 262;//植物栏阳光收集位置的坐标
  68. // float angle = atan((balls[i].y - destY) / (balls[i].x - destX));//通过反正切函数获取夹角
  69. // balls[i].xoff = 40 * cos(angle);
  70. // balls[i].yoff = 40 * sin(angle);//4是指定的飞跃速度
  71. // //每次飞跃时消除误差
  72. // balls[i].x -= balls[i].xoff;
  73. // balls[i].y -= balls[i].yoff;
  74. // if (balls[i].x < 262 || balls[i].y < 0)
  75. // {
  76. // balls[i].xoff = 0;
  77. // balls[i].yoff = 0;//偏移量归零
  78. // sunshine += 25;
  79. // balls[i].used = false;//到达目的地后模型消失
  80. // }
  81. //}
  82. }
  83. }

2.15创建僵尸:creatzomb()

  1. void creatzomb()//创建僵尸
  2. {
  3. if (zmCount1 >= 10)
  4. return;
  5. int i;
  6. int zmMax = sizeof(zms) / sizeof(zms[0]);//僵尸的数量
  7. static int zmcount = 0;
  8. static int zmfre = 40;
  9. zmcount++;
  10. if (zmcount >= zmfre)
  11. {
  12. zmfre = 100 + rand() % 100;
  13. zmcount = 0;
  14. for (i = 0; i < zmMax && zms[i].used; i++);//查询可用的僵尸
  15. if (i < zmMax)
  16. {
  17. memset(&zms[i], 0, sizeof(zms[i]));
  18. zms[i].used = true;//表示该僵尸已经用过
  19. zms[i].x = WIN_WIDTH;//僵尸的横坐标
  20. zms[i].zmrow = rand() % 3;
  21. zms[i].y = 173 + (zms[i].zmrow + 1) * 100;//行高近似为100
  22. zms[i].speed = 1;//初始速度
  23. zms[i].blood = 150;//10个豌豆
  24. zms[i].dead = false;
  25. zmCount1++;
  26. }
  27. else
  28. {
  29. printf("创建僵尸失败\n");
  30. }
  31. }
  32. }

2.16更新僵尸参数:updatezomb()

  1. void updatezomb()//更新僵尸的数据
  2. {
  3. int zmMax = sizeof(zms) / sizeof(zms[0]);//僵尸的数量
  4. static int count = 0;
  5. count++;
  6. if (count > 2)
  7. {
  8. count = 0;
  9. for (int i = 0; i < zmMax; i++)
  10. {
  11. if (zms[i].used)//被使用则更新
  12. {
  13. zms[i].x -= zms[i].speed;
  14. if (zms[i].x <= 28)
  15. {
  16. //printf("GAME OVER\n");
  17. //MessageBox(NULL, "over", "over", 0);//显示对话框
  18. //exit(0);//退出程序(待优化)
  19. gameStatus = FAIL;//游戏失败
  20. }
  21. }
  22. }
  23. }
  24. static int count2 = 0;
  25. count2++;
  26. if (count2 > 8)
  27. {
  28. count2 = 0;
  29. for (int i = 0; i < zmMax; i++)//更新僵尸的图片帧
  30. {
  31. if (zms[i].used)
  32. {
  33. if (zms[i].dead)//判断僵尸死亡
  34. {
  35. zms[i].frameIndex++;
  36. if (zms[i].frameIndex >= 20)
  37. {
  38. zms[i].used = false;
  39. killcount++;
  40. if (killcount == ZM_MAX)
  41. gameStatus = WIN;//游戏胜利
  42. }
  43. }
  44. else if (zms[i].eating)
  45. {
  46. zms[i].frameIndex = (zms[i].frameIndex + 1) % 21;
  47. }
  48. else
  49. {
  50. zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;//防止越界
  51. }
  52. }
  53. }
  54. }
  55. }

2.17发射豌豆:shoot()

  1. void shoot()//发射豌豆
  2. {
  3. static int count6 = 0;
  4. if (++count6 < 5)
  5. return;
  6. count6 = 0;
  7. int lines[3] = { 0 };//表示3行草坪
  8. int zmMax = sizeof(zms) / sizeof(zms[0]);//僵尸数量
  9. int peaMax = sizeof(bullets) / sizeof(bullets[0]);
  10. int dangerX = WIN_WIDTH;//- imgzombie[0].getwidth();//发射豌豆的距离(可调节)
  11. for (int i = 0; i < zmMax; i++)
  12. {
  13. if (zms[i].used && zms[i].x < dangerX)//僵尸出现在草坪上
  14. {
  15. lines[zms[i].zmrow] = 1;//僵尸出现的行表示为1
  16. }
  17. }
  18. for (int i = 0; i < 3; i++)//遍历草坪
  19. {
  20. for (int j = 0; j < 9; j++)
  21. {
  22. if (map[i][j].type == PEA + 1 && lines[i])//植物类型是1代表豌豆射手,可以发射子弹
  23. {
  24. map[i][j].shootTimer++;
  25. if (map[i][j].shootTimer > 15)//调节豌豆发射的频率(可调节)
  26. {
  27. map[i][j].shootTimer = 0;
  28. int k;
  29. for (k = 0; k < peaMax && bullets[k].used; k++);//查找可用子弹
  30. if (k < peaMax)
  31. {
  32. bullets[k].used = true;
  33. bullets[k].pearow = i;//第i+1
  34. bullets[k].speed = 3;
  35. bullets[k].blast = false;//最开始没有发生爆炸
  36. bullets[k].frameindex = 0;
  37. int peax = 144 + j * 81;//植物种植的横坐标
  38. int peay = 179 + i * 102 + 14;//植物种植的纵坐标,位置可自行调整
  39. //发射位置是豌豆左上角坐标加上一个身位的嘴部,-10可自行调整,只是为了更接近原版
  40. bullets[k].x = peax + imgPLANT[map[i][j].type - 1][0]->getwidth() - 10;
  41. bullets[k].y = peay + 5;
  42. }
  43. }
  44. }
  45. }
  46. }
  47. }

2.18更新子弹参数:updatebullets()

  1. void updatebullets()//更新子弹的数据
  2. {
  3. int bulletMax = sizeof(bullets) / sizeof(bullets[0]);//子弹数目
  4. for (int i = 0; i < bulletMax; i++)
  5. {
  6. if (bullets[i].used)
  7. {
  8. bullets[i].x += bullets[i].speed;//更新x坐标
  9. if (bullets[i].x > WIN_WIDTH)
  10. {
  11. bullets[i].used = false;//对超出边界的豌豆回收
  12. }
  13. if (bullets[i].blast)//子弹符合爆炸条件
  14. {
  15. bullets[i].frameindex++;
  16. if (bullets[i].frameindex >= 4)
  17. {
  18. bullets[i].used = false;//爆炸结束
  19. }
  20. }
  21. }
  22. }
  23. }

2.19豌豆->僵尸:checkbullet()

  1. void checkbullet()//豌豆->僵尸
  2. {
  3. int bulletMax = sizeof(bullets) / sizeof(bullets[0]);
  4. int zombMax = sizeof(zms) / sizeof(zms[0]);
  5. for (int i = 0; i < bulletMax; i++)//遍历子弹
  6. {
  7. if (bullets[i].used == false || bullets[i].blast)
  8. continue;//如果子弹还未出现或已经爆炸则无需做爆炸检测
  9. for (int j = 0; j < zombMax; j++)//遍历僵尸
  10. {
  11. if (zms[j].used == false)
  12. continue;//僵尸还未出现无需检测
  13. int x1 = zms[j].x + 80;
  14. int x2 = zms[j].x + 110;//僵尸模型的范围
  15. int x = bullets[i].x;//子弹的坐标
  16. if (zms[j].dead == false && bullets[i].pearow == zms[j].zmrow && x > x1 && x < x2)//保证僵尸和子弹在同一行
  17. {
  18. //僵尸死亡不用做碰撞检测
  19. zms[j].blood -= 20;//20滴血
  20. bullets[i].blast = true;//子弹爆炸
  21. bullets[i].speed = 0;
  22. if (zms[j].blood <= 0)
  23. {
  24. zms[j].dead = true;//僵尸死亡
  25. zms[j].speed = 0;
  26. zms[j].frameIndex = 0;
  27. }
  28. break;
  29. }
  30. }
  31. }
  32. }

2.20僵尸->植物:checkeat()

  1. void checkeat()//僵尸->植物
  2. {
  3. int zmcount = sizeof(zms) / sizeof(zms[0]);
  4. for (int i = 0; i < zmcount; i++)//遍历僵尸
  5. {
  6. if (zms[i].dead)
  7. continue;
  8. int samerow = zms[i].zmrow;
  9. for (int k = 0; k < 9; k++)//只遍历相同行的9个格子
  10. {
  11. if (map[samerow][k].type == 0)//该格子没有种植植物
  12. continue;
  13. int plantX = 144 + k * 81;//植物图片左上角的坐标
  14. int x1 = plantX + 10;//植物的左边界
  15. int x2 = plantX + 60;//植物的右边界
  16. int x3 = zms[i].x + 80;//僵尸的左边界
  17. if (x3 > x1 && x3 < x2)
  18. {
  19. if (map[samerow][k].catched)//植物被捕获
  20. {
  21. map[samerow][k].deadtime ++;
  22. if (map[samerow][k].deadtime > 100)
  23. {
  24. map[samerow][k].deadtime = 0;
  25. map[samerow][k].type = 0;
  26. zms[i].eating = false;
  27. zms[i].frameIndex = 0;
  28. zms[i].speed = 2;
  29. map[samerow][k].catched = false;
  30. }
  31. }
  32. else
  33. {
  34. map[samerow][k].catched = true;
  35. map[samerow][k].deadtime = 0;//死亡倒计时
  36. zms[i].eating = true;//僵尸开吃
  37. zms[i].speed = 0;
  38. zms[i].frameIndex = 0;
  39. }
  40. }
  41. }
  42. }
  43. }

2.21碰撞检测:collisioncheck()

  1. void collisioncheck()//碰撞检测
  2. {
  3. checkbullet();//子弹碰撞
  4. checkeat();//僵尸捕获植物
  5. }

2.22更新植物参数:updatePlant()

  1. void updatePlant()//更新植物
  2. {
  3. static int count = 0;//频度控制
  4. if (++count > 2)
  5. {
  6. count = 0;
  7. for (int i = 0; i < 3; i++)
  8. {
  9. for (int j = 0; j < 9; j++)
  10. {
  11. if (map[i][j].type > 0)
  12. {
  13. map[i][j].frameindex++;//从第一张图片到最后一张图片
  14. //判断能否打开文件,打不开证明是最后一帧
  15. if (imgPLANT[map[i][j].type - 1][map[i][j].frameindex] == NULL)
  16. {
  17. map[i][j].frameindex = 0;//再初始化,从第一帧重新开始
  18. }
  19. }
  20. }
  21. }
  22. }
  23. }

2.23更改参数:updategame()

  1. void updateGame()//在每次循环后更改相应的参数
  2. {
  3. updatePlant();//更新植物
  4. creatsunshine();//创建阳光
  5. updatesunshine();//更新阳光
  6. creatzomb();//创建僵尸
  7. updatezomb();//更新僵尸
  8. shoot();//发射豌豆
  9. updatebullets();//更新豌豆
  10. collisioncheck();//碰撞检测
  11. }

2.24起始菜单:startUI()

  1. void startUI()//起始菜单
  2. {
  3. mciSendString("play res/bg.mp3", 0, 0, NULL);
  4. IMAGE imgBG, imgMenu1, imgMenu2;
  5. loadimage(&imgBG, "res/menu.png");
  6. loadimage(&imgMenu1, "res/menu1.png");//选中选项卡
  7. loadimage(&imgMenu2, "res/menu2.png");//未选中选项卡
  8. int flag = 0;//表示是否选中,选中则为1
  9. while (1)
  10. {
  11. BeginBatchDraw();
  12. putimage(0, 0, &imgBG);
  13. putimagePNG(474, 75, flag ? &imgMenu2 : &imgMenu1);
  14. ExMessage msg;
  15. if (peekmessage(&msg))
  16. {
  17. if (msg.message == WM_LBUTTONDOWN &&
  18. msg.x > 474 && msg.x < 474 + 300 && msg.y>75 &&
  19. msg.y < 75 + 140)
  20. {
  21. flag = 1;//表示选中
  22. }
  23. else if (msg.message == WM_LBUTTONUP && flag)
  24. {
  25. EndBatchDraw();
  26. break;
  27. }
  28. }
  29. EndBatchDraw();
  30. }
  31. mciSendString("close res/bg.mp3", 0, 0, 0);
  32. }

2.25画面巡视:viewScene()

  1. void viewScene()//视角移动
  2. {
  3. int xMax = WIN_WIDTH - imgBg.getwidth();//窗口宽-图片宽(坐标为负数)
  4. vector2 point[9] = { {550,80},{530,160},{630,170},{530,200},{515,270},
  5. {565,370},{605,340},{705,280},{690,340} };//阅览僵尸站位(可修改)
  6. int index[9];
  7. for (int j = 0; j < 9; j++)
  8. {
  9. index[j] = rand() % 11;//僵尸起始帧序号
  10. }
  11. int scenecount = 0;
  12. //画面巡视
  13. for (int i = 0; i >= xMax; i -= 2)
  14. {
  15. BeginBatchDraw();
  16. putimage(i, 0, &imgBg);
  17. scenecount++;
  18. for (int k = 0; k < 9; k++)//绘制僵尸站位
  19. {
  20. putimagePNG(point[k].x - xMax + i, point[k].y, &imgzmStand[index[k]]);
  21. if (scenecount >= 10)
  22. index[k] = (index[k] + 1) % 11;
  23. }
  24. if (scenecount >= 10)
  25. scenecount = 0;
  26. EndBatchDraw();
  27. Sleep(5);
  28. }
  29. //画面停留时间(可修改)
  30. for (int i = 0; i < 50; i++)
  31. {
  32. BeginBatchDraw();
  33. putimage(xMax, 0, &imgBg);
  34. for (int k = 0; k < 9; k++)
  35. {
  36. putimagePNG(point[k].x, point[k].y, &imgzmStand[index[k]]);
  37. index[k] = (index[k] + 1) % 11;
  38. }
  39. EndBatchDraw();
  40. Sleep(30);
  41. }
  42. //画面返回
  43. for (int k = xMax; k <= -112; k += 2)
  44. {
  45. BeginBatchDraw();
  46. putimage(k, 0, &imgBg);
  47. scenecount++;
  48. for (int i = 0; i < 9; i++)
  49. {
  50. putimagePNG(point[i].x - xMax + k, point[i].y, &imgzmStand[index[i]]);
  51. if (scenecount >= 10)
  52. index[i] = (index[i] + 1) % 11;
  53. }
  54. if (scenecount >= 10)
  55. scenecount = 0;
  56. EndBatchDraw();
  57. Sleep(5);
  58. }
  59. }

2.26工具栏下降:barsDown()

  1. void barsDown()//工具栏下降
  2. {
  3. int hight = imgBar.getheight();//获取工具栏的高
  4. for (int i = -hight; i <= 0; i++)
  5. {
  6. BeginBatchDraw();
  7. putimage(-112, 0, &imgBg);
  8. putimagePNG(250, i, &imgBar);
  9. for (int k = 0; k < PLANT_COUNTS; k++)
  10. {
  11. int x = 338 + k * 65;
  12. int y = 6 + i;
  13. putimage(x, y, &imgCard[k]);
  14. }
  15. EndBatchDraw();
  16. Sleep(10);
  17. }
  18. }

2.27判定游戏结束:checkOver()

  1. bool checkOver()//游戏结束画面
  2. {
  3. bool ret = false;
  4. if (gameStatus == WIN)//游戏胜利
  5. {
  6. mciSendString("play res/win.mp3", 0, 0, NULL);
  7. Sleep(2000);
  8. mciSendString("close res/win.mp3", 0, 0, NULL);
  9. IMAGE* img7 = NULL;
  10. loadimage(img7, "res/gameWin.png");
  11. putimage(0, 0, img7);
  12. ret = true;
  13. }
  14. else if (gameStatus == FAIL)
  15. {
  16. mciSendString("play res/lose.mp3", 0, 0, NULL);
  17. Sleep(2000);
  18. mciSendString("close res/lose.mp3", 0, 0, NULL);
  19. IMAGE* img9 = NULL;
  20. loadimage(img9, "res/gameFail.png");
  21. putimage(0, 0, img9);
  22. ret = true;
  23. }
  24. return ret;
  25. }

2.28主函数

  1. int main()
  2. {
  3. gameInit();
  4. startUI();
  5. viewScene();
  6. barsDown();
  7. int timer = 0;//实现帧等待
  8. bool flag = true;
  9. while (1)//循环退出条件:用户单击
  10. {
  11. userclick();
  12. timer += getDelay();//运行时间间隔
  13. if (timer > 10)//运行时间间隔大于10毫秒
  14. {
  15. flag = true;
  16. timer = 0;//再初始化
  17. }
  18. if (flag)
  19. {
  20. flag = false;
  21. updateWindow();
  22. updateGame();
  23. if (checkOver())
  24. break;
  25. }
  26. }
  27. system("pause");//暂停
  28. return 0;
  29. }

3.头文件tools.h(工具文件)

  1. #pragma once
  2. #include <graphics.h>
  3. void putimagePNG(int picture_x, int picture_y, IMAGE* picture);
  4. int getDelay();

4.源文件tools.cpp(工具文件)

  1. #include "tools.h"
  2. // 载入PNG图并去透明部分
  3. void _putimagePNG(int picture_x, int picture_y, IMAGE* picture) //x为载入图片的X坐标,y为Y坐标
  4. {
  5. DWORD* dst = GetImageBuffer(); // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
  6. DWORD* draw = GetImageBuffer();
  7. DWORD* src = GetImageBuffer(picture); //获取picture的显存指针
  8. int picture_width = picture->getwidth(); //获取picture的宽度,EASYX自带
  9. int picture_height = picture->getheight(); //获取picture的高度,EASYX自带
  10. int graphWidth = getwidth(); //获取绘图区的宽度,EASYX自带
  11. int graphHeight = getheight(); //获取绘图区的高度,EASYX自带
  12. int dstX = 0; //在显存里像素的角标
  13. // 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
  14. for (int iy = 0; iy < picture_height; iy++)
  15. {
  16. for (int ix = 0; ix < picture_width; ix++)
  17. {
  18. int srcX = ix + iy * picture_width; //在显存里像素的角标
  19. int sa = ((src[srcX] & 0xff000000) >> 24); //0xAArrggbb;AA是透明度
  20. int sr = ((src[srcX] & 0xff0000) >> 16); //获取RGB里的R
  21. int sg = ((src[srcX] & 0xff00) >> 8); //G
  22. int sb = src[srcX] & 0xff; //B
  23. if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
  24. {
  25. dstX = (ix + picture_x) + (iy + picture_y) * graphWidth; //在显存里像素的角标
  26. int dr = ((dst[dstX] & 0xff0000) >> 16);
  27. int dg = ((dst[dstX] & 0xff00) >> 8);
  28. int db = dst[dstX] & 0xff;
  29. draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)
  30. | ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)
  31. | (sb * sa / 255 + db * (255 - sa) / 255);
  32. }
  33. }
  34. }
  35. }
  36. // 适用于 y <0 以及x<0的任何情况
  37. void putimagePNG(int x, int y, IMAGE* picture) {
  38. IMAGE imgTmp, imgTmp2, imgTmp3;
  39. int winWidth = getwidth();
  40. int winHeight = getheight();
  41. if (y < 0) {
  42. SetWorkingImage(picture);
  43. getimage(&imgTmp, 0, -y,
  44. picture->getwidth(), picture->getheight() + y);
  45. SetWorkingImage();
  46. y = 0;
  47. picture = &imgTmp;
  48. }
  49. else if (y >= getheight() || x >= getwidth()) {
  50. return;
  51. }
  52. else if (y + picture->getheight() > winHeight) {
  53. SetWorkingImage(picture);
  54. getimage(&imgTmp, x, y, picture->getwidth(), winHeight - y);
  55. SetWorkingImage();
  56. picture = &imgTmp;
  57. }
  58. if (x < 0) {
  59. SetWorkingImage(picture);
  60. getimage(&imgTmp2, -x, 0, picture->getwidth() + x, picture->getheight());
  61. SetWorkingImage();
  62. x = 0;
  63. picture = &imgTmp2;
  64. }
  65. if (x > winWidth - picture->getwidth()) {
  66. SetWorkingImage(picture);
  67. getimage(&imgTmp3, 0, 0, winWidth - x, picture->getheight());
  68. SetWorkingImage();
  69. picture = &imgTmp3;
  70. }
  71. _putimagePNG(x, y, picture);
  72. }
  73. int getDelay() {//时间差函数
  74. static unsigned long long lastTime = 0;
  75. unsigned long long currentTime = GetTickCount();//获取游戏开始到现在的时间
  76. if (lastTime == 0) {
  77. lastTime = currentTime;
  78. return 0;
  79. }
  80. else {
  81. int ret = currentTime - lastTime;
  82. lastTime = currentTime;
  83. return ret;//返回此次调用与上次调用的时间差
  84. }
  85. }

5.源文件vector2.cpp(工具文件:实现贝塞尔曲线)

  1. //头文件要求
  2. #include <cmath>
  3. #include "vector2.h"
  4. //加法
  5. vector2 operator +(vector2 x, vector2 y) {
  6. return vector2(x.x + y.x, x.y + y.y );
  7. }
  8. //减法
  9. vector2 operator -(vector2 x, vector2 y) {
  10. return vector2(x.x - y.x, x.y - y.y);
  11. }
  12. // 乘法
  13. vector2 operator *(vector2 x, vector2 y) {
  14. return vector2(x.x * y.x - x.y * y.y, x.y * y.x + x.x * y.y);
  15. }
  16. // 乘法
  17. vector2 operator *(vector2 y, float x) {
  18. return vector2(x*y.x, x*y.y);
  19. }
  20. vector2 operator *(float x, vector2 y) {
  21. return vector2(x * y.x, x * y.y);
  22. }
  23. //叉积
  24. long long cross(vector2 x, vector2 y) { return x.y * y.x - x.x * y.y; }
  25. //数量积 点积
  26. long long dot(vector2 x, vector2 y) { return x.x * y.x + x.y * y.y; }
  27. //四舍五入除法
  28. long long dv(long long a, long long b) {//注意重名!!!
  29. return b < 0 ? dv(-a, -b)
  30. : (a < 0 ? -dv(-a, b)
  31. : (a + b / 2) / b);
  32. }
  33. //模长平方
  34. long long len(vector2 x) { return x.x * x.x + x.y * x.y; }
  35. //模长
  36. long long dis(vector2 x) { return sqrt(x.x * x.x + x.y * x.y); }
  37. //向量除法
  38. vector2 operator /(vector2 x, vector2 y) {
  39. long long l = len(y);
  40. return vector2(dv(dot(x, y), l), dv(cross(x, y), l));
  41. }
  42. //向量膜
  43. vector2 operator %(vector2 x, vector2 y) { return x - ((x / y) * y); }
  44. //向量GCD
  45. vector2 gcd(vector2 x, vector2 y) { return len(y) ? gcd(y, x % y) : x; }
  46. vector2 calcBezierPoint(float t, vector2 p0, vector2 p1, vector2 p2, vector2 p3) {
  47. float u = 1 - t;
  48. float tt = t * t;
  49. float uu = u * u;
  50. float uuu = uu * u;
  51. float ttt = tt * t;
  52. vector2 p = uuu * p0;
  53. p = p + 3 * uu * t * p1;
  54. p = p + 3 * u * tt * p2;
  55. p = p + ttt * p3;
  56. return p;
  57. }

6.头文件vector2.h(工具文件:实现贝塞尔曲线)

  1. #pragma once
  2. //头文件要求
  3. #include <cmath>
  4. struct vector2 {
  5. vector2(int _x=0, int _y=0) :x(_x), y(_y) {}
  6. vector2(int* data) :x(data[0]), y(data[1]){}
  7. long long x, y;
  8. };
  9. //加法
  10. vector2 operator +(vector2 x, vector2 y);
  11. //减法
  12. vector2 operator -(vector2 x, vector2 y);
  13. // 乘法
  14. vector2 operator *(vector2 x, vector2 y);
  15. vector2 operator *(vector2, float);
  16. vector2 operator *(float, vector2);
  17. //叉积
  18. long long cross(vector2 x, vector2 y);
  19. //数量积 点积
  20. long long dot(vector2 x, vector2 y);
  21. //四舍五入除法
  22. long long dv(long long a, long long b);
  23. //模长平方
  24. long long len(vector2 x);
  25. //模长
  26. long long dis(vector2 x);
  27. //向量除法
  28. vector2 operator /(vector2 x, vector2 y);
  29. //向量膜
  30. vector2 operator %(vector2 x, vector2 y);
  31. //向量GCD
  32. vector2 gcd(vector2 x, vector2 y);
  33. //贝塞尔曲线
  34. vector2 calcBezierPoint(float t, vector2 p0, vector2 p1, vector2 p2, vector2 p3);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/571947
推荐阅读
相关标签
  

闽ICP备14008679号