爱心鱼

爱心鱼_第1张图片
爱心鱼_第2张图片
爱心鱼_第3张图片

1、准备工作

搭建html网页结构

绘制背景

文件的目录如下:

爱心鱼_第4张图片

tinyHeart.html中的代码如下:

爱心鱼_第5张图片

之所以有两个canvas,是因为渲染压力比较大。

定义的样式如下:

爱心鱼_第6张图片

以上,html网页结构搭建好了。

在js文件夹里面新建mian.js文件,然后在tinyHeart.html中引入该文件。

1、绘制背景

main.js脚本已经引入tinyHeart.html中了。假设mian.js里面有很多函数。

我们获得了两个画笔,两个画布。下面来分一下,哪些内容放在canvas1上面,哪些在canvas2上面。

后面的那个canvas2(z-index:0在后面):背景,海葵ane,果实

前面那个canvas1(z-index:1在前面):大小鱼,浮动的dust,浮游生物,成绩,吃果实白色圈圈特效,喂小鱼的特效。

游戏如何动起来?就是需要一个一帧一帧刷新。比如小鱼走动,就是位移不断刷新。定义函数gameloop()

使用了一个函数requestAnIMFrame(gameloop),相对于setInterval和setTimeout更科学。当前绘制完成以后,根据机器性能来确定,间隔多长时间会绘制下一帧。而setInterval和setTimeout是指定时间。万一指定内容很大,不能绘制怎么办?

但是会导致一个问题,fps,每秒多少帧,会有一个动态的时间间隔。

不同的浏览器需要配饰,这里有个commonFunction.js文件,已经打包好,直接拷贝,然后用。先在tinyHeart.html文件中引用。

测试一下,可以循环打印了。

因为循环执行的时间间隔不一定,所以需要知道两帧之间时间间隔。

测试一下:帧与帧之间的时间间隔是不稳定的。

如果小人在走,给一个不稳定的时间差,就可以非匀速运动了。运动比较平稳。

新建一个background.js文件,然后其中写函数:

function drawBackground() { ctx2.drawImage(bgPic,0,0,canWidth,canHeight) }

在main.js中定义一个var bgPic变量。初始化的时候,指定src属性 。定义变量var canWidth和 canHeight,获取canvas的宽和高。初始化的时候canWidth = can1.width;     canHeight = can1.height;

把 drawBackground();放在 gameloop()中。background.js加载到html文件中。

2、回顾几个API

先回顾几个api:

beginPath() 方法开始一条路径,或重置当前的路径。

提示:请使用这些方法来创建路径:moveTo()、lineTo()、

arcTo() 以及 arc()。

提示:请使用stroke()方法在画布上绘制确切的路径。

beginPath()起始一条路径,或重置当前路径

closePath()创建从当前点回到起始点的路径

strokeStyle设置或返回用于笔触的颜色、渐变或模式

stroke()绘制已定义的路径

lineCap设置或返回线条的结束端点样式

lineWidth设置或返回当前的线条宽度

3、绘制海葵

第一阶段,绘制静态海葵,第二阶段再做动画。就是一个直线。描绘一个路径。如何画?定义一个类,类里设置一个数组,数组每个成员都是一个海葵。数组包括海葵属性,位置,长度等。

新建一个文件ane.js,写一个海葵的类:aneObj

在main.js中初始化函数init中,新建海葵对象,并初始化一下。ane.js引入html中。一个坑:js脚本引入的时候,注意顺序关系,否者可能会出现某函数未定义的情况。

4、果实绘制(静态果实)

黄色果实和蓝色果实

使用的API:drawImage()

关键在于果实是在不断长大的,成熟后开始飘离海葵。飘离速度也是各有不同。

