HTML5“爱心鱼”游戏总结

HTML5“爱心鱼”游戏总结

目录

  1.页面搭建

  2.画蓝色的海洋

     3.画随海水摆动的漂浮物

     4.画随海水摆动的海葵

     5.画静态的大鱼和小鱼

     6.鼠标控制大鱼的游向

     7.给大鱼、小鱼加基本动画(眼睛眨动,尾巴不停的摇摆)

     8.小鱼跟随大鱼游动

     9.画果实

     10.大鱼吃果实、大鱼喂小鱼

     11.特效(大鱼吃果实产生白色涟漪,大鱼喂小鱼产生橙色涟漪)

     12.游戏分值计算和小鱼生命值判断

     13.游戏开始前的处理

     14.游戏结束后的处理

  游戏演示:点击这里

  源代码:   点击这里

  本文是对用HTML5开发的“爱心鱼”游戏总结,主要涉及的知识点有:HTML5 canvas API、轮播序列帧动画、“物体池”、碰撞检测、JS中的Math对象、setInterval()、贝塞尔曲线、模块化开发、面向对象编程思想。

页面搭建

  整个页面由三部分组成,都采用绝对定位的方式,使其水平居中。第一部分包含两个canvas,分为上下两层,上层即canvas1绘制fishes,text,dust,wave,下层即canvas2绘制background,ane,fruit;第二部分包含一个div,主要用于页面一加载时,显示游戏规则;第三部分也包含一个div,主要用于游戏结束时,显示玩家的得分情况。

  建立tinyHeart.css文件用于给html添加样式,美化页面。

  建立tinyHeart.js文件用于实现该游戏的功能。里面包含基本数据、定义三种状态、各种图片的创建、数据对象、业务对象、功能性函数、主函数、事件绑定函数这8大模块。

画蓝色的海洋

  主要用到canvas API中的drawImage(),将images中的背景图片画到canvas2上

画随海水摆动的漂浮物

  指定漂浮物的总个数,通过for循环、随机数、正弦函数为每个漂浮物指定图片、显示位置、摆动幅度来营造海水流动的氛围。

  关键点:a.给漂浮物定义一个角度属性,使其随事间变化

this.alpha+=deltaTime*0.0008;//注意这个角度需要和海葵摆动的角度相同,deltaTime为每帧画面的间隔时间

      b.利用正弦函数,可以得到一个[-1,1]之间的随机值,用来控制漂浮物左右摆动

var l=Math.sin(this.alpha);//[-1,1]

      c.给每个漂浮物定义一个幅度属性,用随机数来初始化,用来控制漂浮物左右摆动的幅度

this.amp[i]=Math.random()*15+30;//第i个漂浮物摆动的幅度

      d.在canvas1上画漂浮物

for(var i=0;i<this.num;i++){
   var curImgIndex=this.index[i];
   ctx.drawImage(dustImg[curImgIndex],this.x[i]+l*this.amp[i],this.y[i]);//将指定图片画到指定位置上
}

画随海水摆动的海葵

    实现海葵摆动的大致思路是:利用二次贝塞尔曲线来画海葵,在二次贝塞尔曲线的结束点处使用正弦函数控制从而形成摆动效果。  

    关于贝塞尔曲线的简单学习:

    先看下维基百科上的图解:

       二次贝塞尔曲线的结构   
           二次贝塞尔曲线的结构
    
       
          二次贝塞尔曲线的动态演示

    可以看到,二次贝塞尔曲线由几部分构成:

      P0到P1的连续点Q0,描述一条线性贝塞尔曲线

      P1到P2的连续点Q1,描述一条线性贝塞尔曲线

      Q0到Q1的连续点B(t),描述一条二次贝塞尔曲线   

    上述中,P0称为起始点,P2称为结束点,P1称为控制点,我们需要做的就是把海葵的底部的x轴坐标当做起始点,控制点在该起始点的垂直上方,结束点是由正弦曲线控制的,把结束点的x轴坐标值交给正弦函数处理。在正弦函数中,将随时间变化的一个角度值alpha 当做正弦函数的x,把海葵结束点的x当做正弦函数的y,这样海葵的x值就可由正弦函数控制,从而营造出随海水左右摆动的效果。

    关于Math.sin(x)方法,返回的是一个-1到1之间的值,参数x是一个弧度表示的角,将角度乘以 0.017453293 (2PI/360)即可转换为弧度。

    关键代码:

this.alpha+=deltaTime*0.0008;//海葵摆动的角度值随时间变化
var l=Math.sin(this.alpha);//使得海葵可以左右摆动
ctx.moveTo(this.rootx[i],this.rooty);//将笔触移动到海葵生长的位置
this.headx[i]=this.rootx[i]+l*this.amp[i];//当前海葵头部的具体位置(若想让果实跟着头部一起动,这句必不可少)
ctx.quadraticCurveTo(this.rootx[i],canHeight-150,this.headx[i],this.heady[i]);//画二次贝塞尔路径

   海葵的绘制还使用了canvas API中的渐变对象,画出了一条条渐变的海葵,除此之外还设置了透明度,使得海葵看起来更漂亮。

var gradient=ctx.createLinearGradient(this.rootx[i],canHeight,this.headx[i]+l*this.amp[i],this.heady[i]);//创建渐变对象
gradient.addColorStop(0,'#003300');//设置颜色断点值
gradient.addColorStop(0.3,'#336600');
gradient.addColorStop(0.6,'#339933');
gradient.addColorStop(0.8,'#99CC33');
gradient.addColorStop(1,'#00CC00');
ctx.strokeStyle=gradient;//使线条的颜色为渐变色

画静态的大鱼和小鱼

    绘制静态的大鱼、小鱼时,整体分为鱼的眼睛、身体、尾巴这三部分,将预先准备好的大鱼和小鱼的各部分的图片用drawImage()函数画到canvas1上,为了方便后期实现大鱼、小鱼随意游动的特效,在绘制图片之前先用ctx.translate(newX,newY)重新映射canvas1坐标原点的位置[即将canvas1的坐标原点映射到(this.x,this.y)处],在绘制大、小鱼身体各部分图片时的坐标位置永远是相对于canvas1坐标原点的位置

    注:此处重新映射了canvas1的坐标原点位置,为了不影响其他功能的实现,故在映射之前先用ctx.save()保存画布当前的状态,在绘图完成之后再用ctx.restore()恢复到最近一次保存的canvas状态

    关键代码:

ctx.save();//因为要重新映射画布圆点的位置和旋转画布,故需要先保存画布的当前状态
ctx.translate(this.x,this.y);//将画布的原点位置重新映射到(this.x,this.y)处
ctx.drawImage(this.momTail[tailCount],-this.momTail[tailCount].width*0.5+30,-this.momTail[tailCount].height*0.5);
ctx.drawImage(pic,-pic.width*0.5,-pic.height*0.5);
ctx.drawImage(this.momEye[eyeCount],-this.momEye[eyeCount].width*0.5,-this.momEye[eyeCount].height*0.5);
ctx.restore();

鼠标控制大鱼的游向

    实现鼠标控制大鱼,可以在上层画布canvas1上(绘制鱼的层)使用addEventListener()函数侦听onMouseOver事件:

can1.addEventListener('mousemove',onMouseMove, false);

    这里又涉及到addEventListener()方法,引用MDN的解释:

    addEventListener() 方法将指定的事件监听器注册到目标对象上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素, document 本身, window , 或者 XMLHttpRequest.

    也可给上层画布canvas1上(绘制鱼的层)绑定onmousemove事件(本游戏中我采用的方法)

can1.οnmοusemοve= getMousePos;//getMousePos()为获得鼠标位置的函数

    获得鼠标位置的函数:

//获取鼠标的位置
function getMousePos(e){
    if(state!=GAMEOVER){
        if(e.offsetX||e.layerX){
            mx=e.offsetX ? e.offsetX : e.layerX;
            my=e.offsetY ? e.offsetY : e.layerY;
        }
    }
}    

    在检测鼠标位置时,需要考虑到浏览器的兼容性,使用layerX和layer来兼容Firefox浏览器,其他的浏览器都可以用offset来实现。这里有一篇详细介绍的文章

    检测到鼠标的位置后,可以在画大鱼时将鼠标的移动位置传递进去也可将鼠标的位置设为全局变量(本游戏我采用),然后需要用到lerpDis()函数将大鱼坐标趋近于鼠标的坐标。

//让一个数值趋向于另一个数值
//aim:被趋近于的值
//cur:当前值
//rate:趋近程度
function lerpDis(aim,cur,rate){ var delta=aim-cur; return cur+delta*rate; }

    通过上面函数就可以使cur(当前值)趋近于aim(目标值),radio表示趋近的程度,以百分比表示
可以举个例子: aim(目标值)是6,cur( 当前值)是3, 趋近程度90%, 即:3+(6-3)*0.9=5.7, 返回的值可以趋近于当前值6. 它的作用是可以使得动画变得更加平滑。      

   而在移动大鱼时,只需用translate()这个函数来重新映射can1的坐标原点位置即可。

   接着需要让大鱼随着鼠标旋转:这里用到的第一个知识点就是Math.atan2(y, x)函数,即Javascript反正切函数:

   Math.atant2(y, x)返回的是正X轴和点 (x, y) 与原点连线 之间的角度,这个角度是一个 -PI到 PI之间的弧度,表示点 (x, y) 对应的偏移角度。这是一个逆时针角度,以弧度为单位。
        注意此函数接受的参数:先传递 y 坐标,然后是 x 坐标。还需要注意的是 atan2 接受单独的 x 和 y 参数,而 atan 接受两个参数的比值。

   由于 atan2 是 Math 的静态方法,所以应该像这样使用:Math.atan2(),下面是例子:

Math.atan2( ±0, -0 ) // ±PI. 
Math.atan2( ±0, +0 ) // ±0.

    用到的第二个知识点就是rotate()函数,使用方法:ctx.rotate(angle)旋转当前画布,其中angle表示旋转的角度,以弧度计算.

    检测到鼠标的角度后,可以用lerpAngle()函数让一个角度值趋近于另一个角度值,控制大鱼朝着鼠标运动:

//让一个角度值趋向于另一个角度值
//aimAngle:目标值的角度(将被趋近的那个值)
//curAngle:当前值
//rate:表示趋近程度,是一个百分比的值
function lerpAngle(aimAngle,curAngle,rate){
    var gapAngle=curAngle-aimAngle;
    if(gapAngle>Math.PI){
        gapAngle=gapAngle-2*Math.PI;
    }
    if(gapAngle<-Math.PI){
        gapAngle=gapAngle+2*Math.PI; } return aimAngle+gapAngle*rate; }

    关键点:a.根据鼠标的位置与大鱼当前的位置重新映射can1的坐标原点,用ctx.translate(x,y)------游动

        b.根据鼠标的角度和大鱼的当前角度旋转画布,用ctx.rotate(degree);------旋转

//lerp x,y(更新鱼的当前位置)
this.y=lerpDis(my,this.y,0.019);
this.x=lerpDis(mx,this.x,0.019);
        
//lerp angle(更新鱼的当前角度值)
var deltaY=my-this.y;//鼠标所在位置的y值与大鱼所在的位置的y值之差
var deltaX=mx-this.x;//鼠标所在位置的x值与大鱼所在的位置的x值之差
var aimAngle=Math.atan2(deltaY,deltaX)+Math.PI;//当前值(被趋近于点)的角度值(极坐标系下),用反正切函数算得
this.angle=lerpAngle(aimAngle,this.angle,0.80);//更新大鱼的当前角值
ctx.translate(this.x,this.y);//将画布的原点位置重新映射到(this.x,this.y)处
ctx.rotate(this.angle);//旋转画布 

