C语言实现植物大战僵尸----学习过程

本文是一位学生使用C++和EasyX库开发植物大战僵尸游戏的学习记录,详细介绍了从项目初始化、图片加载、鼠标事件处理、植物拖动与种植、植物摇摆、游戏循环优化到添加游戏启动菜单的整个过程。作者通过实践学习,逐步理解并应用了C++编程、游戏逻辑和图形界面设计的相关知识。
摘要由CSDN通过智能技术生成

说在前头:

这个学习记录是我很久之前写的了,而自从把项目提交上去之后我就再也没有更新过这个文章,现在再更新估计也不记得写的是什么了(还有是因为懒_(:з」∠)_ ),不过我已经把演示视频和源代码以及图片资源什么的都上传到b站了,各位可以去看一下。

【C/C++】大一生自制植物大战僵尸_哔哩哔哩_bilibili

源代码和图片音频资源都在视频置顶评论里面,

下面的内容就是很久之前写的了,如果感兴趣的也可以看一看。


大一下学期c语言课程设计要我们用c语言制作一款游戏,之前网上冲浪时候发现了c语言实现植物大战僵尸的教程,就想来跟着教程做一遍,并记录下自己的学习过程与经验。

前排分享我所学习的视频和文章:

【可能是B站最好的植物大战僵尸教程了!C语言实现完整版植物大战僵尸!】

可能是B站最好的植物大战僵尸教程了!C语言实现完整版植物大战僵尸!_哔哩哔哩_bilibili​www.bilibili.com/video/BV1vM4y1X7Kb/?share_source=copy_web&vd_source=02083e443bb09953d9f519d1f2a2601b正在上传…重新上传取消

(12条消息) C语言手写-植物大战僵尸_程序员Rock的博客-CSDN博客​blog.csdn.net/pzjdoytt/article/details/128875554

如果有人也想做可以和我一起发出来学习进度呀~也算是督促自己学习了~

本文章是我原本在知乎里写的,想着就顺便在这里也发一遍,看的人说不定多一些呢~


3.25:创建主场景

开始学习

首先是安装Visual Studio和EasyX,前者是用来敲代码的,后者是一种专门用于c++的输出图片工具,

安装过程就给省略拉,想看的可以在网上找教程~

然后就是创建新项目,以及下载植物大战僵尸的资源包。

这是那个教程视频里的群提供的

以及,将属性中的字符集改成使用多字节字符集,不然easyx中的一些函数就用不了。

具体教程在教程文章的开头里也有

之后就是开始写代码了~

首先是头文件:

为了防止有人直接白嫖而不学习,就用图片的形式表示了~

然后是主函数:

目前来说需要两个函数,一个是用来初始化游戏,另一个用来更新并显示游戏窗口。对于图片输出,添加easyx后就可以使用IMAGE变量,也就是专门用来表示图片的变量。

另外,在下面的内容中,只要遇到需要用到easyx中已经写好的什么什么函数的,我会在所述的函数前加个“easyx的”,方便进行叙述以及区别原本自带的函数和需要自己写的函数。

游戏初始化函数

