C语言游戏实战(12):植物大战僵尸(坤版)

植物大战僵尸

前言:

本游戏使用C语言和easyx图形库编写,通过这个项目我们可以深度的掌握C语言的各种语言特性和高级开发技巧,以及锻炼我们独立的项目开发能力,

在开始编写代码之前,我们需要先了解一下游戏的基本规则和功能:

游戏界面:游戏界面是一个矩形区域,玩家可以在区域内进行植物的放置和铲除等操作。

僵尸:僵尸会从游戏界面的右侧向左测移动,靠近植物后会停下来吃植物。

植物:不同的植物有不同的功能,在这里我们可以将植物分为三大类:

1. 生产型植物(如太阳花):这种植物的特点是在一定的时间生产出太阳,以增加太阳的产量。

2.发射型植物(如豌豆射手):这种植物的特点是发射相应类型的子弹,对僵尸产生伤害和特定的效果。

3. 爆炸性植物(火爆辣椒):这种植物的特点是对一定区域的所有僵尸产生高额伤害。(一次性植物)

接下来,我们将通过以下几个步骤来实现这个游戏:

初始化游戏界面。

实现僵尸的创建、僵尸的更新、僵尸吃植物的检测。

实现的植物的放置。

实现植物的功能:

对于生产型植物我们需要写出阳光的生产,以及阳光的收集操作。

对于发射型植物我们需要判断植物是否发射子弹,发射子弹后还需更新子弹、检测子弹与僵尸的碰撞。

对于爆炸型植物,我们需要判断僵尸是否在爆炸范围内,然后杀死在爆炸范围内的僵尸。

1. 初始化游戏界面

我们需要先将游戏地图、卡牌和卡牌槽绘制出来。可以利用windows自带的画图工具测出游戏地图的位置,从而将这些卡牌和卡牌槽放置到合适的位置。

//地图
putimage(-150, 0, &img);
//卡牌槽
putimagePNG(80, -10, &imgBar);	
//植物卡牌
	for (int i = 0; i < PLANT_COUNT + 2; i++)
	{
		if(i==PLANT_COUNT)
			putimagePNG(163 + i * 65 + 8, -5, &imgCards[i]);
		else
			putimagePNG(163 + i * 65, 0, &imgCards[i]);
	}

2.  放置植物

因为需要在这个9*5的草地区域内放置植物,所以我们得计算出每个草方块的位置。我利用画图软件测出每个草方块大约是长81、宽100个像素点。 第一个草方块距离游戏窗口大约101个像素点。

这样就可以得出每个草方块的位置:256 - 150 + col * 81;100 + row * 100 - 15; 

每个植物都有相似的特性,所以我们需要写一个放置植物信息的结构体。然后创建一个5*9的结构体数组,用来表示每个草方块上的植物。

enum {
	WAN_DOU, TAI_YANG, LA_JIAO, KUN_KUN, JIAN_GUO,
	HAN_BING_WAN_DOU, YING_TAO, SAN_TOU_WAN_DOU, PLANT_COUNT
};
struct plant
{
	int type;//植物类型
	int frameIndex;//植物动作帧
	//bool catched;//是否被吃
	int blood;//血量

	int shootTime;//植物子弹的射速

	int timer;//阳光生产的时间
	int x, y;//植物坐标
	bool shoot;//判断植物是否处于发射状态
};
struct plant map[5][9];

 在 Windows 编程中,我们可以利用下面的变量获取鼠标信息。

ExMessage msg;
msg.message
ExMessage msg;
static int status = 0;
if (peekmessage(&msg))//判断有没有消息
{
	if (msg.message == WM_LBUTTONDOWN)//左键按下
	{
        //鼠标是否在卡牌的位置按下
		if (msg.x > 163 && msg.x < 163 + 65 * (PLANT_COUNT+2) && msg.y < 96)
		{
			//mciSendString("play res/audio/bleep.mp3 alias BGM4", NULL, NULL, NULL);
			PlaySound("res/audio/bleep.wav", NULL, SND_FILENAME | SND_ASYNC);
			//PlaySound("res/sunshine.wav", NULL, SND_FILENAME | SND_ASYNC);

			int index = (msg.x - 163) / 65;
			//坤坤
		/*	if (index + 1 == KUN_KUN && sunshine >= 100)
			{
				curPlant = index + 1;
				status = 1;
				curX = msg.x;
				curY = msg.y;
				sunshine -= 100;
			}*/
			curPlant = index + 1;
			status = 1;
			curX = msg.x;
			curY = msg.y;
		}
	}
	else if (msg.message == WM_MOUSEMOVE && status == 1)//鼠标移动
	{
		curX = msg.x;
		curY = msg.y;
	}
	else if (msg.message == WM_LBUTTONUP)//鼠标放下
	{
		if (msg.x > 256 - 150 && msg.x < Wide - 70 && msg.y > 100 && msg.y < 590)
		{
			int row = (msg.y - 100) / 102;
			int col = (msg.x - 256 + 150 ) / 81;
			
			
			if (map[row][col].type == 0 && curPlant != PLANT_COUNT + 1 && curPlant != PLANT_COUNT + 2)
			{
				//printf("%d\n", map[col][row].type);
				map[row][col].type = curPlant;
				map[row][col].frameIndex = 0;

				if(curPlant!=0)
					PlaySound("res/audio/coffee.wav", NULL, SND_FILENAME | SND_ASYNC);

				if (curPlant == 5)
					map[row][col].blood = KUNKUN_BLOOD * 50;
				else
					map[row][col].blood = KUNKUN_BLOOD;

				map[row][col].shootTime = 0;
				map[row][col].shoot = false;

				map[row][col].x = 256 - 150 + col * 81;
				map[row][col].y = 100 + row * 100 - 15;
				
			}
			else if (map[row][col].type != 0 && curPlant == PLANT_COUNT + 1)
			{
				PlaySound("res/audio/coffee.wav", NULL, SND_FILENAME | SND_ASYNC);

				map[row][col].type = 0;
				map[row][col].blood = 0;
				
			}
		}
	}
	else if (msg.message == WM_RBUTTONDOWN)//鼠标右键
	{
		curPlant = 0;
		status = 0;
	}

3. 僵尸

 3.1 创建僵尸

僵尸也是和植物一样需要创建一个结构体存放信息。然后创建一个结构体数组,作为一个僵尸池,当需要创建一个僵尸时,我们就从这个池里取一个未被使用的僵尸。

struct zm
{
	int x, y;//僵尸的坐标
	int frameIndex;//僵尸动作帧
	bool used;//僵尸是否被使用
	int speed;//僵尸每一次移动的位移
	int row;//僵尸所在行
	int blood;//僵尸血量
	bool dead;//僵尸是否死亡
	bool eating;//僵尸是否在吃植物
	bool boom;//僵尸是否被炸死
	int zmSpeed;//僵尸的移动快慢
};
struct zm zms[ZM_MAX];
//找一个可用的僵尸
int i;
for (i = 0; i < zmMax && zms[i].used && zms[i].dead == false; i++);

到一定的时间就创建一个僵尸。


void createZM()
{
	if (zmCount >= zmCount_max)
		return;

	static int zmFre = 500;
	static int count = 0;
	
    //控制僵尸的生成快慢
	count++;
	if (count > zmFre)
	{
		count = 0;
		zmFre = 200;
		int i = 0;
		int zmMax = sizeof(zms) / sizeof(zms[0]);
		for (i = 0; i < zmMax && zms[i].used && zms[i].dead == false; i++);
		if (i < zmMax)
		{
			memset(&zms[i], 0, sizeof(zms[i]));
			zms[i].used = true;
			zms[i].speed = 2;
			zms[i].row = rand() % 5;
			zms[i].y = 100 + (zms[i].row) * 100 + 70;
			zms[i].x = Wide;
			zms[i].blood = ORDINARY_ZM_BLOOD;
			zms[i].dead = false;
			zms[i].boom = false;
			zms[i].zmSpeed = 4;
			zmCount++;
		}
	}
}

3. 2 检测僵尸对植物的伤害检测

僵尸靠近植物就将僵尸置为吃东西的状态,减植物的血量,当植物的血量小于等于0时,就去除植物。

void checkZM2Zhiwu()
{
	char name[64];
	int zCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < zCount; i++)
	{
		//killCount = 0;
		if (zms[i].dead)continue;

		//zms[i].chomptime = 0;
		int row = zms[i].row;
		for (int j = 0; j < 9; j++)
		{
			//if (map[row][j].type == 0)continue;
			
			//
			int zhiwuX = 101 + j * 81;
			int x1 = zhiwuX;
			int x2 = zhiwuX + 81;
			int x3 = zms[i].x + 100;
			if (x3 >= x1 && x3 <= x2)
			{
				if (map[row][j].blood <= 0 || (map[row][j].type == 0 && zms[i].eating != false))
				{
					map[row][j].blood = 0;
					map[row][j].type = 0;
					zms[i].eating = false;
					//zms[i].frameIndex = 0;
					zms[i].speed = 3;
				}
				else if (map[row][j].type != 0)
				{
					//mciSendString("play ZM_BGM repeat", NULL, NULL, NULL);
					//mciSendString("play name repeat", NULL, 0, NULL);
					zms[i].eating = true;
					zms[i].speed = 0;
					if (map[row][j].type != 3 && map[row][j].type != 7)
						map[row][j].blood--;
					//zms[i].frameIndex = 0;
				}

			}
			else if (x3 > 830)
			{
				zms[i].eating = false;
				zms[i].speed = 3;
			}
		}
	}
}

4. 植物子弹

4.1 发射子弹

当僵尸出现在游戏界面时,与该僵尸同一行的发射性植物就发射出子弹。

同样需要创建一个结构体存放子弹的信息,然后创建一个子弹池。

//子弹
struct bullet
{
	double x, y;//子弹的坐标
	bool used;//子弹是否被使用
	int row;//子弹所在行
	int speed;//子弹速度
	bool blast;//是否发生爆炸
	int frameIndex;//帧序号
};
//坤坤
struct bullet bullets[1000];

 以坤坤为例:

void shoot()
{
	int lines[5] = { 0 };
	int bulletMax = sizeof(bullets) / sizeof(bullets[0]);
	
	int zmCount = sizeof(zms) / sizeof(zms[0]);
	int dangerX = Wide - 80;
	for (int i = 0; i < zmCount; i++)
	{
		if (zms[i].dead == false && zms[i].x < dangerX && zms[i].x>100)
		{
			lines[zms[i].row] = 1;
		}
	}

	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			//坤坤
			if (map[i][j].type == KUN_KUN + 1 && lines[i])
			{
				map[i][j].shootTime++;
				if (map[i][j].shootTime > 20)
				{
					map[i][j].shootTime = 0;
                    //子弹池
					int k;
					for (k = 0; k < bulletMax && bullets[k].used; k++);
					if (k < bulletMax)
					{
						map[i][j].frameIndex = 3;
						bullets[k].used = true;
						bullets[k].row = i;
						bullets[k].speed = 10;

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

						int zwX = 256 - 150 + j * 81;
						int zwY = 100 + i * 100 - 15;
						bullets[k].x = zwX + imgPlant[map[i][j].type - 1][0]->getwidth() - 10;
						bullets[k].y = zwY + 5;
					}
				}
			}

			
		}
	}
}

