全网最详细用c语言实现植物大战僵尸游戏(下)

 日刷百题,题刷百日!
归纳编程学习的感悟,
记录奋斗路上的点滴,
希望能帮到一样刻苦的你!
如有不足欢迎指正!
共同学习交流!
🌎欢迎各位→点赞 👍+ 收藏⭐️ + 留言​📝
冰冻三尺非一日之寒,水滴石穿非一日之功。

一起加油! 

游戏效果演示

植物大战僵尸


让大家久等了,各位看官们!

续接上文(上文链接植物大战僵尸(上)),我们完成了有关植物部分设计,接下来设计僵尸部分啦!

 九、创建僵尸和更新僵尸数状态

创建全局变量 ,僵尸结构体,里面包括僵尸的位置x,y,僵尸的照片帧,僵尸行走的速度,僵尸所在行,僵尸血量,僵尸状态,在创建一个僵尸结构体数组,相当于僵尸池,里面十个僵尸

struct zm
{
	int x, y;
	int frameIndex;//照片帧序号
	bool use;
	int speed;//僵尸的速度
	int row;
	int blood;
	bool dead;
	bool eating;//正在吃植物
};
struct zm zms[10];//僵尸个数
IMAGE imgzm[22];//存放僵尸图片数组
IMAGE imgzmdead[20];//存放僵尸死亡图片数组
IMAGE imgeat[21];//存放僵尸吃植物图片数组

在初始化gameInit()函数中,加载僵尸图片

//初始化僵尸数据
	memset (zms, 0, sizeof(zms));//初始化僵尸结构体
	for (int i = 0; i < 22; i++)
	{
		sprintf_s(plantname, sizeof(plantname), "res/zm/%d.png",i+1);//将僵尸文件名存储到字符数组
		loadimage(&imgzm[i], plantname);//加载僵尸图片
	}

在updategame()修改游戏数据 函数中设置创建僵尸函数

createzm();//创建僵尸
updatezm();//更新僵尸的状态

接下来我们写创建僵尸函数,首先找到一个没有用过的僵尸,使他处于使用状态,初始位置在屏幕最右边,随机出现在任意一行,僵尸处于正常行走状态

void createzm()
{
	
	int i = 0;
	int zmmax = sizeof(zms) / sizeof(zms[0]);//僵尸个数
	for (i = 0; i < zmmax && zms[i].use; i++);//寻找没用的僵尸
	if(i < zmmax)//找到可以用的僵尸
	{
		zms[i].use = true;
		zms[i].x = WIN_WIDTH;
        zms[i].row = rand() % 4;
		zms[i].y = 172 + 90 * (zms[i].row);
		zms[i].speed = 1;//一帧走1个像素
		zms[i].frameIndex = 0;
			
		
		zms[i].blood = 100;
		zms[i].dead = false;
		zms[i].eating = false;
    }
}

僵尸创建函数就写好了,但是像阳光一样,每调用一次就创建一个僵尸,一瞬间就创建完10个僵尸,如何修改呢?

与创建阳光球一样创造俩个静态变量,使用控制频率技巧。

表示调用50次该函数,才创建第一僵尸,也就是50帧创建一个僵尸,后面每隔100~200帧创建一个僵尸。

void createzm()
{
	
	static int zmfre = 50;
	static int count = 0;
	count++;
	if (count > zmfre)
	{
		count = 0;
		zmfre = rand() % 100 + 100;
		int i = 0;
		int zmmax = sizeof(zms) / sizeof(zms[0]);//僵尸个数
		for (i = 0; i < zmmax && zms[i].use; i++);//寻找没用的僵尸
		if (i < zmmax)//找到可以用的僵尸
		{
			

			zms[i].use = true;
			zms[i].frameIndex = 0;
			zms[i].x = WIN_WIDTH;
			zms[i].row = rand() % 4;
			zms[i].y = 172 + 90 * (zms[i].row);
			zms[i].speed = 1;//一帧走1个像素
			zms[i].blood = 100;
			zms[i].dead = false;
			zms[i].eating = false;

			
		}
	}
	
}

接下来像做阳光一样,在updategame()修改游戏数据 函数中设置更新僵尸状态函数

createzm();//创建僵尸

