六盘水住房和城乡建设部网站,广州高端优秀网站改版设计公司,佛山关键词优化,wordpress文章编辑函数C 实现植物大战僵尸#xff08;四#xff09;
C 实现植物大战僵尸#xff0c;完结撒花#xff08;还有个音频稍卡顿的性能问题#xff0c;待有空优化解决#xff09;。目前基本的功能模块已经搭建好了#xff0c;感兴趣的友友可自行尝试编写后续游戏内容
因为 C 站不能…C 实现植物大战僵尸四
C 实现植物大战僵尸完结撒花还有个音频稍卡顿的性能问题待有空优化解决。目前基本的功能模块已经搭建好了感兴趣的友友可自行尝试编写后续游戏内容
因为 C 站不能上传动图所以游戏实际效果可看后续文章更新插一条试玩视频
后面项目全部源代码会上传至 C 站待上传
十三 实现僵尸吃植物
实现和原 UP 有差异僵尸捕获植物感觉很奇怪不如设计成植物同样有血量当植物血量为 0 时植物死亡
调整植物和僵尸结构体以及增加变量
/* 僵尸相关结构和变量 */
#define MAX_ZOMBIE_NUM 10
#define MAX_ZOMBIE_DEAD_PIC_NUM 10
#define MAX_ZOMBIE_EAT_PIC_NUM 21
#define MAX_ZOMBIE_PIC_NUM 22
typedef struct Zombie {int x; //当前 X 轴坐标int y; //当前 Y 轴坐标int frameId; //当前图片帧编号int speed; //僵尸移动的速度int row; //僵尸所在行int blood; //默认僵尸血条为 100bool isDead; //僵尸是否死亡bool isEating; //僵尸是否在吃植物, 这些状态改用枚举更好, 待优化bool used; //是否在使用
} Zombie;
Zombie zombies[MAX_ZOMBIE_NUM];
IMAGE imgZombies[MAX_ZOMBIE_PIC_NUM];
IMAGE imgDeadZombies[MAX_ZOMBIE_DEAD_PIC_NUM];
IMAGE imgZombiesEat[MAX_ZOMBIE_EAT_PIC_NUM];/* 植物相关结构和变量 */
typedef struct Plant // 植物结构体
{int type; //植物类型, -1 表示草地int frameId; //表示植物摆动帧int blood; //植物血量
} Plant;游戏初始化接口 gameInit加载图片至内存
for (int i 0; i MAX_ZOMBIE_EAT_PIC_NUM; i) //加载僵尸吃植物图片
{sprintf(name, res/zm_eat/0/%d.png, i 1);loadimage(imgZombiesEat[i], name);
}游戏更新窗口接口渲染图片至输出窗口
for (int i 0; i MAX_ZOMBIE_NUM; i) //渲染僵尸
{if (zombies[i].used) {if (zombies[i].isDead) putimagePNG(zombies[i].x, zombies[i].y 30, imgDeadZombies[zombies[i].frameId]);else if (zombies[i].isEating) putimagePNG(zombies[i].x, zombies[i].y 30, imgZombiesEat[zombies[i].frameId]);else putimagePNG(zombies[i].x, zombies[i].y 30, imgZombies[zombies[i].frameId]);}
}更新游戏属性的接口增加 eatPlants
/* 更新游戏属性的接口 */
void updateGame()
{updatePlantsPic();createSunshine();updateSunshine();createZombie();updateZombie();shoot();updateBullets();collsionCheck();eatPlants();
}/* 移除死亡的植物 */
Plant* plantDeath(Plant* plant)
{assert(plant);if (plant-type PEA) //释放对应种植植物内存free((PeaShooter*)plant);else if (plant-type SUNFLOWER)free((SunFlower*)plant);Grass* grassPtr (Grass*)calloc(1, sizeof(Grass)); //重置为草地assert(grassPtr);grassPtr-plant.type -1;return (Plant*)grassPtr;
}/* 僵尸吃植物接口 */
void eatPlants()
{PeaShooter* peaShooter NULL;int row 0, plantX 0, zombieCurrX 0;for (int i 0; i MAX_ZOMBIE_NUM; i) //遍历是否存在僵尸{if (zombies[i].used !zombies[i].isDead) //僵尸正在使用中, 且存活{row zombies[i].row;for (int j 0; j GRASS_GRID_COL; j) //遍历当前行是否存在植物{if (plants[row][j]-type PEA) {plantX GRASS_LEFT_MARGIN j * GRASS_GRID_WIDTH 5; zombieCurrX zombies[i].x 80;if (zombieCurrX plantX 10 zombieCurrX plantX 60) //当僵尸已经到达植物附近{zombies[i].isEating true;plants[row][j]-blood - 1; //植物扣血if (plants[row][j]-blood 0) //植物被杀死{plants[row][j] plantDeath(plants[row][j]); //移除死亡的植物zombies[i].frameId 0;zombies[i].isEating false; //僵尸解除吃植物状态}}}}}}
}最后更新僵尸状态在这里进行帧处理
void updateZombie()
{static int CallCnt 0; //延缓函数调用次数if (CallCnt 3) return;CallCnt 0;for (int i 0; i MAX_ZOMBIE_NUM; i){if (zombies[i].used){if (zombies[i].isDead){if (zombies[i].frameId MAX_ZOMBIE_DEAD_PIC_NUM) //僵尸死亡则更换死亡帧zombies[i].used false; //重置僵尸状态}else if (zombies[i].isEating){zombies[i].frameId zombies[i].frameId % MAX_ZOMBIE_EAT_PIC_NUM; //僵尸更换图片帧}else{zombies[i].x - zombies[i].speed; //僵尸行走zombies[i].frameId zombies[i].frameId % MAX_ZOMBIE_PIC_NUM; //僵尸更换图片帧}if (zombies[i].x 170) //目前先这样写待优化{printf(GAME OVER !);MessageBox(NULL, over, over, 0);exit(0);}}}
}效果展示
僵尸会对一条道路上的植物进行啃食在啃食期间会正常受到豌豆射手的攻击啃食结束后植物死亡 十四 向日葵生成阳光
实现和原 UP 有差异想保留原随机阳光球逻辑所以这里是做了兼容处理逻辑具体实现如下
向日葵结构体增加变量
enum SUN_SHINE_STATUS { UNUSED, PRODUCE, GROUND, COLLECT };/* 向日葵结构体 */
typedef struct SunFlower
{Plant plant;/* 这里也可以使用数组, 一个向日葵有多个阳光球成员*/SunShineBall sunShine; //向日葵生产的阳光球int timeInterval; //向日葵生产阳光的计时器int status; //向日葵生产的阳光球状态float t; //贝塞尔曲线时间点float speed; //阳光球移动速度vector2 p1, p2, p3, p4; //贝塞尔曲线位置点vector2 pCurr; //当前阳光球的位置
} SunFlower;实现向日葵生产阳光的接口
需要注意的是在收集向日葵生产太阳球时需要重置贝塞尔曲线
/* 实现向日葵生产太阳球 */
void produceSunShine()
{SunFlower* sunFlower NULL;for (int i 0; i GRASS_GRID_ROW; i) //遍历二维指针数组{for (int j 0; j GRASS_GRID_COL; j){if (plants[i][j]-type SUNFLOWER){sunFlower (SunFlower*)plants[i][j];switch (sunFlower-status){case COLLECT:sunFlower-t sunFlower-speed; //设置贝塞尔曲线开始时间sunFlower-pCurr sunFlower-p1 sunFlower-t * (sunFlower-p4 - sunFlower-p1); //构建贝塞尔曲线if (sunFlower-t 1) { sunShineVal 25;sunFlower-status UNUSED;resetVecotrVal(sunFlower, i, j);}break;case GROUND:if (--sunFlower-timeInterval 0) //超时则阳光消失{sunFlower-status UNUSED; //重置状态sunFlower-timeInterval MAX_TIME_INTERVAL * (4 rand() % 5);}break;case PRODUCE:sunFlower-t sunFlower-speed; //设置贝塞尔曲线开始时间sunFlower-pCurr calcBezierPoint(sunFlower-t,sunFlower-p1, sunFlower-p2, sunFlower-p3, sunFlower-p4); //构建贝塞尔曲线if (sunFlower-t 1){sunFlower-t 0;sunFlower-status GROUND;sunFlower-timeInterval MAX_TIME_INTERVAL * (4 rand() % 5);}break;case UNUSED:if (--sunFlower-timeInterval 0){sunFlower-status PRODUCE;sunFlower-timeInterval MAX_TIME_INTERVAL * (4 rand() % 5);}break;default:printf(ERROR);break;}}}}
}/* 重置贝塞尔曲线坐标值 */
void resetVecotrVal(SunFlower* sunFlower, int x, int y)
{assert(sunFlower);if (sunFlower-status COLLECT){sunFlower-p1 sunFlower-pCurr;sunFlower-p4 vector2(262, 0);sunFlower-t 0;const float distance dis(sunFlower-p1 - sunFlower-p4);sunFlower-speed 1.0 / (distance / 16.0);}else if (sunFlower-status UNUSED){const int distance (50 rand() % 50); //只往右抛即可const int currPlantX GRASS_LEFT_MARGIN y * GRASS_GRID_WIDTH 5;const int currPlantY GRASS_TOP_MARGIN x * GRASS_GRID_HIGHT 10;sunFlower-t 0;sunFlower-timeInterval MAX_TIME_INTERVAL * (4 rand() % 5);sunFlower-speed 0.05;sunFlower-p1 vector2(currPlantX, currPlantY);sunFlower-p2 vector2(sunFlower-p1.x distance * 0.3, sunFlower-p1.y - 100);sunFlower-p3 vector2(sunFlower-p1.x distance * 0.7, sunFlower-p1.y - 100);sunFlower-p4 vector2(currPlantX distance, currPlantY imgPlant[SUNFLOWER][0]-getheight() - imgSunShineBall[0].getheight());}
}在更新游戏属性的接口中调用
/* 更新游戏属性的接口 */
void updateGame()
{updatePlantsPic();createSunshine();produceSunShine();updateSunshine();createZombie();updateZombie();shoot();updateBullets();collsionCheck();eatPlants();
}其次在种植向日葵的时候需要进行新增成员的初始化
/* 种植植物接口, 主要释放草格子内存, 二维指针数组对应位置,指向初始化的植物 */
Plant* growPlants(Plant* plant, int type, int x, int y)
{assert(plant);free((Grass*)plant); //释放该位置草格子内存if (type PEA) //根据类型初始化 PeaShooter{PeaShooter* peaShooter (PeaShooter*)calloc(1, sizeof(PeaShooter)); //calloc 函数替代 malloc, 省略 memsetassert(peaShooter);peaShooter-shootSpeed DEFAULT_SHOOT_TIME; //豌豆射击速度, 或者叫豌豆发射子弹的时间间隔, -1 表示可发射子弹peaShooter-plant.blood 100;return (Plant*)peaShooter;}else if (type SUNFLOWER) //根据类型初始化 SunFlower{SunFlower* sunFlower (SunFlower*)calloc(1, sizeof(SunFlower));assert(sunFlower);sunFlower-plant.type 1;sunFlower-plant.blood 100;sunFlower-timeInterval MAX_TIME_INTERVAL * (4 rand() % 5); //增加游戏随机性/* 初始化贝塞尔曲线 */const int distance (50 rand() % 50); //只往右抛即可const int currPlantX GRASS_LEFT_MARGIN y * GRASS_GRID_WIDTH 5;const int currPlantY GRASS_TOP_MARGIN x * GRASS_GRID_HIGHT 10;sunFlower-t 0;sunFlower-speed 0.05;sunFlower-p1 vector2(currPlantX, currPlantY);sunFlower-p2 vector2(sunFlower-p1.x distance * 0.3, sunFlower-p1.y - 100);sunFlower-p3 vector2(sunFlower-p1.x distance * 0.7, sunFlower-p1.y - 100);sunFlower-p4 vector2(currPlantX distance, currPlantY imgPlant[SUNFLOWER][0]-getheight() - imgSunShineBall[0].getheight());return (Plant*)sunFlower;}
}在更新阳光球接口添加新增更新向日葵生产阳光球帧的逻辑
/* 更新随机阳光球接口, 主要更新随机阳光球的图片帧和处理飞跃状态时的 X Y 轴偏移 */
void updateSunshine()
{for (int i 0; i MAX_BALLS_NUM; i) {if (balls[i].used){if (balls[i].y balls[i].destination)balls[i].y 2; //每次移动两个像素else //当阳光下落至目标位置时, 停止移动{if (balls[i].timer MAX_TIME_INTERVAL) balls[i].timer;else balls[i].used false;}balls[i].frameId balls[i].frameId % SUM_SHINE_PIC_NUM; //修改当前图片帧编号, 并在到达 SUM_SHINE_PIC_NUM 时重置图片帧为 0}else if (balls[i].xOffset) //阳光球处于飞跃状态{if (balls[i].y 0 balls[i].x 262){const float angle atan((float)(balls[i].y - 0) / (float)(balls[i].x - 262)); //不断调整阳光球的位置坐标balls[i].xOffset 16 * cos(angle);balls[i].yOffset 16 * sin(angle);balls[i].x - balls[i].xOffset;balls[i].y - balls[i].yOffset;}else{balls[i].xOffset 0; //阳光球飞至计分器位置, 则将 xOffset 置 0, 且加上 25 积分balls[i].yOffset 0;sunShineVal 25;}}}/* 更新向日葵生产的日光 */SunFlower* sunFlower NULL;for (int i 0; i GRASS_GRID_ROW; i) //遍历二维指针数组{for (int j 0; j GRASS_GRID_COL; j){if (plants[i][j]-type SUNFLOWER){sunFlower (SunFlower*)plants[i][j];if (sunFlower-status GROUND || sunFlower-status PRODUCE)sunFlower-sunShine.frameId sunFlower-sunShine.frameId % SUM_SHINE_PIC_NUM; //修改当前图片帧编号, 并在到达 SUM_SHINE_PIC_NUM 时重置图片帧为 0 }}}
}在收集随机阳光接口中添加上收集向日葵生产的日光 新增逻辑
/* 收集随机阳光接口 */
void collectSunShine(ExMessage* msg)
{IMAGE* imgSunShine NULL;for (int i 0; i MAX_BALLS_NUM; i) //遍历阳光球{if (balls[i].used) //阳光球在使用中{imgSunShine imgSunShineBall[balls[i].frameId]; //找到对应的阳光球图片if (msg-x balls[i].x msg-x balls[i].x imgSunShine-getwidth() msg-y balls[i].y msg-y balls[i].y imgSunShine-getheight()) //判断鼠标移动的位置是否处于当前阳光球的位置{PlaySound(res/audio/sunshine.wav, NULL, SND_FILENAME | SND_ASYNC); //异步播放收集阳光球音效balls[i].used false; //将阳光球状态更改为未使用 (飞跃状态, 因为 xOffset 赋值了)const float angle atan((float)(balls[i].y - 0) / (float)(balls[i].x - 262)); //使用正切函数balls[i].xOffset 16 * cos(angle); //计算 X 轴偏移balls[i].yOffset 16 * sin(angle); //计算 Y 轴偏移}}}/* 收集向日葵生产的日光 */SunFlower* sunFlower NULL;for (int i 0; i GRASS_GRID_ROW; i) //遍历二维指针数组{for (int j 0; j GRASS_GRID_COL; j){if (plants[i][j]-type SUNFLOWER){sunFlower (SunFlower*)plants[i][j];imgSunShine imgSunShineBall[sunFlower-sunShine.frameId]; //找到对应的阳光球图片if (sunFlower-status GROUND) {if (msg-x sunFlower-pCurr.x msg-x sunFlower-pCurr.x imgSunShine-getwidth() msg-y sunFlower-pCurr.y msg-y sunFlower-pCurr.y imgSunShine-getheight()) //判断鼠标移动的位置是否处于当前阳光球的位置{PlaySound(res/audio/sunshine.wav, NULL, SND_FILENAME | SND_ASYNC); //异步播放收集阳光球音效sunFlower-status COLLECT;resetVecotrVal(sunFlower, i, j); //更改曲线坐标}}}}}
}最后只需要在 updateWindow 接口中渲染一下向日葵生产的阳光即可
SunFlower* sunFlower NULL;
for (int i 0; i GRASS_GRID_ROW; i) //渲染向日葵阳光
{for (int j 0; j GRASS_GRID_COL; j){if (plants[i][j]-type SUNFLOWER){sunFlower ((SunFlower*)plants[i][j]);if (sunFlower-status UNUSED){putimagePNG(sunFlower-pCurr.x, sunFlower-pCurr.y,imgSunShineBall[sunFlower-sunShine.frameId]);}} }
}效果展示
向日葵可以生产阳光生产阳光球后会以类似抛物线的形式贝塞尔曲线随机掉落在右一格的位置。鼠标移动至阳光球处阳光将会被收集阳光值增加 25 十五 片头僵尸展示
优化片头效果实现函数如下开局会先展示路边的僵尸
/* 展示界面的僵尸相关变量 */
#define VIEW_ZOMBIE_NUM 9
#define VIEW_ZOMBIE_PIC_NUM 11
IMAGE imgViewZombies[VIEW_ZOMBIE_PIC_NUM];/* 游戏开始前展示僵尸 */
void viewScence()
{int Xmin WIN_WIDTH - imgBg.getwidth(); //-500vector2 zombieVec[VIEW_ZOMBIE_NUM] { //展示场景中, 僵尸初始位置{550,80},{530,160},{630,170},{530,200},{515,270},{565,370},{605,340},{705,280},{690,340}};int frameIndexArr[VIEW_ZOMBIE_NUM];for (int i 0; i VIEW_ZOMBIE_NUM; i)frameIndexArr[i] rand() % VIEW_ZOMBIE_PIC_NUM;int cycleNum 0; //利用循环计数, 解决僵尸抖动过快for (int x 0; x Xmin; x - 2) //缓慢移动展示僵尸{BeginBatchDraw(); //双缓冲解决闪屏putimage(x, 0, imgBg);cycleNum; //当循环十次后, 更换每只僵尸的帧图片for (int i 0; i VIEW_ZOMBIE_NUM; i) //循环僵尸个数{putimagePNG(zombieVec[i].x - Xmin x, zombieVec[i].y, imgViewZombies[frameIndexArr[i]]); //渲染僵尸图片if (cycleNum 2)frameIndexArr[i] (frameIndexArr[i]) % VIEW_ZOMBIE_PIC_NUM; //更换帧图}if (cycleNum 2) cycleNum 0; //重置循环计数EndBatchDraw();Sleep(5);}//停留 3 S 展示for (int k 0; k MAX_TIME_INTERVAL / 2; k){BeginBatchDraw(); //双缓冲解决闪屏putimage(Xmin, 0, imgBg); //相当于把图片向左移动 500 个像素for (int i 0; i VIEW_ZOMBIE_NUM; i) //循环僵尸个数{putimagePNG(zombieVec[i].x, zombieVec[i].y, imgViewZombies[frameIndexArr[i]]); //渲染僵尸图片frameIndexArr[i] (frameIndexArr[i]) % VIEW_ZOMBIE_PIC_NUM; //更换帧图}EndBatchDraw();Sleep(30);}//移动回主界面cycleNum 0;for (int x Xmin; x 0; x 2){BeginBatchDraw(); //双缓冲解决闪屏putimage(x, 0, imgBg);cycleNum; //当循环十次后, 更换每只僵尸的帧图片for (int i 0; i VIEW_ZOMBIE_NUM; i) //循环僵尸个数{if (zombieVec[i].x - Xmin x 0){putimagePNG(zombieVec[i].x - Xmin x, zombieVec[i].y, imgViewZombies[frameIndexArr[i]]); //渲染僵尸图片if (cycleNum 2)frameIndexArr[i] (frameIndexArr[i]) % VIEW_ZOMBIE_PIC_NUM; //更换帧图}}if (cycleNum 2) cycleNum 0; //重置循环计数EndBatchDraw();Sleep(5);}
}在主函数中调用 效果展示
游戏开场会缓慢的移动窗口至马路边停顿观察路边僵尸僵尸会一摇一摇的抖动然后游戏镜头会再缓慢移动至原界面 十六 植物栏滑动
在上述游戏界面拉回主界面过程中植物菜单栏会缓慢滑动出现具体实现如下
/* 植物栏滑动 */
void barsDown()
{int imgBarHeight imgBar.getheight();for (int i -imgBarHeight; i 6; i) //这里因为微调了植物卡片位置为 6{BeginBatchDraw();putimage(0, 0, imgBg); //渲染地图if (i 0) putimagePNG(250, i, imgBar); //但植物栏的位置为 0else putimagePNG(250, 0, imgBar); //渲染植物栏for (int j 0; j PLANT_CNT; j) //遍历植物卡牌putimage(PIC_LEFT_MARGIN j * PIC_WIDTH, i, imgCards[j]); //渲染植物卡牌 EndBatchDraw();Sleep(10);}Sleep(1000);
}在主函数中调用 效果展示
在上述开场游戏界面拉回主界面过程中植物菜单栏会缓慢滑动出现 十六 判断游戏结束
相关结构和变量
/* 游戏输赢相关的结构和变量 */
enum { GAMEING, WIN, FAIL };
#define INGAME_ZOMBIE_NUM 15
int killZombies 0;
int gameStatus GAMEING;创建僵尸接口时判断杀死的僵尸是否满足该局僵尸的数目了如果是则不再创建
/* 创建僵尸接口, 主要用于初始化僵尸 */
void createZombie()
{if (killZombies INGAME_ZOMBIE_NUM) return;static int zombieCallCnt 0; //延缓函数调用次数并增加些随机性static int randZombieCallCnt 500;if (zombieCallCnt randZombieCallCnt) return;randZombieCallCnt 300 rand() % 200;zombieCallCnt 0;for (int i 0; i MAX_ZOMBIE_NUM; i) //找一个未在界面的僵尸初始化{if (!zombies[i].used){zombies[i].row rand() % GRASS_GRID_ROW; //僵尸出现在第几行(从 0 开始)zombies[i].x WIN_WIDTH;zombies[i].y zombies[i].row * GRASS_GRID_HIGHT; //出现在草地的任意一格上zombies[i].frameId 0;zombies[i].speed 1; //僵尸的移动速度zombies[i].blood 100; //默认僵尸血条为 100zombies[i].isDead false; //僵尸存活zombies[i].isEating false;zombies[i].used true;break; //结束循环}}
}在原子弹和僵尸碰撞接口 collsionCheck 中 若杀死僵尸数大于或等于该局游戏僵尸数目则改变游戏状态 原更新僵尸接口中若僵尸已移动至最左端则游戏失败 最后在 main 函数中调用检验游戏状态的函数即可判断游戏输赢
checkGameOver 会用到 在线 MP3 音频转 WAV
/* 判断游戏输赢 */
IMAGE imgGameOver; //工具栏图片
bool checkGameOver()
{if (gameStatus WIN){Sleep(500);PlaySound(res/audio/win.wav, NULL, SND_FILENAME | SND_ASYNC); //异步播放音效loadimage(0, res/gameWin.png);return true;}else if (gameStatus FAIL){Sleep(500);PlaySound(res/audio/lose.wav, NULL, SND_FILENAME | SND_ASYNC); //异步播放音效loadimage(imgGameOver, res/gameFail.png);putimagePNG(300, 140, imgGameOver);return true;}return false;
}/* 主函数 */
int main()
{gameInit(); //不能把 startUI 放在 gameInit 前, gameInit 包含了创建游戏图形窗口startUI();viewScence();barsDown();updateWindow(); //窗口视图展示int timer 0; //用以计时 20 毫秒更新一次while (1){userClick(); //监听窗口鼠标事件timer getDelay();if (timer 20){updateWindow(); //更新窗口视图updateGame(); //更新游戏动画帧if (checkGameOver()) break; //判断游戏输赢timer 0;}}destroyPlants(); //释放内存system(pause);return 0;
}效果展示 一些游戏体验优化
① 豌豆不能太提前射击僵尸
在射击接口 shoot 里校验僵尸和窗口右端的距离即可 ② 卡牌太阳值不够不能选取
如果阳光值不够选取植物则渲染为灰色阳光值不够不能种植该植物且植物有冷却时间在冷却时间内植物不能种植
/* 游戏体验优化, 阳光值不足或植物冷却时不能种植 */
IMAGE imgBlackCards[PLANT_CNT]; //植物不能种植卡片
IMAGE imgFreezeCards[PLANT_CNT]; //植物冷却卡片
#define PEA_FREEZE_TIME 500
#define SUMFLOWER_FREEZE_TIME 200
static int peaPlantInterval 500;
static int sumFlowerPlantInterval 200;enum PLANT_CARD_STATUS { BRIGHT, GREY, FREEZE };
int plantCardStatus[PLANT_CNT]; //植物卡片状态数组更新植物卡牌状态函数代码
/* 更新植物卡牌状态 */
void updatePlantCardStatus()
{for (int i 0; i PLANT_CNT; i) //判断植物卡牌状态{if (i PEA){if (sunShineVal 100) //阳光值不够plantCardStatus[i] GREY; //卡片灰色else if (sunShineVal 100 peaPlantInterval PEA_FREEZE_TIME) //阳光值够但在冷却时间内plantCardStatus[i] FREEZE; //卡片冻结elseplantCardStatus[i] BRIGHT; //卡片原色}else if (i SUNFLOWER){if (sunShineVal 50)plantCardStatus[i] GREY;else if (sunShineVal 50 sumFlowerPlantInterval SUMFLOWER_FREEZE_TIME)plantCardStatus[i] FREEZE;elseplantCardStatus[i] BRIGHT;}}
}修改植物栏滑动逻辑
/* 植物栏滑动 */
void barsDown()
{int imgBarHeight imgBar.getheight();updatePlantCardStatus();for (int i -imgBarHeight; i 6; i) //这里因为微调了植物卡片位置为 6{BeginBatchDraw();putimage(0, 0, imgBg); //渲染地图if (i 0) putimagePNG(250, i, imgBar); //但植物栏的位置为 0else putimagePNG(250, 0, imgBar); //渲染植物栏for (int j 0; j PLANT_CNT; j) //遍历植物卡牌{if (plantCardStatus[j] BRIGHT)putimage(PIC_LEFT_MARGIN j * PIC_WIDTH, i, imgCards[j]); //渲染植物卡牌else if (plantCardStatus[j] GREY)putimage(PIC_LEFT_MARGIN j * PIC_WIDTH, i, imgBlackCards[j]);elseputimage(PIC_LEFT_MARGIN j * PIC_WIDTH, i, imgFreezeCards[j]);} EndBatchDraw();Sleep(10);}Sleep(1000);
}种植植物时记得扣除太阳值和重置冷却
/* 种植植物接口, 主要释放草格子内存, 二维指针数组对应位置,指向初始化的植物 */
Plant* growPlants(Plant* plant, int type, int x, int y)
{assert(plant);free((Grass*)plant); //释放该位置草格子内存if (type PEA) //根据类型初始化 PeaShooter{PeaShooter* peaShooter (PeaShooter*)calloc(1, sizeof(PeaShooter)); //calloc 函数替代 malloc, 省略 memsetassert(peaShooter);peaShooter-shootSpeed DEFAULT_SHOOT_TIME; //豌豆射击速度, 或者叫豌豆发射子弹的时间间隔, -1 表示可发射子弹peaShooter-plant.blood 100;//扣除太阳值和重置冷却sunShineVal - 100;peaPlantInterval 0;updatePlantCardStatus();return (Plant*)peaShooter;}else if (type SUNFLOWER) //根据类型初始化 SunFlower{SunFlower* sunFlower (SunFlower*)calloc(1, sizeof(SunFlower));assert(sunFlower);sunFlower-plant.type 1;sunFlower-plant.blood 100;sunFlower-timeInterval MAX_TIME_INTERVAL * (4 rand() % 5);/* 初始化贝塞尔曲线 */const int distance (50 rand() % 50); //只往右抛即可const int currPlantX GRASS_LEFT_MARGIN y * GRASS_GRID_WIDTH 5;const int currPlantY GRASS_TOP_MARGIN x * GRASS_GRID_HIGHT 10;sunFlower-t 0;sunFlower-speed 0.05;sunFlower-p1 vector2(currPlantX, currPlantY);sunFlower-p2 vector2(sunFlower-p1.x distance * 0.3, sunFlower-p1.y - 100);sunFlower-p3 vector2(sunFlower-p1.x distance * 0.7, sunFlower-p1.y - 100);sunFlower-p4 vector2(currPlantX distance, currPlantY imgPlant[SUNFLOWER][0]-getheight() - imgSunShineBall[0].getheight());sunShineVal - 50; //扣除太阳值和重置冷却sumFlowerPlantInterval 0;updatePlantCardStatus();return (Plant*)sunFlower;}
}原 updatePlantsPic 接口中更新 peaPlantInterval 和 sumFlowerPlantInterval
/* 更新植物图片帧接口, 主要用于实现植物摇摆 */
void updatePlantsPic()
{peaPlantInterval;sumFlowerPlantInterval;updatePlantCardStatus();for (int i 0; i GRASS_GRID_ROW; i) //遍历二维指针数组{for (int j 0; j GRASS_GRID_COL; j){if (plants[i][j]-type PEA //找到非草地的植物imgPlant[plants[i][j]-type][plants[i][j]-frameId] NULL) //将植物图片增加一, 判断是否到达图片帧末尾 plants[i][j]-frameId 0; //重置图片帧为零}}
}最后修改渲染卡片窗口的 updateWindow 函数 效果展示
如果阳光值不够选取植物则渲染为灰色阳光值不够不能种植该植物且植物有冷却时间在冷却时间内植物不能种植 ③ 添加各种音乐
加上音效
初始背景音乐
/* 游戏开始前的菜单界面 */
void startUI()
{IMAGE imageBg, imgMenu1, imgMenu2;loadimage(imageBg, res/menu.png);loadimage(imgMenu1, res/menu1.png);loadimage(imgMenu2, res/menu2.png);PlaySound(res/audio/bg.wav, NULL, SND_FILENAME | SND_ASYNC); //异步播放音效bool mouseStatus false; //0 表示鼠标未移动至开始游戏位置while (1) {BeginBatchDraw(); //双缓冲解决闪屏putimage(0, 0, imageBg);putimagePNG(UI_LEFT_MARGIN, UI_TOP_MARGIN, mouseStatus ? imgMenu2 : imgMenu1); //根据鼠标是否移动至游戏开始位置, 显示不同的图片ExMessage msg;if (peekmessage(msg)) //监听鼠标事件{if (msg.x UI_LEFT_MARGIN msg.x UI_LEFT_MARGIN UI_WIDTH msg.y UI_TOP_MARGIN msg.y UI_TOP_MARGIN UI_HIGHT) //当鼠标移动至开始游戏位置, 界面高亮{putimagePNG(UI_LEFT_MARGIN, UI_TOP_MARGIN, imgMenu2);mouseStatus true; //表示鼠标移动至开始游戏位置, 如果一直不移动鼠标则一直高亮if (msg.message WM_LBUTTONDOWN) //当鼠标点击时, 进入游戏{PlaySound(0, 0, SND_FILENAME);EndBatchDraw();return; //结束函数}}else mouseStatus false; //当鼠标未移动至开始游戏位置, 界面不高亮}EndBatchDraw();}
}片头背景音乐 僵尸来了背景音乐
在 createZombie 接口中添加如下代码
if (createZombies 1) PlaySound(res/audio/zombiescoming.wav, NULL, SND_FILENAME | SND_ASYNC); //异步播放音效选取植物背景音乐 种植物音乐种到不合适地方的音乐 豌豆射击的音乐 花了两块大洋买了原曲支持一下其实是为了游戏背景曲哈哈 遗留问题
音频播放同时播放两个音频可以实现功能就是没用到其它音频库导致游戏试玩时当有大量音频需要加载播放时会稍有卡顿待有空找个 Win 音频三方库优化一下吧
全部源代码和资源文件待后续把项目上传