HTML5小游戏之爱心鱼
项目的github地址:https://github.com/chenmonkey/game
在线演示网址:https://chenmonkey.github.io/game/
准备工作:
1.搭建html环境
新建tinyHeart.html文件<div class="all_bg">
<div id="allcanvas">
<canvas id="canvas1" width="800" height="600"></canvas>
<canvas id="canvas2" width="800" height="600"></canvas>
</div>
</div>
<div id="allcanvas">
<canvas id="canvas1" width="800" height="600"></canvas>
<canvas id="canvas2" width="800" height="600"></canvas>
</div>
</div>
其中,canvas1和canvas2叠加在一起,使用absolute绝对定位,用z-index设置堆叠顺序。
2.绘制背景
(1)新建main.js文件(主要的js文件)
想要boay内容加载完之后执行一个函数:windows.onload
使用canvas API,获得场景:ctx1=can1.getContext('2d')
让游戏动起来,即刷新:requestAnimationFrame(gameloop)
***requestAnimationFrame在不同浏览器上需要进行不同的配置(commonFunction.js)
(2)绘制背景:background.js(cxt2.drawImage(src,x1,y1,x2,y2))
第一阶段
1.静止的海葵(ane.js)
在ane.js中:
定义一个类aneObj
初始化:aneObj.prototype.init=function(){}:确定每个海葵的位置
绘制海葵:aneObj.prototype.draw=function(){}
2.果实绘制(黄色果实和蓝色果实,果实从有到无,漂浮速度不同,飘到屏幕外消失)(fruit.js)
a. 状态
活跃:在海葵上生长,长大后向上漂浮
不活跃:排队等候
b. 在fruit.js中
定义一个类fruitObj
初始化:fruitObj.prototype.init=function(){}:确定果实的活跃状态
使用到的API:drawImage()
定义规则:1)长大=》成熟。
2)当一个果实漂浮出去之后,告诉闲置的果实,要有一个需要出生了。
3)保持屏幕上有15个果实
(1)静态果实
绘制果实:fruitObj.prototype.draw=function(){}:在海葵上绘制果实
果实生长:fruitObj.prototype.born=function(i){}:找到一个位置让果实出生
果实更新状态:fruitObj.prototype.update=function(){}
(2)果实上浮(fruit.js)
果实增长:this.l[i]+=this.spd[i]*deltaTime;
果实上浮:this.y[i]-=this.spd[i]*7*deltaTime;
(3)果实数量控制(绘制蓝色果实,屏幕上保持有15个果实)
function fruitMonitor(){} //监视果实的状态
function sendFruit(){} //判断哪些果实处于闲置状态,哪些果实活着
3. 大鱼绘制(画大鱼,身子,尾巴,眼睛)
(1)画大鱼
1)使用到的API:
translate() //canvas的API,转换画布的用户坐标
rotate(); //canvas的API,绘图在画布中都显示为旋转的
Math.atan2(y,x) //javascript的API,反正切,返回值为数字(PI to -PI)
2)使用到的方法
大鱼初始化:momObj.prototype.init = function(){}
绘制大鱼: momObj.prototype.draw = function(){}
***中draw()方法中:context.translate(x,y)函数可以使画布的原点坐标变为(x,y),即画笔从这个点开始画。因为我们画完一部分内容之后希望重新定义画笔的属性,所以用context.save()和context.restore()包裹例如context.translate(),context.fillStyle等属性设定
(2)大鱼随鼠标移动、旋转
知识点:极坐标
1)获取鼠标移动的值
在main.js中添加方法:鼠标移动:function onMouseMove(e){}
2)在mom.js中
大鱼随鼠标移动:this.x=lerpDistance(mx, this.x, 0.98); //0.99>>0.98大鱼的运动速度变缓
this.y=lerpDistance(my, this.y, 0.99);
this.y=lerpDistance(my, this.y, 0.99);
大鱼和鼠标之间的角度差:var deltaY=my-this.y;//鼠标跟大鱼的坐标差
var deltaX=mx-this.x;
var beta=Math.atan2(deltaY,deltaX);
var deltaX=mx-this.x;
var beta=Math.atan2(deltaY,deltaX);
大鱼的角度不断趋向鼠标的角度:this.angle=lerpAngle(beta,this.angle,0.6); // 0.9>>0.6,大鱼的反应变灵敏
4.大鱼吃果实
(1)大鱼和果实的碰撞检测
如果大鱼和果实的距离足够近,则判断大鱼吃掉该果实;如果距离足够远,则没有吃到该果实
1)在fruit.js中,添加dead()方法
2)在momFruitCollision.js中,判断大鱼跟每一个果实的距离:调用封装函数var l=calLength2(fruit.x[i], fruit.y[i], mom.x, mom.y),若l<900,则fruit.dead(i),果实死亡。
(2)优化
当我们在浏览器中打开html页面,一段时间不去管它之后,我们会发现下面情况。
原因:这是因为我们在绘制果实的时候,它的尺寸是和deltaTime成正比的。
if(this.l[i]<=14){this.l[i]+=this.spd[i]*deltaTime;//果实增长(随时间逐渐增长)
}
5.小鱼绘制
和大鱼绘制相同
第二阶段
1.小鱼身体活动完善
(1)尾巴摆动
在main.js中:定义babyTail数组,存放小鱼尾巴资源,var babyTail=[];
在draw()方法里:小鱼尾巴摆动
for(var i=0;i<7;i++)
{
babyTail[i]=new Image();
babyTail[i].src="img/babyTail"+i+".png";
}
在baby.js中,定义计时器以及记录当前图片序号的变量:
{
babyTail[i]=new Image();
babyTail[i].src="img/babyTail"+i+".png";
}
在baby.js中,定义计时器以及记录当前图片序号的变量:
this.babyTailTimer=0;//计时器
this.babyTailCount=0;//记录当前图片序号的变量
this.babyTailCount=0;//记录当前图片序号的变量
在draw方法中:绘制小鱼尾巴:ctx1.drawImage(babyTail[babyTailCount],-babyTail[babyTailCount].width*0.5+25,-babyTail[babyTailCount].height*0.5);
(2)眨眼睛
同小鱼摇尾巴相似,只不过眨眼睛是随机的
if(this.babyEyeCount==0)//如果小鱼睁着眼睛
{
this.babyEyeInterval=Math.random()*1500+2000;//睁眼睛时间为1500到3500毫秒之间
}else
{
this.babyEyeInterval=200;//眯眼睛时间为200毫秒
}
{
this.babyEyeInterval=Math.random()*1500+2000;//睁眼睛时间为1500到3500毫秒之间
}else
{
this.babyEyeInterval=200;//眯眼睛时间为200毫秒
}
(3)身体变白
跟小鱼摇尾巴相似,只不过当小鱼身体完全变白后,游戏结束,不恢复
if(this.babyBodyTimer>300)
{
this.babyBodyCount=this.babyBodyCount+1;
this.babyBodyTimer%=300;
if(this.babyBodyCount>19)
{
this.babyBodyCount=19;
}
}
{
this.babyBodyCount=this.babyBodyCount+1;
this.babyBodyTimer%=300;
if(this.babyBodyCount>19)
{
this.babyBodyCount=19;
}
}
2.大鱼动画完善
(1)大鱼喂小鱼
判断大鱼和小鱼的距离
function momBabyCollision()
{
var l=calLength2(mom.x,mom.y,baby.x,baby.y);
if(l<900)
{
//小鱼恢复满血状态
baby.babyBodyCount=0;
}
}
{
var l=calLength2(mom.x,mom.y,baby.x,baby.y);
if(l<900)
{
//小鱼恢复满血状态
baby.babyBodyCount=0;
}
}
(2)大鱼摇尾巴
同小鱼摇尾巴
(3)大鱼眨眼睛
同小鱼眨眼睛
(4)大鱼身体升级准备
a. 在main.js中
在init()方法中,实例化dataObj变量:data=new dataObj();
在gameloop()方法中,执行draw()方法:data.draw();
b. 在data.js中
起始状态(大鱼吃果实之前):this.fruitNum=0;//大鱼吃果实数量
this.double=1;//大鱼吃果实游戏分值(若吃一个蓝色果实,则分值增倍)
this.double=1;//大鱼吃果实游戏分值(若吃一个蓝色果实,则分值增倍)
大鱼喂完小鱼之后身体状态恢复原态:dataObj.prototype.reset=function(){}
在画布1上画出:大鱼吃果实数量(未喂小鱼之前),大鱼游戏分值:dataObj.prototype.draw=function(){}
c. 在collision.js中
在momFruitCollision()方法中,判断大鱼是否吃到蓝色果实,若是,分值为2
if(fruit.fruitType[i]=="blue")
{
data.double=2;
}
{
data.double=2;
}
在momBabyCollision()方法中,大鱼若喂小鱼,则身体恢复原态,即执行data.reset()方法
(5)大鱼身体升级
大鱼身体有两种颜色,橘色和蓝色,当吃到橘色果实时,大鱼身体变橘色;当吃到蓝色果实时,身体变白色。
在main.js中://大鱼身体变化
for(var i=0;i<8;i++)
{
bigBodyOrg[i]=new Image();
bigBodyBlu[i]=new Image();
bigBodyOrg[i].src="img/bigSwim"+i+".png";
bigBodyBlu[i].src="img/bigSwimBlue"+i+".png";
}
for(var i=0;i<8;i++)
{
bigBodyOrg[i]=new Image();
bigBodyBlu[i]=new Image();
bigBodyOrg[i].src="img/bigSwim"+i+".png";
bigBodyBlu[i].src="img/bigSwimBlue"+i+".png";
}
在mom.js中:1)this.bigBodyCount=0;//(大鱼身体)记录当前图片序号的变量
2)在draw方法中,通过判断double的值来确定大鱼身体的颜色,当double=1时,身体为橘色;否则,身体为蓝色 3)在collision中,判断大鱼与果实之间的距离,当大鱼吃到果实时,
a. mom.bigBodyCount++;//大鱼身体变化计数;
b. if(mom.bigBodyCount>7){mom.bigBodyCount=7; }
(6)游戏分值计算(1)
在data.js中:(1)添加addScore方法,计算游戏的分值(this.score+=this.fruitNum*100*this.double;)
(2)删除reset方法,因为与addScore方法中相重复
在collision.js中,当大鱼碰到小鱼之后,执行addScore方法 (data.addScore();)
游戏分值计算(2)
游戏设想:(1)只有大鱼吃到果实,大鱼碰到小鱼,小鱼身体才会恢复;
(2)只有游戏未结束,大鱼才能继续吃果实,喂小鱼;否则,大鱼不能继续吃果实喂小鱼,而且鼠标不能移动,并会出现GAMEOVER字样。
代码实现:(1)在data.js中,添加gameOver属性(this.gameOver=false)
(2)在baby.js中,当小鱼身体完全变白,data.gameOver=true(即游戏结束)
(3)在collision.js中的momBabyCollision方法中,判断大鱼吃的果实数量是否大于0切游戏是否未结束,是则继续游戏;否则结束游戏
if(data.fruitNum>0 && !data.gameOver){}
3、动画特效
(1)大鱼吃果实特效
大鱼每碰到一个果实就要产生一个白色的圈
物体池(pool):存储很多相同的物体
检测是否有闲着的物体
半径逐渐增大,颜色逐渐减弱,半径和颜色呈反比关系
绘图API
1)在wave.js中,白色圆圈初始化:waveObj.prototype.init=function(){}
绘制白色圆圈:waveObj.prototype.draw=function(){}
白色圆圈出生:waveObj.prototype.born=function(x,y){}
2)在collision.js的momFruitCollision()方法中,当大鱼碰到果实,执行wave.born(fruit.x[i],fruit.y[i]);
3)在main.js中,在init()方法中,wave=new waveObj();wave.init();
在gameloop()方法中,wave.draw();
(2)大鱼喂小鱼特效
跟大鱼吃果实特效相似
(3)海葵摆动特效
a. 绘制二次贝塞尔曲线(起始点和控制点不动,结束点摆动)
b. 正弦函数(控制海葵摆动的范围)
c. this.rootx=[];//起始点的x坐标,y坐标不需要设,即画布底部
this.headx=[];//结束点的x坐标
this.heady=[];//结束点的y坐标
this.amp=[];//海葵摆动的幅度
this.alpha=0;//正弦函数的角度
this.headx=[];//结束点的x坐标
this.heady=[];//结束点的y坐标
this.amp=[];//海葵摆动的幅度
this.alpha=0;//正弦函数的角度
(4)果实长在海葵上面(即使海葵飘动)
在ane.js中,海葵的头部坐标:this.headx[i]=this.rootx[i]+l*this.amp[i];
this.heady[i]=canHeight-250+Math.random()*50;
在fruit.js中,果实的坐标:var aneID=Math.floor(Math.random()*ane.num);//海葵的坐标(在所有海葵中随机找一个)
this.aneNO[i]=aneID; //找到一个位置让果实出生
var NO=this.aneNO[i];
this.x[i]=ane.headx[NO];
this.y[i]=ane.heady[NO];
this.aneNO[i]=aneID; //找到一个位置让果实出生
var NO=this.aneNO[i];
this.x[i]=ane.headx[NO];
this.y[i]=ane.heady[NO];
(5)漂浮物制作(随水流轻轻左右摇摆)
三角函数sin()
漂浮物的摆动跟海葵的摆动同步
在dust.js中,dustObj.prototype.init=function(){}
dustObj.prototype.draw=function(){}
在main.js中,init()方法中://漂浮物
for(var i=0;i<7;i++)
{
dustPic[i]=new Image();
dustPic[i].src="img/dust"+i+".png";
}
dust=new dustObj();
dust.init();
for(var i=0;i<7;i++)
{
dustPic[i]=new Image();
dustPic[i].src="img/dust"+i+".png";
}
dust=new dustObj();
dust.init();
draw()方法中:dust.draw();