4. 2  更新子弹

不断的改变加子弹的横坐标,当子弹出游戏界面时,子弹就被置为未被使用。

以坤坤为例:

void updateBullets_kunkun()
{
	int countMax = sizeof(bullets) / sizeof(bullets[0]);
	for (int i = 0; i < countMax; i++)
	{
		if (bullets[i].used)
		{
			bullets[i].x += bullets[i].speed;
			
			if (bullets[i].x > Wide)
			{
				bullets[i].used = false;
			}

		}
	}
}

 4.3 检测子弹对僵尸的伤害

当子弹靠近僵尸时减去僵尸的一定量血量,然后对僵尸造成一定的效果(例如,坤坤子弹的效果是击退僵尸,那么我们只需要更改僵尸的横坐标即可。),最后将子弹置为未被使用。

以坤坤为例:

void checkBullet2Zm_kunkun()
{
	int bCount = sizeof(bullets) / sizeof(bullets[0]);
	int zCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < bCount; i++)
	{
		{
			for (int j = 0; j < zCount; j++)
			{
				if (zms[j].used == false)continue;
				int x1 = zms[j].x + 80;
				int x2 = zms[j].x + 110;
				int x = bullets[i].x;
				if (zms[j].dead == false &&
					bullets[i].row == zms[j].row && x > x1 && x < x2 && bullets[i].used) {
					zms[j].blood -= 20;
					zms[j].x += 1;
					bullets[i].blast = true;
					bullets[i].speed = 0;
					//bullets[i].x = 0;

					if (zms[j].blood <= 0)
					{
						killCount++;

						zms[j].dead = true;
						zms[j].speed = 0;
						zms[j].frameIndex = 0;
					}
					break;
				}
			}
		}
	}
}

 5. 爆炸性植物

爆炸性植物是一次性的,当植物的动作帧执行完最后一帧后植物就置为死亡,在爆炸范围内的僵尸也死亡。

以火爆辣椒为例:

void checkBoom2Zm()
{
	int zCount = sizeof(zms) / sizeof(zms[0]);

	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			//火爆辣椒
			else if (map[i][j].type == LA_JIAO + 1)
			{
				if (map[i][j].frameIndex > 7)
				{
					for (int k = 0; k < zCount; k++)
					{
						if (zms[k].used == false)continue;

						if (zms[k].row == i && zms[k].x < Wide - 80 - 70 && zms[k].dead == false)
						{
							killCount++;

							zms[k].boom = true;
							zms[k].dead = true;
							zms[k].speed = 0;
							zms[k].frameIndex = 0;
							zms[k].blood = 0;
						}
					}
					if (map[i][j].frameIndex > 14)
					{
						map[i][j].type = 0;
						map[i][j].frameIndex = 0;
					}
				}
			}
		}
	}
}

6. 小推车

 创建存放小车信息的结构体数组,小推车一条路一辆共5辆,所以我们只需写一个大小为5的结构体数组即可。

//小推车
struct car
{
	bool move;//是否处于移动状态
	int x, y;//位置
	bool used;//是否被使用
};
struct car cars[5];

放置小推车 

	for (int i = 0; i < 5; i++)
	{
		cars[i].x = 50;
		cars[i].y = 100 + i * 100 - 15;
		cars[i].used = true;
		cars[i].move = false;
	}

检测小推车对小车的伤害 

当僵尸的横坐标小于等于小推车最左端的坐标时,小车置为移动状态,处于小推车左边的僵尸死亡。

void checkcars2Zm()
{
	for (int i = 0; i < 5; i++)
	{
		int carsX = cars[i].x + 70;
		for (int j = 0; j < ZM_MAX; j++)
		{
			if (zms[j].used && zms[j].dead == false && zms[j].row == i)
			{
				int zmX = zms[j].x + 80;
				if (carsX > zmX && cars[i].used)
				{
					if (cars[i].move == false)
						cars[i].move = true;
					else
					{
						killCount++;
						zms[j].dead = true;
						zms[j].speed = 0;
						zms[j].frameIndex = 0;
					}
				}
			}
		}
	}
}

更新小推车的位置

当小车被置为移动时,小推车开始移动。

void updatecar()
{
	for (int i = 0; i < 5; i++)
	{
		if (cars[i].move)
		{
			cars[i].x += 20;
		}
		if (cars[i].x > Wide)
		{
			cars[i].move = false;
			cars[i].used = false;
		}
	}
}

源码:

test.cpp文件: 

#include"game.h"

//判断文件是否存在
bool fileExist(const char* name)
{
	FILE* pf = fopen(name, "r");
	if (pf == NULL)
	{
		return false;
	}
	else
	{
		fclose(pf);
		return true;
	}
}

