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

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

一起加油! 

序言:

这个游戏的实现主要是纯c语言+easyx库(c++输出图片的工具),所以在编写该项目前要安装easyx库,我使用的vs2019专业版,若是社区版的vs2019,easyx可j检测到,直接安装;而专业版话需要手动安装easyx(点击手动安装教程);还需要注意的一点是在创建新项目时,代码后缀保持.cpp不变,因为c++才能支持easyx。


游戏效果演示

植物大战僵尸2


一、实现最开始游戏场景

按照序言要求,创建好.cpp的新项目,然后打开该项目所在文件夹导入素材,如图所示

#include<stdio.h>
#include <graphics.h>//easyx图形库头文件,需要安装easyx图形库
int main(void)
{
	gameInit();//初始化
	updatewindow();//更新(渲染)界面
	
	system("pause");
	return 0;
}

gameInit()用来初始化游戏,updatewindow()更新游戏界面。

#define WIN_WIDTH    900
#define WIN_HEIFHT    600
IMAGE imgBg;//表示背景图片变量
IMAGE imgbar;//表示工具栏图片变量
void gameInit()
{
//加载有背景的图片,把字符集修改成”多字符集“
	loadimage(&imgBg, "res/Background_2.jpg");
	loadimage(&imgbar, "res/bar5.png");
//创建游戏的图形窗口
	initgraph(WIN_WIDTH, WIN_HEIFHT,1);//同时显示控制台窗口和图形窗口,不需要控制台去掉1即可
}

创建俩个全局图片变量用来存储背景、工具栏图片,在初始化函数中将文件图片加载(储存)到图片变量中,initgraph创建一个900*600大小的游戏窗口(因为没有渲染图片,所以打印出来是900*600大小的黑框。

注意:在使用loadimage函数会报错,处理办法将把字符集修改成”多字符集“)