僵尸有俩个位置变换:1.更新僵尸图片帧序号 ,2.更新僵尸图片位置

我们先写更新僵尸图片位置,僵尸到达离左边屏幕170个像素,代表输了

void updatezm()//1.更新僵尸图片帧序号 2.更新僵尸图片位置
{
	int zmmax = sizeof(zms) / sizeof(zms[0]);
  //更新僵尸位置
	for (int i = 0; i < zmmax; i++)
	{
		if (zms[i].use)
		{
			zms[i].x = zms[i].x-zms[i].speed  ;
			if (zms[i].x < 170)
			{
				printf("game over\n");//操作台输出文本
				MessageBox(NULL, "over", "over", 0);//图像界面输出消息对话框
				exit(0);//结束
			}
		}
	}

}

注:一个MessageBox最多由四个部分组成。 因而,它的Show()方法的重载最多有四个参数,分别是内容(text)、标题(caption)、按钮(buttons)、图标(icon) 。函数的语法如下:

MessageBox.Show(text, caption, buttons, icon);//弹出MessageBox窗口

僵尸走的速度太快了,这里有俩个处理方法:

第一种将zms[i].speed = 1;//一帧走1个像素,zms[i].speed=0.5(俩帧走一个像素);

第二种使用控制频率技巧,表示每3帧才走一个像素;

void updatezm()//1.更新僵尸图片帧序号 2.更新僵尸图片位置
{
static int count = 0;
	count++;
	if (count > 2)
	{
       count=0;
      //更新僵尸图片位置
    }
}

下一步写 更新僵尸图片帧序号

这个比较简单,即更新zms[i].frameIndex 数据


   //更新僵尸图片帧序号
	for (int i = 0; i < zmmax; i++)
	{
		if (zms[i].use)
		{
			zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;
		}
	}

如果觉得僵尸走的太快了,像滑铲,也可以向上面一样使用控制频率技巧

//更新僵尸图片帧序号
	static int count2 = 0;
	count2 ++;
	if (count2 > 1)
	{
		count2 = 0;
		for (int i = 0; i < zmmax; i++)
		{
			if (zms[i].use)
			{
				zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;
			}
		}
	}

接下来,我们需要看效果,进行渲染僵尸图片即可

//渲染僵尸图片
	int zmmax = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < zmmax; i++)
	{
		if (zms[i].use)
		{
			//IMAGE* img = &imgzm[zms[i].frameIndex];

			//IMAGE* img = ((zms[i].dead) ? imgzmdead : imgzm);
			
			IMAGE* img  = imgzm;
			img = img + zms[i].frameIndex;//指针跳过几个IMAGE数据类型大小
			putimagePNG(zms[i].x, zms[i].y-45,img);

		}

		
	}

看效果图

十、发射(创建)子弹和更新子弹状态

定义子弹的结构体,子弹有爆炸状态和正常状态子弹,而且此处定义子弹行数是为了和僵尸行数对比,方便实现后面的碰撞情况

//子弹的数据类型
struct bullet
{
	int x, y;
	int row;
	bool use;
	int speed;
	bool blast;//是否爆炸
	int frameIndex;//子弹爆炸的帧图片
};
struct bullet bullets[30];
IMAGE imgbulletnormal;
IMAGE imgbulletblast[4];//子弹爆炸图片数组

然后在初始化函数gameInit()中, 初始化子弹

//初始化子弹数据
	memset(bullets, 0, sizeof(bullets));//初始化子弹结构体
	loadimage(&imgbulletnormal, "res/bullets/bullet_normal.png");//加载子弹图片

在更新游戏数据中创建俩个函数,发射子弹和更新子弹数据

shoot();//发射子弹
updatebullet();//更新子弹数据

设计发射子弹函数,怎么设计呢?

遍历4行8列植物数组,如果有豌豆并且有僵尸,就发射子弹,怎么该行判断有僵尸呢?上面僵尸结构体中的行数就有用了,如果该行有僵尸且僵尸走到一段距离时,则该行设置为1,表示该行有僵尸。

如何发射子弹,找一个没有被使用的子弹,对该子弹结构体赋值,对于子弹的初始行数,根据豌豆植物和僵尸所在行进行设计,子弹的坐标根据植物坐标进行调整设计在豌豆喷嘴处,为了不让子弹瞬间被用完,用上面常用的控制频率技巧。代码如下