游戏初始化中用到了easyx中的loadimage加载图片函数,相当于是给程序一个图片,但是还没有将这个图片显示出来,左边是变量名,右边是该图片在你的电脑中的实际地址(从这里应该能看出来地址只需要从源代码所在的文件夹位置开始找就行了,前面D://什么的可以省略)

在这里也用到了easyx的initgraph创建窗口函数,就是让程序创建一个自定义长宽的窗口,而长宽则可以用define宏定义来方便修改。

在这里使用了900*600的窗口大小

这时候如果运行程序,则会出现一个900*600的黑屏窗口,要想让图片显示则要写一个更新游戏窗口函数

更新游戏窗口函数

用到了easyx的putimage显示图片函数,即让程序将之前所获得的图片显示出来。最后那个是图片变量名,而前两个即为图片在窗口中的位置,用二维坐标图来表示:

左上角为原点,两个数字分别为x和y,则图片的x,y值即为图片左上角相对于原点的坐标。

由此,便完成了基础的图片显示工作,此时运行程序便是这样的:

至此,第一节课就到这里辣~

自测问题:

IMAGE变量不成立+超多超多错误信息,且程序无法运行。

解决方法:

由于是c语言学习,所以一开始就自己把代码后缀从cpp改成c了,而easyx只支持c++,所以后缀仍保持cpp不变,而也可以正常写c语言代码辣~


3.27:实现植物卡牌

这节课的目的就是在输出背景图片的基础上再输出植物卡槽+卡牌。

同理,先定义easyx中的IMAGE变量,然后加载图片,最后再是找到图片二维坐标并输出图片。

而由于卡槽图片的边框是黑色的,所以这里需要用到图片透明度改变函数来去掉黑色边框(相当于是剪辑视频时候,有的素材需要去掉绿幕),而这里用到了另一个写好的代码:

也是植物大战僵尸资源包自带的

太过深奥就不细看了2333

用这个代码则需要引入新的头文件:

具体教程在视频中有

引用其他文件的代码一般都说都说这样做的

之后就是显示植物卡牌:

直接使用数组变量便于写代码

枚举变量

在这里用到for循环来一个一个加载图片,而由于每个图片的名字不一样,地址不一样,所以用到了stdio.h中的sprintf_s函数来生成并改变卡牌的文件名,同时用到了枚举变量(目前还不确定植物卡牌做几个,而Cards放最后一个又可以一直表示最终所有卡牌的数量,即在该代码中Cards用int表示就是2,可以用到for循环中,甚为精妙!)。

之后便是显示图片,同时每一次显示都要改变卡牌的二维坐标值。

运行程序:

完美!(虽然不是我做的_(:з」∠)_ )

至此,第二节课就到这里辣~


3.30 :判断是否正确点击植物卡牌

前两次学习所输出的图片都是静态的,即一次输出并一直持续直到程序结束,而植物的摆动,僵尸的移动等都是动态的,即不断输出新的图片,所以需要循环来实现效果。

用while循环来不断刷新画面,图中的userClick是接下来要写的判断鼠标点击的新函数

而用无限循环的话,不断输出图片又会导致图片一闪一闪的,所以需要用到双缓冲。

即开始缓冲和结束缓冲

由此程序就可以做到既不断输出图片又不会做到图片一闪一闪的。而双缓冲我感觉是个挺重要的东西,但是这次由于时间原因就放到下一次再仔细学习一下双缓冲的原理吧。

接下来就是鼠标的活儿了。

userClick函数

新东西有点多,我会一点一点介绍(下面两个else if还没学到):

就是定义一个easyx中的ExMessage结构体的变量,而这个ExMessage是一个已经给定的结构体类型,官方介绍链接如下:

EasyX 文档 - ExMessage​docs.easyx.cn/zh-cn/exmessage

就相当于struct msg,但是这个struct里面的所有变量都已经给好了

用我的话来说就是在easyx.h库中已经定义好了该结构体类型及其所含变量,而上图是直接使用并以msg命名一个新的该结构体变量,之后便可以用该结构体中定义好的所有变量,包括message, x, y等。

可以很明显地看出是结构体变量

该图只展示了这个结构体里面变量的一部分

而后第一个也是最外面的if语句是用到了easyx的peekmessege函数来判断鼠标是否在屏幕中进行任意一次行为,官方介绍链接如下:

EasyX 文档 - peekmessage​docs.easyx.cn/zh-cn/peekmessage

这个&目前还不是很清楚,应该是有关指针之类的吧

结合这张图来讲,用我的话来说就是peekmessage是判断括号中的msg是否进行任意行为了(就是那个结构体中的任意变量),如果有任意一个,则符合peeekmessage判断的获取到了消息,并返回true,数值为1

peekmessage函数的返回值判断

如果进行了则再细分鼠标点击的用途,如果鼠标是进行了一次左键点击行为,就是那个WM_LBUTTONDOWN,类似的还有:

这个也是在那个ExMessage链接里面的

则再进入一个if语句中,即判断鼠标点击是否落在了植物卡牌的坐标上,如果是的话,status的值变成1,便于进行下一步,就是拖动植物。

图片中的数字也是需要自己用画图工具量出卡牌在二维坐标的范围

而其中那个index则是用于测试,即测试是否点击到了植物卡牌。从代码中可以看出,点击第一个卡牌,则index的值为0,点击第二个卡牌,index的值为1,以此类推。而printf则是输出index的值。但是由于easyx的initgraph函数目前只有两个输出,就是屏幕的长宽,所以我们在右边再加个1,就可以做到在运行程序时出现的附带的命令提示符中显示点击结果。

这个1在这节课过后便会删掉,因为只是用来测试的

视频演示

点击第一个卡牌出现0,点击第二个卡牌出现1,如果成功出现,则说明写对了。

至此,第三节课的前一半便到这里辣~


4.1 :实现在成功点击植物卡牌之后的植物的拖动

拖动植物就要在这个if语句中进行了:

用int型的curX和curY表示拖动过程中植物的二维坐标。

需提前定义好

原理:在选中植物时,会在鼠标所指位置输出显示植物动态画面的第一张图片,而在种下时,由于植物是要动态移动的,就是左右摇摆差不多(掐住豌豆射手的头,左右摇摆(08乱入)),所以需要连续加载植物的每一帧图片。

但是由于每一个植物的图片数不一样,再额外给每个植物创建数组会使代码变得冗杂,所以我们可以用到指针数组!

这个Cards是enum枚举变量中的

我们在玩一种很新的指针数组(),大致含义就是定义一个二维数组,每一行都分别表示一个植物,而20是储存指向IMAGE变量的指针,即每一个数组元素都是一个指针变量,指向一帧图片。豌豆射手有13张,向日葵有18张,故我们设置20个,保证比最大图片数还要大。

定义完之后,我们需要对这个数组进行初始化(我也不知道为什么_(:з」∠)_)

这个函数要写在gameInit函数中

用到了string.h中的memset函数,将imgPlants二维数组中的所有元素全置为0。

接下来就是加载植物的时间!

第一个for循环已经讲过,就是加载植物卡牌图片的,主要是讲第二个for循环

两个for循环可以做到加载每一种植物的卡牌和动态图片,看其中第二个for循环,先用sprintf_s生成植物的文件名,后我们在加载图片时需要判断该图片是否存在,因为循环设定的是20,即加载20个图片,但是植物图片本身没有那么多,所以就要判断是否存在,不存在就不加载。判断就要用到fileExist函数:

新函数,其实也没多少东西,就是用文件函数用只读打开一下该图片文件,如果能打开就关闭一下然后返回true值,如果不能打开就意味着没有这个文件,那就返回false。这里需要注释一下bool变量:即只能存储true和false,对应数值就是1和0,就是用bool定义的变量,要么是0,要么是1(总感觉有点奇怪)

之后要做的就是为imgPlants数组分配内存,因为原先imgPlants数组由于使用了memset函数使得数组中每个元素都为0,这时候如果直接loadimage则会导致程序崩溃,因为指针没有对象可指。而这里的new IMAGE也是一种很新的东西,之后我会再去了解。而之所以不用malloc函数分配内存,是因为malloc只支持c,不支持c++。之后便是加载图片了。

加载完图片之后,我们便可以渲染拖动过程中的植物了,就是把加载好的图片一个一个显示出来。

此时需要前一步的判断,即判断鼠标是否点击到了卡牌的二维坐标范围内。

同curX一样定义一个全局变量curPlants

定义完成之后,在判断是否点击到卡牌位置处,用index赋予curPlants数值。

有了判断,便可以真正渲染拖动过程中的植物了:

这个语句是在updateWindow函数中

easyx和tool.h中的putimagePNG函数,输出一个去掉黑色边框的植物。前两个是x,y坐标值,后面是该元素的地址,由于是二维数组,所以不再需要取地址符&。

上面一些列过程可以举个例子来走一下流程,方便理解:

比如点击第一个卡牌,豌豆射手,那么index的值就为0,curPlants的值就为1,那么输出的就是imgPlants[0][0]。

(其实我感觉有点麻烦,为什么不直接把index改成全局变量,这样就不需要curPlants了,也不知道后续有没有更改,在学习一段时间后,我应该会按照自己的想法改一改代码吧)

对于那个-32,我们可以先去掉运行看看:

源自教程视频

如图,无论鼠标怎么移动,光标一直显示在植物的左上方,相对没有移动。这是因为光标所指示位置的是图片的左上角,所以需要更改一下图片的输出位置:

我自己的改法

教程中的改法

教程中是用到了一个新的指针变量,对我来言更加复杂了,而且又额外占据内存,所以我直接减去32,实现的效果是一样的(doge):

如图,光标显示在植物的正中央了,也符合游戏中的体现。

至此,第三节课的后一半便到这里辣~(话说这个第三节课真是不容易,花了我三天的时间(虽然大多数时间并没有在学习吧。。。))

自测问题:

提示fopen函数不太安全,但是我们这又不至于泄露或者被偷啥的,所以不用担心。

解决方法:

【C语言】解决error C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead...​blog.csdn.net/muzihuaner/article/details/109886974正在上传…重新上传取消

找到项目属性,点击C/C++,点击预处理器,在预处理器定义中加上 _CRT_SECURE_NO_WARNINGS 即可。


4.3 (第四节):实现植物的种植

在实现植物卡牌的选择以及拖动后,我们便可进行植物的种植了:

​在此之前先要强调的是,我学的这个课的老师所写的拖动并种植植物的逻辑是:鼠标左键按住植物卡牌后出现植物图片(WM_LBUTTONDOWN 即鼠标左键按下去一次),但是此时不要松开左键,就一直拖动鼠标,同时植物图片也在跟随移动(WM_MOUSEMOVE 即鼠标/光标移动),直到光标放在可种植的草坪上,此时再松开左键,植物图片便出现在草坪上了(WM_LBUTTONUP 即鼠标左键松开一次)(这个地方我又讲了一次,听不懂就是你的问题喽~)。这与真正游戏中有所区别,这个之后也是我想自己更改的地方,不过目前还是跟着学吧。

种植就要对每个方块草坪进行测定了,先看看我们游戏草坪画面:

​是一个3行9列的草坪,而每一个草坪块都有存在植物与不存在植物两种状态其中之一,以及存在植物的种类等,为了方便表示,我们就可以定义一个结构体数组:

这个也是个全局变量,即不放在任何函数中

type表示植物种类,frameIndex表示序列帧,即植物后面要左右摇摆之时的帧数表示。同后面要用到其他的再写新的变量。时不要忘了给map数组初始化:

​之后用一个3*9的二维数组来存储每一个草坪块的状态。之后便是对鼠标左键松开之后的判定了:

在这里我才发现curPlants的用处,就是储存植物种类,0为没有选中,1为选中第一种植物。

其实定义的时候就写好了,只不过我忘了qaq

emmmm,不过还是感觉先赋值给index,再把index+1后赋值给curPlants没有什么必要啊.....

​这里用了一个index来表示点击到卡槽中第几个卡牌,338即为x轴上第一个卡牌的位置

​直接去掉index改成curPlants = 卡槽位置 + 1

这样优化就方便多了,毕竟少了一个变量,okok继续。

还是回到刚才那个有WM_LBUTTONUP的图片:

实际上如果那个else if只有这两个东西:

​那么在松开左键之时,跟随光标一起移动的植物会瞬间消失,植物也没有种下。其实也不难理解,因为松开之后肯定是种下植物或者想换一个植物种呗,那么就要删去当前一直跟随光标移动的植物。此时status为0则不能进行这个else if语句:

​植物跟随光标移动

而且植物种类也回归到0,即没有植物。所以这个可以说是完成植物种植后的清扫工作。

但是我们肯定是要种植物的拉~所以使用map数组来表示:

​这里的256其实就是在图片中最左边草坪块的左边的x值,179和489即为最上边草坪块的最上边的y值和最下边草坪块的最下边y值,而用到这个if语句其实就是要判断松开鼠标左键之时光标所处的位置是否在草坪上,如果没有那还中个毛呢~而map是包含了3行9列的结构体,那么map[][],第一个中括号的数肯定要为0到2之间,第二个中括号的数肯定要在0到8之间,但是草坪块的x值y值是一个范围,如map[0][0]表示左上角第一个草坪块的状态,但是草坪块的xy值只要在一个区间内,那么都是表示这个草坪块(不知道我说清楚了没....),所以用row(行)和col(列)来表示该草坪块所处的行数与列数。

​这里的179即为最上端的y值,256为最左端的x值,102即为每个草坪块的上下行的y值之差,即每个草坪块的长度,81为宽度。之后便可用printf测试一下是否放置精确,老样子,initgraph后面要加个1。

至此,便完成了植物的放置检测工作,而且所要放置植物的草坪块的行列值也确定了,接下来就是给该草坪块的状态进行更新:

​而在放置植物之前,我们需要先判断这个草坪块有没有已经存在植物,如果存在那就肯定不能种了,所以加个if语句判断该草坪块的种类type是否为0,是0则继续,将这个草坪块的种类type更改为curPlants的值(两者都是表示植物的种类)然后将该草坪块的植物帧数状态frameIndex置为0,即为植物第一帧,第一张图片。存储完植物的鸽各种信息之后,便可以开始渲染/输出植物了!

​这个语句是在updateWindow里面,即专门渲染/输出图片的函数

其实要是只渲染该草坪块的植物,未免有些麻烦,就直接把所有草坪块都渲染一遍!故而用到了两个for循环,先第一行从左到右一个一个渲染,再第二行.......用if语句判断该行有没有植物,没有就不渲染,由此再把植物的二维坐标值弄出来,这里的256,81,179,102其实全是刚才剪掉以及除掉的,现在再重新加上乘上变成图片中的二维坐标值,再表示植物种类和植物帧数状态。注意这里的imgPlants,其实就是之前的指针二维数组,就是用这个提前加载了所有植物的每一帧图片。之后再用putimagePNG输出。

至此,第四节课就到这里了。

抽时间做的可视化图,应该可以帮助理解吧~


4.5(第五、六节):实现植物的摇摆以及优化游戏循环和游戏渲染程序

在成功把植物种下之后,我们就要进行植物摇摆的输出了。目前代码中的updateWindow()只是输出一个图片,而即只有一个putimagePNG函数,那么我们就另加一个新函数,来不断改变/更新map结构体中frameIndex的数值:

​即为updateGame()函数

原理就是在种下植物的鼠标操作后,updateWindow()输出该植物的第一帧图片,而后updateGame()再进行一系列判断,后更新序列帧的值,即自加一(map[i][j].frameIndex++),以下是具体代码:

​独立的函数

原理就是用两个for循环遍历所有草坪块,判断该草坪块上是否有植物(即map[i][j].type是否大于0),如果有,则序列帧自加1。而由于每个植物的序列帧总数不一样,故而之后需要再判断以下指针数组的元素中是否再有指针。plantsType即为植物种类,之所以要减去1是因为数组元素序列就是从0开始的(这地方我都差点没搞懂我到学了什么啊= =)。index同理,即为序列帧。判断是否为NULL,即是否为空指针/数值是否为0,如果是的话,那就代表一组序列帧已经输出完毕了,那么就重置序列帧的数字,因为植物的摇摆是循环输出的。这时候我们运行一下:

是输出成功了,但是植物也开始鬼畜起来了(doge)

大致是因为代码运行速度很快,渲染的速度也跟着快起来了,输出序列帧图片间隔都是在毫秒之内。

所有我们需要用延迟控制一下摇摆速度。如果直接用sleep(30),是可以做到延迟了,但是操作会很不舒服。。。。总之自己加上这个再运行一下就知道为什么了,而且再30毫秒之间如果再进行鼠标信息,那么也会跟着延迟30秒(emmm我的表述可能不太清楚),总之就是如果让玩家在进行一次鼠标信息后,在给定的30毫秒延迟间隔内,无论玩家进行多少次鼠标信息都不会进行反馈,那么就可以获得更好的游戏体验,就是。。。。怎么说呢。。。。。就是让玩家在未完成上一步操作之前,不会再因为鼠标信息而进行下一步操作而使得游戏操作变得冗杂,这样表述应该可以理解吧。。。。

所以我们不用sleep函数,而用到以下东西:

​全部是在main函数中加的东西

getDelay()函数也是tool.h中的函数,简单来说就是每次代码走到它的时候,它就会返回从上一次调用到这一次调用所用的时间,单位为毫秒,所以我们用timer储存一下,就可以实现鼠标信息只能最多每30ms提供一次,而flag也是进行判断的。简单来说就是userClick中接收鼠标信息,之后走到getDelay函数,timer储存从上一次走到getDelay函数到这一次走到这个函数的时间间隔(其实就可以等同于看作两次接收鼠标信息的时间间隔)。如果时间大于30ms,那么就可以继续updateWindow和updateGame,如果没有,那么就不走这两个,简单来说就是最后一次接收的鼠标信息被废弃了,没有反馈,这样就可以了。

其实这个鼠标信息间隔对植物摇摆没什么影响,因为植物摇摆又不需要鼠标信息,故而在这里可以理解为每30ms渲染一次植物序列帧所在的图片。而真正影响到的应该是拖动过程中的植物,显而易见,如果那个延迟过大,比如50ms什么的,那么在拖动过程中我们会很明显地感觉到掉帧现象,其实就是渲染两个二维坐标间的图片的时间间隔过大,感觉一卡一卡的。而我认为这一系列函数在之后有更大的用处。

至此,第五节课和第六节课的学习就到这里了。


4.7(第七节) 显示游戏启动菜单

都开始做游戏了,没有启动菜单怎么行!今天就开始搞一个~

由于这一部分是和其他代码有所区别的,所以我们再加一个函数:

​startUI显示游戏启动菜单

附上该函数全代码:

​先说明,我和老师的代码有所不同,简单来说就是我进行了一些更改来更贴近原游戏。

首先便要用到三个图片,一个是最开始的背景图片,还有两个点击开始的图片,不过一个是正常亮度,另一个是高亮用来在鼠标移动到这个图片上面时输出的。然后便是while循环,因为这里的图片输出也是动态的。

(在这里用imgMenu1表示普通图片,imgMenu2表示高亮图片)

老规矩,双缓冲,然后是输出图片,不过在这里老师用到了三目运算符,这样整个while循环对于那个点击开始的图片就只需要用到一个putimagePNG就可以了。(显然在每个if语句下面放输出会更加容易想到,但是如果真是这样的话会导致高亮和普通图片来回反复切换。具体原因大概是,如果那样写了,以第一个if为例,原本输出imgMenu1,光标二维坐标在图片范围内且进行鼠标移动的话,就输出imgMenu2,之后后面的else if都跳过,然后回到最开始的,会再次输出imgMenu1,之后在进行第一个if语句,这样就会导致高亮和普通图片来回反复切换,所以不能这样用,只用一个putimagePNG即可。

接下来就是开始分析每个if和else if语句!

​因为用到的函数什么的都是已经接触过的,所以就简略说一下:

第一个if已经说过了,跳过。

下面那个else if是判断鼠标是否在那个图片的外面,如果是的话就输出imgMenu1。

在下面else if是判断鼠标是否在图片范围内进行一次左键按下,如果是的话就可以启用最后一个else if语句了。

最后一个else if语句就是在判断鼠标左键是否松开一次,然后就是结束这个函数了,因为已经点击开始游戏了。

在老师教的基础上又实现了自己的想法,好耶!qwq

至此,第七节课的学习就到这里了。

顺便更新一下可视化流程:

​越来越多了呢2333


4.10 自己优化一下代码:

视频中所教的点击植物到种下植物的过程是需要在拖动的过程中一直保持鼠标左键按下不松手,这与原本游戏中的设计是有悖的,所以我就想自己改一下(其实也不难):

​在UserClick函数中

将原先的WM_LBUTTONUP改为WM_LBUTTONDOWN,同时再加上鼠标左键进行一次按下行为之后提供的数值为1的status,再加上判断是否在草坪块内,即做到了不用一直按着鼠标左键不放手。

其实还挺简单的~


4.22(第八、九节) 创建与显示随机阳光

这段时间忙着中期检查,过了之后又摸鱼了一段时间,抱歉~~

在中期检查的时候我看到别人做的都挺快的,有的甚至感觉已经做完了,必须要赶快了!

哦对了,准备中期检查的时候我顺便又加上了背景音乐,既然有背景音乐那就先把播放音乐部分先讲讲:

播放音乐需要用到这两个库文件:

然后就可以用函数播放音乐了~

​播放音乐不需要像图片那样先加载再显示,可以一步到位:play表示的是播放,如果换成close那就是停止播放,中间写的是音乐文件地址,而后面那个repeat就是一直重复播放,而后面三个0目前没找到有什么意义。而如果想要修改音量:

​就要这样

先用open打开(理论上应该也是需要先open然后再play的,但是直接play好像也行(doge)),然后需要给这个音乐文件取个名字,这里取成bg了(那个alias也是固定的),然后就是play,最后就是调整音量。教程里面教的是80就是80%的音量,但是我都调到200了音量还是减少的,目前也没搞太清楚。。。回头我试着把播放音乐的全过程做一个独立的总结吧。

而后续的音乐以及音效想必就不用我来说了吧~在适当的位置写就行了,根据需要设置是否repeat。

音乐部分讲完了,接下来就是阳光部分!

首先就是给阳光定义一个结构体,这是理所当然的嘛~

​这里课程里面所讲的用到“池”的概念其实感觉有点模糊,我的理解就是把阳光球都放在一个池子里面,用哪个就从池子中取出来一个,暂且先这么理解吧,也不知道和植物帧数那个结构体有什么区别。

然后就是表示阳光的每一帧图片:

​还有别忘了用memset初始化数组:

位于gameInit()中

然后就是loadimage加载每一帧阳光图片:

​也位于gameInit()中

然后就是创建与显示阳光了。

在这里我觉得那个“池”又有新的理解,因为教程中是把createSun()和updateSun()这两个函数放在updateGame()中的,如图:

​这也是第一次开始在自写函数中再写新的自写函数

所以我猜测应该是针对某一个阳光球进行创建与显示,就是在“池”中拿一个阳光球然后进行后续行动。不管怎样,先看createSun():

​因为阳光的出现是随机且不能频率过快,所以我们用到了随机函数,下面进行细讲:

如何才能让代码再经过200帧之后才进行下一步呢?我觉得这个步骤能完美做到,而且可以套用在其他程序中,值得长期记忆:

先用static 定义一个count变量,在不断调用这个函数,也就是等待下一个阳光球出现的过程中,count的数值不会再被更新为0,而是会由于count++而不断增加,而随机帧数则用到了rand()函数:

c语言中rand()函数的用法笔记_rand()函数怎么用_魔戒咕噜侠的博客-CSDN博客​blog.csdn.net/chikey/article/details/66970397

这里有细致讲解,我就先直接用了2333(学是肯定会学的!)

​简单来说,这样就可以做到每次调用时fre都会更新为200到400之间的一个任意数字,再结合下面的if语句就可以做到了在200到400帧内一个随机帧数过程后,创建一个阳光。

而如果要用到rand(),则必须要有time.h库函数以及:

​位于gameInit()中

这样rand()就可以比较好地输出随机数了。

然后是if判断语句:

​简单来说,就是循环判断“池”中是否取完且取到的阳光球是否已经在被使用,如果都通过的话(未取完且取到的阳光球未被使用),那么就可以更新该阳光球的状态了:

改为已经被使用,序列帧为0,初始掉落x坐标也为随机,最终掉落y坐标也为随机,而初始y坐标为固定,并清空timer(乍一看感觉没必要,因为memset已经初始化为0过了,但是这个其实是为了给已经使用过一次的阳光球的,使用过一次,那么timer就不是0了)

阳光已经创建完毕!那么接下来就是显示阳光时间!

​同理,判断是否取完且取到的是否正在被使用,如果是被使用的话,那么就更新序列帧,且在计时器保持为0之前阳光球的y值不断自加,而如果已经到达要掉落的位置,那么计时器就开始计时,直到100帧后,阳光球消失,used改为false,回到池子中去。

这样就实现了阳光的掉落与掉在地上后还要保存一段时间,但是此时我们还无法捡起阳光,那就是下节课的内容拉!

至此,第八第九课的内容就到这里了。

更新一下思维导图~

​又变大好多了qwq

其实还想改一下草坪块的,毕竟原本是三行的,想改成五行的,但是就这个阳光,看视频+导图+文章我就从一上午搞到一下午(虽然也有可能是在此过程中也在摸鱼吧qaq),还是等之后有时间再搞吧!一定会做完的!

或者可以在五一?不行,那也太能鸽了()


5.1(第十节) 收集阳光,显示阳光值

还真是鸽到了五一啊_(:з」∠)_不废话了,广快更新!

在学新东西之前我将草坪块进行了更新,由原来的三行换成了五行:

​也是为了防止查重嘛

改动还挺简单的,基本上就是改一下草坪块范围和长与宽,然后增加一下草坪块数组的数量就欧了~

捡阳光主要是在userClick分析鼠标这个函数中,而阳光球又是用“池”来表示的,所以我们再独立写一个函数:

​位于userClick函数中

​w和h分别是阳光球图片的长与宽,这里用到的也是easyx中的函数,之后便是for循环遍历所有阳光球,判断某一个阳光球是否被使用,以及是否鼠标点击到范围内。判断成功的话sunSum阳光总数则自加25。

​独立定义

但是目前还没有做出捡起阳光后阳光球迅速飞到左上角那个地方的过程。

之后就是设置字体,就是左上角那个显示阳光数的:

​具体过程如下所示,但是我感觉有亿点点发里服笑(),就不再了解了_(:з」∠)_:

​位于gameInit()函数中

​位于updateWindow()函数中

然后就实现了视频中的效果拉~

至此,第10节课就学习到这里了。


5.4(第十一、十二节) 创建僵尸,实现僵尸的行走动作:

开更开更!

在学到如何减慢僵尸速度后,我又自己把植物的速度也减慢了。

废话不多说,广快讲解!

​增加僵尸的代码其实一大部分和阳光球的代码差不多,都是用到“池”的概念,不过僵尸的图片帧需要另外进行刷新,不能和僵尸向左行进的这个刷新一样,因为如果这样的话那么僵尸的图片帧就会刷新的非常块,看起来跟鬼畜了一样_(:з」∠)_。

而这两个函数是和创建阳光球的函数放到一起的:

​位于updateGame()中

首先是createZM(),不过在此之前,先要定义一下僵尸的结构体数组:

​没什么介绍的

然后再是createZM():

​和阳光球的创建基本相同,也是需要用到计数器和随机函数。(那个zmFre一开始设置为10是想要让测试的时候僵尸来得快一点,节省时间)

同时在这里我有一个非常非常需要强调的点!!!

​这个for循环是一个独立的循环!

具体就是:从头开始找出一个还没被取走的僵尸,然后保存这个i值(第几个),再让代码往下走。

xy分别是僵尸出来时候的坐标。

接下来是updateZM():

​这个static定义的count我也不必多说,忘了的可以自己去看上一节的创建阳光,而为什么要在这里加这个,主要是因为即使speed=1,速度还是太快(就是游戏每走一帧,僵尸就往左边走一像素),于是就改慢了一下。之后的for循环也是和阳光球的一样,唯一要变的就是if判断一下僵尸是否走到草坪最左边了(就是游戏要寄了_(:з」∠)_),如果走到了,那就结束游戏音乐,再弹出一个窗口显示游戏结束,点下去之后结束程序。

教程视频说的是待优化,不过我觉得应该是不会再优化了= =,所以之后我准备自己搞!

之后就是要独立减慢一下僵尸帧数播放速率,static定义一下count2,后面的应该也能看懂,我就不加以赘述了。

有了这样一个好方法,那么我感觉也可以运用到输出植物上:

​for循环是遍历草坪块

这样植物摇摆看起来就舒服多了(虽然感觉很不平滑= =)

那么这样就可以了吗?打咩!僵尸的图片还没有加载和渲染捏!

先初始化一下僵尸结构体数组:

位于gameInit()中

然后加载一下僵尸图片:

也位于gameInit()中

之后是输出僵尸图片:

位于updateWindow()中,这里好像对y又进行了更改,不过应该不需要了解2333,感觉直接进行加减数字也可以

这样的话应该就可以了,按照我的理解做,做不成你来打我!=w=

至此,第10、11节就学习到这里了。


本文章持续更新,欢迎各位一同学习进步。

  • 31
    点赞
  • 159
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值