C语言学习记录8

书接上文,这几天完成了以下任务:

1.阳光:随机掉落,收集动画,不捡拾消失功能。

2.僵尸:随机生成,行走动画。

3.豌豆子弹:发射判定,发射动画,碰撞检测,爆炸特效等。

同时对游戏制作的基本框架有了一个更加深入的认识。

今天主要聊下对象的处理。

例如僵尸,我们需要先创建一个结构体储存僵尸的基本信息,也就是僵尸的“身份证”。这个“身份证”里储存了僵尸的属性。如xy横纵坐标来表示僵尸位置,由于僵尸是一行行进攻的,也可以再定义一个row变量来表示僵尸所处的行,后续处理起来会更加方便。位置确定了,僵尸的血量也可以通过定义一个变量blood来表示。那行走时的动画怎么办呢,动画本质上是一系列图片的快速播放,我们可以自己去找这样的一组图片,它们循环播放就是僵尸行走的动画,然后给每个图片都标好序数,这样,我们可以在僵尸的“身份证”里定义一个动画帧序数,用来表示当前播放到第几帧。通过不断刷新界面,僵尸就可以完成行走动作啦。

除了行走动画,僵尸也要设计死亡动画,还要判定僵尸是否死亡等等,这些都可以写入“身份证”里。

另外,在编写时,我们可以利用池的概念。所谓池,就是若干个对象的集合。当我们需要生成僵尸的时候,不是直接创造的,而是从池里拿出一个僵尸来,使用完后再放回去。那么这里如何表示僵尸是否使用呢?就需要在“身份证”里再加入一个布尔型变量used,若为真,那么僵尸处于场上,若为假,那么僵尸位于池中。

经过多方研究,最终僵尸的“身份证”明细如下

struct zm {
	int x, y;
	int row;
	int frameIndex;
	bool used;
	int speed;
	int health;
	bool dead;
};

再创建僵尸池。

struct zm zms[10];

这样,创建工作就完成啦。

然后是我们的初始化工作。我们需要将zms结构体数组中的所有变量先清零,也就是初始化。

memset(zms,0,sizeof(zms));

通过这样一个内存赋值语句将zms清零

再导入图片

	for (int i = 0; i < 22; i++) {
		sprintf_s(name, sizeof(name), "res\\zm\\%d.png", i + 1);
		loadimage(&imgZM[i], name);
	}

这边把图片名设计成连续的数字,用sprintf函数依次把文件名赋给name,再以name为路径把图片导入到imgZM中(imgZM是之前创建的图片数组)C语言学习记录8_第1张图片

这样我们前期工作就完成了。

接下来制作生成模块。

生成僵尸的时候,先从池里拿出一个来,如果未被使用,则进行生成,如果被使用了,则换下一个

int i;
int zmMax = sizeof(zms) / sizeof(zms[0]);
for (i = 0; i < zmMax && zms[i].used; i++);
if (i < zmMax) {
	zms[i].used = true;
	zms[i].x = WIN_WIDTH;
	zms[i].speed = 3;
	zms[i].row = rand() % 3;
	zms[i].y = 172 + (1 + zms[i].row) * 100;
	zms[i].health = 100;
}

这里定义了僵尸的初始值。zmMax是池子的大小,可以在创建之初就进行修改。使用了rand函数来随机僵尸出现的行数,需要在初始化里配置一个随机种子。

有些同学可能对这里的for语句感到奇怪,因为这个语句并没有后续的内容。

其实它只是负责生成没有被使用僵尸的序号。

那我们的僵尸这样就生成好了,只不过是数据层面的生成,并不能在图上显示出来。

而且这样生成的僵尸是静态的,为此我们需要把这个僵尸的参数传入更新模块,让僵尸的参数动态变化,需要动态变化的值有动画帧序数,僵尸的横坐标,血量等。

