爱心鱼实践 优化

下一阶段,让大小鱼动起来,眼睛,尾巴。小鱼会饿死的判断,大鱼吃果实的变化。海葵摇动,game over 生命值判断。大鱼喂小鱼的动作。

1、小鱼动画

尾巴摆动、眨眼睛、得不得果实会身体变白。

小鱼的尾巴图片是babyTail0 - babyTail7 八张图片。

要做的就是八张图片组成的序列帧在游戏里轮播,就成了动画。

2、鱼尾摇动

babyTail0 - babyTail7 八张图片组成的序列帧在游戏里轮播,如何在游戏里实现,很简单,就是每隔一个固定的时间,执行下一张图片。然后连续起来就是一个动画。首先一个图片数组,放八张图片,一个计时器,每隔一张图片,绘制下一张图片。在main.js中定义一个鱼尾数组,var babyTail = [ ] ; 这个数组在init中初始化:

下面设置计时器,就是每隔一段时间,提醒我们执行下一个动画图片。首先定义两个变量:一个变量是计时器,另一个是记录当前图片序号的变量,就是现在执行到哪一帧了。

10、11行。

draw在gameloop里面每一帧都是要执行的,所以在draw循环中,每一帧计时器都会工作,

36-42行,如果两帧时间间隔大于50,尾巴数加一,%8是因为只有七张,第8张的时候,自动到第一张。同理时间间隔也是,大于50就取余。

50,51行,绘制鱼尾巴的时候,不再是this.babyTail,而是图片数组。告诉它执行到第几帧了。这里就定义一个临时变量babyTailCount。这样的话,小鱼的尾巴应该就能摆动起来了。


3、小鱼眨眼睛

小鱼眼睛有两个状态,一个是眯起来,一个是睁开,就表示小鱼眨眼睛的动作。

小鱼眨眼睛不是像尾巴那样,眨眼睛时间更随机,不是固定时间,这样更生动。比如上次隔离2秒,下一次3秒。但是小鱼眯起眼的状态是固定的,比如200ms。先定义一个小鱼眼睛的数组,图片放进去。同理在main.js中定义一个var babyEye = [ ] 变量。在init中初始化一下。在init中初始化一下:

同样在baby.js跟尾巴一样,定义一个计数器,时间控制器。

14行表示当前图片持续多长时间。比如圆睁的图片需要持续2秒,眯眼睛200毫秒,圆睁的图片需要持续3秒,眯眼睛200毫秒,这样一直循环。

首先对Timer计数,如果计时器超过时间间隔,提醒执行下一帧。让它自身加一,但是不超过2,因为总共两张图片。对2取模。同时计时器归零。对当前时间间隔取模。恢复起始状态。如果是第一帧,眯起眼睛来的,下一帧就是睁开眼睛,我们希望持续时间比较长,如果这次的鱼眼睛是1,那么时间间隔2-3.5秒。0状态是眯着眼睛,是200毫秒。

把这个时间技术反映在眼睛图片上面,首先把babyEye换成数组。同时告诉它执行到哪一帧了。


4、小鱼身体变白

大鱼不能及时把能量传给小鱼就会变白。

小鱼身体变白通过一个序列帧来表现,这个序列帧跟小鱼尾巴这个序列帧是一样的,间隔相同时间,执行一张图片。但是跟小鱼尾巴稍有不同的是,小鱼尾巴是循环播放的。小鱼身体变白是一个有去无回的动作。因为是一个gameover的判断,当小鱼身体完全变白。就表示挂了。这里先把动画表现出来。

在main.js中定义一个babyBoby = [ ] ,来放小鱼身体的图片资源。同样在init函数中初始化一下,

动画的序列帧放在babyBoby这个数组里。

回到baby类下面,定义一个babyBody的定时器,来控制时间,定义一个变量来存储当前执行到哪一帧。

16.17行。

回到draw这个函数里面做一个时间的监控:

先让计时器来存储时间,如果时间大于300毫秒,这时候提醒我们执行下一个序列帧。让序列帧计数器自动加一。当大于19的时候,停在这一帧,并提示gameover。

接下来这个计数器反应在小鱼身体的绘制当中,首先来定义一个临时变量。