//初始化豌豆子弹的帧图片数组
void bulletsInit()
{
	//坤坤篮球
	loadimage(&imgBulletNormal, "res/bullets/basketball.png", 40, 40);
	memset(bullets, 0, sizeof(bullets));
	//初始化豌豆子弹的帧图片数组
	loadimage(&imgBallBlast[3], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png");
	for (int i = 0; i < 3; i++)
	{
		float k = (i + 1) * 0.2;
		loadimage(&imgBallBlast[i], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png",
			imgBallBlast[3].getwidth() * k,
			imgBallBlast[3].getheight() * k, true);
	}
	//豌豆子弹
	loadimage(&imgBulletNormal_wandou, "res/bullets/bullet_normal.png");
	memset(bullets_wandou, 0, sizeof(bullets_wandou));
	//初始化豌豆子弹的帧图片数组
	loadimage(&imgBallBlast_wandou[3], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png");
	for (int i = 0; i < 3; i++)
	{
		float k = (i + 1) * 0.2;
		loadimage(&imgBallBlast_wandou[i], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png",
			imgBallBlast_wandou[3].getwidth() * k,
			imgBallBlast_wandou[3].getheight() * k, true);
	}
	//寒冰豌豆子弹
	loadimage(&imgBulletNormal_hanbing, "res/bullets/PeaIce/PeaIce_0.png");
	memset(bullets_hanbing, 0, sizeof(bullets_hanbing));
	//初始化豌豆子弹的帧图片数组
	loadimage(&imgBallBlast_hanbing[3], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png");
	for (int i = 0; i < 3; i++)
	{
		float k = (i + 1) * 0.2;
		loadimage(&imgBallBlast_hanbing[i], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png",
			imgBallBlast_hanbing[3].getwidth() * k,
			imgBallBlast_hanbing[3].getheight() * k, true);
	}
	//三头豌豆子弹
	loadimage(&imgBulletNormal_santou, "res/bullets/bullet_normal.png");
	memset(bullets_santou, 0, sizeof(bullets_santou));
	//初始化豌豆子弹的帧图片数组
	loadimage(&imgBallBlast_santou[3], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png");
	for (int i = 0; i < 3; i++)
	{
		float k = (i + 1) * 0.2;
		loadimage(&imgBallBlast_santou[i], "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png",
			imgBallBlast_santou[3].getwidth() * k,
			imgBallBlast_santou[3].getheight() * k, true);
	}
}

void gameInit()
{
	char name[64];
	//音效
	mciSendString("open res/bg.mp3 alias BGM", NULL, NULL, NULL);
	mciSendString("open res/audio/UraniwaNi.mp3 alias BGM2", NULL, NULL, NULL);
	mciSendString("open res/audio/WateryGraves.mp3 alias BGM3", NULL, NULL, NULL);
	mciSendString("open res/audio/readysetplant.mp3 alias BGM4", NULL, NULL, NULL);
	mciSendString("open res/audio/chomp.mp3 alias ZM_BGM", NULL, NULL, NULL);
	
	loadimage(&img, "res/map/map0.jpg");
	loadimage(&imgBar, "res/bar5.png");
	//loadimage(&imgnotify, "res/screen/PanelBackground.png");
	
	memset(imgPlant, 0, sizeof(imgPlant));
	memset(map, 0, sizeof(map));
	memset(cars, 0, sizeof(cars));

	//开场动画
	for (int i = 0; i < 29; i++)
	{
		sprintf_s(name, sizeof(name), "res/yuanshen/%d.png", i + 1);
		loadimage(&imgopena[i], name, 1196, 670);
	}

	//植物卡牌
	for (int i = 0; i < PLANT_COUNT + 2; i++)
	{
		sprintf_s(name, sizeof(name), "res/Cards/card_%d.png", i + 1);
		loadimage(&imgCards[i], name,64,89);

		for (int j = 0; j < 20; j++)
		{
			sprintf_s(name, sizeof(name), "res/zhiwu/%d/%d.png",i , j + 1);
			//先判断文件是否存在
			if (fileExist(name))
			{
				imgPlant[i][j] = new IMAGE;
				if (i != 3)
				{
					loadimage(imgPlant[i][j], name);
				}
				else
				{
					loadimage(imgPlant[i][j], name,65,100);
				}
			}
			else
			{
				break;
			}
		}
	}

	memset(balls, 0, sizeof(balls));
	for (int i = 0; i < 29; i++)
	{
		sprintf_s(name, sizeof(name), "res/sunshine/%d.png", i + 1);
		loadimage(&imgSunshineBall[i], name);
	}

	curPlant = 0;
	sunshine = 50;

	initgraph(Wide, Hight);

	//设置字体
	LOGFONT f;
	gettextstyle(&f);
	f.lfHeight = 30;
	f.lfWeight = 15;
	strcpy(f.lfFaceName, "Segoe UI Black");
	f.lfQuality = ANTIALIASED_QUALITY;//抗锯齿效果
	settextstyle(&f);
	setbkmode(TRANSPARENT);//字体透明
	setcolor(BLACK);

	//初始化子弹的帧图片数组
	bulletsInit();

	//初始化僵尸数据
	memset(zms, 0, sizeof(zms));
	for (int i = 0; i < 22; i++)
	{
		sprintf_s(name, sizeof(name), "res/zm/%d.png", i + 1);
		loadimage(&imgZm[i], name);
	}

	killCount = 0;
	zmCount = 0;
	gameStatus = GOING;

	//初始化僵尸
	for (int i = 0; i < 38; i++)
	{
		sprintf_s(name, sizeof(name), "res/zm_dead/%d.png",i + 1);
		loadimage(&imgZMDead[i], name);
	}

	for (int i = 0; i < 21; i++)
	{
		sprintf_s(name, sizeof(name), "res/zm_eat/%d.png", i + 1);
		loadimage(&imgZMEat[i], name);
	}

	for (int i = 0; i < 11; i++)
	{
		sprintf_s(name, sizeof(name), "res/zm_stand/%d.png", i + 1);
		loadimage(&imgZmStand[i], name);
	}
	for (int i = 0; i < 20; i++)
	{
		sprintf_s(name, sizeof(name), "res/zm_dead2/%d.png", i + 1);
		loadimage(&imgzmboom[i], name);
	}
	//小推车
	loadimage(&imgcar, "res/Screen/car.png");
	for (int i = 0; i < 5; i++)
	{
		cars[i].x = 50;
		cars[i].y = 100 + i * 100 - 15;
		cars[i].used = true;
		cars[i].move = false;
	}
	//StartButton
	loadimage(&imgready, "res/Screen/Boom.png");
}

void drawZm()
{
	int zmCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < zmCount; i++)
	{
		if (zms[i].used)
		{//IMAGE* img = (zms[i].dead) ? &imgZMDead[zms[i].frameIndex] : &imgZm[zms[i].frameIndex];
			IMAGE* img = NULL;
			if (zms[i].dead)
			{
				if (zms[i].boom == true)
					img = &imgzmboom[zms[i].frameIndex];
				else
					img = &imgZMDead[zms[i].frameIndex];
			}
			else if (zms[i].eating) img = &imgZMEat[zms[i].frameIndex];
			else img = &imgZm[zms[i].frameIndex];

			putimagePNG(zms[i].x, zms[i].y - img->getheight(), img);
		}
	}
}

void drawSunshine()
{
	int ballMax = sizeof(balls) / sizeof(balls[0]);
	for (int i = 0; i < ballMax; i++)
	{
		//if (balls[i].used || balls[i].xoff)
		if(balls[i].used)
		{
			IMAGE* img = &imgSunshineBall[balls->frameIndex];
			//putimagePNG(balls[i].x, balls[i].y, img);
			putimagePNG(balls[i].pCur.x, balls[i].pCur.y, img);
		}
	}
}

void drawBullets_kunkun()
{
	int bulletsMax = sizeof(bullets) / sizeof(bullets[0]);
	for (int i = 0; i < bulletsMax; i++)
	{
		if (bullets[i].used) {
			if (bullets[i].blast) {
				IMAGE* img = &imgBallBlast[bullets[i].frameIndex];
				putimagePNG(bullets[i].x, bullets[i].y - 10, img);
			}
			else {
				putimagePNG(bullets[i].x, bullets[i].y, &imgBulletNormal);
			}
		}
	}
}

void drawBullets_wandou()
{
	int bulletsMax = sizeof(bullets_wandou) / sizeof(bullets_wandou[0]);
	for (int i = 0; i < bulletsMax; i++)
	{
		if (bullets_wandou[i].used) {
			if (bullets_wandou[i].blast) {
				IMAGE* img = &imgBallBlast_wandou[bullets_wandou[i].frameIndex];
				putimagePNG(bullets_wandou[i].x, bullets_wandou[i].y, img);
			}
			else {
				putimagePNG(bullets_wandou[i].x, bullets_wandou[i].y, &imgBulletNormal_wandou);
			}
		}
	}
}

void drawBullets_hanbing()
{
	int bulletsMax = sizeof(bullets_hanbing) / sizeof(bullets_hanbing[0]);
	for (int i = 0; i < bulletsMax; i++)
	{
		if (bullets_hanbing[i].used) {
			if (bullets_hanbing[i].blast) {
				IMAGE* img = &imgBallBlast_hanbing[bullets_hanbing[i].frameIndex];
				putimagePNG(bullets_hanbing[i].x, bullets_hanbing[i].y, img);
			}
			else {
				putimagePNG(bullets_hanbing[i].x, bullets_hanbing[i].y, &imgBulletNormal_hanbing);
			}
		}
	}
}

void drawBullets_santou()
{
	int bulletsMax = sizeof(bullets_santou) / sizeof(bullets_santou[0]);
	for (int i = 0; i < bulletsMax; i++)
	{
		if (bullets_santou[i].used) {
			if (bullets_santou[i].blast) {
				IMAGE* img = &imgBallBlast_santou[bullets_santou[i].frameIndex];
				putimagePNG(bullets_santou[i].x, bullets_santou[i].y, img);
			}
			else {
				putimagePNG(bullets_santou[i].x, bullets_santou[i].y, &imgBulletNormal_santou);
			}
		}
	}
}

void drawBullets()
{
	//坤坤
	drawBullets_kunkun();
	//豌豆
	drawBullets_wandou();
	//寒冰豌豆
	drawBullets_hanbing();
	//三头豌豆
	drawBullets_santou();
}

void show()//渲染游戏画面
{
	BeginBatchDraw();

	putimage(-150, 0, &img);
	putimagePNG(80, -10, &imgBar);
	char scoreText[8];
	char scoreText1[16];
	sprintf_s(scoreText, sizeof(scoreText), "%d", sunshine);
	sprintf_s(scoreText1, sizeof(scoreText1), "Wave %d zombies", wava_count);
	//sprintf_s(name, sizeof(name), "res/yuanshen/%d.png", i + 1);
	outtextxy(105, 60, scoreText);
	outtextxy(700, 570, scoreText1);
	//outtextxy(700, 570, "s");

	//小推车
	for (int i = 0; i < 5; i++)
	{
		if(cars[i].used)
			putimagePNG(cars[i].x, cars[i].y, &imgcar);
	}

	//植物卡牌
	for (int i = 0; i < PLANT_COUNT + 2; i++)
	{
		if(i==PLANT_COUNT)
			putimagePNG(163 + i * 65 + 8, -5, &imgCards[i]);
		else
			putimagePNG(163 + i * 65, 0, &imgCards[i]);
	}


	//植物
	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			if (map[i][j].type > 0)
			{
				/*int x = 256 - 150 + j * 81;
				int y = 100 + i * 100 - 15;*/
				int PlantType = map[i][j].type - 1;
				int index = map[i][j].frameIndex;
				if (map[i][j].type != 4 && map[i][j].type != YING_TAO + 1 && map[i][j].type != LA_JIAO + 1)
				{
					putimagePNG(map[i][j].x, map[i][j].y, imgPlant[PlantType][index]);
				}
				else if (map[i][j].type == YING_TAO + 1)
				{
					if (index == 8)
						putimagePNG(map[i][j].x - 75, map[i][j].y-35, imgPlant[PlantType][index]);
					else
						putimagePNG(map[i][j].x - 22, map[i][j].y, imgPlant[PlantType][index]);
				}
				else if (map[i][j].type == LA_JIAO + 1)
				{
					if (index > 7)
						putimagePNG(100, map[i][j].y - 35, imgPlant[PlantType][index]);
					else
						putimagePNG(map[i][j].x, map[i][j].y, imgPlant[PlantType][index]);
				}
				else
				{
					putimagePNG(map[i][j].x + 5, map[i][j].y - 20, imgPlant[PlantType][index]);
				}
			}
		}
	}

	//渲染子弹
	drawBullets();

	//铲子
	if (curPlant != PLANT_COUNT + 1)
	{
		IMAGE* img = imgPlant[8][0];
		putimagePNG(163 + 8 * 65 + 8, 0, img);
	}

	//僵尸
	drawZm();

	//渲染拖动中的植物
	if (curPlant > 0)
	{
		IMAGE* img = imgPlant[curPlant - 1][0];
		putimagePNG(curX - img->getwidth() / 2, curY - img->getheight() / 2, img);
	}

	//渲染阳光
	drawSunshine();
	EndBatchDraw();
}

void collectSunshine(ExMessage* msg)
{
	int count = sizeof(balls) / sizeof(balls[0]);
	int w = imgSunshineBall[0].getwidth();
	int h = imgSunshineBall[0].getheight();
	for (int i = 0; i < count; i++)
	{
		if (balls[i].used)
		{
			//int x = balls[i].x;
		    //int y = balls[i].y;
			int x = balls[i].pCur.x;
			int y = balls[i].pCur.y;

			if (msg->x > x && msg->x<x + w
				&& msg->y>y && msg->y < y + h)
			{
				sunshine += 25;
				//balls[i].used = false;
				balls[i].status = SUNSHINE_COLLECT;
				//音效
				//mciSendString("play res/sunshine.mp3", 0, 0, 0);
				//不支持MP3格式
				PlaySound("res/sunshine.wav", NULL, SND_FILENAME | SND_ASYNC);
				balls[i].p1 = balls[i].pCur;
				balls[i].p4 = vector2(100, 0);
				balls[i].t = 0;
				float distance = dis(balls[i].p1 - balls[i].p4);
				float off = 8;
				balls[i].speed = 1.0 / (distance / off);
				break;
				/*float destX = 0;
				float destY = 262;
				float angle = atan((y - destY) / (x - destX));
				balls[i].xoff = 4 * cos(angle);
				balls[i].yoff = 4 * sin(angle);*/
			}
		}
	}
}

void userClick()
{
	ExMessage msg;
	static int status = 0;
	if (peekmessage(&msg))//判断有没有消息
	{
		if (msg.message == WM_LBUTTONDOWN)//左键按下
		{
			if (msg.x > 163 && msg.x < 163 + 65 * (PLANT_COUNT+2) && msg.y < 96)
			{
				//mciSendString("play res/audio/bleep.mp3 alias BGM4", NULL, NULL, NULL);
				PlaySound("res/audio/bleep.wav", NULL, SND_FILENAME | SND_ASYNC);
				//PlaySound("res/sunshine.wav", NULL, SND_FILENAME | SND_ASYNC);

				int index = (msg.x - 163) / 65;
				//坤坤
			/*	if (index + 1 == KUN_KUN && sunshine >= 100)
				{
					curPlant = index + 1;
					status = 1;
					curX = msg.x;
					curY = msg.y;
					sunshine -= 100;
				}*/
				curPlant = index + 1;
				status = 1;
				curX = msg.x;
				curY = msg.y;
			}
			else
			{
				collectSunshine(&msg);
			}
		}
		else if (msg.message == WM_MOUSEMOVE && status == 1)//鼠标移动
		{
			curX = msg.x;
			curY = msg.y;
		}
		else if (msg.message == WM_LBUTTONUP)//鼠标放下
		{
			if (msg.x > 256 - 150 && msg.x < Wide - 70 && msg.y > 100 && msg.y < 590)
			{
				int row = (msg.y - 100) / 102;
				int col = (msg.x - 256 + 150 ) / 81;
				
				
				if (map[row][col].type == 0 && curPlant != PLANT_COUNT + 1 && curPlant != PLANT_COUNT + 2)
				{
					//printf("%d\n", map[col][row].type);
					map[row][col].type = curPlant;
					map[row][col].frameIndex = 0;

					if(curPlant!=0)
						PlaySound("res/audio/coffee.wav", NULL, SND_FILENAME | SND_ASYNC);

					if (curPlant == 5)
						map[row][col].blood = KUNKUN_BLOOD * 50;
					else
						map[row][col].blood = KUNKUN_BLOOD;

					map[row][col].shootTime = 0;
					map[row][col].shoot = false;

					map[row][col].x = 256 - 150 + col * 81;
					map[row][col].y = 100 + row * 100 - 15;
					
				}
				else if (map[row][col].type != 0 && curPlant == PLANT_COUNT + 1)
				{
					PlaySound("res/audio/coffee.wav", NULL, SND_FILENAME | SND_ASYNC);

					map[row][col].type = 0;
					map[row][col].blood = 0;
					
				}
				else if (curPlant == PLANT_COUNT + 2)
				{

					if (zmCount >= ZM_MAX)
						return;

					int i = 0;
					int zmMax = sizeof(zms) / sizeof(zms[0]);
					for (i = 0; i < zmMax && zms[i].used; i++);
					if (i < zmMax)
					{
						PlaySound("res/audio/coffee.wav", NULL, SND_FILENAME | SND_ASYNC);
						memset(&zms[i], 0, sizeof(zms[i]));
						zms[i].used = true;
						zms[i].speed = 2;
						zms[i].row = row;
						zms[i].y = 100 + (zms[i].row) * 100 + 70;
						zms[i].x = 256 - 150 + (col - 1) * 81;
						zms[i].blood = ORDINARY_ZM_BLOOD;
						zms[i].dead = false;
						zms[i].boom = false;
						zms[i].zmSpeed = 4;
						zmCount++;
					}
				}
			}
		}
		else if (msg.message == WM_RBUTTONDOWN)
		{
			curPlant = 0;
			status = 0;
		}
	}
}

void createSunshine()
{

	static int count = 0;
	static int fre = 200;
	count++;
	if (count >= fre)
	{
		fre = 200 + rand() % 200;
		count = 0;

		//从阳光池取一个可以使用的
		int ballMax = sizeof(balls) / sizeof(balls[0]);

		int i;
		for (i = 0; i < ballMax && balls[i].used; i++);
			if (i >= ballMax)return;

			balls[i].used = true;
			balls[i].frameIndex = 0;
		/*	balls[i].x = 160 + rand() % 600;
			balls[i].y = 60;
			balls[i].destY = (rand() % 4) * 90 + 160;*/
			balls[i].timer = 0;
		/*	balls[i].xoff = 0;
			balls[i].yoff = 0;*/

			balls[i].status = SUNSHINE_DOWN;
			balls[i].t = 0;
			balls[i].p1 = vector2(160 + rand() % 600, 60);
			balls[i].p4 = vector2(balls[i].p1.x, (rand() % 4) * 90 + 160);
			int off = 2;
			float distance = balls[i].p4.y - balls[i].p1.y;
			balls[i].speed = 1.0 / (distance / off);
	}

	int ballMax = sizeof(balls) / sizeof(balls[0]);
	//向日葵生产阳光
	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			if (map[i][j].type == TAI_YANG + 1)
			{
				map[i][j].timer++;
				if (map[i][j].timer > 100)
				{
					map[i][j].timer = 0;

					int k;
					for (k = 0; k < ballMax && balls[k].used; k++);
					if (k >= ballMax)return;

					balls[k].used = true;
					balls[k].p1 = vector2(map[i][j].x, map[i][j].y);
					int w = (40 + rand() % 50) * (rand() % 2 ? 1 : -1);
					balls[k].p4 = vector2(map[i][j].x + w,
						map[i][j].y + imgPlant[TAI_YANG][0]->getheight() - imgSunshineBall[0].getheight());
					balls[k].p2 = vector2(balls[k].p1.x + w * 0.3, balls[k].p1.y - 100);
					balls[k].p3 = vector2(balls[k].p1.x + w * 0.7, balls[k].p1.y - 100);
					balls[k].status = SUNSHINE_RPODUCT;
					balls[k].speed = 0.05;
					balls[k].t = 0;
				}
			}
		}
	}

}