设置一个池子,池子里面有足够数量的果实。每次只需要挑选出需要出生的果实。让它出生,长大往上飘。飘出屏幕就死了。果实有两种主要状态:活跃、不活跃。怎么判断是否活跃?屏幕有15个果实,整个果实池,设置足够大,比如30个,只要有果实需要,池子里面一定有闲置的果实。池子中果实数量有足够多,估算一下。30个果实有两种状态,一种是闲置的,还有一种状态,被指定一种任务,要开始在海葵上生长了,长大之后往上飘,飘出屏幕,告诉它任务完成,回去排队。排队概念,就是把状态set到某个闲置的状态。有任务的海葵又有两种状态,一种是海葵上正在长大,一种是飘的过程。

下面开始写任务:

新建一个文件:fruit.js ,定义一个类。设立数组,来控制很多数量的海葵。

在main.js文件中,定义var fruit变量,在main.js函数的init函数中,fruit = new fruitObj() ; fruit.init() ,再循环gameloop中绘制果实fruit.draw() ,在html文件中引入fruit.js文件。

图片资源绘制的果实有两种状态,一种蓝色,一种黄色。要把图片资源加载进去。

规划如下:认为池子中所有的果实,都是在执行任务的,包括长大,成熟,飘出去,飘出屏幕。这些果实的状态都是活着的,所以上面12行初始化的时候,应该为true。

fruitObj.prototype.draw() 里面先一个for循环,画果实。画果实的步骤,首先找到一个定位,找到一个海葵find an ane ,然后长大grow,最后开始飘fly up。

首先写一下出生函数fruitObj.prototype.bron,随机找一个海葵,总共有50个海葵,记录一下海葵的位置,因为果实是长在海葵上面的。需要知道海葵的坐标。在海葵的数量(50)中随机找一个,并且aneID要是整数值,使用一下floor。x坐标为ane.x[aneID] ,y坐标: canHeight-ane.len[aneID];这两个属性对应坐标值,这个对应的果实的x,y值,在fruitObj中记录一下,添加一个this.x和this.y。初始化的时候,fruitObj.prototype.init函数里初始化个0就行了。出生的时候,fruitObj.prototype.bron给一个正确的值、

再画的时候,就可以用到坐标值了。fruitObj.prototype.draw时候,先不用管果实大小,born是出生时候画的,所以放在函数fruitObj.prototype.init中。初始化的时候让所有果实都出生。效果如下。

有两个问题,首先位置有偏差,因为绘制的时候drawImage()时候从0 0 点开始绘制,所以要减去fruit宽度和高度的一半。

另外一个问题,不是30个果实,因为会有重复的,所以要判断一下,海葵上是不是长了果实。如果长了果实,就找下一个海葵。(这个不做深入了)

5、果实绘制(果实上浮)

果实出生时候是不断变大的。所以要需要控制一下。就是绘制图片的时候,一开始绘制很小的图片,然后慢慢加大。在fruitObj中添加一个属性,this.l 代表果实的长度。出生的时候,this.l[i]为0.  fruitObj.prototype.draw中,l会随着时间不断增长。也即是this.l[i] += 0.01*deltaTime。这个deltaTime是每两帧之间的时间间隔。然后画果实的位置,需要变成this.x[i] - this.l[i] *0.5 ,this.y[i] - this.l[i] *0.5 。这时候果实会不断变大,不会停止,所以增加一个判断。如果果实大于14,就停止长大。然后开始往上飘。往上飘的本质就是y坐标在不断变小。

但是有个问题,就是速度比较单一。我们给它一个不同的成长和往上飘的速度。

在fruitObj中添加一个属性this.spd,成长和往上飘的速度。初始化的时候这里给它一个随机的速度:19行

然后画图的时候:

每个果实有个不同的速度。33行成长,37行往上飘。

飘出去之后,做一个判断。飘出之后果实的任务就完成了,所以要变成待命状态。做一个检测,如果y坐标小于10,alive状态就要变成false。只有alive状态为true时候才做成长和往上飘的事情。所以要进行一下判断:

6、果实绘制(果实数量控制)

并不是所有果实成熟后飘上去就完了。而是控制屏幕中果实数量在一定数量范围。

添加游戏规则:保持屏幕上有15个果实。