85,86小鱼身体绘制。将之前的this.babyBody替换成图片数组。把后面的引用也替换一下。完成小鱼变白的动画。


gameover判断,后面再做。


2-1 大鱼喂小鱼的逻辑判断

跟我们之前大鱼吃果实的逻辑判断是一样的。打开之前碰撞的实现:

如法炮制一下:判断鱼妈妈和小鱼的距离:

当大鱼和小鱼的距离小鱼900,小鱼会复原到初始状态。也就是计数器复原到0帧,满血复活。

把这个函数放在循环当中。

102行。

大鱼小鱼碰撞检测就写好了。

这里有一个问题,就是大鱼如果没有吃到果实,碰小鱼,小鱼应该不是复原的。这里暂时先不写,先来做一下大鱼的动画。

2-2、大鱼动画

大鱼动画跟小鱼类似,也分成三个部分。摇尾巴、眨眼睛、身体升级。稍有不同,身体升级有所不同。大鱼身体变化有两种情况,一种是吃到黄色果实,一种是吃到蓝色果实。蓝色果实有特殊威力,当前分值加倍。吃到黄色果实,身体变成黄色,吃到蓝色果实,由蓝色逐渐升级。另外大鱼尾巴序列帧一直循环播放。

小鱼摇尾巴,是一连串序列帧循环播放,通过时间计数器和帧计数器就可以实现。让时间逐渐增加,帧的计数器反应到小鱼的绘制里面。同样在main.js做一个大鱼尾巴存储器,把图片放在数组里。var momTail = [ ] 。init初始化的时候,如下:

大鱼mom.js里面定义两个控制器。

10,11行。

在draw循环里面,对这两个计数器计算。

需要让它反应在图画的绘制中:

画大鱼的时候,不再是只画一个图片了,而是根据时间绘制序列帧。49行所示。现在就可以让大鱼摆动尾巴了。

2-3 大鱼眨眼睛

小鱼眨眼睛怎么实现?

类似写一下大鱼的眼睛动画:

main.js定义一个var momEye = [ ] 数组变量。init中初始化一下、

回到mom类中,定义三个变量:

同样在draw循环里做变量的计算:

把计数器反应在大鱼眼睛绘制的过程中:

删除this.bigEye变量

2-4 大鱼身体升级准备

由于大鱼身体升级动画会牵扯到游戏分值计算。所以我们在身体动画之前,先把分值计算先做一部分。新建一个文件,保存为data.js 。打开html文件,加载data.js。在data.js中定义一个dataObj类。在main.js中定义一个data变量。init 初始化的时候让这个变量成为一个dataObj类型。var data = new dataObj();

在data.js中,添加对应的属性如下:

定义果实数量和double,这个是蓝色果实数量翻倍,就是2。初始状态下为1 。只要两个相乘,就是得到的分数。跟着两个数值有关系的,一个是大鱼和果实碰撞的时候。

15行,蓝色果实时候,分数就会加倍。

如何获得蓝色果实这个信息?

在fruit.js文件中,有一个fruitType。之前已经定义了。任何一个果实是orange还是blue。这里有一个区别。

在这里检测一下:

说一下游戏的机制,大鱼吃果实,累积,回来给小鱼吃。大鱼把果实转化成能量给小鱼之后,大鱼的能量状态应该回归初始状态。这关系到在两个碰撞检测中出现的一些行为。当大鱼跟小鱼碰撞的时候,当前果实数量归零,恢复到初始状态。data.js中定义归零的方法:

在碰撞检测中调用归零函数

把吃到的果实数字和double写在页面上:第一个画布上画UI:

data.draw()放在主循环里面。


2-5 身体升级

跟绘制小鱼动画类似,先把图片导入进来。定义大鱼的两个序列的动画帧,一个是蓝色,一个是橙黄色。在main.js中定义两个数组,在init中初始化一下:

回到mom类,增加一个变量,记住当前大鱼身体是哪一个帧:

这个变量在哪里发生变化?跟果实碰撞的时候,mom.momBodyCount ++;但是有一个问题,序列帧0-7,最大到第8帧。如果大于7,就一直停留在7帧。下图15-17行:

这时候计数器就做好了。需要把计数器反应在大鱼身体的绘制上面。同样定义一个临时变量。先判断是吃的黄色还是蓝色果实,分别绘制不同颜色的身体:

之前定义的this.body变量就不需要了。

大鱼碰撞小鱼的时候,序列帧恢复:36行:

让大鱼变成无色的。


2-6 游戏分值计算1

在data类中,进行调整一下,定义字体,字体居中(默认left),需要绘制的是得分,所以在dataObj中加一个变量score,初始化是0,如何计算呢?大鱼跟小鱼碰撞,当前分值加到score上。

在collision.js中,果实数量已经做了记录。在这个时候,在打小鱼碰撞中,要做一个更新数值:

在data.js中增加一个方法。

需要做的事情:计算得分,当前果实数量归零。double状态恢复到原始状态。

因为在大鱼碰小鱼时候做这些事情:

在data.js中绘制一下score的值:22行

上面个的17-19行,不需要每次画图都初始化一下,直接放到main.js中init中好了。定义一次就好了。

上面两个值,不需要,删掉吧。结果开始报错,分数不能显示。在大小鱼碰撞检测的时候,有一个reset() 

我们不需要reset这个函数了,直接在addScore中改变了fruitNum和double。删掉34,35行。

现在就可以正常显示了:



2-7 游戏分值计算2

其他边角的东西完成掉:大鱼没有吃果实,去碰小鱼,小鱼也会被点亮,这里是不合适的。大鱼没有果实,是救不了小鱼的。需要检查一下大鱼吃到的果实数量。看一下检测在哪。大鱼跟果实碰撞时候,fruitNum会加加。只有这个值大于0的时候, 大鱼才能跟小鱼产生有效的碰撞。所以加一个检测的条件,如果data.fruitNum大于0

没有吃到果实碰小鱼,小鱼是不会被点亮的。

小鱼颜色白色之后,应该gameover了。需要对这个状态做一个检测。就是小鱼变白,显示游戏结束。在小鱼身体动画这一块应该也有这个判断。在data.js的dataObj中添加一个变量,来记录当前游戏状态。this.gameOver = false。小鱼颜色变白之后,游戏检测,gameOver变成true。

gameOver之后,鼠标不能控制大鱼,大鱼不能吃果实,显示gameOver字样。下面一个一个接着做:

大鱼不能吃果实:增加一个条件检测,只有gameOver为false之后,才能进行大鱼和果实距离判断:

同样,只有!gameOver为true时候,才能进行大小鱼碰撞检测。

下面开始gameOver状态下,鼠标不能控制大鱼。鼠标检测是在main.js通过onMouseMove控制的。同样添加监控条件:

小鱼死了之后,鼠标移动就不能控制大鱼了。

gameOver之后,需要显示给玩家,gameOver了。在data类里面处理:在dataObj里面,如果gameOver,它是true状态,小鱼变白,就是true。在画布上绘制字样:

优化gameover显示,渐渐出现,有一定的过渡。开始字是完全透明,后来增加到完全白色。通过使用RGBA。data.js中,alpha初始化为0.然后随着时间增加而增加:

7行和20-23行。上面个0.0001改成0.0005

给gameover和score添加白色阴影:

再增加一点点东西:在样式首尾添加save 和restore 

避免这里面的样式会作用于其他地方。



3-1 大鱼吃果实特效1

大鱼吃果实,会有白色圈圈,涟漪。喂小鱼,小鱼产生涟漪。本质上一样,只是颜色,大小,快慢不一样。


大鱼吃果实特效:大鱼每碰到一个果实产生一个圈。就像有一个篮子,里面有很多球,我不管什么时候找你要一个球,你都能从篮子里面拿一个给我。并且拿了你的球之后,隔一段时间还给你,你再放回篮子。这样就保证,不管我什么时候找你要一个球,你都能够拿一个给我。写一下这个特效实现过程,首先需要一个物体池,放很多相同物体。检测是否有闲置的物体,有,就拿出来执行任务。半径增大,颜色减弱。这两个数字是反比关系。还有绘图API.