此处是上面出现的easyx库中函数的用法链接(loadimage函数,initgraph函数

下一步将代码与显示屏交互

void updatewindow()
{
	
	putimage(0, 0, &imgBg);//渲染图片
}

此处便完成最开始游戏场景布置。

二.实现游戏顶部工具栏

像上面一样,先加载工具栏图片,在渲染工具栏图片,因为工具栏图片是叠加到背景图片上,所以需要去除工具栏图片黑色边框,所以渲染函数用putimagePNG(此函数来源于一个写好代码)

用之前将写好的代码复制在  该项目所在文件夹  ,然后点击项目名,选择添加——>现有项——>选中俩个代码文件。

用putimagePNG函数需要包含该文件头文件

#include"tools.h"

在 gameInit()初始化函数中加载工具栏图片 

loadimage(&imgbar, "res/bar5.png");

在updatewindow()函数渲染工具栏图片

putimagePNG(250, 0, &imgbar);//渲染工具栏(透明背景)图片

三.实现游戏顶部工具栏的植物卡牌

​​有多个卡牌,我们就用图片数组储存卡牌图片,用枚举来表示卡牌种类

enum {PEA,SUNFLOWER,PLANT_COUNT};//枚举所有卡牌种类

IMAGE imgcards[PLANT_COUNT];//表示植物卡牌图片数组变量

发现PLANT_COUNT表示2,也就是卡牌种类个数,我们在枚举中插入别的卡牌类型,那PLANT_COUNT依然表示卡牌种类个数,这是一个小技巧,后面需要添加其他卡牌就容易了

如法炮制,在初始化函数中加载卡牌图片,在更新界面函数中渲染图片

在gameInit()函数中

//初始化(加载)植物卡牌图片
	char plantname[64] = { 0 };
for (int i = 0; i < PLANT_COUNT; i++)
	{
		
		//生成植物卡牌文件名
		sprintf_s(plantname, sizeof(plantname), "res/Cards/card_%d.png", i + 1);//将植物卡牌文件名存储到字符数组
		loadimage(&imgcards[i], plantname);

}

在updatewindow()函数中

//渲染卡牌图片
for (int i = 0; i < PLANT_COUNT; i++) 
	{
		int x = 338 + i * 65;//卡牌左上角坐标
		int y = 6;
			
		putimage(x, y, &imgcards[i]);

				
	}

四、实现植物的种植

这个操作属于用户操作,需要在主函数中增加用户点击函数,用来处理接受的鼠标信息,每一个游戏都是死循环,赢或者输都会跳出循环,进入下一个关卡或者结束游戏

int main(void)
{
	gameInit();
	while (1)
	{
		userclick();
		updatewindow();//更新(渲染)界面
			
	}
system("pause");
	return 0;
}

这样处理后,界面会出现闪烁,因为在死循环中会不停打印变换界面,这里我们在更新界面函数中用一个双缓冲,它的作用是将所有图片准备好在一片内存中,在一起打印出来,就不会闪烁了

void updatewindow()
{
   BeginBatchDraw();//双缓冲(先打印在一片内存内,然后一起打印出来)
	putimage(0, 0, &imgBg);//渲染图片
	putimagePNG(250, 0, &imgbar);//渲染(透明背景)图片
	//渲染卡牌图片
for (int i = 0; i < PLANT_COUNT; i++) 
	{
		int x = 338 + i * 65;//卡牌左上角坐标
		int y = 6;
		putimage(x, y, &imgcards[i]);

				
	}
	

	EndBatchDraw();//结束双缓冲
}

我们先判断界面有没有鼠标消息,用peekmessage这个函数(函数是bool类型,有消息则返回true,没有消息返回为false,第一个参数是ExMessage类型的结构体指针,第二个参数是获取消息的范围,因为我们获取的鼠标消息,所以写EX_MOUSE)

对于ExMessage类型结构体里面有message成员,该成员值如下

介绍完这个函数和结构体,我们就设计植物的种植。在这里我们和原版一样实现的效果是我们点击卡牌图片一下,然后移动鼠标,植物跟着鼠标移动,然后在草坪上点击,即种植完成。

首先接受鼠标信息

(1)因为鼠标点击一下瞬间,既有左键按下,又有左键抬起,那么在卡牌区域检测到左键抬起,说明是选中该植物

(2)因为需要有植物图片跟随鼠标移动,那么,设置一个静态变量status=0,选中植物卡牌即便为1,鼠标在选中植物卡牌后且左键抬起状态的鼠标坐标位置即植物图片位置,定义一个全局变量接收鼠标坐标,然后在该移动坐标位置渲染植物图片,就有跟随效果了

(3)移动之后要种植,创建一个植物结构体和4行8列的结构体数组(为了方便对应草坪4行8列区域种植植物情况),检测到鼠标左键按下状态在草坪区域,根据鼠标坐标确定所在草坪具体的哪行哪列,分析该位置有无植物,当没有植物即种下鼠标之前选中的植物curplant,确定种植植物的位置,方便后面渲染种植植物

int curx, cury;//当前选中植物在移动过程中的位置
int curplant=0;//0表示没有选中,1表示选中第一种植物,2表示选中第二种植物
struct plant
{
	int type;//0表示没有植物,1表示选中第一种植物,2表示选中第二种植物
	int frameIndex;//序列帧的序号
	
   
    int timer;//喷射阳光的计时器
	int x, y;//植物坐标
};
struct plant map[4][8];//4行8列的植物数组,每个元素是一个植物结构体
void userclick()
{
	ExMessage msg;//这个结构体变量用于保存鼠标消息
	static int status= 0;
	
	if (peekmessage(&msg,EX_MOUSE))//判断鼠标消息,有返回真,无返回假
	{
	if (msg.message == WM_LBUTTONUP)//鼠标左键弹起
	{
		if (msg.x > 338 && msg.x < 338 + 65 * PLANT_COUNT && msg.y < 96)
		{
			int index = (msg.x - 338) / 65;//植物卡片,0.代表第一张,1.代表第二张
			//printf("%d ", index);
			status = 1;
			curplant = index + 1;//植物,1.代表豌豆,2.代表向日葵
			
		}
		
	}
else if (msg.message == WM_MOUSEMOVE && status == 1)//鼠标移动
	{
		curx = msg.x;
		cury = msg.y;
	}
	else if (msg.message == WM_LBUTTONDOWN)//鼠标左键按下
	{

        if (msg.x > 261 && msg.y > 167 && msg.y < 531)//鼠标落在草坪区域
		{
			int row = (msg.y - 167) / 91;
			int col = (msg.x - 261) / 81;

			
			if (map[row][col].type == 0)
			{
				
				map[row][col].type = curplant;
				map[row][col].frameIndex = 0;
				//int x = 261 + 81 * j;
				//int y = 167 + 91 * i + 14;

				map[row][col].x = 261 + 81 * col;
				map[row][col].y = 167 + 91 * row + 14;
                
		         map[row][col].eated=false;
             }
			//printf("%d,%d\n", row, col);
        
	   }

	 	curplant = 0;//在任意区域点击即可取消选则
		status = 0;
	}
	
}

初始化植物图片

然后将植物图片加载到植物图片数组里,豌豆有17张图片,向日葵有20张图片,我们设置一个2行10列图片指针数组来加载植物图片,将植物图片加载到植物数组中去

注意的是在加载过程中需要判断plantname中的文件地址是否存在,存在才加载,不存在则跳过该行植物文件。

IMAGE* imgplant[PLANT_COUNT][20];//表示存放植物图片地址的指针数组


bool fileExist(const char* name)//打开成功返回真,打开失败返回假
{
	FILE* fp = fopen(name, "r");//若错误,则点击代码文件名选中属性,关掉c/c++中的SDL检查
	if (fp == NULL)
	{
		return false;
	}
	else
	{
		fclose(fp);
		return true;
	}
}


void gameInit()
{
memset(imgplant, 0, sizeof(imgplant));//将指针数组内容置为NULL,目的防止野指针
memset(map, 0, sizeof(map));//将植物数组初始化,使里面的成员都为0.包括type也为0(表示没有植物)
for (int i = 0; i < PLANT_COUNT; i++)
	{
		
		for (int j = 0; j < 20; j++)
		{
			sprintf_s(plantname, sizeof(plantname), "res/zhiwu/%d/%d.png", i ,j+1);//将植物文件名存储到字符数组
			//判断文件是否存在
			if (fileExist(plantname))
			{
				imgplant[i][j] = new IMAGE;  //C++中分配内存
				loadimage(imgplant[i][j], plantname);
			}
			else
			{
				break;
			}
		}
	}
}

接下来渲染拖动过程的植物和渲染种下之后的植物

(1)在渲染拖动过程中植物图片,为了使鼠标箭头位于植物图片中央,需要对渲染位置做一点修改

img->getwidth()  表示获取该图片的宽度

(2)渲染草坪上的植物时,要注意的点的时,植物是随时动的,我们渲染的是植物第一帧图片.所以我们要改变frameIndex的值,需要一个改变数据的函数

void updatewindow()
{
	BeginBatchDraw();//双缓冲(先打印在一片内存内,然后一起打印出来)

   //渲染拖动过程中的植物
	if (curplant==PEA+1)
	{
		IMAGE* img = imgplant[curplant - 1][0];//表示存放豌豆植物图片地址的指针
		putimagePNG(curx - img->getwidth() / 2, cury - img->getwidth() / 2, img);
	}
	else if (curplant == SUNFLOWER+1 )
	{
		IMAGE* img = imgplant[curplant - 1][0];//表示存放向日葵植物图片地址的指针
		putimagePNG(curx - img->getwidth() / 2, cury - img->getwidth() / 2, img);
	}



    //渲染草坪上植物
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 8; j++)
			if (map[i][j].type > 0)
			{
				//int x = 261 + 81 * j;
				//int y = 167 + 91 * i + 14;
				//putimagePNG(x, y, imgplant[map[i][j].type - 1][map[i][j].frameIndex]);
				putimagePNG(map[i][j].x,map[i][j]. y, imgplant[map[i][j].type - 1][map[i][j].frameIndex]);
			}
	}

接下来我们需要改变frameIndex图片帧的数据,创建一个更改数据的函数

int main(void)
{
	gameInit();
	
	
	while (1)
	{
		userclick();
		updatewindow();//更新(渲染)界面
	    updategame();//改变游戏数据
	
	}
	system("pause");
	return 0;
}
void updategame()//修改游戏数据
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 8; j++)
		{
			if (map[i][j].type > 0)
			{

				map[i][j].frameIndex++;
				if (imgplant[map[i][j].type - 1][map[i][j].frameIndex] == NULL)
				{
					map[i][j].frameIndex = 0;
				}
			}
		}
	}
}