所以一开始初始化的时候,不应该就让所有的果实出生,添加一个判断,当前屏幕上有点多少个果实是活着。在fruit.js中,添加一个监视功能。判断当前屏幕上有多少个果实是活着的。活着的状态,要做一个数据统计。如果活着的果实少于15个,要发出一个果实,也就是让果实出生。但是有一个细节,哪个果实是闲置,那个是活着的,要判断一下。这里做一个循环,当前果实alive状态是false,就让它出生。

66行添加 sendFruit() 函数执行一下。

同时把fruitMonitor放在主循环里面,每一帧都要判断。

另外:黄色果实和蓝色果实两种。一是他们资源不一样,二是功能不一样。大鱼吃一个黄色果实得10分,蓝色果实可以让吃到的黄色果实数量加倍。

下面做蓝色果实:

区别果实类型,fruitObj增加一个属性,fruitType,果实类型。在初始化的时候,先让它为空。出生的时候,再给一个具体的类型。

做一个随机,让果实是蓝色或者黄色。在绘制图片的时候,根据果实类型,绘制相应的图片。在外面定义一下:

根据果实类型,设置pic的资源:

画图的时候,不在使用this.orange了,而是使用pic如下更改

35行下标掉了,补上,如下:

7、大鱼绘制

画大鱼的身子,尾巴,眼睛

使用的API:

translate() ,

rotate(),  这两个canvas的api

Math.atan2(y,x)   反正切,y在前,JavaScript的api

同时返回是一个 -π 到π

新建一个mom.js,在html中引用。在mom.js中,定义类 momObj,初始化函数init和画图函数draw

main.js中大鱼的对象定义一下。var mom;

main.js 中init时候 给mom赋值,同时初始化。

大鱼吃到的果实,身体会变化,尾巴也会变化。这里先不做动画。

大鱼的属性,包括眼睛,身体,尾巴。

初始化的时候,加载一下资源。如下:

下面开始绘制,把大鱼绘制在中间,而且是第一个canvas上面。先画上去各个部分,然后调整位置,把draw放到主循环中。如下:

大鱼已经画出来了,但是并不像图片的样子,比较粗啊。原图的线条比较细。为什么?

canvas1是一个透明的,只有需要绘制的东西,才不是透明,其他没有绘制的地方,都是透明的。覆盖在第二个canvas上面。需要把前面一帧clear一下,把他清空掉,然后绘制新的。循环里面添加这个功能:64行

在干净的画布上绘制。然后结果:

正常了。大鱼的坐标并不是很对。一是为了调节三张图片之间的距离,同时有利于大鱼旋转这个动作。大鱼是一直跟着鼠标的,为了旋转方便,使用前面介绍的translate方法。给它指定一个相对的原点值。因为这个仅适用大鱼,所以加一个save和restore,来确定使用范围。让这些属性仅使用大鱼:

20行,translate之后,this.x ,this.y就是原点,所以下面坐标都需要改一下:

三张图片的中心位置都固定在一个点了,现在把尾巴往后移动一点;

8、大鱼随着鼠标移动

获取鼠标的位置,首先main.js定义一个鼠标位置变量,var mx,var my

。在初始化的时候,初始化成canvas的中间,也就是mx = canWidth*0.5,my = canHeight*0.5。要捕捉到鼠标的动作,首先canvas添加检测鼠标的事件。把事件放到canvas1上,如图,鼠标移动的时候,就可以监听了:

下面定义一下onMouseMove(e)

打印一下,可以看出,鼠标移动的时候,就获取到了鼠标的坐标。offsetX,offsetY发生事件的地点在事件源元素的坐标系统中的 x 坐标和 y 坐标。

如何让鱼妈妈位置坐标跟随鼠标呢?

在momObj.prototype.draw中做这些事情:

首先做一个lerp ,x,y  什么是ler?就是让一个值趋向目标值。

在commonFunction.js中:

aim是目标值,cur是当前值,ratio是一个系数。然后delta是两点之间距离。比如这次位置是aim+10,delta是10 ,ratio是0.9 ,那么下一次位置就是aim + 9 。 

这个时候,大鱼的位置,就会一直跟着鼠标动。大鱼中心位置就是鼠标位置。但是大鱼没有旋转,不是头向着鼠标。在极坐标中,大鱼的方向是π或者-π,看旋转方向,顺时针为π。鼠标和大鱼之间会有一个坐标差。坐标差形成一个夹角。就是鼠标相对于大鱼的角度。大鱼的角度要追随坐标差的角度。添加一个属性,然后初始化:

大鱼的角度知道,然后计算大鱼和鼠标之间的角度:

让大鱼的角度不断趋近与鼠标角度lerp angle:

这个函数是在commonFunction中

画图的时候,调用lerpAngle函数。获得这个角度后,让鱼妈妈旋转起来。旋转画布,且只作用于大鱼。并且在translate后面做这个命令。为什么?因为只有先转将相对移动原点,然后才旋转。调整相应的旋转速度,和追寻鼠标速度就可以了。

9、大鱼和果实的碰撞检测

原理:检测大鱼和果实的距离

每一帧的时候判断一下,大鱼和这个果实之间的距离是不是足够近。如果是,就是把果实吃掉了。

新建一个collision.js文件。文件里只有碰撞检测功能。把js保存到html中。

有很多果实,我们要一个循环进行判断。首先判断是不是alive状态,然后判断果实跟大鱼之间的距离,如果足够近,就吃掉啦。

使用一下commonFunction函数中的距离平方函数:

这个就是距离平方封装好的函数。

下面进行判断大鱼和果实距离:

第12行,如果距离小于900,就吃掉果实啦。“吃掉果实”是在fruit.js中添加一个方法。功能让当前果实死亡状态,也就是让alive状态改变。

然后使用这个方法就好了:

10、优化

切换界面之后,果实一直在长大。这是个bug啊。所以优化一下。

因为绘制果实的时候,跟deltaTime成正比的。deltaTime是两帧之间的时间间隔。但是切换到其他页面时候,这个页面的帧就停止了。所以切换过来,两帧的时间间隔就特别大。

给它设置一个最大上限就好了,如上图。

Date.now()返回的是1970年到现在的毫秒数。

11、小鱼绘制

新建一个baby.js,定义一个小鱼的类 babyObj;

在主函数中定义一下小鱼:

并且在main.js中的init()函数中,

下面开始在baby.js中绘制小鱼

1、像大鱼一样,想把图片资源绘制上去。2、让小鱼动起来。3、让小鱼跟随鱼妈妈走。

在babyObj中定义小鱼图片变量。初始化的时候加载。如下进行加载

下面开始绘制小鱼:(小鱼的位置修改了一下)

把draw放到主循环gameloop里面:

小鱼已经画上去了。

开始转移原点坐标:

通过save和restore保存状态。这时候小雨的坐标就成了0 0 点。

调整眼睛身体尾巴的位置:注意先画的在下面,后画的在上面。

下面让小鱼动起来,就是让小鱼的当前坐标对大鱼一个lerp,让它趋向大鱼坐标。

小鱼就跟着大鱼动起来了。下面让小鱼跟着大鱼旋转。就是一个角度的lerp的过程。

先定义angle属性:

然后旋转:

总结:

首先是



爱心鱼_第7张图片

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

1、小鱼动画

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

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

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

2、鱼尾摇动

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

爱心鱼_第8张图片

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

爱心鱼_第9张图片

10、11行。

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

爱心鱼_第10张图片

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

爱心鱼_第11张图片

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

3、小鱼眨眼睛

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

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

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

爱心鱼_第12张图片

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

爱心鱼_第13张图片

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

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

爱心鱼_第14张图片

4、小鱼身体变白

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

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

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

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

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

爱心鱼_第15张图片

16.17行。

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

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

爱心鱼_第16张图片

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

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

gameover判断,后面再做。

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

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