void  shoot()
{
	int lines[4] = { 0 };
	int zmcount = sizeof(zms) / sizeof(zms[0]);
	int dangerx = 750;
	int bulletmax = sizeof(bullets) / sizeof(bullets[0]);
	for (int i = 0; i < zmcount; i++)
	{
		if (zms[i].use && zms[i].x < dangerx)
		{
			lines[zms[i].row] = 1;//该行有僵尸设为1

		}
	}
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 8; j++)
		{
			if (map[i][j].type == PEA + 1 && lines[i])
			{
				static int count = 0;
				count++;
				if (count > 10)
				{
					
					count = 0;
					int k = 0;
					for (k = 0; k < bulletmax && bullets[k].use; k++);
					if (k < bulletmax)
					{
						bullets[k].use = true;
						bullets[k].row = i;
						bullets[k].speed = 9;

						int zwx = 261 + 81 * j;//植物的坐标
						int zwy = 167 + 91 * i + 14;
						bullets[k].x = zwx +
                  imgplant[map[i][j].type - 1][0]->getwidth() - 10;//子弹的坐标
						bullets[k].y = zwy + 5;

						bullets[k].blast = false;
						bullets[k].frameIndex = 0;

					}
				}
			}
		}
	}
}

然后我们设计  updatebullet()更新子弹数据函数。

这个函数设计比较简单,就是更新每帧子弹位置变换的数据,一旦子弹到达屏幕最右边,则回收子弹

void  updatebullet()
{
	int countmax = sizeof(bullets) / sizeof(bullets[0]);
	for (int i = 0; i < countmax; i++)
	{
		if (bullets[i].use)
		{
			bullets[i].x = bullets[i].x + bullets[i].speed;
			if (bullets[i].x > WIN_WIDTH)
			{
				bullets[i].use = false;
			}
		}
	}
}

然后渲染子弹

//渲染子弹
	int bulletmax = sizeof(bullets) / sizeof(bullets[0]);
		for (int i = 0; i < bulletmax; i++)
		{
			if (bullets[i].use)
			{
				putimagePNG(bullets[i].x, bullets[i].y, &imgbulletnormal);
			}
	 }

十一、实现子弹和僵尸碰撞效果(子弹击中僵尸)

到目前为止已经设计好了子弹运动和僵尸运动,接下来应该处理子弹和僵尸碰撞效果了,我们创建僵尸结构体定义了初始100滴血量,死亡状态和死亡图片数组,在创建子弹结构体时,设置了爆炸状态,以及爆炸图片数组。

第一步,将初始化(加载)子弹爆炸图片和僵尸死亡图片

注意:子弹爆炸效果实现通过逐步放大碎片子弹,展现爆炸效果

//初始化爆炸子弹数据
	loadimage(&imgbulletblast[3], "res/bullets/bullet_blast.png");//爆炸子弹数组的最后一张图片为源爆炸图片
	for (int i = 0; i < 3; i++)
	{
		float k = (i + 1) * 0.2;
		loadimage(&imgbulletblast[i], "res/bullets/bullet_blast.png",
			imgbulletblast[3].getwidth() * k, imgbulletblast[3].getheight() * k, true);//等比例缩小爆炸图片
	}

	//初始化僵尸死亡图片
	for (int i = 0; i < 20; i++)
	{
		sprintf_s(plantname, sizeof(plantname), "res/zm_dead/%d.png", i + 1);
		loadimage(&imgzmdead[i], plantname);
	}

第二步,改变帧序号,改变子弹爆炸的帧序号和僵尸死亡的帧序号,在更新子弹数据函数和更新僵尸状态函数中进行编写,这里的就会引出一个问题,什么时候更新子弹爆炸和僵尸死亡图片帧序号,在判断子弹和僵尸碰撞时改变,那么先写一个检测子弹和僵尸碰撞检测函数,这个函数属于数据更改,放在更新数据函数中

void updategame()
{
collisioncheck();//碰撞检测
}

之所以在碰撞检测函数中封装子弹和僵尸碰撞检测函数,考虑到后面还有僵尸吃植物的情况

