大家好!我是super喵二,这两个游戏是我学了h5之后,开始做的。主要是参照了慕课网的视频 :话不多说,先讲思路吧:

1.爱心小鱼(这个小游戏是看着视频敲的,之前不是太熟悉的时候):先上图

一、当然是需要两个canvas啦:

看图分析就是一个canvas用于画背景,芦苇,和果实

另一个canvas画鱼,记录分数,以及产生的水圈

二、绘制:

芦苇:定义一个芦苇类,包含芦苇根的位置,以及芦苇的高度,和透明值,在芦苇类上定义初始化与绘制方法,主要注意取好芦苇与芦苇宽度之间的随机值,以及芦苇高度的变化,重要的是芦苇来回摆动的振幅大小的取法:取一个振幅随机值,然后画芦苇的头部的位置时可乘上振幅的变化。

/*绘制海葵*/

var anobj=function(){
    /*this.x=[];//海葵之间的宽度
    this.len=[];//海葵的高度*/
    this.rootx=[];
    this.headx=[];
    this.heady=[];
    this.amp=[];//振幅
    this.alpha=0;
    
}
anobj.prototype.num=50;//海葵的数量

/*海葵初始化*/
anobj.prototype.inite=function(){
    for(var i=0;i<this.num;i++){
        this.rootx[i]=i*16+Math.random()*20;//i*10定义海葵间的宽度,再加个随机值
        this.headx[i]=this.rootx[i];
        this.heady[i]=canHeight-250+Math.random()*50;
        this.amp[i]=Math.random()*50+50;
    }
    
}
/*海葵绘制*/
anobj.prototype.draw=function(){
    
    this.alpha+=deltaTime*0.0008;
    var l=Math.sin(this.alpha);//[-1,1]
    ctx2.save();
    ctx2.globalAlpha=0.6;//透明度
    ctx2.lineWidth=20;
    ctx2.lineCap="round";
    ctx2.strokeStyle="#3b1541";
    for(var i=0;i<this.num;i++)
    {
        //beginPath,moveTo,LineTo,stroke,strokeStyle,lineWidth,lineCap,globalAlpha(透明度)
        ctx2.beginPath();
        ctx2.moveTo(this.rootx[i],canHeight);
        this.headx[i]=this.rootx[i]+l*this.amp[i];
        ctx2.quadraticCurveTo(this.rootx[i],canHeight-100,this.headx[i],this.heady[i]);
        ctx2.stroke();
        
    }
    ctx2.restore();
}
View Code

果实:定义一个果实类,属性主要包括果实颜色,存活状态(boolen值),果实大小,果实速度,以及果实才开始生长的位置。主要方法包含:初始化,绘制,出生,死亡,及画面上果实数量的监视。绘画时主要用到的

context.drawImage(img,x,y,width,height);//在画布上定位图像,并规定图像的宽度和高度。当然果实的位置会变化,所以x,y,width,height是随每一帧变化的,主函数中定义了帧变化的时间
var fruitObj=function(){//定义一个fruitObj类

    this.alive=[];//属性,boolean值
    this.orange=new Image();
    this.blue=new Image();
    this.x=[];
    this.y=[];
    this.l=[];
    this.aneNo=[];
    this.fruitType=[];//果实颜色状态
    this.spd=[];//每个果实的速度
    
    
}
fruitObj.prototype.num=30;//果实数量为30,prototype 属性使您有能力向对象添加属性和方法。

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.spd[i]=Math.random()*0.017+0.003;//[0.003,0.017)
        /*this.born(i);//初始化的时候让所有果实出生*/
        this.fruitType[i]=" ";
        this.aneNo[i]=0;
    }
    this.orange.src="img/fruit.png";
    this.blue.src="img/blue.png";
}

/*果实从长大到成熟*find an ane,grow,fly up*/
fruitObj.prototype.draw=function(){
    for(var i=0;i<this.num;i++){
        if(this.alive[i]){
            if(this.fruitType[i]=="blue"){
                var pic=this.blue;
            }else{
                var pic=this.orange;
            }
            if(this.l[i]<=14){
                var NO=this.aneNo[i];
                this.x[i]=ane.headx[NO];
                this.y[i]=ane.heady[NO];
                this.l[i]+=this.spd[i]*deltaTime;//deltaTime表示每一帧的时间间隔
        }
        else{
            this.y[i]-=this.spd[i]*2*deltaTime;
        }
        //在画布上定位图像,并规定图像的宽度和高度
        ctx2.drawImage(pic,this.x[i]-this.l[i]*0.5,this.y[i]-this.l[i]*0.5,this.l[i],this.l[i]);
        if(this.y[i]<10){
            this.alive[i]=false;
        }
      }
    }
}