注:因为植物指针数组不是每个都有文件,所以需要判断,若该数组内容为NULL,则从头再开始帧序号

解决植物摇摆状态变换过快

运行文件我们发现植物运动很快,因为在main()函数中没有加延时,会循环的非常快,不断改变帧序号,所以我们需要优化循环,我们就想到Sleep,发现这样后植物跟不上鼠标移动,会有迟滞感,

我们选择计时来解决这个问题

这里有一个写在我们之前tool.cpp函数中延时程序,从第二次调用以后,返回值都是当前调用和上一次调用的时间差

当时间将大于50ms,我们才跟新画面和数据,但是处理用户操作时随时的,而用Sleep时,中间停顿的时间是处理不了用户点击的

bool flag = true;
while (1)
	{
		userclick();
		timer = timer + getDelay();
		if (timer > 50)
		{
			flag = true;
			timer = 0;
		}
		if (flag)
		{
			flag = false;
			
			updatewindow();//更新(渲染)界面
			updategame();//改变游戏数据

			//Sleep(10);
			
		}
		
	}

当然上面觉得比较啰嗦的话,也可以这样


while (1)
	{
		userclick();
		timer = timer + getDelay();
		if (timer > 50)
		{
			
			timer = 0;
			updatewindow();//更新(渲染)界面
			updategame();//改变游戏数据

			//Sleep(10);
			
		}
			
	}