在main.js函数里定义一个变量叫wave。就是白色圈。新建一个wave类,就是白色圈。在main.js的init()中新创建一个waveObj实例。waveObj定义如下:首先圆圈的位置,当前物体是否可以执行任务。死活的状态,半径值,颜色因为跟半径成反比,就省略一个变量。先定义有10个物体。给每个物体初始化,每个都是死的,都是可以用的。

在main.js中初始化一下:

初始化好了之后,需要绘制。只有活着的,执行任务的才能被绘制。

这个draw需要每一帧都执行,所以放在gameloop中:

再解释一下,如何实现圆圈特效。首先定义一个很多物体的池。每个物体有一个状态。首先判断一下每个物体的状态是不是满足条件。是不是闲着,闲着的话,就拿出来执行任务。说道是否闲着,就需要一个判断。做一个循环,来一个一个判断是否满足条件:

每次只需要一个物体,所以找到一个之后,就马上跳出循环,使用return。否则全部画出来。

思考,什么时候使用born?当大鱼跟果实碰撞的时候,出现一个白色全。所以在碰撞函数里面,执行出现白色圈。24行


3-2 大鱼吃果实特效2

把wave.js添加到html文件。

下面要做:在born里面,把这个alive的状态改成true。然后在draw里面当alive状态为true时候,就会被绘制出来的。另外有一个半径值。初始化为0

出生的时候,半径重新初始化为20、

在draw中绘制圆圈:绘制圆圈的API。context.arc()绘制圆圈的方法。相应的方法自己查吧。另外一个api,strokeStyle,描边样式,它的值,是一个颜色值。还一个lineWidth,线条宽度。是一个像素值。如下绘制圆圈,绘制在第一个canvas上。在大鱼碰撞果实的时候,果实的位置,作为参数传递进去:

在wave.js中做连接:

出生的圆圈就获得了位置。下面开始绘制圆周的函数:

想一下这个问题:这个圆圈是要慢慢消失的。所以半径变大,颜色逐渐消失23行时间间隔,进行控制,27-30行进行控制:

注意alpha的值,当alpha为0的时候,完全透明,看不见了。检测一下r[i] ,大于100,就消失,25行。

3-3 大鱼吃果实特效3

这里有一个错误,21行改成this.alive[i] 。33行添加ctx1.stroke(); 32行拼写错误rgba。

调整相关参数:速度和大小。初始圈圈大小10,其他参数如下:

19,38行,保存状态,避免样式会污染别的地方。

下面定义线宽,阴影大小,阴影颜色


3-4 大鱼喂小鱼果实特效1


main.js中跟定义wave一样,定义一个halo。新建一个halo.js、在html文件中加载这个halo.js、halo.js中定义一个haloObj。定义一系列参数。一系列数组。状态是否为活着。相关初始化如下

位置和状态,半径:

希望这个draw每一帧都会被绘制,添加到gameloop中:halo.draw()

触发机制,就是出生方法。见下一节


3-5 大鱼喂小鱼果实特效2

当大鱼跟小鱼碰撞的时候,绘制圆圈。也就是在碰撞检测里面绘制

,44行

其中传入参数,是baby的位置。在halo.js中定义出生函数:

检测程序是不是有问题,结果报错,main.js中draw未定义。在main.js中,init 中 halo = mew haloObj() ;halo.init() ,这时候就没有报错了。

看某个函数是不是执行了,可以在里面console.log 一下。下面开始绘制圆圈:半径随着时间增大,半径大于100时,任务执行完了,更改alive状态,跳出本次循环。同时颜色alpha值,跟半径成反比关系。参数有了,就开始绘制了。开始一个路径,画圆。圆的半径,弧度,等等。画好之后,定义描边样式,开始描边。  

在22-24行定义圆圈样式。

36行错误,改成alpha = this.r[i] / 100.  30行改成0.05,半径增大比较快一点。改一下圆圈颜色为橙黄色: 203,91,0


3-6 海葵摆动动画

下面海葵一直左右摆动,使用序列帧实现,也是可以的。但是要是比较平滑的话,要求序列帧可能比较高。现在用实时绘制的方法。

绘制二次贝塞尔曲线。在三维建模等用的也比较多。


让起始点和控制点不动,结束点运动,做一个摆动。形成一个曲线摆动。那么怎么做结束点摆动?使用一个正弦或者余弦函数。