void updatSunshine()
{
	int ballMax = sizeof(balls) / sizeof(balls[0]);
	for (int i = 0; i < ballMax; i++)
	{
		if (balls[i].used)
		{
			balls[i].frameIndex = (balls[i].frameIndex + 1) % 29;
			if (balls[i].status == SUNSHINE_DOWN)
			{
				struct sunshineBall* sun = &balls[i];
				sun->t += sun->speed;
				sun->pCur = sun->p1 + sun->t * (sun->p4 - sun->p1);
				if (sun->t >= 1)
				{
					sun->status = SUNSHINE_GROUND;
					sun->timer = 0;
				}
			}
			else if (balls[i].status == SUNSHINE_GROUND)
			{
				balls[i].timer++;
				if (balls[i].timer > 100)
				{
					balls[i].used = false;
					balls[i].timer = 0;
				}
			}
			else if (balls[i].status == SUNSHINE_COLLECT)
			{
				struct sunshineBall* sun = &balls[i];
				sun->t += sun->speed;
				sun->pCur = sun->p1 + sun->t * (sun->p4 - sun->p1);
				if (sun->t > 1)
				{
					sun->used = false;
					sunshine += 25;
				}
			}
			else if (balls[i].status == SUNSHINE_RPODUCT)
			{
				struct sunshineBall* sun = &balls[i];
				sun->t += sun->speed;
				sun->pCur = calcBezierPoint(sun->t,sun->p1, sun->p2, sun->p3, sun->p4);
				if (sun->t > 1)
				{
					sun->status = SUNSHINE_GROUND;
					sun->timer = 0;
				}

			}
		}
	}
}

int mciZmTime = 0;
void createZM()
{
	if (zmCount >= zmCount_max)
		return;

	static int zmFre = 5000000;
	static int count = 0;
	
	count++;
	if (count > zmFre)
	{
		if (mciZmTime == 0)
		{
			mciSendString("play res/audio/awooga.mp3", 0, 0, 0);
			mciZmTime++;
		}
		count = 0;
		zmFre = 202 - 20 * wava_count;
		int i = 0;
		int zmMax = sizeof(zms) / sizeof(zms[0]);
		for (i = 0; i < zmMax && zms[i].used && zms[i].dead == false; i++);
		if (i < zmMax)
		{
			memset(&zms[i], 0, sizeof(zms[i]));
			zms[i].used = true;
			zms[i].speed = 2;
			zms[i].row = rand() % 5;
			zms[i].y = 100 + (zms[i].row) * 100 + 70;
			zms[i].x = Wide;
			zms[i].blood = ORDINARY_ZM_BLOOD;
			zms[i].dead = false;
			zms[i].boom = false;
			zms[i].zmSpeed = 4;
			zmCount++;
		}
	}
}

int zmSpeed = 6;
void updateZM()
{
	int zmMax = sizeof(zms) / sizeof(zms[0]);
	static int count[ZM_MAX] = { 0 };
	//更新僵尸的位置
	for (int i = 0; i < zmMax; i++)
	{
		count[i]++;
		if (count[i] >= (gameStatus == GOING ? zms[i].zmSpeed : zmSpeed))
		{
			//printf("%d ", (gameStatus == GOING ? zms[i].zmSpeed : zmSpeed));
			count[i] = 0;

			if (zms[i].used)
			{
				if (zms[i].dead)
				{
					zms[i].frameIndex++;
					if (zms[i].boom == true)
					{
						if (zms[i].frameIndex >= 20)
						{
							//printf("%d ", killCount);
							zms[i].used = false;
							zms[i].row = 0;
							//killCount++;
							if (killCount >= ZM_MAX)
								gameStatus = WIN;
							else if (killCount >= zmCount_max)
							{
								wava_count++;
								zmCount_max *= 2;
								killCount = 0;
								zmCount = 0;
							}
						}
					}
					else
					{
						if (zms[i].frameIndex >= 38)
						{
							//printf("%d ", killCount);

							zms[i].used = false;
							zms[i].row = 0;
							
							if (killCount >= ZM_MAX)
								gameStatus = WIN;
							else if (killCount >= zmCount_max)
							{
								wava_count++;
								zmCount_max *= 2;
								killCount = 0;
								zmCount = 0;
							}
						}
					}
				}
				else if (zms[i].eating)
				{

					//mciSendString("play res/audio/chomp.mp3", 0, 0, 0);
					//mciSendString("play res/audio/chompsoft.mp3", 0, 0, 0);
					zms[i].frameIndex = (zms[i].frameIndex + 1) % 21;
				}
				else
				{
					zms[i].frameIndex++;
					if (zms[i].frameIndex >= 22)
					{
						zms[i].frameIndex = 0;
					}
				}
				zms[i].x -= zms[i].speed;
				if (zms[i].x < 0)
				{
					//printf("GAME OVER\n");
					//MessageBox(NULL, "over", "over", 0);//待优化
					//exit(0);//待优化
					gameStatus = FAIL;
				}
			}
		}
	}
}