给大鱼、小鱼加基本动画(眼睛眨动,尾巴不停的摇摆)

    大鱼和小鱼的动画包括:大、小鱼眼睛的眨动,尾巴的摇摆,和身体颜色的变化。这些都是利用相同的原理,即轮播序列帧。

    首先说大小鱼身体颜色动画:预先准备好身体慢慢变色的素材图片,建立一个数组,用for循环将这些素材的地址全都放进数组中,依次加载身体颜色的图片,对大鱼来说,用if语句判断吃到蓝色果实则身体逐渐变蓝,否则变橙色。对小鱼来说,在预计的时间内没有和大鱼碰撞或者发生碰撞时大鱼没有吃到果实,则小鱼身体逐渐变白,完全变白即游戏结束。

    至于眼睛眨动,和上面的不同,这个步骤需要准备眼睛睁开和闭合的两张图片,然后定义一个时间间隔的变量,因为睁开眼睛的时间较长且这个时间是在一定范围内随机的,而眨动眼睛的时间很短,所以可以这样:

this.babyEyeTimer+=deltaTime;
if(this.babyEyeTimer>this.babyEyeInterval){
      //当达到某一时间值后,根据当前显示的是哪张图片,决定过多久以后,播放下一张图片
      this.babyEyeCount=(this.babyEyeCount+1)%2;
      if(this.babyEyeCount==0){
          //当前显示的是“睁眼”的那张图片
          this.babyEyeInterval=Math.random()*1500+2000;//为了使鱼眼睛“睁”,“闭”的有规律,故采用随机数
      }else{
          //当前显示的是“闭眼”的那张图片
          this.babyEyeInterval=200; } this.babyEyeTimer=0;//计时器清0,为下一次计时做准备 }

小鱼跟随大鱼游动

    实现原理同大鱼跟随鼠标游动

画果实

    此处使用了“物体池”,“物体池”的概念见“特效”部分。因为“果实是不断出生的,故果实对象除了init()方法、draw()方法,还要有dead()方法、sendFruit()方法、monitorFruit()方法、born()方法

    关键方法实现代码:

    sendFruit()方法:----找到可以出生的果实

this.sendFruit=function(){
      //产生新的果实
      for(var i=0;i<this.num;i++){
          if(!this.alive[i]){
              //第i颗果实若在休眠状态,就可以出生
              this.born(i);
              return;//一旦找到新的果实出生,就不再找新的果实
           }
      }
 };

    monitorFruit()方法:-----用于监控当前屏幕上果实的数量,从而在满足一定条件时允许产生新果实

this.monitorFruit=function(){
     var number=0;//用于记录当前活着的果实数量
     for(var i=0;i<this.num;i++){
         if(this.alive[i]){
             number++;
         }
     }
     if(number<15){
         //当活着的果实个数低于20个的话,产生新果实
         this.sendFruit(); return; } }

    born()方法:------产生新果实

this.born=function(i){
     //主要是确定每个果实将出生在哪株海葵上
     var aneID=Math.floor(Math.random()*ane.num);//得到一个随机的下标
     this.alive[i]=true;//一旦该果实被重生,它就可以执行任务
     this.size[i]=0;//指定第i个果实出生时的大小
     this.aneId[i]=aneID;//第i颗果实将来要长在下标为aneId的那株海葵上(在果实出生时告诉他会长在哪株海葵上)
     var randomFruit=Math.random();//用于产生不同种类的果实(此值为:[0,1)之间的一个数)
     if(randomFruit<0.2){
         //产生蓝色果实
         this.fruitType[i]='blue';
     }else{
         //产生黄色果实
         this.fruitType[i]='orange';
     }
};

大鱼吃果实、大鱼喂小鱼

    大鱼吃果实,大鱼喂小鱼的实现方法是一样的,都是利用碰撞检测的方法,即计算两者之间的距离,当这个距离小于某个值时,就认为发生了碰撞,即大鱼吃掉了果实,大鱼给小鱼喂食了。

    此游戏利用的碰撞检测方法(即计算两者之间的距离):

//计算两点之间的距离
//果实(对象1)的坐标:(x1,y1)
//大鱼(对象2)的坐标:(x2,y2)
function distance(x1,y1,x2,y2){
    return Math.pow(x1-x2,2)+Math.pow(y1-y2,2);//此处用勾股定理计算的
}

    喂小鱼时,需要检测大鱼是否吃到了(积累了)果实,如果那时大鱼吃的有果实,那么碰撞有效,产生特效,小鱼身体颜色恢复红色,并计分值。如果那时没有吃到果实,则碰撞无效,没有特效,小鱼颜色继续为白色,不计分值。这里面主要是检测喂小鱼时,大鱼是否吃到了(积累了)果实。
    检测大鱼吃到了(积累了)果实可以使用一个计数器,每当大鱼和果实碰撞计数器就加一,当大鱼喂小鱼时,判断计数器是否大于0,大于0就有效,然后执行和小鱼碰撞后的一些列事情,并将计数器清零,不大于0就无效。

大鱼吃果实,大鱼喂小鱼的具体实现:

//碰撞检测
function crashTest(){
    //遍历每一颗果实,看它与大鱼之间的距离,若距离小于某值,则认为大鱼将果实吃掉了
    for(var i=0;i<fruit.num;i++){
        if(fruit.alive[i]&&state==RUNNING){
            if(distance(fruit.x[i],fruit.y[i],mom.x,mom.y)<900){
                fruit.dead(i);//一旦被大鱼吃掉,该果实就死去了
                data.eatFruitNum++;//大鱼吃到的果实数增加
                mom.momBodyCount++;//大鱼每吃到一颗果实身体颜色就会变化
                if(mom.momBodyCount>=7){
                    mom.momBodyCount=7;//因为图片资源有限,故当大鱼身体图片播放到最后一张后,就不再变化
 } //判断大鱼吃到的果实类型 if(fruit.fruitType[i]=='blue'){ data.double=2;//当大鱼吃到“蓝色”果实需要做记录,用于最后的分数统计  } whiteWave.bornWhite(i);//使白色涟漪出生  } } } //判断大鱼和小鱼之间的距离,若距离小于某一个值,则认为大鱼有喂小鱼果实 if(distance(mom.x,mom.y,baby.x,baby.y)<900){ if(data.eatFruitNum!=0&&state==RUNNING){ //当大鱼有吃到果实时才能喂小鱼,从而产生橙色涟漪  orgWave.bornOrg(); mom.momBodyCount=0;//大鱼喂小鱼之后,身体就变为白色 baby.babyBodyCount=0;//当大鱼喂小鱼之后,小鱼就恢复了生命 data.upScore();//当大鱼喂小鱼果实后,更新分数  } } }

特效(大鱼吃果实产生白色涟漪,大鱼喂小鱼产生橙色涟漪)

  特效有大鱼吃到果实时候的效果,和大鱼喂小鱼时候的效果:在每次大鱼吃到果实时候就会产生白色涟漪,涟漪由小到大,由不透明到透明逐渐变化。它们实现原理相似:
首先,需要介绍一个”物体池“的概念:

  物体池:规定里面的物体是有一定数量的,当需要从“物体池”中拿出一个物体时,先检测是否有闲置的物体,如有闲着的,才能拿出来执行任务,用完后再放回去。

  设定涟漪的数量,比如15个,初始化半径,还有状态,状态分为false(不显示)和true(显示),初始时状态全部设为false,当大鱼和果实碰撞时,遍历涟漪的数量,找出状态为false的,并将其修改为true,然后执行画涟漪的动作,涟漪变大变透明后,再将其状态修改为false,以便后面继续使用。

  在画涟漪的过程中,有个技巧是将涟漪的半径和透明度设置为反比关系(涟漪的半径越大,透明度越小),当半径大于某个值时,将其状态修改为false,并用break语句跳出:

for(var i=0;i<this.num;i++){
    if(this.alive[i]){
         //当第i个涟漪“活着”的时候,就可以执行任务(即被绘制)
         this.size[i]+=deltaTime*0.06;
         if(this.size[i]>this.maxR){
              this.size[i]=this.maxR;
              this.alive[i]=false;//当涟漪的尺寸大于某一特定值时,该涟漪就死亡
              break; } ctx.beginPath(); this.alpha=1-this.size[i]/60;//随着涟漪半径的扩大,涟漪的透明度在逐渐减小 ctx.strokeStyle='rgba('+this.colorArr[0]+','+this.colorArr[1]+','+this.colorArr[2]+','+this.alpha+')'; ctx.arc(this.x[i],this.y[i],this.size[i],0,Math.PI*2,false);//顺时针画一个圆形路经 ctx.stroke();//将路径画到画布上  ctx.closePath(); } }

   注:因为橙色涟漪和白色涟漪,都是涟漪,故不需要创建两个业务对象,只需要创建一个业务对象和两个数据对象就可。其中业务对象包含的方法有:初始化涟漪、在canvas上画涟漪、产生白色涟漪、产生橙色涟漪(因为两种颜色的涟漪产生条件不同,故此处给涟漪对象创建两个产生涟漪的方法)

this.bornWhite=function(k){
        //产生白色涟漪(产生的位置由被吃掉的果实的位置决定)
        for(var i=0;i<this.num;i++){
            if(!this.alive[i]){
                //当第i个涟漪处于休眠状态,那么它就可以执行任务
                this.alive[i]=true;//当第i个涟漪可以执行任务,就将它改为“活着”状态
                this.size[i]=0;
                this.x[i]=fruit.x[k];
                this.y[i]=fruit.y[k]; break; } } }; this.bornOrg=function(){ //产生橙色涟漪(产生的位置由小鱼的位置决定) for(var i=0;i<this.num;i++){ if(!this.alive[i]){ this.alive[i]=true; this.size[i]=0; this.x[i]=baby.x; this.y[i]=baby.y; break; } } }; }

游戏分值计算和小鱼生命值判断

  分值的计算和大鱼吃到的果实有关,橙色果实是10分,蓝色果实是20分,这要在大鱼和果实碰撞的时候检测,具体的方法是分值计算中,声明一个用于判断吃到果实类型的变量,默认为1,当吃到蓝色果实则赋值为2,利用这个值来做分值计算,当做完计算后把吃到的果实的数量清零,即重新吃果实,并且把该变量重新赋值为1.为下一次的计算做准备。

  小鱼生命值的判断,和分值是直接相关的,当小鱼没有被大鱼喂食物的时候,身体是逐渐变白的,当到全白的时候,生命结束,即游戏结束。

  注:分值的计算是每次大鱼喂小鱼果实后计算。

  分值计算:

this.upScore=function(){
      //用于更新分数
      this.score+=this.eatFruitNum*this.baseScore*this.double;  //this.double就是用于判断吃到果实类型的变量,默认为1,当吃到蓝色果实则赋值为2
      //更新完分数后,恢复默认值,用于进行下一次分数的统计
      this.eatFruitNum=0;
      this.double=1;
};

  小鱼生命值的判断:

if(state==RUNNING){
     //body count
     this.babyBodyTimer+=deltaTime;
     if(this.babyBodyTimer>300){
         this.babyBodyCount++;
         if(this.babyBodyCount>=19){
             //当小鱼身体图片轮播完以后,游戏结束
             this.babyBodyCount=19;//让小鱼显示白色身体(即小鱼死亡)
             data.historyScore=data.score;//保存当前的得分为最高历史得分
             data.saveScore();//用localStorage将当前的得分保存
             showMyScore();//显示我的成绩
             state=GAMEOVER; } this.babyBodyTimer=0;//计时器清零,用于下一次计算  } }

  这中间涉及到小鱼被喂食后身体又恢复的状态,这个比较简单,只要把身体的帧重置到第1帧,也就是小鱼最开始的身体状态就可以了。

游戏开始前的处理

  页面一加载显示与游戏相关的信息,这部分分为上下两部分,上面用于显示相关的游戏规则,下面显示进入游戏的入口。这部分在写CSS样式时使用了box-shadow、border-radius在填充背景颜色时填充为渐变色,美化了该界面。当用户点击“开始游戏”就进入了游戏界面,可以玩游戏。

游戏结束后的处理

  当游戏结束时使“GAMEOVER”渐显出来,同时会有一部分处于半透明状态浮在"GAMEOVER"上面,用于显示本次游戏玩家的得分、最高历史得分、最下面添加了“再玩一次”和“不玩了”功能。

  此部分显示效果同“游戏开始前的处理”。其中 “GAMEOVER”是用canvas API 写上去的,同时用了一些用于字体美化的API。

  • ctx1.shadowBlur: 设置或返回阴影的模糊级数,级数越大,模糊程度越大
  • ctx1.shadowColor:设置或返回用于阴影的颜色,配合shadowBlur,创建阴影效果,
  • ctx1.shadowOffsetX 和 ctx1.shadowOffsetY: 分别设置或返回阴影的水平和垂直距离,可以是正数,负数,0; 当设置为0时,两者都是位于正下方。当为正数时,shadowOffsetX=20表示阴影位于形状left位置右方的20像素处, shadowOffsetY=20表示阴影位于形状top位置下方的20像素处,当为负数时,shadowOffsetX=-20表示阴影位于形状left位置左侧的20像素处, shadowOffsetY=-20表示阴影位于形状top位置上侧的20像素处。
  • ctx1.textAligin:根据锚点,设置或返回文本内容的当前对齐方式。有start(文本在指定位置开始,默认),end(文本在指定位置结束),center(文本的中心被放在指定位置),left(文本左对齐),right(文本右对齐)
  • ctx1.font: 设置或返回画布上文本内容的当前字体属性,可以指定字体样式(font-style),字体粗细(font-weight),字体系列(font-family)等。

  "GAMEOVER"的显示:

  Game Over的显示主要涉及的知识点就是字体透明度的变化,由透明逐渐变为不透明。可以利用”rgba(R,G,B,A)”形式的字符串,A代表alpha,可以指定为0.0(完全透明)和1.0(完全不透明)之间的浮点数值。然后需要一个变量来从0.0逐步增加到1.0,这个变化过程可以使Game Over逐渐显示出来。

  

//绘制GAMEOVER
function showGameOver(ctx){
    textAlpha+=deltaTime*0.0005;//透明度随时间变化
    if(textAlpha>1){
        textAlpha=1;
    }
    ctx.save();//保存画布的当前状态
    ctx.shadowColor='#FFF';
    ctx.shadowBlur=10;
    ctx.globalAlpha=textAlpha; ctx.font='54px Verdana'; ctx.textAlign='center'; ctx.fillStyle='#FFF'; ctx.textBaseline='bottom';//设置文字的对齐方式 ctx.fillText('GAMEOVER',canWidth*0.5,canHeight*0.5);//在canvas上写字 ctx.restore();//恢复画布的状态到最近一次save()的状态 }

   Game Over显示后的界面控制:
    在游戏结束后,需要使玩家失去对界面的控制,即鼠标不能控制大鱼,大鱼和小鱼停止运动。回顾前面的鼠标控制大鱼阶段,已经做到了大鱼响应鼠标移动,现在只需在小鱼完全透明时候,设置state='GAMEOVER';即可,然后在getMousePos(e)函数中,if判断一下,如果游戏结束就不再获取鼠标的位置,进而就不能控制大鱼运动了。

转载于:https://www.cnblogs.com/mujinxinian/p/5677015.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值