在进行更新操作之前,还有一个小细节要处理。就是如果没有限制的话,这个程序会一次性生成池子里的所有僵尸。因为电脑的程序运行是很快的,在很短的时间里这个程序会多次重复,所以我们需要限制僵尸生成的速度。

static int zmFre = 50;//改改改啊
static int count = 0;
count++;
if (count > zmFre) {
	count = 0;
	zmFre = rand() % 200 + 300;
	int i;
	int zmMax = sizeof(zms) / sizeof(zms[0]);
	for (i = 0; i < zmMax && zms[i].used; i++);
	if (i < zmMax) {
		zms[i].used = true;
		zms[i].x = WIN_WIDTH;
		zms[i].speed = 3;
		zms[i].row = rand() % 3;
		zms[i].y = 172 + (1 + zms[i].row) * 100;
		zms[i].health = 100;
	}
}

对生成代码做以上修饰。利用一个静态变量记录程序重复的次数,当重复到一定次数时才执行。

大意是,当程序运行次数达到fre规定的次数时,才会生成僵尸。初始是运行51次后生成第一只僵尸,随后利用rand函数让僵尸出现的频率随机化。

值得注意的是,如果你想降低某个动作发生的频率,也可以借用这种处理。

这样就可以保证僵尸不再扎堆出现了。

接下来进行我们的更新操作。将数据传入更新模块,僵尸的位置变化所需的数据更新在这里完成。

for (int i = 0; i < zmMax; i++) {
	if (zms[i].used) {
		zms[i].x -= zms[i].speed;}

还记得zms[i].used代表了什么吗。这个参数是用于判断僵尸是否在池内的。遍历池的所有僵尸,只对在池外的僵尸进行坐标变换操作

for (int i = 0; i < zmMax; i++) {
	if (zms[i].used) {
		zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;
	}

遍历池里的所有僵尸,只对池外的僵尸进行动画帧序数变换操作。大家可以学习一下这里的数字从1到22循环变化的表达法哦。

同样在生成模块里出现过的问题,僵尸移动太快了,动画播放也太快了,非常鬼畜。

那么如何减缓移动呢?

上文刚刚提到,我们利用一个静态变量进行计数,减缓了僵

尸生成的速度,那么我们可以用同样的方式减低僵尸的移动速度和动画播放速度。这也是为什么我不将这两个函数合并在一起写的原因,因为需要调节不同的频率。比如程序运行总共6次吧,僵尸移动函数段执行2次,动画帧序数变换函数段执行3次。需要定义不同的静态变量来计时。具体实现如下

int zmMax = sizeof(zms) / sizeof(zms[0]);
static int count = 0;
count++;
if (count > 2) {
	count = 0;
	for (int i = 0; i < zmMax; i++) {
		if (zms[i].used) {
			zms[i].x -= zms[i].speed;
			if (zms[i].x < 170) {
				printf("GAME OVER\n");
				MessageBox(NULL, "over", "over", 0);//后期优化游戏结算
				exit(0);
			}
		}
	}
}
static int count2 = 0;
count2++;
if (count2 > 1) {
	count2 = 0;
	for (int i = 0; i < zmMax; i++) {
		if (zms[i].used) {
			zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;
		}
	}
}

中间加了点简陋的失败判定,后续会进行优化。

直到现在,博客写了将近3000字,但我们还差最后一步:渲染。

目前我们所做的所有工作都是数字层面的,要真正实现屏幕上的姜丝,还需要将数据再传入渲染模块。

	int zmCount = sizeof(zms) / sizeof(zms[0]);
	for (int i = 0; i < zmCount; i++) {
		IMAGE* img = &imgZM[zms[i].frameIndex];
		putimagePNG(zms[i].x, zms[i].y - img->getheight(), img);
	}

遍历池里所有的僵尸,将该僵尸的动画帧序数传入指针,指针指向之前导入的照片组。

然后就能实现僵尸行走啦!

 

你可能感兴趣的:(学习)