void shoot()
{
	int lines[5] = { 0 };
	int bulletMax = sizeof(bullets) / sizeof(bullets[0]);
	int bulletMax_wandou = sizeof(bullets_wandou) / sizeof(bullets_wandou[0]);
	int bulletMax_hanbing = sizeof(bullets_hanbing) / sizeof(bullets_hanbing[0]);
	int bulletMax_santou = sizeof(bullets_santou) / sizeof(bullets_santou[0]);
	int zmCount = sizeof(zms) / sizeof(zms[0]);
	int dangerX = Wide - 80;
	for (int i = 0; i < zmCount; i++)
	{
		if (zms[i].dead == false && zms[i].x < dangerX && zms[i].x>100)
		{
			lines[zms[i].row] = 1;
		}
	}

	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			//坤坤
			if (map[i][j].type == KUN_KUN + 1 && lines[i])
			{
				map[i][j].shootTime++;
				if (map[i][j].shootTime > 20)
				{
					map[i][j].shootTime = 0;

					int k;
					for (k = 0; k < bulletMax && bullets[k].used; k++);
					if (k < bulletMax)
					{
						map[i][j].frameIndex = 3;
						bullets[k].used = true;
						bullets[k].row = i;
						bullets[k].speed = 10;

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

						int zwX = 256 - 150 + j * 81;
						int zwY = 100 + i * 100 - 15;
						bullets[k].x = zwX + imgPlant[map[i][j].type - 1][0]->getwidth() - 10;
						bullets[k].y = zwY + 5;
					}
				}
			}

			//豌豆
			else if (map[i][j].type == WAN_DOU + 1 && lines[i])
			{
				map[i][j].shootTime++;
				if (map[i][j].shootTime > 35)
				{
					map[i][j].shootTime = 0;
					int k;
					for (k = 0; k < bulletMax_wandou && bullets_wandou[k].used; k++);
					if (k < bulletMax_wandou)
					{
						//map[i][j].shoot = true;
						//if(map[i][j].frameIndex > 1)
						map[i][j].frameIndex = 2;
						bullets_wandou[k].used = true;
						bullets_wandou[k].row = i;
						bullets_wandou[k].speed = 8;

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

						int zwX = 256 - 150 + j * 81;
						int zwY = 100 + i * 100 - 15;
						bullets_wandou[k].x = zwX + imgPlant[map[i][j].type - 1][0]->getwidth() - 10;
						bullets_wandou[k].y = zwY + 5;
					}
				}
			}

			//寒冰豌豆
			else if (map[i][j].type == HAN_BING_WAN_DOU + 1 && lines[i])
			{
				map[i][j].shootTime++;
				if (map[i][j].shootTime > 35)
				{
					map[i][j].shootTime = 0;
					int k;
					for (k = 0; k < bulletMax_hanbing && bullets_hanbing[k].used; k++);
					if (k < bulletMax_hanbing)
					{
						//map[i][j].shoot = true;
						//if(map[i][j].frameIndex > 1)
						map[i][j].frameIndex = 4;
						bullets_hanbing[k].used = true;
						bullets_hanbing[k].row = i;
						bullets_hanbing[k].speed = 10;

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

						int zwX = 256 - 150 + j * 81;
						int zwY = 100 + i * 100 - 15;
						bullets_hanbing[k].x = zwX + imgPlant[map[i][j].type - 1][0]->getwidth() - 10;
						bullets_hanbing[k].y = zwY + 5;
					}
				}
			}

			//三头豌豆
			else if (map[i][j].type == SAN_TOU_WAN_DOU + 1 && lines[i])
			{
				map[i][j].shootTime++;
				if (map[i][j].shootTime > 35)
				{
					map[i][j].shootTime = 0;
					int k;
					for (int b = 0; b < 3; b++)
					{
						for (k = 0; k < bulletMax_santou && bullets_santou[k].used; k++);
						if (k < bulletMax_santou)
						{
							//map[i][j].shoot = true;
							//if(map[i][j].frameIndex > 1)
							//map[i][j].frameIndex = 2;
							bullets_santou[k].used = true;
							bullets_santou[k].row = i;
							bullets_santou[k].speed = 8;

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

							int zwX = 256 - 150 + j * 81;
							int zwY = 100 + i * 100 - 15;
							bullets_santou[k].x = zwX + imgPlant[map[i][j].type - 1][0]->getwidth() - 30;
							bullets_santou[k].y = zwY + 16;
							direction_santou[k] = b;
							row_santou[k] = i;
						}
					}
				}
			}
		}
	}
}

void updateBullets_kunkun()
{
	int countMax = sizeof(bullets) / sizeof(bullets[0]);
	for (int i = 0; i < countMax; i++)
	{
		if (bullets[i].used)
		{
			//static double angle = 0;
			bullets[i].x += bullets[i].speed;
			//angle += bullets[i].speed;
			//bullets[i].y += 10;

			//bullets[i].x += bullets[i].speed;
			if (bullets[i].x > Wide)
			{
				bullets[i].used = false;
			}

			//待完善
			if (bullets[i].blast)
			{
				bullets[i].frameIndex++;
				if (bullets[i].frameIndex >= 4)
				{
					bullets[i].used = false;
				}
			}
		}
	}
}

void updateBullets_wandou()
{
	int countMax_wandou = sizeof(bullets_wandou) / sizeof(bullets_wandou[0]);
	for (int i = 0; i < countMax_wandou; i++)
	{
		if (bullets_wandou[i].used)
		{
			//static double angle = 0;
			bullets_wandou[i].x += bullets_wandou[i].speed;
			//angle += bullets[i].speed;
			//bullets[i].y += 10;

			//bullets[i].x += bullets[i].speed;
			if (bullets_wandou[i].x > Wide)
			{
				bullets_wandou[i].used = false;
			}

			//待完善
			if (bullets_wandou[i].blast)
			{
				bullets_wandou[i].frameIndex++;
				if (bullets_wandou[i].frameIndex >= 4)
				{
					bullets_wandou[i].used = false;
				}
			}
		}
	}
}

void updateBullets_hanbing()
{
	int countMax_hanbing = sizeof(bullets_hanbing) / sizeof(bullets_hanbing[0]);
	for (int i = 0; i < countMax_hanbing; i++)
	{
		if (bullets_hanbing[i].used)
		{
			//static double angle = 0;
			bullets_hanbing[i].x += bullets_hanbing[i].speed;
			//angle += bullets[i].speed;
			//bullets[i].y += 10;

			//bullets[i].x += bullets[i].speed;
			if (bullets_hanbing[i].x > Wide)
			{
				bullets_hanbing[i].used = false;
			}

			//待完善
			if (bullets_hanbing[i].blast)
			{
				bullets_hanbing[i].frameIndex++;
				if (bullets_hanbing[i].frameIndex >= 4)
				{
					bullets_hanbing[i].used = false;
				}
			}
		}
	}
}

void updateBullets_santou()
{
	int countMax_santou = sizeof(bullets_santou) / sizeof(bullets_santou[0]);
	for (int i = 0; i < countMax_santou; i++)
	{
		if (bullets_santou[i].used)
		{
			if (direction_santou[i] == MIDDLE)
			{//static double angle = 0;
				bullets_santou[i].x += bullets_santou[i].speed;

				//bullets[i].x += bullets[i].speed;
				if (bullets_santou[i].x > Wide)
				{
					bullets_santou[i].used = false;
				}	
			}
			else if (direction_santou[i] == UP)
			{
				int destY = 85 + (row_santou[i] - 1) * 100;
				//int zwX = 256 - 150 + j * 81;
				float angle = atan(0.6);
				bullets_santou[i].x += bullets_santou[i].speed;
				bullets_santou[i].y -= bullets_santou[i].speed * tan(angle);
				//printf("%d\n", row_santou[i]);
				//printf("destY = %d\n", destY);
				//printf("bullets[i].y = %lf\n", bullets[i].y);
				if (bullets_santou[i].y <= destY + 16)
				{
					direction_santou[i] = MIDDLE;
				}

			}
			else if (direction_santou[i] == DOWN)
			{
				int destY = 85 + (row_santou[i] + 1) * 100;
				float angle = atan(0.6);
				bullets_santou[i].x += bullets_santou[i].speed;
				bullets_santou[i].y += bullets_santou[i].speed * tan(angle);
				if (bullets_santou[i].y >= destY + 16)
				{
					direction_santou[i] = MIDDLE;
				}
			}
			//待完善
			if (bullets_santou[i].blast)
			{
				bullets_santou[i].frameIndex++;
				if (bullets_santou[i].frameIndex >= 4)
				{
					bullets_santou[i].used = false;
				}
			}
		}
	}
}

void updateBullets()
{
	//坤坤
	updateBullets_kunkun();
	//豌豆射手
	updateBullets_wandou();
	//寒冰豌豆
	updateBullets_hanbing();
	//三头豌豆
	updateBullets_santou();
}

void checkBullet2Zm_kunkun()
{
	int bCount = sizeof(bullets) / sizeof(bullets[0]);
	int zCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < bCount; i++)
	{
		//if (bullets[i].used || bullets[i].blast == false)
		{
			for (int j = 0; j < zCount; j++)
			{
				if (zms[j].used == false)continue;
				int x1 = zms[j].x + 80;
				int x2 = zms[j].x + 110;
				int x = bullets[i].x;
				if (zms[j].dead == false &&
					bullets[i].row == zms[j].row && x > x1 && x < x2 && bullets[i].used) {
					zms[j].blood -= 20;
					zms[j].x += 1;
					bullets[i].blast = true;
					bullets[i].speed = 0;
					//bullets[i].x = 0;

					if (zms[j].blood <= 0)
					{
						killCount++;

						zms[j].dead = true;
						zms[j].speed = 0;
						zms[j].frameIndex = 0;
					}
					break;
				}
			}
		}
	}
}

void checkBullet2Zm_wandou()
{
	int bCount = sizeof(bullets_wandou) / sizeof(bullets_wandou[0]);
	int zCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < bCount; i++)
	{
		//if (bullets[i].used || bullets[i].blast == false)
			for (int j = 0; j < zCount; j++)
			{
				if (zms[j].used == false)continue;
				int x1 = zms[j].x + 80;
				int x2 = zms[j].x + 110;
				int x = bullets_wandou[i].x;
				if (zms[j].dead == false &&
					bullets_wandou[i].row == zms[j].row && x > x1 && x < x2 && bullets_wandou[i].used) {
					zms[j].blood -= 20;
					bullets_wandou[i].blast = true;
					bullets_wandou[i].speed = 0;
					//bullets_wandou[i].x = 0;

					if (zms[j].blood <= 0)
					{
						killCount++;

						zms[j].dead = true;
						zms[j].speed = 0;
						zms[j].frameIndex = 0;
					}
					break;
				}
			}
	}
}

void checkBullet2Zm_hanbing()
{
	int bCount = sizeof(bullets_hanbing) / sizeof(bullets_hanbing[0]);
	int zCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < bCount; i++)
	{
		//if (bullets[i].used || bullets[i].blast == false)
		for (int j = 0; j < zCount; j++)
		{
			if (zms[j].used == false)continue;
			int x1 = zms[j].x + 80;
			int x2 = zms[j].x + 110;
			int x = bullets_hanbing[i].x;
			if (zms[j].dead == false &&
				bullets_hanbing[i].row == zms[j].row && x > x1 && x < x2 && bullets_hanbing[i].used) {
				zms[j].blood -= 20;
				zms[j].zmSpeed = 7;
				bullets_hanbing[i].blast = true;
				bullets_hanbing[i].speed = 0;
				//bullets_hanbing[i].x = 0;

				if (zms[j].blood <= 0)
				{
					killCount++;
					zms[j].dead = true;
					zms[j].speed = 0;
					zms[j].frameIndex = 0;
					zms[j].zmSpeed = 4;
				}
				break;
			}
		}
	}
}