后面中小型游戏都可以这样用,后面也会经常用到。(该技巧再下面成为控制频率技巧)

如果还觉比较快,当然也可以单独对种植植物里面,植物图片帧进行频率控制

后面大家再调试时觉的阳光球或者僵尸等移动过快,都可以再更新阳光球数据或者更新僵尸数据等函数中进行控制频率处理

五、创建游戏启动菜单界面

在主函数中,启动菜单放在开始

我们开始创建这个函数,开始加载菜单界面和俩个明暗菜单按钮,因为这个界面是要等待用户点击,所以是一个循环,像用户点击函数一样,我们接受鼠标信息,若鼠标左键按下状态在开始按钮区域中,则渲染高亮开始按钮图片,若鼠标左键弹起状态在开始按钮区域中,则结束循环。

void startUI()
{
	
	IMAGE imgBj, imgMenun1, imgMenun2;
	loadimage(&imgBj, "res/menu.png");
	loadimage(&imgMenun1, "res/menu1.png");
	loadimage(&imgMenun2, "res/menu2.png");
	int flag = 0;
	while (1)
	{
		BeginBatchDraw();
		
		putimage(0, 0, &imgBj);
		putimagePNG(475, 75, flag ? &imgMenun2 : &imgMenun1);


		ExMessage msg;
		if (peekmessage(&msg, EX_MOUSE))//判断鼠标消息,有返回真,无返回假
		{
			if (msg.message == WM_LBUTTONDOWN && msg.x > 474 && msg.x < 474 + 300 && msg.y>75 && msg.y < 75 + 140)
			{
				flag = 1;
				
			}
			else if (msg.message == WM_LBUTTONUP && msg.x > 474 && msg.x < 474 + 300 && msg.y>75 && msg.y < 75 + 140)
			{
				//return;
				
				EndBatchDraw();//点击到开始按钮,在跳出循环之前结束缓冲
				break;
			}
		}
		EndBatchDraw();//没有点击开始按钮,需要结束缓冲
	}


}

六、创建阳光球

创建阳光球结构体和一个结构体数组,表示10个阳光球,阳光球分为俩个部分,一种是自由落体,另一种是向日葵产生的抛物线运动的阳光球,这里引入一个工具文件vector2.cpp,将该文件添加到现有项里面去。