void  collisioncheck()
{
	checkbullet_to_zm();//子弹对僵尸的碰撞检测
	
}

首先,先找一个出现且没有爆炸的子弹,若子弹在的该行有正常行走的僵尸,且子弹在僵尸胸前到背后区间位置时,则子弹速变为0,开始进入爆炸状态,僵尸被击中扣除5点血量,直到僵尸血量为0,僵尸停下来,状态变成死亡状态

void checkbullet_to_zm();;//子弹对僵尸的碰撞检测
{
	int bulletmax = sizeof(bullets) / sizeof(bullets[0]);
	int zmmax = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < bulletmax; i++)
	{
		if (bullets[i].use == false || bullets[i].blast)
			continue;           //若子弹没出现或发生爆炸,则不执行后面检测,执行下一个子弹检测


		for (int j = 0; j < zmmax; j++)
		{
			if (zms[j].use == false)   //跳过没出现的僵尸
				continue;
			int x1 = zms[j].x + 80;
			int x2 = zms[j].x + 110;
			if (zms[j].dead == false && bullets[i].row == zms[j].row && bullets[i].x > x1 && bullets[i].x < x2)//发生检测碰撞,死了不检测
			{
				PlaySound("res/audio/peacrush1.wav", NULL, SND_FILENAME | SND_ASYNC);
				//mciSendString("play res/audio/splat2.mp3", 0, 0, 0);
				zms[j].blood = zms[j].blood - 5;
				bullets[i].blast = true;
				bullets[i].speed = 0;

				//检测血量是否为0(在碰撞时方便检测)
				if (zms[j].blood <= 0)
				{
					zms[j].dead = true;
					zms[j].speed = 0;
					zms[j].frameIndex = 0;

				}
				break;//这颗子弹检测结束,不用和下一个僵尸检测
			}
		}
	}
}

第三步,通过碰撞检测后,僵尸和子弹状态发生变化,此时以子弹和僵尸碰撞后的状态为条件来进行判断何时发生子弹爆炸和僵尸死亡图片的帧序号变化

在updatebullet()中更改子弹爆炸帧序号

void  updatebullet()
{
	int countmax = sizeof(bullets) / sizeof(bullets[0]);
	for (int i = 0; i < countmax; i++)
	{
		if (bullets[i].use)
		{
			bullets[i].x = bullets[i].x + bullets[i].speed;
			if (bullets[i].x > WIN_WIDTH)
			{
				bullets[i].use = false;//子弹到边界回收
			}
			//子弹碰撞时bullets[i].blast设为真,子弹碰撞后状态为爆炸状态
			if (bullets[i].blast)
			{
				bullets[i].frameIndex++;//如果子弹碰撞,则帧序号改变
				if (bullets[i].frameIndex >= 4)
				{
				bullets[i].use = false;//爆炸结束回收子弹
				}

			}
		}
	}
}

在updatezm()中更新僵尸死亡图片帧序号

//更新僵尸图片帧序号
	static int count2 = 0;
	count2++;
	if (count2 > 1)
	{
		count2 = 0;
		for (int i = 0; i < zmmax; i++)
		{
			if (zms[i].use)
			{
				if (zms[i].dead)//僵尸与子弹碰撞后,僵尸变为死亡状态
				{
					zms[i].frameIndex++;
					if (zms[i].frameIndex >= 20)
					{
						zms[i].use = false;//变灰烬结束,回收僵尸
						
						
					}
				}
				
				else//正常行走状态
				{
					zms[i].frameIndex = (zms[i].frameIndex + 1) % 22 ;//从下标1开始,因初始化下标0
				}
			}
		}
	}

最后对碰撞后的子弹和僵尸图片进行渲染,对原来的僵尸和子弹图片的渲染函数进行修改。

在updatewindow()进行渲染动作