fruitObj.prototype.born=function(i){
    /*floor() 方法执行的是向下取整计算,它返回的是小于或等于函数参数,并且与之最接近的整数。*/
    /*var aneID=Math.floor(Math.random()*ane.num);
    this.x[i]=ane.rootx[aneID];
    this.y[i]=ane.heady[aneID];*/
    this.aneNo[i]=Math.floor(Math.random()*ane.num);
    this.l[i]=0;
    this.alive[i]=true;
    var van=Math.random();
    if(van<0.3){
        this.fruitType[i]="blue";
    }
    else{
        this.fruitType[i]="orange";
    }
    
}

/*果实死亡*/
fruitObj.prototype.dead=function(i){
    
    fruit.alive[i]=false;
}


/*果实数量监视器*/
function fruitMoniter(){
    var num=0;
    for(var i=0;i<fruit.num;i++){
        if(fruit.alive[i]) num++;
    }
    if(num<18){
        senFruit();
        return;
    }
}

/*果实重新开始任务*/
function senFruit(){
    
    for(var i=0;i<fruit.num;i++){
        if(!fruit.alive[i]){
            fruit.born(i);
            return;
        }
    }
}
View Code
大鱼:定一个大鱼类,包含坐标的变化,角度的变化,鱼眼睛,尾巴,以及身体的绘制。注意鱼眼睛与尾巴是不停变化的,而且变化速度相对每一帧较快,所以分别需要设置眼睛与尾巴的帧,角度的变化主要是引入了一个角度,距离变化的公用js,这里就不细说了。
var momObj=function(){
    this.x;
    this.y;
    this.angle;
    //this.bigEye=new Image();
    //this.bigBody=new Image();
    //this.bigTail=new Image();
    
    this.momTailTimer=0;
    this.momTailCount=0;
    
    this.momEyeTimer=0;
    this.momEyeCount=0;
    this.momEyeInterval=1000;
    
    this.momBodyCount=0;
}

momObj.prototype.init=function(){
    this.x=canWidth*0.5;
    this.y=canHeight*0.5;
    this.angle=0;
    //this.bigEye.src="img/bigEye0.png";
    //this.bigBody.src="img/bigSwim0.png";
    //this.bigTail.src="img/bigTail0.png";
    
}

momObj.prototype.draw=function(){
    
    this.x=lerpDistance(mx,this.x,0.92);
    this.y=lerpDistance(my,this.y,0.92);
    /*计算坐标差*/
    //Math。atan2(x,y);
    var deltaY=my-this.y;
    var deltaX=mx-this.x;
    var beta=Math.atan2(deltaY,deltaX)+Math.PI;

    //angle要不断趋向于beta
    this.angle=lerpAngle(beta,this.angle,0.6);


    //mom tail
    this.momTailTimer+=deltaTime;
    if(this.momTailTimer>50){
        this.momTailCount=(this.momTailCount+1)%8
        this.momTailTimer%=50;
    }
    
    //mom eye
    this.momEyeTimer+=deltaTime;
    if(this.momEyeTimer>this.momEyeInterval)
    {
        this.momEyeCount=(this.momEyeCount+1)%2;
        this.momEyeTimer%=this.momEyeInterval;
        if (this.momEyeCount==0) {
            this.momEyeInterval=Math.random()*1500+2000;
        } 
        else{
            this.momEyeInterval=200;
        }
    }
    

    ctx1.save();
    ctx1.translate(this.x,this.y);//translate之后,画鱼,相对位置
    ctx1.rotate(this.angle);
    var momTailCount=this.momTailCount;
    ctx1.drawImage(momTail[momTailCount],-momTail[momTailCount].width*0.5+30,-momTail[momTailCount].height*0.5);
    var momBodyCount=this.momBodyCount;
    if (data._double==1) {
        ctx1.drawImage(momBodyOrg[momBodyCount],-momBodyOrg[momBodyCount].width*0.5,-momBodyOrg[momBodyCount].height*0.5);
    } else{
        ctx1.drawImage(momBodyBlue[momBodyCount],-momBodyBlue[momBodyCount].width*0.5,-momBodyBlue[momBodyCount].height*0.5);
    }
    
    var momEyeCount=this.momEyeCount;
    ctx1.drawImage(momEye[momEyeCount],-momEye[momEyeCount].width*0.5,-momEye[momEyeCount].height*0.5);
    ctx1.restore();
}
View Code

小鱼同上:(小鱼多了身体颜色的变化)

