为了账号安全,请及时绑定邮箱和手机立即绑定

HTML5小游戏---爱心鱼(上)

难度中级
时长 2小时34分
学习人数
综合评分9.50
139人评价 查看评价
9.6 内容实用
9.5 简洁易懂
9.4 逻辑清晰
  • 好,接着我们画了大鱼,大鱼的身子,尾巴,眼睛,因为这三部分我们是通过三张图片把它拼起来的,所以我们用到了这些API: translate(); rotate(); Math.atan2(y,x); 如果前两个API如果不用也可以,比如我们用到了这两个API绘画的时候就会更方便,主要是介绍它的坐标的时候会更清晰,哦,我们来看一下, ctx1.save(); ctx1.translate(this.x,this.y); ctx1.rotate(this.angle); ctx1.drawImage(this.bigTail,-this.bigTail.width*0.5+30,-this.bigTail.height*0.5); ctx1.drawImage(this.bigBody,-this.bigBody.width*0.5,-this.bigBody.height*0.5); ctx1.drawImage(this.bigEye,-this.bigEye.width*0.5,-this.bigEye.height*0.5); ctx1.restore(); 同样的在这里,save与restore之间,这些内容才起作用,首先我们通过一个translate把原点移到this.x和this.y的位置,这个时候,我们在计算它的坐标的时候,比方说drawImage里面的第二和第三个参数就非常简单非常容易,我们只需要计算一个位移就可以了,然后我们还做了一个rotate,旋转画布,把当前的画布,以translate中参数为原点,进行旋转,这主要是有利于我们计算角度值,这个角度我们又用到了另外一个非常重要的数学函数,反正切,我们要注意反正切的返回值是-PI到PI之间,所以我们在lerp的时候呢,也进行了一个调整,大家一定要注意
    查看全部
    1 采集 收起 来源:课程总结

    2016-06-25

  • 好,第二个步骤是绘制海葵,绘制海葵用到了一些API,我们来看一下这些API,回顾一下我们所写的。尤其是大家要掌握这些API如何来作用于场景, 首先这对API,save和restore我们已经用到了好几次,那就是在这两个API之间的方法或者定义,只作用于它们之间,一旦出了区间范围,这里面的东西就不再起作用了,比如我的lineCap是round等我出去之后,到这底下来,那这个属性就已经消失了,如果在顶上定义lineCap是另外一种类型,那么我restore之后,在这个位置的lineCap的属性就是save之前的属性,那么像这类属性: ctx2.globalAlpha=0.6; ctx2.lineWidth=20; ctx2.lineCap="round"; ctx2.strokeStyle="#3b154e"; 它只需要定义一次就可以了,因为这些都是属于属性类的,而不是命令类,也就是说跟这样的: ctx2.beginPath(); ctx2.moveTo(this.x[i],canHeight); ctx2.lineTo(this.x[i],canHeight-this.len[i]); ctx2.stroke(); 其中的 ctx2.moveTo(this.x[i],canHeight);我要告诉场景到某个位置, ctx2.lineTo(this.x[i],canHeight-this.len[i]);画线段到某个位置, 这一类跟上一类是不一样的,属性哪一类写在for循环外面,定义一次就可以了,但是像这些命令呢,必须写在for循环里面,每一次循环都要执行的,哦,这个是大家要注意的。 画完了海葵,我们画了果实,果实这里面有一个比较重要的概念,那就是池,我们设置了30个果实的池子来一直循环不断的应用这些果实,一旦这个果实离开了屏幕,完成了任务,那把它的状态更改为停滞的状态,然后在这里等待,然后新生的果实有这样的需要的时候,我们就去池子里面找哪一个果实,可以去执行这个任务,我们就把这个任务赋给它,这个概念是比较重要的
    查看全部
    1 采集 收起 来源:课程总结

    2018-03-22

  • 3-1,课程总结 到这里呢,我们第一阶段的课程就学完了,之前呢,我们在游戏课程之初的时候,做了一个阶段1和阶段2的任务分配,现在我们做一些调整,之前本来是要把大鱼喂小鱼这个任务放到阶段1的,我们现在把它放到阶段2去,现在看起来阶段2的任务很多,其实后面会很快把这些任务完成掉,阶段1我们总共学了这么多课程,我们来回顾一下,第一个呢画了海葵,海葵产生果实,果实有一个生长然后向上漂浮的过程,最后是大鱼吃果实,另外我们还画了小鱼对不对,我们来具体的看一下,一个步骤一个步骤的看一下,我们都做了哪一些步骤: 第一个我们搭建了html网页结构,绘制背景,到代码中看一下:搭建网页结构的时候呢,css部分大家可以简单回顾一下,css和html标签类的这些内容呢不是我们本身的重点,大家记得在写的时候,要把js脚本包含到html里面来,另外,我们要在canvas上使用场景来绘制要画的东西,那我们很重要的一个就是要从html里面去获取canvas,也就是使用getElementById这个方法来获取canvas 又用getContext获取canvas的场景,同时,大家在这里一定不要忘了getContext('2d')的参数2d,我们之前在写的时候呢,被这个bug困扰了很久,因为忘记了把这个参数写进去,好,这里还有一个小小的知识点呢,就是我们分了两个canvas来绘制内容,因为我们的内容其实还蛮多的,尤其是海葵,它的数量非常多,同时我们还要画那么大的一个背景,如果我们的海葵,像现在,是静止的,海葵是静止不动的,背景也是静止不动的,这种情况下,canvas2只要画一次就可以了,它不需要在循环帧里面,它不需要gameloop来不断刷新,但是我们到第二阶段的时候,实际上是要画一个动态的海葵,这个时候我们就需要刷新了
    查看全部
    1 采集 收起 来源:课程总结

    2016-06-25

  • 5,通过lerpDistance使得小鱼跟着大鱼移动 这时候,我们再让小鱼动起来,小鱼动起来就是对于大鱼当前坐标的lerp, 就是让小鱼的坐标一直趋向于大鱼的坐标,我们再来看一下大鱼lerp坐标的过程,让它的坐标值趋向于目标值,现在绘画小鱼的时候,小鱼lerp的目标坐标就是大鱼的坐标, babyObj.prototype.draw=function(){ //lerp x,y this.x=lerpDistance(mom.x,this.x,0.98); this.y=lerpDistance(mom.y,this.y,0.98); //ctx1 ctx1.save(); //translate() ctx1.translate(this.x,this.y); ctx1.drawImage(this.babyTail,-this.babyTail.width*0.5+23,-this.babyTail.height*0.5); ctx1.drawImage(this.babyBody,-this.babyBody.width*0.5,-this.babyBody.height*0.5); ctx1.drawImage(this.babyEye,-this.babyEye.width*0.5,-this.babyEye.height*0.5); ctx1.restore(); } 好,我们刷新一下,小鱼已经跟着大鱼走起来了
    查看全部
    1 采集 收起 来源:小鱼绘制

    2016-06-24

  • 2,添加图片属性完善小鱼类的定义 我们绘制小鱼呢,按照之前绘制鱼妈妈的方法,来一步一步回想,第一步先把小鱼的身体图片资源绘制上去,然后第二步是让小鱼 能够动起来,跟着鼠标动,第三步是让小鱼能够旋转,小鱼跟随的并不是我们的鼠标,所以它的移动也不是跟随鼠标的,它应该是跟随鱼妈妈的,它一直跟着妈妈走,所以它的位置和旋转都跟着鱼妈妈,ok,我们先做这些事情,至于它身体的变化,吃到被大鱼拥抱一下的反应,我们后面再来做,我们先把小鱼的图片资源加载进来,定义三个图片变量, var babyObj=function(){ this.x; this.y; this.babyEye=new Image(); this.babyBody=new Image(); this.babyTail=new Image(); } 那么这三个图片类在初始化的时候,对图片资源进行加载, babyObj.prototype.init=function(){ this.x=canWidth*0.5-50; this.y=canHeight*0.5+50; this.babyEye.src="./src/babyEye0.png"; this.babyBody.src="./src/babyFade0.png"; this.babyTail.src="./src/babyTail0.png"; } 好,这样就加载进来了,我们来刷新看一下,ok,没有问题,
    查看全部
    1 采集 收起 来源:小鱼绘制

    2018-03-22

  • 2-11,小鱼绘制 1,创建小鱼类 接着我们就要来继续下一个任务,绘制小鱼,画小鱼和画大鱼原理是一样的,我们来看一下大鱼跟小鱼,大鱼跟小鱼的图片资源也差不多,都是三张图片,然后小鱼也会有一些动画,除了尾巴的摇动以外呢,身体会变色,如果它一直吃不到果实的话,就是鱼妈妈一直不来救它的话,它就会慢慢的失去颜色,当被大鱼碰到之后呢,它就恢复了颜色,好,我们来看一下小鱼的图片资源,小鱼有一个眼睛眨动的动作,除此以外,它的身体有一个颜色退去的过程,这是一个序列帧,另外,它的尾巴会有一个摇动的动画,也是一个序列帧,画大鱼和画小鱼也能借此复习一下,现在我们先建立一个新的文件,然后保存,叫它baby.js然后把这个js文件包含在html里面,那我们首先先建一个关于小鱼的类,然后定义小鱼的一个变量来继承这个类,babyObj,function,它有坐标位置,另外呢需要初始化,把它初始化在屏幕的中央吧,然后跟大鱼保持一定的距离,注意是在大鱼的下方,让小鱼绘制在第一个canvas上,这样一个基本的类就建立了, var babyObj=function(){ this.x; this.y; } babyObj.prototype.init=function(){ this.x=canWidth*0.5-50; this.y=canHeight*0.5+50; } babyObj.prototype.draw=function(){ //ctx1 } 我们来定义一下小鱼,放在鱼妈妈下面,它继承baby类,并且初始化一下。我们来刷新一下,看有没有问题,没有问题,
    查看全部
    1 采集 收起 来源:小鱼绘制

    2016-06-24

  • 2-10:优化: 现在我们来解决这个问题:当我把标签切换一下的时候,这个标签下面的东西是不被执行的,所有里面的代码都是不被执行的,所以我在切换过来的时候,所以这个果实就会变得特别大,这是为什么呢?这是因为我们在绘制果实的时候,它的尺寸是和deltaTime成正比的, if(this.l[i]<=14){ //果实生长时果实长度的变化情况 this.l[i] += this.spd[i]*deltaTime; } deltaTime越大,果实的尺寸越大,deltaTime是帧与帧之间的时间间隔,两帧之间的间隔时间越长呢,果实的尺寸的值就越大,所谓在我当前标签切过来之后,它现在执行的帧已经停止了,当我再切过来的时候,这两帧之间的间隔就会变得特别大,所以果实变的特别大,看起来太诡异了。这个给它定义一下。如果deltaTime大于50ms的时候,deltaTime就等于50ms,我们把它约定一下, 50太大了,我们约定为40, if(deltaTime>40) deltaTime=40;加在gameloop里面。 这样子的话呢,它就不会出现这个问题了,我们现在做的是一个硬性的约定了,把它约定在这个值里,虽然不是科学的,但是它不会出现诡异的事情。 那再一个呢,就是Date.now()这个API我们来看一下它的含义,这有利于帮助我们理解帧与帧之间的距离,Date.now()的返回值是什么呢? The Date.now() method returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC. 从1970年的这个时间一直到现在的时间消逝,所以它返回的是一个真实的绝对值,好,这是一个小插曲,
    查看全部
    1 采集 收起 来源:优化

    2018-03-22

  • 2,在果实类中添加dead方法 function momFruitsCollision(){ for(var i=0;i<fruit.num;i++){ if(fruit.alive[i]){ //calculate length var l=calLength2(fruit.x[i],fruit.y[i], mom.x, mom.y); } } } 如果这个距离l小于某一个值,因为l是一个平方值,30的平方就是900,如果小于900的话,那么这个fruit就被吃掉, 这个果实被吃掉了,需要在果实类里面加一个方法,这个方法的功能就是让当前果实死亡,执行一个死亡的状态,什么叫死亡呢? 就是果实从屏幕上消失了,怎么消失呢?也就是它的alive状态被设置成false,这样子它就消失了, fruitObj.prototype.dead=function(i){ this.alive[i]=false; } 好,就要使用这个dead方法,
    查看全部
  • 1, 老师封装好的函数calLength2来计算大鱼和果实之间距离的平方和 我们再回到ppt来,画大鱼,身子,尾巴,眼睛已经画好了。我们再看一个下面的,我们下一个任务是大鱼吃果实的碰撞检测: 这个原理就是检测大鱼和果实的距离,也就是我们在每一帧的时候去判断一下,大鱼和这个果实距离是不是足够近,足够近的话, 就判定它已经吃到了,然后如果这个距离非常远,然后就判定这个果实没有被吃掉,好,那么我们回到代码中,我们来新建一个文件,Ctrl+N,Ctrl+s,collision.js这个文件里面只存放碰撞检测的功能,首先把这个脚本包含到html里面。我们要做的是判断大鱼和屏幕上每一个果实的距离,如果小于某一个值,我们就认为大鱼把这个果实吃掉了,如果大于这个值,果实仍然继续它的运动 好,现在来写这个功能,因为果实非常的多,我们需要做一个循环,来对比每一个果实,这所有的果实,首先判断它是否在alive状态,如果在alive状态的话,再去判断它跟大鱼之间的距离,判断距离使用它们的坐标差,我们这样得到一个斜边的大小,而实际上我们在计算的时候不需要去开平方,三角里面不需要去开平方,因为开平方或者不开平方对于我们都没有关系,在commonFunctions.js里面有一个封装好的方法: function calLength2(x1, y1, x2, y2) { return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2); } 距离值的平方,它是这两对坐标(x1,y1),(x2,y2)的差的平方和,pow是乘方的函数,次数是2(pow第二个参数),也就是(x1-x2)的平方加上(y1-y2)的平方,这样呢,在一个三角里面我们得到的就是斜边的平方,也就是大鱼跟果实之间距离的平方。我们把这封装好的函数拿过来用,大家可以经常把这些很普通的函数封装好,然后我们每次就不必去写,否则每次去写要写正确这些API如pow,还有乘方的次数,大家封装好就可以很好的去用了
    查看全部
  • 4,给鱼妈妈类增加角度属性并且引入老师封装好的lerpAngle方法。 现在大鱼虽然可以移动,但是它并没有根据鼠标的位置去旋转,如何来旋转它的身体呢? 我们来分析一下:首先大鱼它自己有一个角度,它现在是朝向左的,它现在 身体的角度是多少呢?根据极坐标的规定,极坐标的正方向是向右,那么它现在朝向左,角度就是PI或者-PI. 不管它是往上转还是往下转,如果是往下转(逆时针角度是正的)呢,角度是PI,往上转(顺时针角度是负的)呢,角度就是-PI。如果大家不清楚极坐标的规定,大家可以去搜索一下坐标的知识,大鱼跟鼠标他俩之间会产生一个坐标差。它俩都有各自 的坐标,那么坐标会产生一个坐标差,坐标差值会形成一个三角形,这个三角形有个夹角,这个夹角就是鼠标相对于大鱼 的角度。所以我们得到了第二个角度,而刚才我们讲的大鱼它自身的角度要去追随,我们刚才说到的坐标差的角度 我们回到代码中来,首先给大鱼增加一个属性:this.angle角度。这个angle初始化为0.怎么来计算坐标差? 坐标差需要在每一帧里面去计算,每一帧都去跟随这个角度delta angle,所以每一帧都去计算角度差,这个角度差 怎么来计算。它是用我们刚才讲到的Math.atan2(y,x),用这个API来计算的,首先计算y值,y值的坐标差: deltaY=my-this.y; x值的坐标差: deltaX=mx-this.x; 我们要求的是angle值,我们用另外一个变量beta,beta这个角度就是鼠标与大鱼之间的角度差,我们用反正切返回这个值 beta=Math.atan2(deltaY,deltaX);这样我们得到了这个角度,大鱼的角度this.angle要不断的趋向于这个beta。 所以不断的趋向,我们用一个lerp,这个我们上面做的坐标的lerp是一样的,上一部分是让大鱼的坐标一直趋向于鼠标的 坐标,而我们现在要做的就是让大鱼的角度一直趋向于鼠标的角度,那么 this.angle=lerp 我们看一下这个封装好的API: function lerpAngle(a, b, t) { var d = b - a; if (d > Math.PI) d = d - 2 * Math.PI; if (d < -Math.PI) d = d + 2 * Math.PI; return a + d * t; }
    查看全部
  • 3,初画大鱼非常粗,加上命令显苗条 那么大鱼应该画在哪一个canvas上面呢?是在第一个canvas下面: 我们先不做调整,先把它都画上去,然后再慢慢调整它的位置: momObj.prototype.draw=function(){ ctx1.drawImage(this.bigEye,this.x,this.y); ctx1.drawImage(this.bigBody,this.x,this.y); ctx1.drawImage(this.bigTail,this.x,this,y); } 我们来刷新看一下,大鱼已经画出来了。但是它并不像我们的图片那个样子, 原图的线条(src下的大鱼的body的线条是很细的)是很细的,在这里却变得非常的粗 为什么?我们再来看一下,这个canvas1,它是一个透明的,部分透明的,只绘制需要绘制的东西在上面 然后其他地方都是透明的,然后它覆盖在canvas2上,所以我们每一次绘制的时候,都需要把前面一帧的 内容clear一下。把它清空掉,然后再绘制新的,我们需要在gameloop循环里加上这样一个功能: ctx1.clearRect(0,0,canWidth,canHeight); 从0,0点到canvas的对角线点整个清除掉,然后在干净的画布上面去绘制, 好,这样子绘制出来的东西就是正常的了
    查看全部
    1 采集 收起 来源:大鱼绘制

    2016-06-24

  • 4,修改speed,让它的范围更大,使得果实快慢差距变大 好,我们到浏览器中看一下:可以看到有果实出来了,一开始的时候还是会产生一大片 然后后面就看起来比较自然了,那我们再修改一下数值吧。让它的speed状态随机的范围 更大一点: fruitObj.prototype.init=function(){ for(var i=0;i<this.num;i++){ this.alive[i]=false; this.x[i]=0; this.y[i]=0; this.l[i]=0; this.spd[i]=Math.random()*0.017+0.003;//[0.003,0.02) } this.orange.src="./src/fruit.png"; this.blue.src="./src/blue.png"; } 好,再看一下,好的,现在快慢差距会非常的大
    查看全部
  • 2.修改born方法,增加发出果实方法:sendFruit。 但是在这里还有一个细节,在整个30个果实池里面,哪一个是处于 闲置状态,哪一个是活着的,需要去判断一下,这里还是做一个循环: 如果当前果实的状态是false,就要让这个果实出生,这里需要传一个参数进来,这个i值对应的 果实出生,它要随机一个海葵的位置,而后它的尺寸归零,同时它的alive状态要设置为true。 fruitObj.prototype.born=function(i){ var aneId=Math.floor(Math.random()*ane.num); this.x[i]=ane.x[aneId]; this.y[i]=canHeight-ane.len[aneId]; this.l[i]=0; this.alive[i]=true; } 当它出生之后呢,本次循环结束。每次sendFruit()只要send一个fruit。然后下一次再进入循环。 function sendFruit(){ for(var i=0;i<fruit.num;i++){ if(!fruit.alive[i]){ fruit.born(i); return; } } }
    查看全部
  • 接下来继续细化果实的状态: 1,果实变的太大,太恐怖: 首先果实出生的时候,是由小慢慢长大的,这个过程我们需要来控制一下,这个原理很简单,是一开始绘图的时候, 绘一个很小的图片,然后慢慢的让这个尺寸加大,我们需要给果实类加一个属性: //果实图片的长度 this.l=[]; l初始化的时候是0, 那么果实在出生的时候长度l也是0;因为即便不是初始化,果实在每次刚出生的时候,这个l也要重置一下, 这个l会随着时间逐渐的增长,在draw方法的for循环中,给l加一个常数0.01:this.l[i] += 0.01; 这里需要调整一下成长的速度,这里也是我们第一次用到这个值deltaTime,deltaTime是什么呢? 我们前面讲过:就是每两帧之间的时间间隔。它是用来保证游戏里面动作的流畅,连贯,当你用到一个 随时间变化的变量的时候,一定要使用deltaTime使这个过程变得平滑, 在draw方法的for循环中做如下改变: fruitObj.prototype.draw=function(){ for(var i=0;i<this.num;i++){ this.l[i] += 0.01*deltaTime; ctx2.drawImage(this.orange,this.x[i]-this.l[i]*0.5, this.y[i]-this.l[i]*0.5,this.l[i],this.l[i]); } } 其中drawImage方法的第4个参数和第5个参数是规定绘制这个图片的尺寸。 在浏览器中刷新一下,可以看到果实在慢慢长大,不过挺恐怖的,它现在越来越大。我们需要判断一下:
    查看全部
  • 5,果实的draw方法: /* 哦,另外我们还要画果实。我们这边就要画每一个果实了。它有两个状态,首先是 长在海葵上面,因为果实的数量要比海葵的数量要小,所以我们不用很担心,即便会 有重复,我们先不去管它,ok。我们先做一个循环,在这个循环里面,画果实。 怎么画果实?果实首先要找到一个定位,找到一个海葵,然后grow,长了之后呢 就开始漂了,网上漂fly up。我们首先find an ane,这个时候我们如何找呢? 当前的这个果实要生长在哪里呢?我们直接写一个born函数。 把draw放在gameloop的循环里面。然后到浏览器中看一下情况如何。看, 所有的果实已经在位置上了。只是这个位置有点点偏差。你看这个果实的 位置和这个海葵的位置是不对的,这是为什么呢?因为drawImage这个API呢 它绘制的时候,是从图片的0,0点(即图片的左上角)开始的, 所以这个地方,x值的坐标需要减去这个图片的宽度的一半,同样的,y值的坐标也要减去图片高度的一半。 ctx2.drawImage(this.orange,this.x[i]-this.orange.width*0.5,this.y[i]-this.orange.height*0.5); 来到浏览器中看一下,这样就很好了,每个果实都长在海葵上面,也就是说 大家现在看到的不是30个。这是为什么?因为有重叠,我们没有给它做一个 排除。问题在这个地方,我们是找的随机值,这个随机的时候是会出现重复的: var aneId=Math.floor(Math.random()*ane.num); 怎么样排除重复呢?那就是给海葵做一个记录,判断一下它当前有没有被 占用,如果没有被占用,这个果实就可以生成在这个海葵上,如果这个海葵 上面已经有了一个果实,就找下一个海葵。 */ fruitObj.prototype.draw=function(){ for(var i=0;i<this.num;i++){ /* 我们首先不管果实大小,先把它画上去,再说,我们当前的任务呢是让果实出生, */ ctx2.drawImage(this.orange,this.x[i]-this.orange.width*0.5,this.y[i]-this.orange.height*0.5); } }
    查看全部

举报

0/150
提交
取消
课程须知
1、对html、css基础知识已经掌握。 2、对JavaScript的基础知识掌握,如数组、类、对象。
老师告诉你能学到什么?
1、html5 canvas制作游戏理念 2、html5 canvas 绘图API 3、游戏中的碰撞检测 4、认识几个数学函数 5、物体池概念 6、序列帧动画的控制

微信扫码,参与3人拼团

意见反馈 帮助中心 APP下载
官方微信
友情提示:

您好,此课程属于迁移课程,您已购买该课程,无需重复购买,感谢您对慕课网的支持!