爱心鱼_第17张图片

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

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

爱心鱼_第18张图片

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

爱心鱼_第19张图片

102行。

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

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

2-2、大鱼动画

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

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

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

爱心鱼_第20张图片

10,11行。

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

爱心鱼_第21张图片

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

爱心鱼_第22张图片

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

2-3 大鱼眨眼睛

小鱼眨眼睛怎么实现?

爱心鱼_第23张图片

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

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

爱心鱼_第24张图片

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

爱心鱼_第25张图片

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

爱心鱼_第26张图片

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

删除this.bigEye变量

2-4 大鱼身体升级准备

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

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

爱心鱼_第27张图片

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

爱心鱼_第28张图片

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

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

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

爱心鱼_第29张图片

在这里检测一下:

爱心鱼_第30张图片

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

爱心鱼_第31张图片

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

爱心鱼_第32张图片

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

爱心鱼_第33张图片

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

爱心鱼_第34张图片

2-5 身体升级

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

爱心鱼_第35张图片
爱心鱼_第36张图片

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

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

爱心鱼_第37张图片

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

爱心鱼_第38张图片

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

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

爱心鱼_第39张图片

让大鱼变成无色的。

2-6 游戏分值计算1

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

爱心鱼_第40张图片

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

爱心鱼_第41张图片

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

爱心鱼_第42张图片

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

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

爱心鱼_第43张图片

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

爱心鱼_第44张图片

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

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

爱心鱼_第45张图片

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

现在就可以正常显示了:

爱心鱼_第46张图片

2-7 游戏分值计算2

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

爱心鱼_第47张图片

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

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

爱心鱼_第48张图片

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

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

爱心鱼_第49张图片

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

爱心鱼_第50张图片

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

爱心鱼_第51张图片

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

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

爱心鱼_第52张图片
爱心鱼_第53张图片

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

爱心鱼_第54张图片

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

给gameover和score添加白色阴影:

爱心鱼_第55张图片
爱心鱼_第56张图片

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

爱心鱼_第57张图片

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

3-1 大鱼吃果实特效1

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

爱心鱼_第58张图片

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

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

爱心鱼_第59张图片

在main.js中初始化一下:

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

爱心鱼_第60张图片

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

爱心鱼_第61张图片

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

爱心鱼_第62张图片

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

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

爱心鱼_第63张图片

3-2 大鱼吃果实特效2

把wave.js添加到html文件。

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

爱心鱼_第64张图片

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

爱心鱼_第65张图片

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

爱心鱼_第66张图片

在wave.js中做连接:

爱心鱼_第67张图片

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

爱心鱼_第68张图片

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

爱心鱼_第69张图片

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

爱心鱼_第70张图片

3-3 大鱼吃果实特效3

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

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

爱心鱼_第71张图片

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

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

爱心鱼_第72张图片
爱心鱼_第73张图片

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

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

位置和状态,半径:

爱心鱼_第74张图片

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

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

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

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

,44行

爱心鱼_第75张图片

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

爱心鱼_第76张图片

检测程序是不是有问题,结果报错,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 海葵摆动动画

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

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

爱心鱼_第77张图片
爱心鱼_第78张图片
爱心鱼_第79张图片

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

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

爱心鱼_第80张图片

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

爱心鱼_第81张图片

moveTo起始点是root,控制点是

爱心鱼_第82张图片

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

爱心鱼_第83张图片

现在木有问题了。

爱心鱼_第84张图片

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

爱心鱼_第85张图片

在draw中角度一直变化:

爱心鱼_第86张图片

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

爱心鱼_第87张图片

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

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

3-7 水藻长在海葵上

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

爱心鱼_第88张图片

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

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

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

3-8 漂浮物制作

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

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

爱心鱼_第89张图片

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

爱心鱼_第90张图片

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

main.js中初始化一下

绘制函数:

爱心鱼_第91张图片

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

3-9 总结

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

分析一下学到什么东西:

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

你可能感兴趣的:(爱心鱼)