//渲染僵尸图片
	int zmmax = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < zmmax; i++)
	{
		if (zms[i].use)
		{
			//IMAGE* img = &imgzm[zms[i].frameIndex];

			//IMAGE* img = ((zms[i].dead) ? imgzmdead : imgzm);
			
			IMAGE* img = NULL;
			if (zms[i].dead)
			{
				img = imgzmdead;
			}
			
			else
			{
				img = imgzm;
			}
			img = img + zms[i].frameIndex;//指针跳过几个IMAGE数据类型大小
			putimagePNG(zms[i].x, zms[i].y-45,img);

		}

		
	}
	

	//渲染子弹
	int bulletmax = sizeof(bullets) / sizeof(bullets[0]);
		for (int i = 0; i < bulletmax; i++)
		{
			if (bullets[i].use)
			{ 
				if (bullets[i].blast)
				{
					IMAGE* img = &imgbulletblast[bullets[i].frameIndex];
					putimagePNG(bullets[i].x, bullets[i].y, img);
				}
				else
				{
					putimagePNG(bullets[i].x, bullets[i].y, &imgbulletnormal);
				}
			}
			
	 }

运行一下看一下效果

十二、实现僵尸和植物碰撞效果(僵尸吃植物)

首先,对植物结构体进行增加俩个成员,deadtime表示吃几次植物会死亡,eated表示植物的状态(被吃状态),植物和僵尸进行碰撞后,植物状态变为被吃;在种植植物时,将map[row][col].eated=false;;同样的,创建僵尸时僵尸结构体成员bool eating=false,植物和僵尸进行碰撞后,僵尸状态状态变为吃,当僵尸处于吃状态,则僵尸吃的动作帧改变。

struct plant
{
	int type;//0表示没有植物,1表示选中第一种植物,2表示选中第二种植物
	int frameIndex;//序列帧的序号
	
	bool eated;//是否被吃
	int deadtime;//死亡计数

	int timer;//喷射阳光的计时器
	int x, y;
};

像第十一节一样

第一步,将初始化(加载)僵尸吃植物图片(注:植物被吃时图像画面没有状态改变,不需要初始化)

//初始化僵尸吃东西的图片
for (int i = 0; i < 21; i++)
{
	sprintf_s(plantname, sizeof(plantname), "res/zm_eat/%d.png", i + 1);
	loadimage(&imgeat[i], plantname);
}

第二步,写僵尸和植物的碰撞函数

void  collisioncheck()
{
	checkbullet_to_zm();//子弹对僵尸的碰撞检测
	checkzm_to_plant();//僵尸对植物的碰撞检测

}

首先,先找一个未死且在使用的僵尸,若僵尸所在行有植物,且僵尸嘴部位置在植物左右区间位置时,若僵尸是正常行走状态,则僵尸停下来,状态变成吃植物状态,植物变成被吃状态;若僵尸处于吃植物状态,累计吃植物状态50次,植物消失,植物被吃状态解除(因为这里被吃状态解除,所以之前种植植物时初始化植物不被吃状态可以省略),僵尸速度恢复。

注:这里我们并没有用植物状态作为判断条件,所以可以省略植物结构体中植物状态的成员

void  checkzm_to_plant()
{
	int zmmax = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < zmmax; i++)
	{
		if (zms[i].use ==false|| zms[i].dead)
			continue;//若该僵尸未使用或者死亡,则判断下一个僵尸
		int row = zms[i].row;
		for (int j = 0; j < 8; j++)
		{
			//if (map[row][j].type == 0)
			//	continue;//僵尸所在行的j列没有植物,检测下一列
			if (map[row][j].type > 0)//僵尸所在行有植物
			{
				int plantx = 261 + 81 * j;
				int x1 = plantx + 10;//植物左右距离
				int x2 = plantx + 60;
				int x3 = zms[i].x + 80;//僵尸嘴巴位置
				if (x3 > x1 && x3 < x2)//僵尸和植物发生碰撞
				{
					if (zms[i].eating)//正在吃,修改数据
					{
						mciSendString("play res/audio/zmeat.mp3", 0, 0, 0);
					map[row][j].deadtime++;
					if (map[row][j].deadtime > 50)
					{
						map[row][j].deadtime = 0;
						map[row][j].type = 0;//植物消失
						zms[i].eating = false;
						zms[i].frameIndex = 0;
						zms[i].speed = 1;
						mciSendString("play res/audio/plantDead.mp3", 0, 0, 0);

					}
					}
					else
					{
					map[row][j].eated = true;
					map[row][j].deadtime = 0;
					zms[i].eating = true;
					zms[i].speed = 0;
					zms[i].frameIndex = 0;
					}
				}
			}
		}
	}
}