var babyObj=function(){
    this.x;
    this.y;
    this.angle;
    //this.babyEye=new Image();
    //this.babyBody=new Image();
    //this.babyTail=new Image();
    /*thi.babyTail=[];*/
    
    this.babyTailTimer=0;//绘制尾巴的时间计时器
    this.babyTailCount=0;//尾巴绘制到哪一帧
    
    this.babyEyeTimer=0;
    this.babyEyeCount=0;
    this.babyEyeIntervel=1000;//时间间隔
    
    this.babyBodyTimer=0;
    this.babyBodyCount=0;
}

babyObj.prototype.init=function(){
    
    this.x=canWidth*0.5-50;
    this.y=canHeight*0.5+50;
    this.angle=0;
    //this.babyEye.src="img/babyEye0.png";
    //this.babyBody.src="img/babyFade0.png";
    //this.babyTail.src="img/babyEye0.png";
    /*for(var i=0;i<8;i++)
    {
        this.babyTail[i]=new Image();
        this.babyTail[i].src="img/babyTail"+i+".png";
    }*/
}

babyObj.prototype.draw=function(){
    
    
    this.x=lerpDistance(mom.x,this.x,0.98);
    this.y=lerpDistance(mom.y,this.y,0.98);
    
    //Math。atan2(x,y);
    var deltaY=mom.y-this.y;
    var deltaX=mom.x-this.x;
    var beta=Math.atan2(deltaY,deltaX)+Math.PI;

    //angle要不断趋向于beta
    this.angle=lerpAngle(beta,this.angle,0.6);    
    
    
    //count timer
    this.babyTailTimer+=deltaTime;
    if(this.babyTailTimer>50){//每大于五十毫秒就绘制下一张图片
        this.babyTailCount=(this.babyTailCount+1)%8;
        this.babyTailTimer%=50;//复原
    }
    
    //eye
    this.babyEyeTimer+=deltaTime;
    if(this.babyEyeTimer>this.babyEyeIntervel){//每大于五十毫秒就绘制下一张图片
        this.babyEyeCount=(this.babyEyeCount+1)%2;
        this.babyEyeTimer%=this.babyEyeIntervel;//复原
        if (this.babyEyeCount==0) {
            this.babyEyeIntervel=Math.random()*1500+2000;//[2000ms,3500)
        } else{
            this.babyEyeIntervel=200;//200ms
        }
    }
    
    //body
    this.babyBodyTimer+=deltaTime;
    if(this.babyBodyTimer>300){
        this.babyBodyCount=this.babyBodyCount+1;
        this.babyBodyTimer%=300;
        if(this.babyBodyCount>19)
        {
            this.babyBodyCount=19;
            //game over
            data.gameover=true;
        }
    }
    
    
    
    
    ctx1.save();
    ctx1.translate(this.x,this.y);//0,0
    ctx1.rotate(this.angle);
    //先画的在后面,后画的在前面
    var babyTailCount=this.babyTailCount;
    ctx1.drawImage(babyTail[babyTailCount],-babyTail[babyTailCount].width*0.5+25,-babyTail[babyTailCount].height*0.5);
    /*ctx1.drawImage(this.babyTail[babyTailCount],-this.babyTail[babyTailCount].width*0.5+25,-this.babyTail[babyTailCount].height*0.5);*/
    
    var BabyBodyCount=this.babyBodyCount;
    ctx1.drawImage(babyBody[BabyBodyCount],-babyBody[BabyBodyCount].width*0.5,-babyBody[BabyBodyCount].height*0.5);
    var babyEyeCount=this.babyEyeCount;
    ctx1.drawImage(babyEye[babyEyeCount],-babyEye[babyEyeCount].width*0.5,-babyEye[babyEyeCount].height*0.5);
    
    ctx1.restore();
}
View Code

碰撞检测函数:(大鱼与果实,小鱼与大鱼):当大鱼吃掉果实(蓝色与黄色分数倍数变化),大鱼喂给小鱼,分数增加。

//判断大鱼与果实的碰撞:先判断果实的状态,再判断与大鱼之间的距离
function momFruitCollision()
{
    if (!data.gameover) 
    {
        for (var i=0;i<fruit.num;i++) 
        {
            if(fruit.alive[i])
            {
                var l=calLength2(fruit.x[i],fruit.y[i],mom.x,mom.y);
                if(l<900)
                {
                    //果实被吃掉eat
                    fruit.dead(i);
                    data.fruitNum++;
                    mom.momBodyCount++;
                    if(mom.momBodyCount>7)
                    {
                        mom.momBodyCount=7;
                    }
                    if(fruit.fruitType[i]=="blue")
                    {
                        data._double=2;
                    }//blue
                    wave.born(fruit.x[i],fruit.y[i]);
                }
            }
        
        }    
    }
    
}