void checkBullet2Zm_santou()
{
	int bCount = sizeof(bullets_santou) / sizeof(bullets_santou[0]);
	int zCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < bCount; i++)
	{
		bullets_santou[i].row = (bullets_santou[i].y - 85+5) / 100;
		//if (bullets[i].used || bullets[i].blast == false)
		for (int j = 0; j < zCount; j++)
		{
			//100 + i * 100 - 15

			if (zms[j].used == false)continue;
			int x1 = zms[j].x + 80;
			int x2 = zms[j].x + 110;
			int x = bullets_santou[i].x;
			if (zms[j].dead == false &&
				bullets_santou[i].row == zms[j].row && x > x1 && x < x2 && bullets_santou[i].used) {
				zms[j].blood -= 20;
				bullets_santou[i].blast = true;
				bullets_santou[i].speed = 0;
				//bullets_santou[i].x = 0;

				if (zms[j].blood <= 0)
				{
					killCount++;

					zms[j].dead = true;
					zms[j].speed = 0;
					zms[j].frameIndex = 0;
					zms[j].zmSpeed = 4;
				}
				break;
			}
		}
	}
}

void checkBullet2Zm()
{
	//坤坤
	checkBullet2Zm_kunkun();
	//豌豆
	checkBullet2Zm_wandou();
	//寒冰豌豆
	checkBullet2Zm_hanbing();
	//三头豌豆
	checkBullet2Zm_santou();
}

void checkBoom2Zm()
{
	int zCount = sizeof(zms) / sizeof(zms[0]);

	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			//樱桃
			if (map[i][j].type == YING_TAO + 1)
			{
				if (map[i][j].frameIndex > 8)
				{
					PlaySound("res/audio/cherrybomb.wav", NULL, SND_FILENAME | SND_ASYNC);
					//map[row][col].x = 256 - 150 + col * 81;
					//map[row][col].y = 100 + row * 100 - 15;
					map[i][j].type = 0;
					map[i][j].frameIndex = 0;
					int x1 = 100 + 81 * (j - 1);
					int x2 = 100 + 81 * (j + 2);
					int y1 = 85 + (i - 1) * 100;
					int y2 = 85 + (i + 2) * 100;
					for (int k = 0; k < zCount; k++)
					{
						if (zms[k].used == false)continue;

						int zmX = zms[k].x + imgZm[0].getwidth() / 2;
						int zmY = zms[k].y;
						if (zmX <= x2 && zmX >= x1 && zmY >= y1 && zmY <= y2 && zms[k].dead == false)
						{
							killCount++;

							zms[k].boom = true;
							zms[k].dead = true;
							zms[k].speed = 0;
							zms[k].frameIndex = 0;
							zms[k].blood = 0;
						
						}
					}
				}
			}
			//火爆辣椒
			else if (map[i][j].type == LA_JIAO + 1)
			{
				if (map[i][j].frameIndex > 7)
				{
					if (map[i][j].frameIndex == 8)
						PlaySound("res/audio/firepea.wav", NULL, SND_FILENAME | SND_ASYNC);

					for (int k = 0; k < zCount; k++)
					{
						if (zms[k].used == false)continue;

						if (zms[k].row == i && zms[k].x < Wide - 80 - 70 && zms[k].dead == false)
						{
							killCount++;

							zms[k].boom = true;
							zms[k].dead = true;
							zms[k].speed = 0;
							zms[k].frameIndex = 0;
							zms[k].blood = 0;
						}
					}
					if (map[i][j].frameIndex > 14)
					{
						map[i][j].type = 0;
						map[i][j].frameIndex = 0;
					}
				}
			}
		}
	}
}

void checkZM2Zhiwu()
{
	int chomp = 0;
	//mciSendString("play ZM_BGM repeat", NULL, NULL, NULL);
	char name[64];
	int bCount = sizeof(bullets) / sizeof(bullets[0]);
	int zCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < zCount; i++)
	{
		//killCount = 0;
		if (zms[i].dead)continue;

		//zms[i].chomptime = 0;
		int row = zms[i].row;
		for (int j = 0; j < 9; j++)
		{
			//if (map[row][j].type == 0)continue;
			
			//
			int zhiwuX = 101 + j * 81;
			int x1 = zhiwuX;
			int x2 = zhiwuX + 81;
			int x3 = zms[i].x + 100;
			if (x3 >= x1 && x3 <= x2)
			{
				if (map[row][j].blood <= 0 || (map[row][j].type == 0 && zms[i].eating != false))
				{
					map[row][j].blood = 0;
					map[row][j].type = 0;
					zms[i].eating = false;
					//zms[i].frameIndex = 0;
					zms[i].speed = 3;
				}
				else if (map[row][j].type != 0)
				{
					//mciSendString("play ZM_BGM repeat", NULL, NULL, NULL);
					//mciSendString("play name repeat", NULL, 0, NULL);
					zms[i].eating = true;
					zms[i].speed = 0;
					if (map[row][j].type != 3 && map[row][j].type != 7)
						map[row][j].blood--;
					//zms[i].frameIndex = 0;
				}
				if (zms[i].eating && chomp == 0)
					chomp = 1;

			}
			else if (x3 > 830)
			{
				zms[i].eating = false;
				zms[i].speed = 3;
			}
		}
	}
	static int chomp_time = 0;
	chomp_time++;
	if (chomp&&chomp_time>20)
	{
		chomp_time = 0;
		PlaySound("res/audio/chomp.wav", NULL, SND_FILENAME | SND_ASYNC);
		//mciSendString("play ZM_BGM", NULL, NULL, NULL);
		//printf("1 ");
	}
}

void checkcars2Zm()
{
	for (int i = 0; i < 5; i++)
	{
		int carsX = cars[i].x + 70;
		for (int j = 0; j < ZM_MAX; j++)
		{
			if (zms[j].used && zms[j].dead == false && zms[j].row == i)
			{
				int zmX = zms[j].x + 80;
				if (carsX > zmX && cars[i].used)
				{
					if (cars[i].move == false)
						cars[i].move = true;
					else
					{
						killCount++;
						zms[j].dead = true;
						zms[j].speed = 0;
						zms[j].frameIndex = 0;
					}
				}
			}
		}
	}
}

void collistionCheck()
{
	//子弹对僵尸的检测
	checkBullet2Zm();
	//僵尸对植物的检测
	checkZM2Zhiwu();
	//爆炸对植物的检测
	checkBoom2Zm();
	//小推车对僵尸的检测
	checkcars2Zm();
}

void updatecar()
{
	for (int i = 0; i < 5; i++)
	{
		if (cars[i].move)
		{
			cars[i].x += 20;
		}
		if (cars[i].x > Wide)
		{
			cars[i].move = false;
			cars[i].used = false;
		}
	}
}

void updateGame()
{
	srand((unsigned)time(NULL));
	static int count = 0;
	count++;
	if (count > 4)
	{
		count = 0;
		for (int i = 0; i < 5; i++)
		{
			for (int j = 0; j < 9; j++)
			{
				if (map[i][j].type > 0)
				{
					map[i][j].frameIndex++;
					int PlantType = map[i][j].type - 1;
					int index = map[i][j].frameIndex;
					if (map[i][j].shoot)
					{
						if (map[i][j].frameIndex > 1)
						{
							map[i][j].shoot = false;
						}
					}
					else
					{
						if (imgPlant[PlantType][index] == NULL)
						{
							map[i][j].frameIndex = 0;
						}
					}
				}
			}
		}
	}

	createSunshine();//创建阳光
	updatSunshine();//更新阳光状态

	createZM();//创建僵尸
	updateZM();//更新僵尸状态

	shoot();//发射豌豆子弹
	updateBullets();//更新子弹
	collistionCheck();//实现豌豆子弹的碰撞检测

	updatecar();//更新小推车
}

void menu()
{
	mciSendString("play BGM", NULL, NULL, NULL);

	IMAGE imgBg, imgMenu1, imgMenu2;
	loadimage(&imgBg, "res/menu.png");
	loadimage(&imgMenu1, "res/menu2.png");
	loadimage(&imgMenu2, "res/menu1.png");
	int flag = 0;

	while (1)
	{
		BeginBatchDraw();
		putimage(0, 0, &imgBg);
		putimagePNG(474, 75, flag ? &imgMenu1 : &imgMenu2);

		ExMessage msg;
		if (peekmessage(&msg))
		{
			if (msg.message == WM_LBUTTONDOWN &&
				msg.x > 474 && msg.x < 474 + 300 &&
				msg.y>75 && msg.y < 75 + 140)
			{
				flag = 1;
				PlaySound("res/audio/bleep.wav", NULL, SND_FILENAME | SND_ASYNC);
			}
			else if (msg.message == WM_LBUTTONUP && flag == 1)
			{
				EndBatchDraw();
				break;
			}
		}
		EndBatchDraw();
	}
}

void viewScence()
{
	mciSendString("play BGM3", NULL, NULL, NULL);

	//开头场景时僵尸的位置
	vector2 points[9] = {
		{550,80},{530,160},{630,170},{530,200},{515,270},
		{565,370},{605,340},{705,280},{690,340}
	};
	int index[9];
	for (int i = 0; i < 9; i++)
	{
		index[i] = rand() % 11;
	}

	int count = 0;
	for (int i = 0; i >= -500; i-=3)
	{
		BeginBatchDraw();
		putimage(i, 0, &img);

		count++;
		for (int k = 0; k < 9; k++)
		{
			putimagePNG(points[k].x + 500 + i,
				points[k].y, &imgZmStand[index[k]]);
			if (count >= 10)
			{
				index[k] = (index[k] + 1) % 11;
			} 
		}
		if (count >= 10)count = 0;
		EndBatchDraw();
		Sleep(10);
	}
	
	for (int i = 0; i < 60; i++)
	{
		BeginBatchDraw();

		putimage(-500, 0, &img);
		for (int k = 0; k < 9; k++)
		{
			putimagePNG(points[k].x, points[k].y, &imgZmStand[index[k]]);
			index[k] = (index[k] + 1) % 11;
		}

		EndBatchDraw();
		Sleep(50);
	}

	for (int i = -500; i <= -150; i += 2)
	{
		BeginBatchDraw();

		putimage(i, 0, &img);
		count++;
		for (int k = 0; k < 9; k++)
		{
			putimagePNG(points[k].x + 500 + i,
				points[k].y, &imgZmStand[index[k]]);
			if (count >= 10)
			{
				index[k] = (index[k] + 1) % 11;
			}
			if (count >= 10) count = 0;
		}
		EndBatchDraw();
		Sleep(10);
	}

}