之前画海葵定义x值,和高度。都是一条直线,一个直线从某个点到另外一个点。描边。线的顶端是圆的。改造:

start point是最底边上,不用定义,control point是start point往上移动,也不需要定义。end point就需要定义了。初始化这些坐标:

moveTo起始点是root,控制点是

这时候报错:main.js 80行和fruit.js 63行。因为之前把x的变量替换成了rootx,所以修改fruit.js中修改63,64行。就是果实的成长位置。

现在木有问题了。

海葵有点矮,调整一下头部数值。长高一点。希望让海葵摆动起来,这时候要对headx改造。希望x的值是来回摆动的。使用正弦函数alpha。定义一个角度alpha,是正弦函数的角度。在draw中,角度一直变化的。7行

在draw中角度一直变化:

定义一组海葵振幅。7行。18行初始化一下。

曲线函数的结束点的x坐标,给她一个l*振幅。这样就会在一定范围摆动。只是摆动幅度比较大。调整数值。控制点位置降低一点。基本就可以了。

有一个问题,果实应该长在海葵顶部的。而不是凭空出现。


3-7 水藻长在海葵上

调整海葵头部跟果实同步问题。果实出生确实在海葵顶部,但是海葵摆动起来了,位置对不上去了。果实应该随着海葵摆动。如果让果实跟着海葵头部运动,要让果实知道果实对应哪个海葵。 绘制过程中,海葵的坐标一直在变化的。需要的就是把海葵头部坐标告诉果实。

这里的headx已经改过了。35,36行。

需要的就是把headx,heady告诉果实。保存一下。出生的时候,不需要告诉它坐标值。

绘制果实图片的时候,绘制的是海葵顶部。(改懵逼了。不知所谓)



3-8 漂浮物制作

用的是三角函数sin( )  。漂浮物随着波浪左右摇摆。海葵、漂浮物的摇动都是一致的。

定义一个全局变量,dust。有一系列图片作为dust。定义一个数组dustPic,存放图片。好了。在游戏初始化函数中,dust这个变量指向dust类。除了这个类以外,还会使用一些图片dustPic。

新建一个dust.js。在html中加载进来。定义一个dustObj类,坐标位置,振幅,number,角度alpha。总数为num。初始化:

18行,是任意一张图片的意思。

main.js中初始化一下

绘制函数:

这里面相关初始值跟海葵保持一致,保持摇摆节奏一致。


3-9 总结

通过两个阶段完成任务的,第一个阶段,搭建基本框架,第二阶段让游戏更加丰富。首先分析游戏,怎么设计,游戏首先有游戏的角色,有大鱼小鱼。大鱼一直吃果实,喂小鱼,让小鱼保持有生命。故事背景在海洋中。海葵提供果实,是大鱼小鱼能量来源。

分析一下学到什么东西:

大鱼小鱼动画。动画原理,轮播序列帧实现。比如小鱼:尾巴动画实现,经过一段时间,检查babyTailTimer,符合判断标准时候,执行下一帧。这些动画,眼睛动画比较 特殊,眨眼睛规律不一样,增加了interval,时间跨度。通过定义两个不同的时间跨度,让两个时间跨度来回变,让眨眼睛时间比较短,睁开眼睛时间比较长。核心东西,碰撞接触,大鱼喂小鱼,大鱼吃果实。碰撞检测,通过两者之间的距离。符合一定条件就认为撞在一起了。分值计算,学了一些api,比如shadowColor。shadowBlur,font,textAlign。还有特效,就是大鱼吃果实白色圈圈,大鱼喂小鱼黄色圈圈。有一个很重要概念,物体池。我们是怎么样让它一个个产生,好像无穷无尽。物体池中的物体是有数的,我们规定有多少个,估计够用就好了。原理,拿一个物体,检测,有没有闲置的物体,有的话,拿出来执行任务,完毕了再放回去。还有小的技巧,比如通过两个变量关系,少定义一个变量。还有绘图的api。海葵摆动,是一个技术核心一部分,用了两个部分的结合。二次贝塞尔曲线和正弦曲线,产生摇摆的结果。通过开始点,结束点,控制点,产生摇摆。

你可能感兴趣的:(爱心鱼实践 优化)