function momBabyCollision(){
    if(data.fruitNum>0){
        var l=calLength2(mom.x,mom.y,baby.x,baby.y);
        if(l<900)
        {
            baby.babyBodyCount=0;
            //data=0
               // data.reset();
            mom.momBodyCount=0;
            data.addScore();
            halo.born(baby.x,baby.y);
        }
    
    }
    
    
    
    
}
View Code

分数的变化(以及gameover),大鱼吃到果实后出现圆圈,大鱼喂给小鱼出现圆圈,相对简单,这里就不细说,上代码吧:

var dataObj=function(){
    this.fruitNum=0;//果实数量
    this._double=1;//吃到蓝色果实乘以2
    this.score=0;
    this.gameover=false;
    this.alpha=0;
}
dataObj.prototype.reset=function(){
    this.fruitNum=0;
    this._double=1;
}

dataObj.prototype.draw=function(){
    
    var w=can1.width;
    var h=can1.height;
    
    ctx1.save();
    
    
    
    ctx1.font="20px Verdana";
    ctx1.textAlign="center";
    ctx1.fillStyle="white";
    ctx1.fillText("score "+this.score,w*0.5,h-500);
    ctx1.shadowBlur=10;//阴影,边缘模糊
    ctx1.shadowColor="white";//阴影颜色
    
    //ctx1.fillText("num "+this.fruitNum,w*0.5,h-50);
    //ctx1.fillText("double "+this._double,w*0.5,h-80);
    
    if (this.gameover)
    {
        this.alpha+=deltaTime*0.0005;
        if (this.alpha>1) {
            this.alpha=1;
        }
        ctx1.fillStyle="rgba(255,255,255,"+this.alpha+ ")";//0是透明,1是不透明
        ctx1.fillText("GameOver",w*0.5,h*0.5)//绘制文本
    }
    ctx1.restore(); 
}

dataObj.prototype.addScore=function(){
    this.score+=this.fruitNum*100*this._double;
    this.fruitNum=0;
    this._double=1;
}
View Code
var waveObj=function(){
    this.x=[];//x,y值
    this.y=[];
    this.alive=[];
    this.r=[];//圆圈的半径
}

waveObj.prototype.num=10;
waveObj.prototype.init=function(){
    for (var i=0;i<this.num;i++) {
        this.alive[i]=false;
        this.r[i]=0;
    }
}



waveObj.prototype.draw=function(){
    ctx1.save();
    ctx1.lineWidth=2;
    ctx1.shadowBlur=8;
    ctx1.shadowColor="white";
    for (var i=0;i<this.num;i++) {
        if (this.alive[i])
        {
            //api:arc(),linewidth(),stroke()
            //draw
            this.r[i]+=deltaTime*0.03;//圆圈慢慢增大
            if(this.r[i]>50)
            {
                this.alive[i]=false;
                break;//超过就不绘制
            }
            var alpha=1-this.r[i]/50;//反比【0,1】
            ctx1.beginPath();//开始绘制
            ctx1.arc(this.x[i],this.y[i],this.r[i],0,Math.PI*2);//绘制圆
            ctx1.closePath();
            ctx1.strokeStyle="rgba(255,255,255,"+alpha+")";
            ctx1.stroke();
            
        }
    }
    ctx1.restore();
}

waveObj.prototype.born=function(x,y){
    for (var i=0;i<this.num;i++)
    {
        if (!this.alive[i])
        {
            //born
            this.alive[i]=true;
            this.r[i]=10;
            this.x[i]=x;
            this.y[i]=y;
            return;
        }
    }
}
View Code
var haloObj=function(){
    this.x=[];
    this.y=[];
    this.alive=[];
    this.r=[];
}
haloObj.prototype.num=1;
haloObj.prototype.init=function(){
    for (var i=0;i<this.num;i++) {
        this.x[i]=0;
        this.y[i]=0;
        this.alive[i]=false;
        this.r[i]=0;
    }
}

haloObj.prototype.draw=function(){
    ctx1.save();
    ctx1.lineWidth=2;
    ctx1.shadowBlur=8;
    ctx1.shadowColor="white";
    for (var i=0;i<this.num;i++) {
        if (this.alive[i]) {
            this.r[i]+=deltaTime*0.05;
            if (this.r[i]>80) {
                this.alive[i]=false;
                break;
            }
            var alpha=1-this.r[i]/80;
            ctx1.beginPath();
            ctx1.arc(this.x[i],this.y[i],this.r[i], 0,Math.PI*2);
            ctx1.closePath();
            ctx1.strokeStyle="rgba(203,91,0,"+alpha+")";
            ctx1.stroke();    
        }    
    }
    ctx1.restore();
}