void barsDown()
{
	int height = imgBar.getheight();
	for (int y = -height; y <= -10; y++)
	{
		BeginBatchDraw();

		putimagePNG(80, y, &imgBar);
		for (int i = 0; i < PLANT_COUNT + 2; i++)
		{
			putimagePNG(163 + i * 65, y + 10, &imgCards[i]);
		}
		
		EndBatchDraw();
		Sleep(10);
	}
	for (int i = 0; i < 5; i++)
	{
		putimagePNG(50, 100 + i * 100 - 15, &imgcar);
	}
	mciSendString("close BGM3", NULL, 0, NULL);
	mciSendString("play BGM4", NULL, NULL, NULL);
	Sleep(1000);
	putimagePNG(450 - imgready.getwidth()/2, 300 - imgready.getheight()/2, &imgready);
	Sleep(200);
}

void failScence()
{
	for (int i = -150; i <= -100; i += 1)
	{
		BeginBatchDraw();
		//zmSpeed = 6;
		putimage(i, 0, &img);
		//show();
		drawZm();
		//updateGame();
		createZM();//创建僵尸
		updateZM();//更新僵尸状态
		
		EndBatchDraw();
		Sleep(50);
	}
}

bool checkOver()
{
	int ret = false;
	if (gameStatus == WIN)
	{
		mciSendString("close BGM2", NULL, NULL, NULL);

		loadimage(0, "res/win2.png");
		mciSendString("play res/win.mp3", 0, 0, 0);
		ret = true;
	}
	else if (gameStatus == FAIL)
	{
		mciSendString("close BGM2", NULL, NULL, NULL);

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

void OpeningAnimation()
{
	mciSendString("play res/audio/yuanshen.mp3", 0, 0, 0);
	for (int i = 0; i < 29; i++)
	{
		BeginBatchDraw();
		putimage(-148, -35, &imgopena[i]);

		EndBatchDraw();
		Sleep(50);
	}
	Sleep(4000);
}

int main()
{
	//加载游戏画面
	gameInit();
	//开场动画
	OpeningAnimation();
	menu();
	mciSendString("close BGM", NULL, 0, NULL);
	//游戏开始时的场景切换
	viewScence();
	//游戏开始时卡牌和卡牌槽的下落
	barsDown();
	int timer = 0;
	bool flag = true;
	
	mciSendString("play BGM2 repeat", NULL, NULL, NULL);
	while (1)
	{
		//用户操作
		userClick();
		//游戏更新时间
		timer += getDelay();
		if (timer > 20)
		{
			flag = true;
			timer = 0;
		}
		if (flag)
		{
			flag = false;
			//绘制游戏画面
			show();
			//更新游戏画面
			updateGame();
			if(checkOver()) break;
		}
	}
	closegraph;
	system("pause");
	return 0;
}

game.h文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1

#include<easyx.h>
#include<stdio.h>
#include<time.h>
#include<math.h>
#include"tools.h"
#include<mmsystem.h>
#include"vector2.h"
#pragma comment(lib,"winmm.lib")

#define Wide 900
#define Hight 600
#define KUNKUN_BLOOD 100//非坚果植物的血量
#define ORDINARY_ZM_BLOOD 1270*3//僵尸血量
#define ZM_MAX 1024//创建僵尸的最大数量

enum {
	WAN_DOU, TAI_YANG, LA_JIAO, KUN_KUN, JIAN_GUO,
	HAN_BING_WAN_DOU, YING_TAO, SAN_TOU_WAN_DOU, PLANT_COUNT
};
IMAGE img;//地图
IMAGE imgBar;//卡牌槽
IMAGE imgCards[PLANT_COUNT + 2];//卡牌
IMAGE* imgPlant[PLANT_COUNT + 2][20];//植物动作
IMAGE imgZmStand[11];//开场僵尸的站立
IMAGE imgcar;//小推车
IMAGE imgopena[29];//开场动画
IMAGE imgready;
IMAGE imgnotify;

int curX, curY;//当前选中植物,在移动过程中植物的坐标
int curPlant;// 0:没有选中,1:选中了第一种种植物
int sunshine;

enum { GOING, WIN, FAIL };
int killCount;//已经杀掉的僵尸个数
int zmCount;//已经出现的僵尸个数
int zmCount_max = 1;//每波出现的僵尸数量
int gameStatus;//游戏的状态
int wava_count = 1;//第wava_count波僵尸

struct plant
{
	int type;//植物类型
	int frameIndex;//植物动作帧
	//bool catched;//是否被吃
	int blood;//血量

	int shootTime;//植物子弹的射速

	int timer;//阳光生产的时间
	int x, y;//植物坐标
	bool shoot;//判断植物是否处于发射状态
};
struct plant map[5][9];

enum { SUNSHINE_DOWN, SUNSHINE_GROUND, SUNSHINE_COLLECT, SUNSHINE_RPODUCT };

struct sunshineBall
{
	int x, y;
	int frameIndex;//当前显示的图片帧序号
	int destY;//飘落的目标位置的y坐标
	bool used;//是否在使用
	int timer;

	float xoff;
	float yoff;

	float t;//贝塞尔曲线的时间点
	vector2 p1, p2, p3, p4;
	vector2 pCur;//当前时刻阳光球的位置
	float speed;
	int status;
};

//10个阳光球
struct sunshineBall balls[10];
IMAGE imgSunshineBall[29];

struct zm
{
	int x, y;//僵尸的坐标
	int frameIndex;//僵尸动作帧
	bool used;//僵尸是否被使用
	int speed;//僵尸每一次移动的位移
	int row;//僵尸所在行
	int blood;//僵尸血量
	bool dead;//僵尸是否死亡
	bool eating;//僵尸是否在吃植物
	bool boom;//僵尸是否被炸死
	int zmSpeed;//僵尸的移动快慢
};
struct zm zms[ZM_MAX];
IMAGE imgZm[22];
IMAGE imgZMDead[38];
IMAGE imgzmboom[20];
IMAGE imgZMEat[21];

//子弹
struct bullet
{
	double x, y;//子弹的坐标
	bool used;//子弹是否被使用
	int row;//子弹所在行
	int speed;//子弹速度
	bool blast;//是否发生爆炸
	int frameIndex;//帧序号
};
//坤坤
struct bullet bullets[10000];
IMAGE imgBulletNormal;
IMAGE imgBallBlast[4];
//豌豆
struct bullet bullets_wandou[200];
IMAGE imgBallBlast_wandou[4];
IMAGE imgBulletNormal_wandou;
//寒冰豌豆
struct bullet bullets_hanbing[200];
IMAGE imgBallBlast_hanbing[4];
IMAGE imgBulletNormal_hanbing;
//三头豌豆
struct bullet bullets_santou[200];
IMAGE imgBallBlast_santou[4];
IMAGE imgBulletNormal_santou;
int direction_santou[200];
int row_santou[200];//三头豌豆发射子弹时所在的行数
enum { MIDDLE, UP, DOWN };

//小推车
struct car
{
	bool move;//是否处于移动状态
	int x, y;//位置
	bool used;//是否被使用
};
struct car cars[5];
/*
* 增加植物的步骤:
* 1. 创建子弹的相关变量
* 2. 加载植物子弹图片
* 3. 渲染植物子弹
* 4. 发射植物子弹
* 5. 检查植物子弹与僵尸的碰撞
* 6. 更新植物子弹
*/

vector2.cpp文件(贝塞尔曲线)

//头文件要求
#include <cmath>
#include "vector2.h"

//加法
vector2 operator +(vector2 x, vector2 y) { 
	return vector2(x.x + y.x, x.y + y.y ); 
}

//减法
vector2 operator -(vector2 x, vector2 y) {
	return vector2(x.x - y.x, x.y - y.y);
}

// 乘法
vector2 operator *(vector2 x, vector2 y) {
	return vector2(x.x * y.x - x.y * y.y, x.y * y.x + x.x * y.y);
}

// 乘法
vector2 operator *(vector2 y, float x) {
	return vector2(x*y.x, x*y.y);
}

vector2 operator *(float x, vector2 y) {
	return vector2(x * y.x, x * y.y);
}

//叉积
long long cross(vector2 x, vector2 y) { return x.y * y.x - x.x * y.y; }

//数量积 点积
long long dot(vector2 x, vector2 y) { return x.x * y.x + x.y * y.y; }

//四舍五入除法
long long dv(long long a, long long b) {//注意重名!!! 
	return b < 0 ? dv(-a, -b)
		: (a < 0 ? -dv(-a, b)
			: (a + b / 2) / b);
}

//模长平方
long long len(vector2 x) { return x.x * x.x + x.y * x.y; }

//模长
long long dis(vector2 x) { return sqrt(x.x * x.x + x.y * x.y); }

//向量除法
vector2 operator /(vector2 x, vector2 y) {
	long long l = len(y);
	return vector2(dv(dot(x, y), l), dv(cross(x, y), l));
}

//向量膜
vector2 operator %(vector2 x, vector2 y) { return x - ((x / y) * y); }

//向量GCD 
vector2 gcd(vector2 x, vector2 y) { return len(y) ? gcd(y, x % y) : x; }


vector2 calcBezierPoint(float t, vector2 p0, vector2 p1, vector2 p2, vector2 p3) {
	float u = 1 - t;
	float tt = t * t;
	float uu = u * u;
	float uuu = uu * u;
	float ttt = tt * t;

	vector2 p = uuu * p0;
	p = p + 3 * uu * t * p1;
	p = p + 3 * u * tt * p2;
	p = p + ttt * p3;

	return p;
}

vector2.h文件

#pragma once

//头文件要求
#include <cmath>

struct vector2 {
	vector2(int _x=0, int _y=0) :x(_x), y(_y) {}
	vector2(int* data) :x(data[0]), y(data[1]){}
	long long x, y;
};

//加法
vector2 operator +(vector2 x, vector2 y);

//减法
vector2 operator -(vector2 x, vector2 y);

// 乘法
vector2 operator *(vector2 x, vector2 y);
vector2 operator *(vector2, float);
vector2 operator *(float, vector2);

//叉积
long long cross(vector2 x, vector2 y);

//数量积 点积
long long dot(vector2 x, vector2 y);

//四舍五入除法
long long dv(long long a, long long b);


//模长平方
long long len(vector2 x);

//模长
long long dis(vector2 x);

//向量除法
vector2 operator /(vector2 x, vector2 y);

//向量膜
vector2 operator %(vector2 x, vector2 y);

//向量GCD 
vector2 gcd(vector2 x, vector2 y);

vector2 calcBezierPoint(float t, vector2 p0, vector2 p1, vector2 p2, vector2 p3);

tools.cpp文件(透明贴图)

#include "tools.h"


// 载入PNG图并去透明部分
void _putimagePNG(int  picture_x, int picture_y, IMAGE* picture) //x为载入图片的X坐标,y为Y坐标
{
	DWORD* dst = GetImageBuffer();    // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
	DWORD* draw = GetImageBuffer();
	DWORD* src = GetImageBuffer(picture); //获取picture的显存指针
	int picture_width = picture->getwidth(); //获取picture的宽度,EASYX自带
	int picture_height = picture->getheight(); //获取picture的高度,EASYX自带
	int graphWidth = getwidth();       //获取绘图区的宽度,EASYX自带
	int graphHeight = getheight();     //获取绘图区的高度,EASYX自带
	int dstX = 0;    //在显存里像素的角标

	// 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
	for (int iy = 0; iy < picture_height; iy++)
	{
		for (int ix = 0; ix < picture_width; ix++)
		{
			int srcX = ix + iy * picture_width; //在显存里像素的角标
			int sa = ((src[srcX] & 0xff000000) >> 24); //0xAArrggbb;AA是透明度
			int sr = ((src[srcX] & 0xff0000) >> 16); //获取RGB里的R
			int sg = ((src[srcX] & 0xff00) >> 8);   //G
			int sb = src[srcX] & 0xff;              //B
			if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
			{
				dstX = (ix + picture_x) + (iy + picture_y) * graphWidth; //在显存里像素的角标
				int dr = ((dst[dstX] & 0xff0000) >> 16);
				int dg = ((dst[dstX] & 0xff00) >> 8);
				int db = dst[dstX] & 0xff;
				draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)
					| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)
					| (sb * sa / 255 + db * (255 - sa) / 255);
			}
		}
	}
}