这个可以理解成c语言的结构体类型,红色框代表结构体成员,可以运用这个结构体进行直线,抛物线运动(贝塞尔曲线

贝塞尔曲线函数:通过四个点和t(起始位置t=0,终点位置t=1)确定t时刻在曲线位置坐标

(斜)直线运动:通过俩个点(p1和p4)以及t,确定直线运动的t时刻位置坐标,t=t+speed,pcur=p1+t*(p4-p1)

enum{SUNSHINE_DOWN,SUNSHINE_GROUND,SUNSHINE_COLLECT,SUNSHINE_PRODUCT};
//0.阳光下降 1.阳光着陆 2.阳光收集 3.阳光生产

struct sunshineball
{
	
	int frameIndex;//当前显示图片帧的序号
	//int destY;//飘落的目标位置的y坐标
	bool use;//是否在使用(0代表没有被使用)
	int timer;//计时器,目的使阳光停留几秒
	
	

	float t;//贝塞尔曲线时间点0-1
	vector2 p1, p2, p3, p4;//贝塞尔曲线的四个点
	vector2 pcur;//当前时刻阳光球位置
	float speed;
	int status;//阳光球四个状态
};
struct sunshineball balls[10];
IMAGE imgSunshineball[29];
int sunshine;//阳光值

然后在初始化函数中初始化(加载)阳光球

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

因为阳光球在下落过程中,数据是不断变化的,我们在更新数据函数updategame()写创建阳光球函数和修改阳光球函数

void updategame()//修改游戏数据
{
	
	creatsunshine();//创建阳光
	updatesunshine();//修改阳光状态
}

创建阳光球函数怎么写呢?

阳光球分为俩个部分,一种是自由落体阳光球,另一种是向日葵产生的抛物线运动的阳光球。

6.1 创建自由落体阳光球

先遍历10个阳光球,找到一个没有使用过的,对该阳光球初始化,阳光球状态初始化下降状态,下降只要设置起始点和落地点即可,起始点的横坐标在260-800位置随机出现(随机函数可以看这篇博文随机数的生成),降落的纵坐标在4行草坪高度随机出现,dis()是计算俩个坐标之间的距离(可以看成俩个向量相减的模长),阳光球从起始位置时t=0,到目标位置t=1,所以可以以此计算阳光球的运动快慢,若speed=1.0/2,t=t+speed,则需要俩次阳光才能到目的地。

void creatsunshine()
{
    //创建自由落体阳光
	static int count = 0;
	static int fre = 200;
	count++;
	if (count >= fre)//每400帧获取一个阳光值
	{
		fre = rand() % 200;//200-399的随机值
		count = 0;
		//从阳光池中取一个可以使用的
		int ballmax = sizeof(balls) / sizeof(balls[0]);
		int i = 0;
		for (i = 0; i < ballmax && balls[i].use; i++);//找到未被使用的
		if (i >= ballmax)//未找到
		{
			return;
		}
		balls[i].use = true;//使用
		balls[i].frameIndex = 0;//第0个序列图片
		//balls[i].x = rand() % (800 - 260) + 260;
		//balls[i].destY = (rand() % 5) * 90 + 170;//目标位置
		//balls[i].y = 60;
		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(rand() % (800 - 260) + 260, 60);//把vector2看成一个结构体
		balls[i].p4 = vector2(balls[i].p1.x, (rand() % 5) * 90 + 170);
		int off = 2;
		float distance = dis(balls[i].p4.y - balls[i].p1.y);
		balls[i].speed = 1.0 / (distance / off);//下落速度
	}

     //创建向日葵生产阳光

}

6.2 创建向日葵生产阳光

遍历4行8列的植物数组,若是向日葵,则找一个一个没有用过的太阳球,对太阳球结构体修改成员状态,状态改为生产阳光,起始点为向日葵坐标,终点坐标是向日葵左右100-150作用,中间俩个坐标自己调节,

//向日葵生产阳光
	int ballmax = sizeof(balls) / sizeof(balls[0]);
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 8; j++)
		{
			if (map[i][j].type == SUNFLOWER + 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].use; k++);
					if (k >= ballmax)
					{
						return;
					}
					balls[k].use = true;
					balls[k].p1 = vector2(map[i][j].x, map[i][j].y);

					int w =(100 + rand() % 50)*(rand()%2? 1:-1);
					balls[k].p4 = vector2(map[i][j].x+w,
						map[i][j].y+imgplant[SUNFLOWER][0]->getheight()- imgSunshineball[0].getheight()+10);//喷射下落位置
					balls[k].p2 = vector2(balls[k].p1.x + 0.3 * w, balls[k].p1.y - 50);
					balls[k].p3 = vector2(balls[k].p1.x + 0.7 * w, balls[k].p1.y - 50);
					balls[k].status = SUNSHINE_PRODUCT;
					balls[k].speed = 0.05;
					balls[k].t = 0;

				}
			}
		}
	}

七、收集阳光

收集阳光属于用户操作,收集阳光函数应在用户操作函数中,若鼠标弹起状态位置不是卡牌区域,判断是否在阳光球位置,若是在阳光球位置,改变阳光球的状态(阳光球有四种状态)为收集状态,阳光球的起始位置为点击位置,终点位置为工具栏的阳光图片。

