大家好!我是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(); }
果实:定义一个果实类,属性主要包括果实颜色,存活状态(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; } } }
大鱼:定一个大鱼类,包含坐标的变化,角度的变化,鱼眼睛,尾巴,以及身体的绘制。注意鱼眼睛与尾巴是不停变化的,而且变化速度相对每一帧较快,所以分别需要设置眼睛与尾巴的帧,角度的变化主要是引入了一个角度,距离变化的公用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(); }
小鱼同上:(小鱼多了身体颜色的变化)
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(); }
碰撞检测函数:(大鱼与果实,小鱼与大鱼):当大鱼吃掉果实(蓝色与黄色分数倍数变化),大鱼喂给小鱼,分数增加。
//判断大鱼与果实的碰撞:先判断果实的状态,再判断与大鱼之间的距离 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); } } }
分数的变化(以及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; }
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; } } }
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; } } }
最重要的主函数来了:(其它函数大概分析之后,在主函数里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; } } }
成长到撑起一片天
浙公网安备 33010602011771号