// 适用于 y <0 以及x<0的任何情况
void putimagePNG(int x, int y, IMAGE* picture) {

	IMAGE imgTmp, imgTmp2, imgTmp3;
	int winWidth = getwidth();
	int winHeight = getheight();
	if (y < 0) {
		SetWorkingImage(picture);
		getimage(&imgTmp, 0, -y,
			picture->getwidth(), picture->getheight() + y);
		SetWorkingImage();
		y = 0;
		picture = &imgTmp;
	}
	else if (y >= getheight() || x >= getwidth()) {
		return;
	}
	else if (y + picture->getheight() > winHeight) {
		SetWorkingImage(picture);
		getimage(&imgTmp, x, y, picture->getwidth(), winHeight - y);
		SetWorkingImage();
		picture = &imgTmp;
	}

	if (x < 0) {
		SetWorkingImage(picture);
		getimage(&imgTmp2, -x, 0, picture->getwidth() + x, picture->getheight());
		SetWorkingImage();
		x = 0;
		picture = &imgTmp2;
	}

	if (x > winWidth - picture->getwidth()) {
		SetWorkingImage(picture);
		getimage(&imgTmp3, 0, 0, winWidth - x, picture->getheight());
		SetWorkingImage();
		picture = &imgTmp3;
	}


	_putimagePNG(x, y, picture);
}

int getDelay() {
	static unsigned long long lastTime = 0;
	unsigned long long currentTime = GetTickCount();
	if (lastTime == 0) {
		lastTime = currentTime;
		return 0;
	}
	else {
		int ret = currentTime - lastTime;
		lastTime = currentTime;
		return ret;
	}
}

tools.cpp文件 

#pragma once
#include <graphics.h>

void putimagePNG(int  picture_x, int picture_y, IMAGE* picture);
int getDelay();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/632873.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

倍思/西圣/UHB电容笔怎么选?2024热门电容笔全面性能测评大PK

​在这个追求速度和效率的时代&#xff0c;ipad以其便携性和高性能成为很多上班族、学生党提高生产力和学习效率的重要工具&#xff0c;很多人更是会搭配一支电容笔来进一步发挥ipad的使用价值&#xff0c;可原装的电容笔毕竟价格较高&#xff0c;于是很多人将目光转向平替&…

Day35 代码随想录打卡|二叉树篇---二叉树的层序遍历

题目&#xff1a; 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 方法&#xff1a;二叉树的层序遍历感觉是相对简单的&#xff0c;只需要定义一个队列&#xff0c;从根节点开始放入队列…

SQL靶场搭建

概述 简单介绍一下SQL靶场的搭建&#xff0c;以及在搭建过程中遇到的一些问题。使用该软件搭建靶场相对简单&#xff0c;适合新手小白。当然&#xff0c;也可以在自己的虚拟机下进行搭建&#xff0c;相对来说就较为复杂。本章主要讲解使用Phpstudy进行SQL靶场搭建。 这里我推…

【安装笔记-20240519-Windows-安装测试 Optimizer】

安装笔记-系列文章目录 安装笔记-20240519-Windows-安装测试 Optimizer 文章目录 安装笔记-系列文章目录安装笔记-20240519-Windows-安装测试 Optimizer 前言一、软件介绍名称&#xff1a;Optimizer主页官方介绍 二、安装步骤测试版本&#xff1a;16.5下载链接功能界面 三、应…

Redis篇 有关Redis的认识和Redis的特性应用场景

Redis 一. Redis的基本概念1.1 应用/系统1.2 模块/组件1.3 分布式1.4 集群1.5 主/从1.6 中间件1.7 可用性1.8 响应时长1.9 吞吐 二.Redis的特性三.使用场景 一. Redis的基本概念 1.1 应用/系统 一个应用就是一个组,一个服务器程序 1.2 模块/组件 一个应用,里面有很多功能,每个…

Java开发工具类(JDK、Hutool、Guava)

目录 Java开发常用的工具类1、JDK自带程序读取控制台输入内容&#xff08;调试程序或者学习的时候比较有用&#xff09;Arrays工具类 数组转集合Collections 集合工具类 排序Collections 集合工具类 查找Lambda表达式 操作集合 收集、转map、分组 2、Apache 的 commons-lang3 和…

Serverless应用引擎SAE评测|一分钟部署在线游戏

Serverless应用引擎SAE评测|一分钟部署在线游戏 什么是Serverless应用引擎SAE一分钟部署在线游戏SAE控制台 资源释放其他操作 在进行Serverless应用引擎SAE评测之前&#xff0c;首先需要了解一下什么是SAE。 什么是Serverless应用引擎SAE Serverless应用引擎SAE&#xff08;Se…

C语言程序的编译

目录 一、预处理&#xff08;预编译&#xff09; 二、编译 三、汇编 四&#xff0c;链接 在前面讲到了宏的定义&#xff0c;那么宏在编译时候是如何发生替换的&#xff1f;接下来做一下详细的介绍C语言程序的编译过程主要包括以下几个步骤&#xff1a;预处理、编译、汇编和…

非授权人员进入报警系统

非授权人员进入报警系统基于智能视频分析技术和深度学习技术&#xff0c;非授权人员进入报警系统通过现场已经装好的监控摄像头针对人体进行精准检测&#xff0c;并根据设置的禁入区范围进行判断。通过图像处理和人体识别算法&#xff0c;非授权人员进入报警系统可以在实时监测…

力扣279. 完全平方数

Problem: 279. 完全平方数 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.定义一个int数组dp初始化长度为n1&#xff1b; 2.状态初始化&#xff1a;当n等于0时&#xff0c;dp[0]为0&#xff1b;并且每次每次先初始化dp[i] i,即表示为i时的最大所需完全平方根的…

FreeRTOS_互斥量_学习笔记

互斥量 数值只有0或1 谁获得互斥量&#xff0c;就必须由谁释放同一个互斥量。 但其实在freeRTOS中&#xff0c;任务A获取的互斥锁&#xff0c;任务B也能释放。因此谁上锁谁开锁只是约定&#xff0c;在程序实现上不是强制的。 “可重入的函数"是指&#xff1a;多个任务同时…

VMware ESXi 7.0 U3q 发布 - 领先的裸机 Hypervisor

VMware ESXi 7.0 U3q 发布 - 领先的裸机 Hypervisor VMware ESXi 7.0 Update 3 Standard & All Custom Image for ESXi 7.0U3 Install CD 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-7-u3/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出…

操作系统入门系列-MIT6.828(操作系统工程)学习笔记(二)----课程实验环境搭建(wsl2+ubuntu+quem+xv6)

MIT6.S081&#xff08;操作系统&#xff09;学习笔记 操作系统入门系列-MIT6.828&#xff08;操作系统&#xff09;学习笔记&#xff08;一&#xff09;---- 操作系统介绍与接口示例 操作系统入门系列-MIT6.828&#xff08;操作系统工程&#xff09;学习笔记&#xff08;二&am…

常见算法(1)

1.基本查找/顺序查找 核心&#xff1a;从0索引之后挨个查找 实现代码&#xff1a; public class test {public static void main(String [] arg) throws ParseException {int[] arr {121,85,46,15,55,77,63,49};int number55;System.out.println(bashi(arr,number));}publi…

三分钟学会视频号卖货,真的太简单了!

大家好&#xff0c;我是电商糖果 视频号小店绝对是今年最火的电商平台之一&#xff0c;再加上它刚进军电商行业&#xff0c;大家都想吃到第一口红利。 小店之所以这么受欢迎&#xff0c;其实看中是它背后微信的十几亿真实用户。 微信的流量可以直接进入视频号&#xff0c;因…

企业知识库智能问答系统的实践

1、页面效果 PC端 2、页面效果 手机端 3、主要支持功能 新建会话 历史会话 2、智能问答 支持 文本分类和意图识别&#xff0c;支持基于大模型的对话理解&#xff0c;支持流式对话 3、支持手机端 语音识别 4、主要服务包括 向量库Milvus 向量计算和文本分类服务 …

618必入好物清单!五款人气实用好物推荐

朋友们&#xff01;一年一度的618购物狂欢节又要来啦&#xff01;是不是已经迫不及待想要给自己的购物车添点新货了&#xff1f;小编特地搜罗了五款人气爆棚、实用到没朋友的“必入好物”。从日常生活小物到提升生活品质的利器&#xff0c;精挑细选保证买得开心、用得顺心。赶紧…

ROS | 自定义发布地图

C代码&#xff1a; Step: Python代码:

[实例] Unity Shader 逐像素漫反射与半兰伯特光照

漫反射光照是Unity中最基本最简单的光照模型&#xff0c;本篇将会介绍在片元着色器中实现反射效果&#xff0c;并会采用半兰伯特光照技术对其进行改进。 1. 逐顶点光照与逐像素光照 在Unity Shader中&#xff0c;我们可以有两个地方可以用来计算光照&#xff1a;在顶点着色器…

QT控件QDialog结合QDialogButtonBox实现确认弹窗

项目需要二次确认开启&#xff0c;添加一个确认弹窗&#xff0c;采用QDialog并添加按钮控件。 QDialogButtonBox控件用于添加按钮组&#xff0c;初始化时可以增加标准按键&#xff0c;但是不能自定义按钮文字。 想要更改按键大小&#xff0c;但是没有提供设置组内按钮大小的函数…