void userclick()
{
	ExMessage msg;//这个结构体变量用于保存鼠标消息
	static int status= 0;
	
	if (peekmessage(&msg,EX_MOUSE))//判断鼠标消息,有返回真,无返回假
	{
	if (msg.message == WM_LBUTTONUP)//鼠标左键弹起
	{
		if (msg.x > 338 && msg.x < 338 + 65 * PLANT_COUNT && msg.y < 96)
		{
			
		}
		else
		{
			collectsunshine(&msg);//收集阳光
		}

	}
	else if (msg.message == WM_MOUSEMOVE && status == 1)//鼠标移动
	{
		
	}
	else if (msg.message == WM_LBUTTONDOWN)//鼠标左键按下
	{
		curplant = 0;
		status = 0;
	}
	}
}
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++)//若鼠标左键弹起的位置在阳光里面,则阳光消失,且阳光值加25
	{
		if (balls[i].use)
		{
			//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 + 8) && msg->x<(x + w - 8) && msg->y >(y + 8) && msg->y < (y + h - 8))//鼠标左键弹起的位置在阳光里面
			{
				//balls[i].use = false;
				balls[i].status = SUNSHINE_COLLECT;
				//sunshine += 25;
				//mciSendString("play res/sunshine.mp3", 0, 0, 0);//播放音效(这个音乐需要上一个播放完才能播放下一个)
				PlaySound("res/sunshine.wav", NULL, SND_FILENAME |SND_ASYNC);
				//设置阳光偏移量
				//float destY = 0;
				//float destX = 262;
				//float angle = atan((balls[i].y - destY) / (balls[i].x - destX));
				//balls[i].xoff = 4 * cos(angle);
				//balls[i].yoff = 4 * sin(angle);
				balls[i].p1 = balls[i].pcur;//直线运动不用p2,p3.
				balls[i].p4 = vector2(262,0);
				balls[i].t = 0;
				float distance = dis(balls[i].p1 - balls[i].p4);
				float off = 8;
				balls[i].speed = 1.0 / (distance / off);//实际distance/off帧回到原点
				break;//收集该阳光球后,判断下次点击
			}
		}
	}
}

八、更新阳光球状态

前面我们已经有了四种阳光球状态,然后对这四种状态阳光球进行数据修改,

(1)如果正在使用阳光球处于下降状态,t=t+speed,pcur=p1+t*(p4-p1),通过t就可以得到下降状态的位置,此时阳光球状态变为落地,计时器归0;

(2)如果正在使用阳光球处于落地状态,使太阳球停顿一会再消失;

(3)如果正在使用阳光球处于收集状态,此时运动是斜直线,用疑似贝塞尔曲线确定阳光位置,t=1时,阳光球消失,计数器加25

(4)如果正在使用阳光球处于生产状态,此时运动状态是曲线,用贝塞尔曲线计算位置,t=1,到终点位置,变落地状态

void updatesunshine()
{
	int ballmax = sizeof(balls) / sizeof(balls[0]);
	for (int i = 0; i < ballmax; i++)
	{
		if (balls[i].use)//被使用状态
		{
			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 > 25)
				{
					balls[i].use = 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->use = false;
					sun->timer = 0;
					sunshine += 25;
				}

			}
			else if (balls[i].status == SUNSHINE_PRODUCT)
			{
				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;

				}
			}

		}
	}
}

上面我们设计,当阳光求回到工具栏中,阳光值增加25,我们的目的想使阳光值显示再工具栏上。

有俩方法,一种用图片呈现,另一种直接输出字体

这里我们输出文本,先在初始化函数中设置字体

//设置字体
	LOGFONT f;//当前字体结构体变量
	gettextstyle(&f);//获取当前字体
	f.lfHeight = 30;
	f.lfWidth = 15;
	strcpy(f.lfFaceName, "Segoe UI Black");//修改字体类型
	f.lfQuality = ANTIALIASED_QUALITY;//抗锯齿效果
	settextstyle(&f);//设置字体文本
	setbkmode(TRANSPARENT);//设置字体背景透明
	setcolor(BLACK);

然后渲染阳光值数字,要把十进制数字转化为字符串,再去渲染

   char scoretext[8];
	sprintf_s(scoretext, sizeof(scoretext), "%d", sunshine);//将阳光值存储到字符数组
	outtextxy(276, 67, scoretext);//输出字符数组里面的字符

接下来我们将阳光球渲染出来就可以看到效果了

//渲染阳光球
	int ballmax = sizeof(balls) / sizeof(balls[0]);
	for(int i=0;i<ballmax;i++)
	{
		//if (balls[i].use||balls[i].xoff)
		if(balls[i].use)
		{
			IMAGE* img = &imgSunshineball[balls[i].frameIndex];
			//putimagePNG(balls[i].x, balls[i].y, img);
			putimagePNG(balls[i].pcur.x, balls[i].pcur.y, img);

		}

	}

后续精彩内容,请点击  植物大战僵尸(下) 进行跳转

 完整的代码及素材链接:

植物大战僵尸源码及素材

  • 60
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 45
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

日刷百题

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

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

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

打赏作者

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

抵扣说明:

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

余额充值