爱心鱼实践 第2章 绘制

1、绘制背景

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

爱心鱼实践 第2章 绘制_第1张图片

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

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

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

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

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

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

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

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

爱心鱼实践 第2章 绘制_第2张图片

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

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

爱心鱼实践 第2章 绘制_第3张图片
爱心鱼实践 第2章 绘制_第4张图片

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

新建一个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:

爱心鱼实践 第2章 绘制_第5张图片

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

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

arcTo() 以及 arc()。

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


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

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

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

stroke()绘制已定义的路径

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

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

3、绘制海葵

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

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


爱心鱼实践 第2章 绘制_第6张图片

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


爱心鱼实践 第2章 绘制_第7张图片
爱心鱼实践 第2章 绘制_第8张图片
爱心鱼实践 第2章 绘制_第9张图片

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

黄色果实和蓝色果实

使用的API:drawImage()

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

爱心鱼实践 第2章 绘制_第10张图片

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

下面开始写任务:

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

爱心鱼实践 第2章 绘制_第11张图片

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

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


爱心鱼实践 第2章 绘制_第12张图片

规划如下:认为池子中所有的果实,都是在执行任务的,包括长大,成熟,飘出去,飘出屏幕。这些果实的状态都是活着的,所以上面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给一个正确的值、

爱心鱼实践 第2章 绘制_第13张图片

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

爱心鱼实践 第2章 绘制_第14张图片

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

爱心鱼实践 第2章 绘制_第15张图片
爱心鱼实践 第2章 绘制_第16张图片

另外一个问题,不是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坐标在不断变小。


爱心鱼实践 第2章 绘制_第17张图片
爱心鱼实践 第2章 绘制_第18张图片

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

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

爱心鱼实践 第2章 绘制_第19张图片

然后画图的时候:


爱心鱼实践 第2章 绘制_第20张图片
爱心鱼实践 第2章 绘制_第21张图片

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

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

爱心鱼实践 第2章 绘制_第22张图片





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


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

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

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

爱心鱼实践 第2章 绘制_第23张图片

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

爱心鱼实践 第2章 绘制_第24张图片

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

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

下面做蓝色果实:

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


爱心鱼实践 第2章 绘制_第25张图片

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


爱心鱼实践 第2章 绘制_第26张图片

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

爱心鱼实践 第2章 绘制_第27张图片

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


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

爱心鱼实践 第2章 绘制_第28张图片


爱心鱼实践 第2章 绘制_第29张图片





7、大鱼绘制

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

使用的API:

translate() ,

rotate(),  这两个canvas的api

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

同时返回是一个 -π 到π

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

爱心鱼实践 第2章 绘制_第30张图片

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

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

爱心鱼实践 第2章 绘制_第31张图片

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

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

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

爱心鱼实践 第2章 绘制_第32张图片

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


爱心鱼实践 第2章 绘制_第33张图片
爱心鱼实践 第2章 绘制_第34张图片
爱心鱼实践 第2章 绘制_第35张图片

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

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

爱心鱼实践 第2章 绘制_第36张图片

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

爱心鱼实践 第2章 绘制_第37张图片

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

爱心鱼实践 第2章 绘制_第38张图片

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


爱心鱼实践 第2章 绘制_第39张图片

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

爱心鱼实践 第2章 绘制_第40张图片
爱心鱼实践 第2章 绘制_第41张图片



8、大鱼随着鼠标移动

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

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

爱心鱼实践 第2章 绘制_第42张图片

下面定义一下onMouseMove(e)


爱心鱼实践 第2章 绘制_第43张图片

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

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

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

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

在commonFunction.js中:

爱心鱼实践 第2章 绘制_第44张图片

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

爱心鱼实践 第2章 绘制_第45张图片
爱心鱼实践 第2章 绘制_第46张图片

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

爱心鱼实践 第2章 绘制_第47张图片
爱心鱼实践 第2章 绘制_第48张图片

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

爱心鱼实践 第2章 绘制_第49张图片

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

爱心鱼实践 第2章 绘制_第50张图片
这个函数是在commonFunction中
爱心鱼实践 第2章 绘制_第51张图片

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



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

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

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

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

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

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

爱心鱼实践 第2章 绘制_第52张图片

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

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

爱心鱼实践 第2章 绘制_第53张图片

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

爱心鱼实践 第2章 绘制_第54张图片

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

爱心鱼实践 第2章 绘制_第55张图片



10、优化

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


爱心鱼实践 第2章 绘制_第56张图片

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

爱心鱼实践 第2章 绘制_第57张图片

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

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


11、小鱼绘制

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

爱心鱼实践 第2章 绘制_第58张图片

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

爱心鱼实践 第2章 绘制_第59张图片

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

爱心鱼实践 第2章 绘制_第60张图片

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

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

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

爱心鱼实践 第2章 绘制_第61张图片

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

爱心鱼实践 第2章 绘制_第62张图片

把draw放到主循环gameloop里面:

爱心鱼实践 第2章 绘制_第63张图片


爱心鱼实践 第2章 绘制_第64张图片

小鱼已经画上去了。

开始转移原点坐标:

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

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

爱心鱼实践 第2章 绘制_第65张图片

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

爱心鱼实践 第2章 绘制_第66张图片
爱心鱼实践 第2章 绘制_第67张图片

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

先定义angle属性:

爱心鱼实践 第2章 绘制_第68张图片

然后旋转:

爱心鱼实践 第2章 绘制_第69张图片

总结:

爱心鱼实践 第2章 绘制_第70张图片

首先是


爱心鱼实践 第2章 绘制_第71张图片
爱心鱼实践 第2章 绘制_第72张图片
爱心鱼实践 第2章 绘制_第73张图片
爱心鱼实践 第2章 绘制_第74张图片
爱心鱼实践 第2章 绘制_第75张图片
爱心鱼实践 第2章 绘制_第76张图片

你可能感兴趣的:(爱心鱼实践 第2章 绘制)