haloObj.prototype.born=function(x,y){
    for (var i=0;i<this.num;i++) {
        if (!this.alive[i]) {
            this.x[i]=x;
            this.y[i]=y;
            this.alive[i]=true;
            this.r[i]=10;
        }
    }
}
View Code

最重要的主函数来了:(其它函数大概分析之后,在主函数里new对象时就easy了):

document.body.onload=game;
var can1;
var can2;

var ctx1;//场景
var ctx2;

var canWidth;
var canHeight;

var lastTime;//上一帧被执行的时间
var deltaTime;//两帧间隔的时间差

var bgPic=new Image();//背景

var ane;
var fruit;

var mom;
var baby;

var mx;
var my;

var babyTail=[];//将小鱼尾巴摆动的图片组成一组数组,每隔一段时间绘制
var babyEye=[];//小鱼眨眼睛
var babyBody=[];//身体变白

var momTail=[];
var momEye=[];
var momBodyOrg=[];
var momBodyBlue=[];

var data;

var wave;
var halo;


function game(){
    init();
    lastTime=Date.now();
    deltaTime=0;
    gameloop();
    
}

function init(){
    //获得canvas context 画笔与画布
    can1=document.getElementById("canvas1");
    ctx1=can1.getContext("2d");//fishes,dust,UI,circle
    can2=document.getElementById("canvas2");//background,ane,fruits
    ctx2=can2.getContext("2d");
    bgPic.src="img/background.jpg";
    canWidth=can2.width;
    canHeight=can2.height;
    
    can1.addEventListener('mousemove',onMouseMove,false);
    
    
    ane=new anobj();
    ane.inite();
    
    fruit=new fruitObj();
    fruit.init();
    
    mom=new momObj();
    mom.init();
    
    baby=new babyObj();
    baby.init();
    
    mx=canWidth*0.5;
    my=canHeight*0.5;
    
    for(var i=0;i<8;i++)
    {
        babyTail[i]=new Image();
        babyTail[i].src="img/babyTail"+i+".png";
    }
    
    for(var i=0;i<2;i++)
    {
        babyEye[i]=new Image();
        babyEye[i].src="img/babyEye"+i+".png";
    }
    
    for(var i=0;i<20;i++)
    {
        babyBody[i]=new Image();
        babyBody[i].src="img/babyFade"+i+".png";
    }
    
    
    for (var i=0;i<8;i++) {
        momTail[i]=new Image();
        momTail[i].src="img/bigTail"+i+".png";
    }
    
    for (var i=0;i<2;i++) {
        momEye[i]=new Image();
        momEye[i].src="img/bigEye"+i+".png";
    }
    
    data=new dataObj();
    
    for (var i=0;i<8;i++) {
        momBodyOrg[i]=new Image();
        momBodyBlue[i]=new Image();
        momBodyOrg[i].src="img/bigSwim"+i+".png";
        momBodyBlue[i].src="img/bigSwimBlue"+i+".png";
    }
    
    
    wave =new waveObj();
    wave.init();
    
    halo=new haloObj();
    halo.init();
    
}
function gameloop(){
    requestAnimationFrame(gameloop);//相同效果=setInterval,setTimeout。fps=frame per second
    var now=Date.now();
    deltaTime=now-lastTime;
    lastTime=now;
    if(deltaTime>40) deltaTime=40;
    
    drawBackground();
    
    ane.draw();//每一帧都去绘制海葵
    fruitMoniter();
    fruit.draw();
    
    ctx1.clearRect(0,0,canWidth,canHeight);//因为ctx1覆盖在ctx2上,所以每一帧都要去清楚下重新绘制,在干净的画布上绘制
    mom.draw();
    baby.draw();
  
    momFruitCollision();
    momBabyCollision();
    
    data.draw();
    wave.draw();
    halo.draw();
}

function onMouseMove(e)
{
    if(!data.gameover)
    {
        if(e.offsetX||e.offsetY)
        {
            mx=e.offsetX==undefined?e.layerX:e.offsetX;
            my=e.offsetY==undefined?e.layerY:e.offsetY;
        }
    }
        
}
View Code
posted on 2017-06-01 21:17  super喵二  阅读(369)  评论(0)    收藏  举报
@2017BYsuper喵二