第三步,通过碰撞检测后,僵尸和植物状态发生变化,此时以植物和僵尸碰撞后的状态为条件来进行判断何时发生僵尸吃植物图片的帧序号变化

在updatezm()中更改僵尸吃植物图片帧序号

//更新僵尸图片帧序号
	static int count2 = 0;
	count2++;
	if (count2 > 1)
	{
		count2 = 0;
		for (int i = 0; i < zmmax; i++)
		{
			if (zms[i].use)
			{
				if (zms[i].dead)
				{
					zms[i].frameIndex++;
					if (zms[i].frameIndex >= 20)
					{
						zms[i].use = false;//变灰烬结束,回收僵尸
						
					}
				}
				else if (zms[i].eating)
				{
					zms[i].frameIndex = (zms[i].frameIndex + 1) % 21;
				}
				else//僵尸正常行走状态
				{
					zms[i].frameIndex = (zms[i].frameIndex + 1) % 22 ;//从下标1开始,因初始化下标0
				}
			}
		}

最后对僵尸吃植物的图片进行渲染,对原来的僵尸图片的渲染函数进行修改。

在updatewindow()进行渲染动作

//渲染僵尸图片
	int zmmax = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < zmmax; i++)
	{
		if (zms[i].use)
		{
			//IMAGE* img = &imgzm[zms[i].frameIndex];

			//IMAGE* img = ((zms[i].dead) ? imgzmdead : imgzm);
			
			IMAGE* img = NULL;
			if (zms[i].dead)
			{
				img = imgzmdead;
			}
			else if (zms[i].eating)//正在吃
			{
				img = imgeat;
			}
			else
			{
				img = imgzm;
			}
			img = img + zms[i].frameIndex;//指针跳过几个IMAGE数据类型大小
			putimagePNG(zms[i].x, zms[i].y-45,img);

		}

		
	}

运行程序,我们看一下效果

十三、片头巡场

第一步,屏幕长度是900个像素,图片大小是1400个像素,图片从(0,0)位置一直移动到(-500,0)位置处,每次移动3个像素,停顿1毫秒,这样初步就实现转场,但是没有僵尸,这怎么办呢?我们创建一个vector2类型的结构体,用来储存9个僵尸的位置,然后让僵尸和背景图片同频率移动,就可以看见僵尸出现,此时僵尸是不动的,僵尸出场时是各自摇摆抖动的,所以我们修改站立僵尸图片的帧序号,为了不抖动频率高,我们使用了控制频率技巧,为了使僵尸抖动姿势不同,对于开始初始的站立图片帧做了处理(每个僵尸起始帧图片为随机帧数站立图片)。

第二步,转到最后的位置会停留一会,这个照葫芦画瓢,渲染背景图片,渲染僵尸站立图片,并改变僵尸站立帧序号。

最后一步,是场景转回来,这个就很简单了,将转过去的代码复制下来,位移方向进行修改就好了

在此之前,我们先创建一个站立僵尸图片数组并且初始化

