说明:因为完整动图提交后提示违规,所以这里仅截图示意。如果需要演示视频,在评论中回复即可。
使用VS创建项目,使用空项目模板:
导入素材:在项目目录下,创建res文件夹,把解压后的素材拷贝到res目录下。
代码如下(需要逐行代码视频讲解,可回复“代码讲解“)。
#include <stdio.h>\n#include <graphics.h>\n#include \"tools.h\"\n#include <mmsystem.h>\n#pragma comment(lib, \"winmm.lib\")\n \n#define WIN_WIDTH 900\n#define WIN_HEIGHT 600\n \nenum { WAN_DOU, XIANG_RI_KUI, ZHI_WU_COUT };\nIMAGE imgBg;\nIMAGE imgBar;\nIMAGE imgCards[ZHI_WU_COUT];\nIMAGE* imgZhiWu[ZHI_WU_COUT][20];\nint curZhiWu;\nint curX, curY; //当前选中植物在移动过程中的坐标\n \nstruct zhiWu {\n int type; // >=1 0:没有植物\n int frameIndex;\n};\nstruct zhiWu map[3][9];\nint sunshine;\nint sunshineTable[ZHI_WU_COUT] = { 100, 50 };\n \nvoid gameInit() {\n loadimage(&imgBg, \"res/bg.jpg\");\n loadimage(&imgBar, \"res/bar.png\");\n sunshine = 150;\n curZhiWu = 0;\n memset(imgZhiWu, 0, sizeof(imgZhiWu));\n memset(map, 0, sizeof(map));\n \n char name[64];\n for (int i = 0; i < ZHI_WU_COUT; i++) {\n sprintf_s(name, sizeof(name), \"res/Cards/card_%d.png\", i + 1);\n loadimage(&imgCards[i], name);\n \n for (int j = 0; j < 20; j++) {\n sprintf_s(name, sizeof(name), \"res/zhiwu/%d/%d.png\", i, j + 1);\n imgZhiWu[i][j] = new IMAGE;\n loadimage(imgZhiWu[i][j], name);\n if (imgZhiWu[i][j]->getwidth() == 0) {\n delete imgZhiWu[i][j];\n imgZhiWu[i][j] = NULL;\n }\n }\n }\n \n initgraph(WIN_WIDTH, WIN_HEIGHT, 1);\n // 设置字体:\n LOGFONT f;\n gettextstyle(&f); // 获取当前字体设置\n f.lfHeight = 30; // 设置字体高度为 48\n f.lfWidth = 15;\n strcpy(f.lfFaceName, \"Segoe UI Black\");\n f.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿 \n settextstyle(&f); // 设置字体样式\n setbkmode(TRANSPARENT);\n setcolor(BLACK);\n \n mciSendString(\"play res/bg.mp3 repeat\", 0, 0, 0);\n}\n \nvoid updateWindow() {\n BeginBatchDraw();\n \n putimage(0, 0, &imgBg);\n putimagePNG(250, 0, &imgBar);\n \n for (int i = 0; i < ZHI_WU_COUT; i++) {\n int x = 338 + i * 64;\n int y = 6;\n putimage(x, y, &imgCards[i]);\n }\n \n if (curZhiWu > 0) { // 绘制正在移动的植物\n IMAGE* img = imgZhiWu[curZhiWu - 1][0];\n putimagePNG(curX - img->getwidth() * 0.5, curY - img->getheight() * 0.5, img);\n }\n \n for (int i = 0; i < 3; i++) {\n for (int j = 0; j < 9; j++) {\n if (map[i][j].type > 0) {\n int x = 260 + j * 81.6; // (msg.x - 260) / 81.6;\n int y = 180 + i * 103.6 + 14; // (msg.y - 210) / 103.6;\n int zhiWuIndex = map[i][j].type;\n int frameIndex = map[i][j].frameIndex;\n putimagePNG(x, y, imgZhiWu[zhiWuIndex - 1][frameIndex]);\n }\n }\n }\n \n char scoreText[8];\n sprintf_s(scoreText, sizeof(scoreText), \"%d\", sunshine);\n outtextxy(282 - 10 + 4, 50 + 15 + 2, scoreText);\n EndBatchDraw();\n}\n \nvoid userClick() {\n ExMessage msg;\n static int status = 0;\n if (peekmessage(&msg)) {\n if (msg.message == WM_LBUTTONDOWN) {\n if (msg.x > 338 && msg.x < 338 + 64 * ZHI_WU_COUT && msg.y>6 && msg.y < 96) {\n int index = (msg.x - 338) / 64;\n printf(\"%d\\n\", index);\n status = 1;\n curZhiWu = index + 1; // 1, 2 \n curX = msg.x;\n curY = msg.y;\n }\n }\n else if (msg.message == WM_MOUSEMOVE && status == 1) {\n curX = msg.x;\n curY = msg.y;\n }\n else if (msg.message == WM_LBUTTONUP && status == 1) {\n printf(\"up\\n\");\n if (msg.x > 260 && msg.y < 995 && msg.y > 180 && msg.y < 491) {\n if (sunshine >= sunshineTable[curZhiWu - 1]) {\n sunshine -= sunshineTable[curZhiWu - 1];\n int col = (msg.x - 260) / 81.6;\n int row = (msg.y - 210) / 103.6;\n printf(\"[%d,%d]\\n\", row, col);\n if (map[row][col].type == 0) {\n map[row][col].type = curZhiWu;\n map[row][col].frameIndex = 0;\n }\n }\n }\n status = 0;\n curZhiWu = 0;\n }\n }\n}\n \nvoid updateGame() {\n for (int i = 0; i < 3; i++) {\n for (int j = 0; j < 9; j++) {\n if (map[i][j].type > 0) {\n map[i][j].frameIndex++;\n if (imgZhiWu[map[i][j].type - 1][map[i][j].frameIndex] == NULL) {\n map[i][j].frameIndex = 0;\n }\n }\n }\n }\n}\n \nint main(void) {\n gameInit();\n \n int timer = 0;\n bool flag = true;\n while (1) {\n userClick();\n timer += getDelay();\n if (timer > 20) {\n timer = 0;\n flag = true;\n }\n if (flag) {\n flag = false;\n updateWindow();\n updateGame();\n }\n }\n \n return 0;\n}
添加启动菜单
创建菜单界面,代码如下:
void startUI() {\n IMAGE imgBg, imgMenu1, imgMenu2;\n loadimage(&imgBg, \"res/menu.png\");\n loadimage(&imgMenu1, \"res/menu1.png\");\n loadimage(&imgMenu2, \"res/menu2.png\");\n int flag = 0;\n while (1) {\n BeginBatchDraw();\n putimage(0, 0, &imgBg);\n putimagePNG(474, 75, flag ? &imgMenu2 : &imgMenu1);\n \n ExMessage msg;\n if (peekmessage(&msg)) {\n if (msg.message == WM_LBUTTONDOWN &&\n msg.x > 474 && msg.x < 474 + 300 && msg.y > 75 && msg.y < 75 + 140) {\n flag = 1;\n EndBatchDraw();\n }\n else if (msg.message == WM_LBUTTONUP && flag) {\n return;\n }\n }\n EndBatchDraw();\n }\n}
在main函数中调用菜单,代码如下:
int main(void) {\n gameInit();\n startUI();\n int timer = 0;\n bool flag = true;\n while (1) {\n userClick();\n timer += getDelay();\n if (timer > 20) {\n timer = 0;\n flag = true;\n }\n if (flag) {\n flag = false;\n updateWindow();\n updateGame();\n }\n }\n \n return 0;\n}
生产阳光
熟悉植物大战僵尸的同学都知道,种植植物才能消灭僵尸,但是种植植物,需要先具备一定数量的阳光值。初始的阳光值很小。有两种方式生成阳光:第一种,随机降落少量的阳光;第二种,通过种植向日葵,让向日葵自动生产阳光。我们先实现第一种方式。
定义一个结构体,来表示阳光球。因为阳光是以旋转的方式运动的,所以定义一个图片帧数组,通过循环播放图片帧来实现旋转效果。
IMAGE imgSunshineBall[29]; \nstruct sunshineBall { \n int x, y;\n int frameIndex;\n bool used;\n int destY;\n int timer = 0;\n};\nstruct sunshineBall balls[10];
在gameInit函数中,初始化阳光帧数组。
memset(balls, 0, sizeof(balls));\n for (int i = 0; i < 29; i++) {\n sprintf_s(name, sizeof(name), \"res/sunshine/%d.png\", i + 1);\n loadimage(&imgSunshineBall[i], name);\n }
创建阳光,代码如下。
void createSunshine() {\n int ballMax = sizeof(balls) / sizeof(balls[0]);\n \n static int frameCount = 0;\n static int fre = 400;\n frameCount++;\n if (frameCount >= fre) {\n fre = 200 + rand() % 200; \n frameCount = 0;\n int i;\n for (i = 0; i < ballMax && balls[i].used; i++);\n if (i >= ballMax) return;\n \n balls[i].used = true;\n balls[i].frameIndex = 0;\n balls[i].x = 260 + rand() % (905 - 260);\n balls[i].y = 60;\n balls[i].destY = 180 + (rand() % 4) * 90 + 20;\n balls[i].timer = 0;\n }\n}
修改阳光的位置和帧序号,代码如下。
void updateSunshine() {\n int ballMax = sizeof(balls) / sizeof(balls[0]);\n \n for (int i = 0; i < ballMax; i++) {\n if (balls[i].used) {\n balls[i].frameIndex = (balls[i].frameIndex + 1) % 29;\n if(balls[i].timer == 0) balls[i].y += 2;\n if (balls[i].y >= balls[i].destY) {\n balls[i].timer++;\n if (balls[i].timer > 100) balls[i].used = false;\n }\n }\n }\n}
在updateGame函数中调用以上两个函数 ,以创建阳光并更新阳光的状态。
createSunshine();\nupdateSunshine();
在updateWindow函数中,渲染阳光。
for (int i = 0; i < 10; i++) {\n if (balls[i].used) {\n putimagePNG(balls[i].x, balls[i].y, &imgSunshineBall[balls[i].frameIndex]);\n }\n}
收集阳光
当“阳光球”出现的时候,用户点击阳光球,就可以“收集”这个阳光,当前总的阳光值就会增加25点。在原版的植物大战僵尸游戏中,阳光球被收集后,会慢慢移动到顶部的“工具栏”的左侧。这个阳光球的“移动过程”,我们后续再实现。
定义一个全局变量,表示当前总的阳光值。
int sunshine;
在初始化gameInit中,设置一个初始值。
sunshine = 150;
创建收集阳光的函数,如下:
void collectSunshine(ExMessage* msg) {\n int count = sizeof(balls) / sizeof(balls[0]);\n int w = imgSunshineBall[0].getwidth();\n int h = imgSunshineBall[0].getheight();\n for (int i = 0; i < count; i++) {\n if (balls[i].used) {\n int x = balls[i].x;\n int y = balls[i].y;\n if (msg->x > x && msg->x < x + w && msg->y > y && msg->y < y + h) {\n balls[i].used = false;\n sunshine += 25;\n mciSendString(\"play res/sunshine.mp3\", 0, 0, 0);\n }\n }\n }\n}
在用户点击处理中,调用收集阳光的函数。
#include <mmsystem.h>\n#pragma comment(lib, \"winmm.lib\")\n \nvoid userClick() {\n ExMessage msg;\n static int status = 0;\n if (peekmessage(&msg)) {\n if (msg.message == WM_LBUTTONDOWN) {\n if (msg.x > 338 && msg.x < 338 + 65 * ZHI_WU_COUNT && msg.y < 96) {\n int index = (msg.x - 338) / 65;\n status = 1;\n curZhiWu = index + 1;\n } else {\n collectSunshine(&msg);\n }\n }\n // ...... \n }\n}
显示当前总的阳光值
在gameInit初始化中,设置字体。
LOGFONT f;\ngettextstyle(&f); // 获取当前字体设置\nf.lfHeight = 30; // 设置字体高度为 48\nf.lfWidth = 15;\nstrcpy(f.lfFaceName, \"Segoe UI Black\");\nf.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿 \nsettextstyle(&f); // 设置字体样式\nsetbkmode(TRANSPARENT);\nsetcolor(BLACK);
在updateWindow中绘制阳光值。
char scoreText[8];\nsprintf_s(scoreText, sizeof(scoreText), \"%d\", sunshine);\nouttextxy(276, 67, scoreText);
创建僵尸
创建僵尸的数据模型。这里一共创建了10个僵尸,这10个僵尸全部被消灭后,这个关卡就胜利了。
\nstruct zm {\n int x, y; \n int frameIndex;\n bool used;\n int speed;\n};\nstruct zm zms[10];\nIMAGE imgZM[22];
僵尸数组,以及僵尸序列帧图片数组,在gameInit函数中进行初始化,如下。(注意:把僵尸的素材图片保存到src/zm目录下。)
memset(zms, 0, sizeof(zms));\nsrand(time(NULL));\n \nfor (int i = 0; i < 22; i++) {\n sprintf_s(name, sizeof(name), \"res/zm/%d.png\", i + 1);\n loadimage(&imgZM[i], name);\n}
创建僵尸,代码如下:
void createZM() {\n static int zmFre = 500;\n static int count = 0;\n count++;\n if (count > zmFre) {\n zmFre = rand() % 200 + 300;\n count = 0;\n \n int i;\n int zmMax = sizeof(zms) / sizeof(zms[0]);\n for (i = 0; i < zmMax && zms[i].used; i++);\n if (i < zmMax) {\n zms[i].used = true;\n zms[i].x = WIN_WIDTH;\n zms[i].y = 180 + (1 + rand() % 3) * 100 - 8;\n zms[i].speed = 1;\n }\n }\n}
更新僵尸的数据(僵尸的图片帧序号、僵尸的位置),代码如下:
void updateZM() {\n int zmMax = sizeof(zms) / sizeof(zms[0]);\n \n static int count1 = 0;\n count1++;\n if (count1 > 2) {\n count1 = 0;\n for (int i = 0; i < zmMax; i++) {\n if (zms[i].used) {\n zms[i].x -= zms[i].speed;\n if (zms->x < 236 - 66) {\n printf(\"GAME OVER!\\n\");\n MessageBox(NULL, \"over\", \"over\", 0); //TO DO\n break;\n }\n }\n }\n }\n \n static int count2 = 0;\n count2++;\n if (count2 > 4) {\n count2 = 0;\n for (int i = 0; i < zmMax; i++) {\n if (zms[i].used) {\n zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;\n }\n }\n }\n}
在updateGame函数中,创建僵尸并更新僵尸数据,如下:
createZM();\nupdateZM();
创建绘制僵尸的接口, 如下:
void drawZM() {\n int zmCount = sizeof(zms) / sizeof(zms[0]);\n for (int i = 0; i < zmCount; i++) {\n if (zms[i].used) {\n IMAGE* img = &imgZM[zms[i].frameIndex];\n int x = zms[i].x;\n int y = zms[i].y - img->getheight();\n putimagePNG(x, y, img);\n }\n }\n}
在updateWindow函数中,绘制僵尸,如下:
drawZM();
实现阳光球的飞跃
现在的实现效果是,阳光被点击后,阳光球直接消失了!而原版的植物大战僵尸中,阳光被点击后,阳光会自动飞向左上角的位置,飞到终点后,阳光值才增加25点。我们的实现方式是,阳光球每次飞跃4个点,直到飞到终点,
如下图:
给阳光的结构体添加两个成员,表示飞跃过程中的偏移量:
struct sunshineBall { \n int x, y;\n int frameIndex;\n bool used;\n int destY;\n int timer;\n \n //添加以下两个成员\n float xOff;\n float yOff;\n};
在阳光被创建时,把变异量设置为0, 如下:
void createSunshine() {\n int ballMax = sizeof(balls) / sizeof(balls[0]);\n static int frameCount = 0;\n static int fre = 200;\n frameCount++;\n if (frameCount >= fre) {\n //...略\n balls[i].xOff = 0;\n balls[i].yOff = 0;\n }\n \n}
阳光被点击后,马上修改阳光球的xoff和yoff:
#include <math.h>\n \nvoid collectSunshine(ExMessage* msg) {\n int count = sizeof(balls) / sizeof(balls[0]);\n int w = imgSunshineBall[0].getwidth();\n int h = imgSunshineBall[0].getheight();\n for (int i = 0; i < count; i++) {\n if (balls[i].used) {\n int x = balls[i].x;\n int y = balls[i].y;\n if (msg->x > x && msg->x < x + w &&\n msg->y >y && msg->y < y + h) {\n balls[i].used = false;\n sunshine += 25;\n mciSendString(\"play res/sunshine.mp3\", 0, 0, 0);\n \n // 设置初始偏移量\n float destX = 262;\n float destY = 0;\n float angle = atan((y - destY) / (x - destX));\n balls[i].xOff = 4 * cos(angle);\n balls[i].yOff = 4 * sin(angle);\n }\n }\n }\n}
在阳光飞跃过程中更新阳光的位置,如下:(注意是在飞跃过程中,不断计算偏移量,效果更好。)
void updateSunshine() {\n int ballMax = sizeof(balls) / sizeof(balls[0]);\n for (int i = 0; i < ballMax; i++) {\n if (balls[i].used) {\n //略...\n }\n else if (balls[i].xOff) {\n float destX = 263;\n float destY = 0;\n float angle = atan((balls[i].y - destY) / (balls[i].x - destX));\n balls[i].xOff = 4 * cos(angle);\n balls[i].yOff = 4 * sin(angle);\n \n balls[i].x -= balls[i].xOff;\n balls[i].y -= balls[i].yOff;\n if (balls[i].y < 0 || balls[i].x < 262) {\n balls[i].xOff = 0;\n balls[i].yOff = 0;\n sunshine += 25; \n }\n }\n }\n}
删除原来被点击后,立即更新阳光值的代码。
//sunshine += 25;
修改渲染阳光的判断条件,如下:
for (int i = 0; i < ballMax; i++) {\n if (balls[i].used \n || balls[i].xOff) { //添加这个条件\n IMAGE* img = &imgSunshineBall[balls[i].frameIndex];\n putimagePNG(balls[i].x, balls[i].y, img);\n }\n}
此时已经能够实现阳光的飞跃了,但是飞跃动作太慢了,后期我们再优化。
发射豌豆僵尸靠近时,已经种植的植物豌豆就会自动发射“子弹”,我们先为子弹定义数据类型,如下:
struct bullet {\n int x, y;\n int row;\n bool used;\n int speed;\n};\nstruct bullet bullets[30];\nIMAGE imgBulletNormal;
在gameInit函数中,初始化“豌豆子弹池”和子弹的图片,如下:
loadimage(&imgBulletNormal, \"res/bullets/bullet_normal.png\");\nmemset(bullets, 0, sizeof(bullets));
在僵尸结构体中,添加成员row, 表示该僵尸所在的“行”,方便后续的判断。也可以不加,直接根据僵尸的y坐标来计算。
struct zm {\n int x, y; \n int frameIndex;\n bool used;\n int speed;\n \n int row; //0..2\n};
在createZM函数中,创建僵尸的时候,设置row成员的值,如下:
......\nif (i < zmMax) {\n zms[i].used = true;\n zms[i].x = WIN_WIDTH;\n \n zms[i].row = rand() % 3; // 0..2;\n zms[i].y = 172 + (1 + zms[i].row) * 100;\n \n zms[i].speed = 1;\n}\n......
创建shoot函数,实现豌豆发射子弹,如下:
void shoot() {\n int zmCount = sizeof(zms) / sizeof(zms[0]);\n int directions[3] = { 0 }; \n int dangerX = WIN_WIDTH - imgZM[0].getwidth();\n for (int i = 0; i < zmCount; i++) {\n if (zms[i].used && zms[i].x < dangerX) {\n directions[zms[i].row] = 1;\n }\n }\n \n for (int i = 0; i < 3; i++) {\n for (int j = 0; j < 9; j++) {\n if (map[i][j].type == WAN_DOU+1 && directions[i]) {\n static int count = 0;\n count++;\n if (count > 20) {\n count = 0;\n int k;\n int maxCount = sizeof(bullets) / sizeof(bullets[0]);\n for (k = 0; k < maxCount && bullets[k].used; k++);\n if (k < maxCount) {\n bullets[k].row = i;\n bullets[k].speed = 4;\n bullets[k].used = true;\n \n int zwX = 260 + j * 81.6; // (msg.x - 260) / 81.6;\n int zwY = 180 + i * 103.6 + 14; // (msg.y - 210) / 103.6;\n \n bullets[k].x = zwX + imgZhiWu[map[i][j].type - 1][0]->getwidth()-10;\n bullets[k].y = zwY + 5;\n }\n }\n }\n }\n }\n}
更新子弹的位置,如下:
void updateBullets() {\n int countMax = sizeof(bullets) / sizeof(bullets[0]);\n for (int i = 0; i < countMax; i++) {\n if (bullets[i].used) {\n bullets[i].x += bullets[i].speed;\n if (bullets[i].x > WIN_WIDTH) {\n bullets[i].used = false;\n }\n }\n }\n}
在updateGame函数中,发射子弹并更新子弹的位置,如下:
shoot();\nupdateBullets();
在updateWindow中绘制子弹,如下:
int bulletMax = sizeof(bullets) / sizeof(bullets[0]);\nfor (int i = 0; i < bulletMax; i++) {\n if (bullets[i].used) {\n putimagePNG(bullets[i].x, bullets[i].y, &imgBulletNormal);\n }\n}
子弹和僵尸的碰撞
子弹碰到僵尸之后,子弹会“爆炸”,同时僵尸会“掉血”。我们先给僵尸添加血量成员。
struct zm {\n //略...\n int blood;\n};
并在创建僵尸的时候,把血量初始化为100,如下:
//...\nzms[i].speed = 1;\nzms[i].blood = 100;
子弹在碰到僵尸之后才会爆炸,并显示爆炸图片:
所以,我们在子弹的结构体中添加两个成员,分别表示当前是否已经爆炸,以及爆炸的帧图片序号,如下:
struct bullet {\n //...\n bool blast;\n int frameIndex; \n};\nIMAGE imgBulletBlast[4];
在gameInit函数中对子弹帧图片数组,进行初始化,如下:
loadimage(&imgBulletBlast[3], \"res/bullets/bullet_blast.png\");\nfor (int i = 0; i < 3; i++) {\n float k = (i + 1) * 0.2;\n loadimage(&imgBulletBlast[i], \"res/bullets/bullet_blast.png\", \n imgBulletBlast[3].getwidth()*k,\n imgBulletBlast[3].getheight()*k, true);\n}
在发射子弹shoot函数中,对子弹的blast和帧序号frameIndex进行初始化,如下:
bullets[k].row = i;\nbullets[k].speed = 4;\nbullets[k].used = true;\n \nbullets[k].blast = false;\nbullets[k].blastTime = 0;
在更新子弹的updateBullets函数中,更新子弹爆炸的帧序号,如下:
bullets[i].x += bullets[i].speed;\nif (bullets[i].x > WIN_WIDTH) {\n bullets[i].used = false;\n}\n \nif (bullets[i].blast) {\n bullets[i].blastTime++;\n if (bullets[i].blastTime >= 4) {\n bullets[i].used = false;\n }\n}
进行碰撞检测,检查子弹和僵尸是否发生碰撞,如下:
void collisionCheck() {\n int bCount = sizeof(bullets) / sizeof(bullets[0]);\n int zCount = sizeof(zms) / sizeof(zms[0]);\n for (int i = 0; i < bCount; i++) {\n if (bullets[i].used == false || bullets[i].blast)continue;\n for (int k = 0; k < zCount; k++) {\n int x1 = zms[k].x + 80;\n int x2 = zms[k].x + 110;\n if (bullets[i].row == zms[k].row && bullets[i].x > x1 && bullets[i].x < x2) {\n zms[i].blood -= 20;\n bullets[i].blast = true;\n bullets[i].speed = 0;\n }\n }\n \n }\n}
在updateGame函数中,调用碰撞检测函数,如下:
collisionCheck();
渲染子弹的爆炸效果,如下:
int bulletMax = sizeof(bullets) / sizeof(bullets[0]);\nfor (int i = 0; i < bulletMax; i++) {\n if (bullets[i].used) {\n if (bullets[i].blast) {\n IMAGE* img = &imgBulletBlast[bullets[i].blastTime];\n int x = bullets[i].x + 12 - img->getwidth() / 2;\n int y = bullets[i].y + 12 - img->getheight() / 2;\n putimagePNG(x, y, img);\n \n /*bullets[i].used = false;*/\n }\n else {\n putimagePNG(bullets[i].x, bullets[i].y, &imgBulletNormal);\n }\n \n }\n}
僵尸死亡
僵尸被豌豆子弹击中后,会“掉血”,血量掉光了,就直接KO了,同时变成一堆“黑沙”。
给僵尸结构体添加dead成员,表示是否已经死亡,另外添加一个图片帧数组,用来表示变成成黑沙的过程。
struct zm {\n ......\n bool dead;\n};\nIMAGE imgZmDead[20];
在gameInit中对这个图片帧数组进行初始化。
for (int i = 0; i < 20; i++) {\n sprintf_s(name, sizeof(name), \"res/zm_dead/%d.png\", i + 1);\n loadimage(&imgZmDead[i], name);\n}
在碰撞检测中对僵尸的血量做检测,如果血量降到0,就设置为死亡状态。如下:
void collisionCheck() {\n int bCount = sizeof(bullets) / sizeof(bullets[0]);\n int zCount = sizeof(zms) / sizeof(zms[0]);\n for (int i = 0; i < bCount; i++) {\n if (bullets[i].used == false || bullets[i].blast)continue;\n for (int k = 0; k < zCount; k++) {\n int x1 = zms[k].x + 80;\n int x2 = zms[k].x + 110;\n if (zms[k].dead==false && //添加这个条件\n bullets[i].row == zms[k].row && bullets[i].x > x1 && bullets[i].x < x2) {\n zms[k].blood -= 20;\n bullets[i].blast = true;\n bullets[i].speed = 0;\n \n //对血量进行检测\n if (zms[k].blood <= 0) {\n zms[k].dead = true;\n zms[k].speed = 0;\n zms[k].frameIndex = 0;\n }\n break;\n }\n }\n }\n}
僵尸死亡后,在updateZM中,更新僵尸的状态(变成黑沙发)。如下:
static int count2 = 0;\ncount2++;\nif (count2 > 4) {\n count2 = 0;\n for (int i = 0; i < zmMax; i++) {\n if (zms[i].used) {\n //判断是否已经死亡\n if (zms[i].dead) {\n zms[i].frameIndex++;\n if (zms[i].frameIndex >= 20) {\n zms[i].used = false;\n }\n }\n else {\n zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;\n }\n }\n }\n}
绘制僵尸的黑沙状态,如下:
void drawZM() {\n int zmCount = sizeof(zms) / sizeof(zms[0]);\n for (int i = 0; i < zmCount; i++) {\n if (zms[i].used) {\n //选择对应的渲染图片\n IMAGE* img = (zms[i].dead) ? imgZmDead : imgZM;\n img += zms[i].frameIndex;\n \n int x = zms[i].x;\n int y = zms[i].y - img->getheight();\n putimagePNG(x, y, img);\n }\n }\n}
C语言项目更新中……