void viewscence()
{
	//PlaySound("res/audio/cannotselect.wav", NULL, SND_FILENAME | SND_ASYNC);
	mciSendString("play res/audio/Kitanai Sekai.mp3", 0, 0, 0);
	int xmin =WIN_WIDTH - imgBg.getwidth();//900-1400
	vector2 points[9] = {{560,80},{530,160},{630,170},{540,200},{520,270},{598,290},{610,340},{710,299},{700,340}	};
	int initposture[9];
	for (int i = 0; i < 9; i++)
	{
		initposture[i] = rand() % 11;//起始站姿图片序号
	}
	int count=0;
	for(int x = 0; x >= xmin; x -=3)//每次移动2个像素
	{
		BeginBatchDraw();
		putimage(x, 0, &imgBg);
		count++;
		for (int j = 0; j < 9; j++)
		{
			
			putimagePNG(points[j].x - xmin + x, points[j].y, &imgzmstand[initposture[j]]);
			if (count > 9)//说明移动18帧,站姿图片到下一张
			{
				
				initposture[j] = (initposture[j] + 1) % 11;//变换下一帧
			}
		}
		if (count > 9)
		{
			count = 0;
		}
		Sleep(1);//每次移动俩像素停顿3毫秒
		EndBatchDraw();

    }
	//停留1S左右
	count = 0;
	for (int i = 0; i < 30; i++)
	{
		BeginBatchDraw();
		putimage(xmin, 0, &imgBg);
		
		count++;
		for (int j = 0;j < 9; j++)
		{
			putimagePNG(points[j].x, points[j].y, &imgzmstand[initposture[j]]);
			if (count > 2)
			{

				initposture[j] = (initposture[j] + 1) % 11;//变换下一帧
			}
			
		}
		if (count > 2)
		{
			count = 0;
		}

      	EndBatchDraw();
		Sleep(15);
	}
	
	 count = 0;
	for (int x = 0; x >= xmin; x -= 3)//每次移动2个像素
	{
		BeginBatchDraw();
		putimage(xmin-x, 0, &imgBg);
		count++;
		for (int j = 0; j < 9; j++)
		{
			
			putimagePNG(points[j].x  - x, points[j].y, &imgzmstand[initposture[j]]);
			if (count >9 )//说明移动18帧,站姿图片到下一张
			{

				initposture[j] = (initposture[j] + 1) % 11;//变换下一帧
			}
		}
		if (count > 9)
		{
			count = 0;
		}
		Sleep(1);//每次移动俩像素停顿1毫秒
		EndBatchDraw();
		
	}
	
}

十四、判断游戏输赢

为了方便我们枚举输赢,创建评判输赢的条件,杀十个僵尸就胜利,游戏状态变量

#define ZM_MAX 10
enum{GOING,WIN,FAIL};
int killcount;//已经杀掉的僵尸个数
int zmcount;//已经出现的僵尸个数
int gamestatus;

初始化这些变量

当出现僵尸个数等于10,就不创建了,表示把这出现的全部杀完就结束游戏,当然这句话也可以不加。

僵尸到家时,游戏结束,游戏状态变为失败

僵尸死亡时,则记录死亡个数加1,当僵尸死亡个数到10个,即游戏状态为胜利

接下来在主函数中判断游戏游戏胜利或者失败,每次更新数据后判断,失败或者胜利都退出循环

(1)若游戏状态为胜利,让胜利场景停2秒,在渲染胜利图片

(2)若游戏状态为失败,让失败场景停2秒,在渲染失败图片

bool checkover()
{
	int ret = false;
	if (gamestatus == WIN)
	{
		mciSendString("close res/audio/UraniwaNi.mp3 ", 0, 0, 0);

		mciSendString("play res/win.mp3", 0, 0, 0);
		Sleep(2000);
		loadimage(0, "res/gameWin.png");
		ret = true;
	}
	else if (gamestatus == FAIL)
	{
		mciSendString("close res/audio/UraniwaNi.mp3 ", 0, 0, 0);

		mciSendString("play res/lose.mp3", 0, 0, 0);
		Sleep(2000);
		loadimage(0, "res/fail2.png");
		ret = true;
	}
	return ret;
}

十五、总结

当然还有卡牌及工具栏自动下降没有实现,以及游戏音效没有

我们先说下音效

windows有俩个播放音乐的函数:mciSendString和PlaySound

使用这俩个函数前需要包含一个头文件和加载静态库

(1)mciSendString("close res/audio/UraniwaNi.mp3 ", 0, 0, 0)

mciSendString的函数使用格式如上,第一个参数用来发出指令,一共四个: open 打开音乐文件  play 播放音乐  repeat 重复播放  close 关闭音乐文件

我们插入背景音乐,一般用到play和close(保证音乐在某个区间播放)

注意的是repeat一般放在音乐文件路径后面,其他的在前面

mciSendString支持的格式有mp3

但是它需要前面一个音乐播放完,才能播放下一个音乐,不能实现音效重叠效果

(2)PlaySound函数的用法如下图,支持wav格式音乐,而且可以实现音乐重叠,在收集阳光时连续点击都要有声音,就用PlaySound函数

卡牌及工具栏下降实现还是蛮容易的,这里就不过多叙述了

完整的代码及素材链接:

植物大战僵尸源码及素材

评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日刷百题

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值