js原生捕鱼达人(三)--完结
先给分享下我写完的效果,github有点卡,我没有压缩代码,不过效果可以看到
https://jasonwang911.github.io/
转载请注明‘转载于Jason齐齐的博客http://www.cnblogs.com/jasonwang2y60/’
继续昨天的进行
11》添加金币 相同的创建了coin.js的文件
//添加金币的构造含函数 function Coin(type){ this.type=type; this.x=0; this.y=0; this.cur=0; this.move(); } //Coin的方法有draw move ,暂时想到这两个 Coin.prototype.draw=function(gd){ gd.save(); gd.translate(this.x,this.y); if(this.type<3){ gd.drawImage(JSON['coinAni1'], 0,this.cur*60,60,60, -30,-30,60,60 ); }else{ gd.drawImage(JSON['coinAni2'], 0,this.cur*60,60,60, -30,-30,60,60 ); } gd.restore(); }; //coin是从碰撞的点移动到画布左下角的金币存储中 就是想办法让金币的x移动到0,y的位置移动到oC.height Coin.prototype.move=function(){ var _this=this; setInterval(function(){ //这里速度的减小使用了一种很巧妙的方法,让this.x(碰撞的x)分10次减小到0,同样的y的值也相同 _this.x+=-_this.x/10; _this.y+=(630-_this.y)/10; //在运动的过程中还要旋转金币,也就是更换金币的图片,设置一个cur,使cur++,同时关联draw方法中金币图片的高度来更换图片 _this.cur++; if(_this.cur==10){ _this.cur=0; } },30); }; document.addEventListener('DOMContentLoaded',function(){ var oC=document.getElementById('c1'); var gd=oC.getContext('2d'); var rule=0.05; //开始画炮台 画炮台需要先加载资源,然后再画,这里没有使用面向对象的概念 loadImage(resource,function(){ //设置炮的初始位置,初始位置在资源文件中已经写明 var c=new Cannon(4); c.x=431; c.y=570; //存放炮弹的数组 var arrBullet=[]; //存放鱼的数组 var arrFish=[]; //存放金币的数组 var arrCoin=[]; setInterval(function(){ gd.clearRect(0,0,oC.width,oC.height); //画鱼 鱼从左右两边同时随机出现 实现这个的原理是Math.random()是0-1的数,定时器的触发时间是30ms一秒钟30多条鱼的诞生有些多,所以在这里我们需要修改规则rule来降低鱼出现的概率,当rule=0.05(概率为原来的20%),再加入一个参数decoration,然后用Math.random()-0.5得出的这个值的范围时0.5到-0.5,这样正负的概率分别为50%,这样我们就能继续进行鱼诞生的方向了; var decoration=Math.random()-0.5; if(Math.random()<rule){ if(decoration<0){ var f1=new Fish(rnd(1,6)); f1.x=oC.width+50; f1.y=rnd(0,oC.height); // f1.rotate=rnd(91,269); }else{ var f1=new Fish(rnd(1,6)); f1.x=-50; f1.y=rnd(0,oC.height); // f1.rotate=rnd(-89,89); } arrFish.push(f1); } for(var i=0;i<arrFish.length;i++){ arrFish[i].draw(gd); } //和炮弹一样,对鱼进行性能优化,再鱼游出屏幕一定范围之后,便将鱼从鱼的数组中清除 for(var i=0;i<arrFish.length;i++){ if(arrFish[i].x<-50 || arrFish[i].x>(oC.width+50) || arrFish[i].y<0 || arrFish[i].y>(oC.height+50)){ arrFish.splice(i,1); i--; } } //炮是在炮台上的,可以在画炮台的时候一起画出来,画之前为了避免重绘,需要先清除画布 gd.drawImage(JSON['bottom'], 0,0,765,70, 0,532,765,70 ); //调用炮的方法draw来画炮 和鱼的转动相同,当点击画布的时候,炮需要跟随鼠标的指向来转动,这里在转动的时候我们改改变炮的转动角度,然后重新不停的删除,再画炮 这个效果思路和画鱼相同,需要配合定时器来实现 c.draw(gd); //将当次点击所产生的炮弹画出来 for(var i=0;i<arrBullet.length;i++){ arrBullet[i].draw(gd); } //这里由于炮弹不停的被创造,数组中也变得越来越大,当炮弹到达一定位置(移出屏幕)的时候,应该清除前面没用的炮弹,避免性能的浪费。注意,我们需要检测每个鱼和炮弹的位置(外层循环鱼,内层循环子弹),有我就是整个数组所有参数身上的x y for(var i=0;i<arrBullet.length;i++){ if(arrBullet[i].x<0 || arrBullet[i].x>oC.width || arrBullet[i].y>oC.height || arrBullet[i].y<0){ arrBullet.splice(i,1); i--; } } //进行碰撞检测,这里只做了简单的碰撞检测,我们把每个模型(鱼和子弹)都考虑成了原型,当两个物体的距离小于两个物体的半径之和的时候表明两个物体碰撞,注意,需要循环检测存在的所有的鱼,我们可以提前做一个函数来判断这个距离,当得到碰撞距离的时候函数返回true,当得到没有碰撞的距离的时候,函数返回的是false,这个函数是每条鱼身上的一个方法,每条鱼在游动的时候都在不停的计算这个值,并不停的返回真或者假来供我们判断是否和子弹碰撞 for(var i=0;i<arrFish.length;i++){ for(var j=0;j<arrBullet.length;j++){ if(arrFish[i].isIn(arrBullet[j].x, arrBullet[j].y)){ //金币生成的起始坐标就是碰撞时候的鱼的坐标,注意代码位置,不能放到删除鱼之后 var x=arrFish[i].x; var y=arrFish[i].y; //鱼的类型关系到金币的类型,在这里存鱼的类型,下面创造金币的时候用 var type=arrFish[i].type; //在鱼碰到了之后,我们需要做的是让相互碰撞的鱼和子弹都消失,也就是从鱼和子弹的数组中删除 arrFish.splice(i,1); i--; arrBullet.splice(j,1); j--; //碰撞之后生成金币 var coin=new Coin(type); coin.x=x; coin.y=y; arrCoin.push(coin); } } } //画金币 for(var i=0;i<arrCoin.length;i++){ arrCoin[i].draw(gd); } console.log(arrCoin.length); },30); //当点击画布的时候炮的角度对着鼠标点击的位置,并进行重绘 oC.onclick=function(ev){ //这里需要梳理鼠标点击的位置和炮旋转角度之间的关系(附图说明--炮的旋转角度.png) var x=ev.clientX-oC.offsetLeft- c.x; var y= c.y-(ev.clientY-oC.offsetTop); //计算角度,注意角度的公式tan是临边比对边,和数学公式的有所不同 Math.atan2(y,x);并且这里是弧度转角度,需要在com.js中添加a2d的函数 var d=90-a2d(Math.atan2(y,x)); c.rotate=d; //当点击的时候生成炮弹,所以在点击事件中添加炮弹 var bullet=new Bullet(c.type); //炮弹的位置和旋转角度和炮的位置和旋转角度相同, bullet.x= c.x; bullet.y= c.y; bullet.rotate = c.rotate; //注意炮弹不能画在这里,如果画在这里会被画炮和炮台时所清空,当然潘丹并不是只画一个,可以用一个数组来存储所画出来的炮弹,然后在炮旋转重绘的时候同时添加炮弹,为了让点击事件和定时器都能用到这个数组,这个数组应该写到事件和定时器的父级的变量空间中 /*bullet.draw(gd);*/ //讲当次点击画布所创建的炮弹存入arrBullet中 arrBullet.push(bullet); }; }); },false); </script>
13》添加死鱼 创建了diefish.js的文件
/** * Created by Jason on 2016/11/3. */ //构造死鱼的构造函数 死鱼的属性有type x y cur翻转 rotate move function DieFish(type){ this.type=type; this.x=0; this.y=0; this.rotate=0; this.cur=0; this.move(); } //死鱼的方法有draw move 注意,鱼死的时候的方向和隐形需要修正,如果不修正则都会成为默认方向 DieFish.prototype.draw=function(gd){ var w=FISH_SIZE[this.type].w; var h=FISH_SIZE[this.type].h; gd.save(); gd.translate(this.x,this.y); gd.rotate(d2a(this.rotate)); //修复阴影 if(this.rotate>90&&this.rotate<270){ gd.scale(1,-1); } gd.drawImage(JSON['fish'+this.type], 0,(this.cur+4)*h,w,h, -w/2,-h/2,w,h ); gd.restore(); }; //死鱼的move方法 是更换图片翻肚皮,思路和鱼摆尾巴相似 DieFish.prototype.move=function(){ var _this=this; setInterval(function(){ _this.cur++; //当死鱼的图片换一遍以后,停止更换图片,以便后面死鱼消失,注意定时器的时间,控制图片的更换 if(_this.cur==4){ _this.cur=0; } },250); };
index.html文件
<script src="js/resource.js"></script> <script src="js/com.js"></script> <script src="js/fish.js"></script> <script src="js/cannon.js"></script> <script src="js/bullet.js"></script> <script src="js/coin.js"></script> <script src="js/diefish.js"></script> <script> document.addEventListener('DOMContentLoaded',function(){ var oC=document.getElementById('c1'); var gd=oC.getContext('2d'); var rule=0.05; //开始画炮台 画炮台需要先加载资源,然后再画,这里没有使用面向对象的概念 loadImage(resource,function(){ //设置炮的初始位置,初始位置在资源文件中已经写明 var c=new Cannon(4); c.x=431; c.y=570; //存放炮弹的数组 var arrBullet=[]; //存放鱼的数组 var arrFish=[]; //存放金币的数组 var arrCoin=[]; //存放死鱼的数组 var arrDieFish=[]; setInterval(function(){ gd.clearRect(0,0,oC.width,oC.height); //画鱼 鱼从左右两边同时随机出现 实现这个的原理是Math.random()是0-1的数,定时器的触发时间是30ms一秒钟30多条鱼的诞生有些多,所以在这里我们需要修改规则rule来降低鱼出现的概率,当rule=0.05(概率为原来的20%),再加入一个参数decoration,然后用Math.random()-0.5得出的这个值的范围时0.5到-0.5,这样正负的概率分别为50%,这样我们就能继续进行鱼诞生的方向了; var decoration=Math.random()-0.5; if(Math.random()<rule){ if(decoration<0){ var f1=new Fish(rnd(1,6)); f1.x=oC.width+50; f1.y=rnd(0,oC.height); // f1.rotate=rnd(91,269); }else{ var f1=new Fish(rnd(1,6)); f1.x=-50; f1.y=rnd(0,oC.height); // f1.rotate=rnd(-89,89); } arrFish.push(f1); } for(var i=0;i<arrFish.length;i++){ arrFish[i].draw(gd); } //和炮弹一样,对鱼进行性能优化,再鱼游出屏幕一定范围之后,便将鱼从鱼的数组中清除 for(var i=0;i<arrFish.length;i++){ if(arrFish[i].x<-50 || arrFish[i].x>(oC.width+50) || arrFish[i].y<0 || arrFish[i].y>(oC.height+50)){ arrFish.splice(i,1); i--; } } //炮是在炮台上的,可以在画炮台的时候一起画出来,画之前为了避免重绘,需要先清除画布 gd.drawImage(JSON['bottom'], 0,0,765,70, 0,532,765,70 ); //调用炮的方法draw来画炮 和鱼的转动相同,当点击画布的时候,炮需要跟随鼠标的指向来转动,这里在转动的时候我们改改变炮的转动角度,然后重新不停的删除,再画炮 这个效果思路和画鱼相同,需要配合定时器来实现 c.draw(gd); //将当次点击所产生的炮弹画出来 for(var i=0;i<arrBullet.length;i++){ arrBullet[i].draw(gd); } //这里由于炮弹不停的被创造,数组中也变得越来越大,当炮弹到达一定位置(移出屏幕)的时候,应该清除前面没用的炮弹,避免性能的浪费。注意,我们需要检测每个鱼和炮弹的位置(外层循环鱼,内层循环子弹),有我就是整个数组所有参数身上的x y for(var i=0;i<arrBullet.length;i++){ if(arrBullet[i].x<0 || arrBullet[i].x>oC.width || arrBullet[i].y>oC.height || arrBullet[i].y<0){ arrBullet.splice(i,1); i--; } } //进行碰撞检测,这里只做了简单的碰撞检测,我们把每个模型(鱼和子弹)都考虑成了原型,当两个物体的距离小于两个物体的半径之和的时候表明两个物体碰撞,注意,需要循环检测存在的所有的鱼,我们可以提前做一个函数来判断这个距离,当得到碰撞距离的时候函数返回true,当得到没有碰撞的距离的时候,函数返回的是false,这个函数是每条鱼身上的一个方法,每条鱼在游动的时候都在不停的计算这个值,并不停的返回真或者假来供我们判断是否和子弹碰撞 for(var i=0;i<arrFish.length;i++){ for(var j=0;j<arrBullet.length;j++){ if(arrFish[i].isIn(arrBullet[j].x, arrBullet[j].y)){ //金币生成的起始坐标就是碰撞时候的鱼的坐标,注意代码位置,不能放到删除鱼之后 var x=arrFish[i].x; var y=arrFish[i].y; //鱼的类型关系到金币的类型,在这里存鱼的类型,下面创造金币的时候用 var type=arrFish[i].type; //在鱼碰到了之后,我们需要做的是让相互碰撞的鱼和子弹都消失,也就是从鱼和子弹的数组中删除 //鱼死的时候的rotate也要存一下,供后面死鱼使用,来确保活鱼和死鱼的方向相同 var rotate=arrFish[i].rotate; arrFish.splice(i,1); i--; arrBullet.splice(j,1); j--; //碰撞之后生成金币 var coin=new Coin(type); coin.x=x; coin.y=y; arrCoin.push(coin); //碰撞之后生成死鱼 var dieFish=new DieFish(type); dieFish.x=x; dieFish.y=y; dieFish.rotate=rotate; arrDieFish.push(dieFish); //清除死鱼,注意死鱼不是一直在清除,使用的一次性定时器,每隔一段时间便清除最前面的一条死鱼 setTimeout(function(){ arrDieFish.shift(); i--; },1000) } } } //画金币 for(var i=0;i<arrCoin.length;i++){ arrCoin[i].draw(gd); } //画死鱼 for(var i=0;i<arrDieFish.length;i++){ arrDieFish[i].draw(gd); } console.log(arrDieFish.length); },30); //当点击画布的时候炮的角度对着鼠标点击的位置,并进行重绘 oC.onclick=function(ev){ //这里需要梳理鼠标点击的位置和炮旋转角度之间的关系(附图说明--炮的旋转角度.png) var x=ev.clientX-oC.offsetLeft- c.x; var y= c.y-(ev.clientY-oC.offsetTop); //计算角度,注意角度的公式tan是临边比对边,和数学公式的有所不同 Math.atan2(y,x);并且这里是弧度转角度,需要在com.js中添加a2d的函数 var d=90-a2d(Math.atan2(y,x)); c.rotate=d; //当点击的时候生成炮弹,所以在点击事件中添加炮弹 var bullet=new Bullet(c.type); //炮弹的位置和旋转角度和炮的位置和旋转角度相同, bullet.x= c.x; bullet.y= c.y; bullet.rotate = c.rotate; //注意炮弹不能画在这里,如果画在这里会被画炮和炮台时所清空,当然潘丹并不是只画一个,可以用一个数组来存储所画出来的炮弹,然后在炮旋转重绘的时候同时添加炮弹,为了让点击事件和定时器都能用到这个数组,这个数组应该写到事件和定时器的父级的变量空间中 /*bullet.draw(gd);*/ //讲当次点击画布所创建的炮弹存入arrBullet中 arrBullet.push(bullet); }; }); },false); </script>
13》添加渔网 渔网的思路和死鱼的基本一致
新添加了web.js文件
/** * Created by Jason on 2016/11/4. */ //网的尺寸 var WEB_SIZE=[ null, {x:332,y:373,w:87,h:86}, {x:13,y:413,w:108,h:106}, {x:177,y:369,w:125,h:124}, {x:252,y:179,w:149,h:149}, {x:1,y:244,w:160,h:154}, {x:21,y:22,w:198,h:199}, {x:241,y:0,w:180,h:179} ]; //构造渔网的构造函数 思路和死鱼的相同 渔网的方法有type x y scal move function Web(type){ this.type=type; this.x=0; this.y=0; this.scale=1; } //渔网的方法有 draw 思路和子弹相同,因为都是在一张图上,所以需要先把长宽位置x y获取 Web.prototype.draw=function(gd){ var x=WEB_SIZE[this.type].x; var y=WEB_SIZE[this.type].y; var w=WEB_SIZE[this.type].w; var h=WEB_SIZE[this.type].h; gd.save(); gd.translate(this.x,this.y); gd.scale(this.scale,this.scale); gd.drawImage(JSON['web'], x,y,w,h, -w/2,-h/2,w,h ); gd.restore(); };
index.js文件 index文件调整了生成鱼,死鱼,金币,渔网的位置,越靠前越向下层,炮弹打中鱼先有死鱼再有渔网,然后生成金币,活鱼在最上层
<script src="js/resource.js"></script> <script src="js/com.js"></script> <script src="js/fish.js"></script> <script src="js/cannon.js"></script> <script src="js/bullet.js"></script> <script src="js/coin.js"></script> <script src="js/diefish.js"></script> <script src="js/web.js"></script> <script> document.addEventListener('DOMContentLoaded',function(){ var oC=document.getElementById('c1'); var gd=oC.getContext('2d'); var rule=0.05; //开始画炮台 画炮台需要先加载资源,然后再画,这里没有使用面向对象的概念 loadImage(resource,function(){ //设置炮的初始位置,初始位置在资源文件中已经写明 var c=new Cannon(4); c.x=431; c.y=570; //存放炮弹的数组 var arrBullet=[]; //存放鱼的数组 var arrFish=[]; //存放金币的数组 var arrCoin=[]; //存放死鱼的数组 var arrDieFish=[]; //放渔网的数组 var arrWeb=[]; setInterval(function(){ gd.clearRect(0,0,oC.width,oC.height); //画死鱼 for(var i=0;i<arrDieFish.length;i++){ arrDieFish[i].draw(gd); } //画渔网 for(var i=0;i<arrWeb.length;i++){ arrWeb[i].draw(gd); } //画金币 for(var i=0;i<arrCoin.length;i++){ arrCoin[i].draw(gd); } //画鱼 鱼从左右两边同时随机出现 实现这个的原理是Math.random()是0-1的数,定时器的触发时间是30ms一秒钟30多条鱼的诞生有些多,所以在这里我们需要修改规则rule来降低鱼出现的概率,当rule=0.05(概率为原来的20%),再加入一个参数decoration,然后用Math.random()-0.5得出的这个值的范围时0.5到-0.5,这样正负的概率分别为50%,这样我们就能继续进行鱼诞生的方向了; var decoration=Math.random()-0.5; if(Math.random()<rule){ if(decoration<0){ var f1=new Fish(rnd(1,6)); f1.x=oC.width+50; f1.y=rnd(0,oC.height); // f1.rotate=rnd(91,269); }else{ var f1=new Fish(rnd(1,6)); f1.x=-50; f1.y=rnd(0,oC.height); // f1.rotate=rnd(-89,89); } arrFish.push(f1); } for(var i=0;i<arrFish.length;i++){ arrFish[i].draw(gd); } //和炮弹一样,对鱼进行性能优化,再鱼游出屏幕一定范围之后,便将鱼从鱼的数组中清除 for(var i=0;i<arrFish.length;i++){ if(arrFish[i].x<-50 || arrFish[i].x>(oC.width+50) || arrFish[i].y<0 || arrFish[i].y>(oC.height+50)){ arrFish.splice(i,1); i--; } } //炮是在炮台上的,可以在画炮台的时候一起画出来,画之前为了避免重绘,需要先清除画布 gd.drawImage(JSON['bottom'], 0,0,765,70, 0,532,765,70 ); //调用炮的方法draw来画炮 和鱼的转动相同,当点击画布的时候,炮需要跟随鼠标的指向来转动,这里在转动的时候我们改改变炮的转动角度,然后重新不停的删除,再画炮 这个效果思路和画鱼相同,需要配合定时器来实现 c.draw(gd); //将当次点击所产生的炮弹画出来 for(var i=0;i<arrBullet.length;i++){ arrBullet[i].draw(gd); } //这里由于炮弹不停的被创造,数组中也变得越来越大,当炮弹到达一定位置(移出屏幕)的时候,应该清除前面没用的炮弹,避免性能的浪费。注意,我们需要检测每个鱼和炮弹的位置(外层循环鱼,内层循环子弹),有我就是整个数组所有参数身上的x y for(var i=0;i<arrBullet.length;i++){ if(arrBullet[i].x<0 || arrBullet[i].x>oC.width || arrBullet[i].y>oC.height || arrBullet[i].y<0){ arrBullet.splice(i,1); i--; } } //进行碰撞检测,这里只做了简单的碰撞检测,我们把每个模型(鱼和子弹)都考虑成了原型,当两个物体的距离小于两个物体的半径之和的时候表明两个物体碰撞,注意,需要循环检测存在的所有的鱼,我们可以提前做一个函数来判断这个距离,当得到碰撞距离的时候函数返回true,当得到没有碰撞的距离的时候,函数返回的是false,这个函数是每条鱼身上的一个方法,每条鱼在游动的时候都在不停的计算这个值,并不停的返回真或者假来供我们判断是否和子弹碰撞 for(var i=0;i<arrFish.length;i++){ for(var j=0;j<arrBullet.length;j++){ if(arrFish[i].isIn(arrBullet[j].x, arrBullet[j].y)){ //金币生成的起始坐标就是碰撞时候的鱼的坐标,注意代码位置,不能放到删除鱼之后 var x=arrFish[i].x; var y=arrFish[i].y; //鱼的类型关系到金币的类型,在这里存鱼的类型,下面创造金币的时候用 var type=arrFish[i].type; //在鱼碰到了之后,我们需要做的是让相互碰撞的鱼和子弹都消失,也就是从鱼和子弹的数组中删除 //鱼死的时候的rotate也要存一下,供后面死鱼使用,来确保活鱼和死鱼的方向相同 var rotate=arrFish[i].rotate; arrFish.splice(i,1); i--; arrBullet.splice(j,1); j--; //碰撞之后生成渔网 var web=new Web(type); web.x=x; web.y=y; arrWeb.push(web); //和死鱼相同,渔网生成一段时间后也需要消失 setTimeout(function(){ arrWeb.shift(); i--; },1000); //碰撞之后生成金币 var coin=new Coin(type); coin.x=x; coin.y=y; arrCoin.push(coin); //碰撞之后生成死鱼 var dieFish=new DieFish(type); dieFish.x=x; dieFish.y=y; dieFish.rotate=rotate; arrDieFish.push(dieFish); //清除死鱼,注意死鱼不是一直在清除,使用的一次性定时器,每隔一段时间便清除最前面的一条死鱼 setTimeout(function(){ arrDieFish.shift(); i--; },1000) } } } console.log(arrDieFish.length); },30); //当点击画布的时候炮的角度对着鼠标点击的位置,并进行重绘 oC.onclick=function(ev){ //这里需要梳理鼠标点击的位置和炮旋转角度之间的关系(附图说明--炮的旋转角度.png) var x=ev.clientX-oC.offsetLeft- c.x; var y= c.y-(ev.clientY-oC.offsetTop); //计算角度,注意角度的公式tan是临边比对边,和数学公式的有所不同 Math.atan2(y,x);并且这里是弧度转角度,需要在com.js中添加a2d的函数 var d=90-a2d(Math.atan2(y,x)); c.rotate=d; //当点击的时候生成炮弹,所以在点击事件中添加炮弹 var bullet=new Bullet(c.type); //炮弹的位置和旋转角度和炮的位置和旋转角度相同, bullet.x= c.x; bullet.y= c.y; bullet.rotate = c.rotate; //注意炮弹不能画在这里,如果画在这里会被画炮和炮台时所清空,当然潘丹并不是只画一个,可以用一个数组来存储所画出来的炮弹,然后在炮旋转重绘的时候同时添加炮弹,为了让点击事件和定时器都能用到这个数组,这个数组应该写到事件和定时器的父级的变量空间中 /*bullet.draw(gd);*/ //讲当次点击画布所创建的炮弹存入arrBullet中 arrBullet.push(bullet); }; }); },false); </script>
14》添加声音文件
需要添加两个声音文件
①碰撞的时候添加金币的声音文件
②点击的时候添加发射炮弹的声音文件
//碰撞的时候添加金币的声音文件 var oA=new Audio(); oA.src='snd/coin.wav'; oA.play(); //当点击的时候发射炮弹,所以在点击的时候创造炮弹发射声音文件 var oA=new Audio(); oA.src='snd/cannon.mp3'; oA.play();
15》大炮的后坐力前面忘记写了,现在补上 其实就是大炮的一个方法
//构建炮的后坐力方法 使用this.cur和draw的时候图片的位置相关联,通过更换图片来实现炮的运动, Cannon.prototype.emitChange=function(){ var _this=this; //注意当图片更换一轮的时候,需要清除定时器来保证大炮不是一直在那里动,所以大炮的有个定时器的属性需要添加 clearInterval(_this.timer); _this.timer=setInterval(function(){ _this.cur++; if(_this.cur==5){ _this.cur=0; clearInterval(_this.timer); } },30); };
16》到这里,基本的捕鱼达人已经完成,还有金币的性能优化,网的性能优化,死鱼的性能优化,和前面的是一样一样的,还有好多功能没有实现,大家可以自己按照之前的思路去实现,有时间的话我也会补全的,比如说计分系统,比如说鲨鱼等等
下面把每个文件的完整版给些出
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> *{ padding: 0; margin: 0; } body{ background:#000; text-align:center; overflow:hidden; } #c1{ background:url(img/game_bg_2_hd.jpg); margin:40px auto; } </style> <script src="js/resource.js"></script> <script src="js/com.js"></script> <script src="js/fish.js"></script> <script src="js/cannon.js"></script> <script src="js/bullet.js"></script> <script src="js/coin.js"></script> <script src="js/diefish.js"></script> <script src="js/web.js"></script> <script src="js/init.js"></script> </head> <body> <canvas id="c1" width="800" height="600"></canvas> </body> </html>
fish.js
/** * Created by Jason on 2016/11/2. */ //各种不同的鱼的图片的数据 var FISH_SIZE=[ null, {w: 55, h: 37, collR: 17}, {w: 78, h: 64, collR: 24}, {w: 72, h: 56, collR: 20}, {w: 77, h: 59, collR: 22}, {w: 107, h: 122, collR: 29} ]; //使用面向对象的思想创建对象的属性和方法,属性写在构造函数中,方法放在原型上 //先画一条鱼,鱼的属性有鱼的种类type 位置x,y 游动时候尾巴运动cur 游动的方向rotate 游动的速度iSpeed 向前运动move 先创建鱼的构造函数 function Fish(type){ this.type=type; this.x=0; this.y=0; this.cur=0; this.rotate=0; this.iSpeed=1; this.collR=FISH_SIZE[this.type].collR; this.move(); } //在添加鱼的方法 画鱼draw 鱼的运动move Fish.prototype.draw=function(gd){ //鱼的图片的宽高,不同鱼的不同宽高不相同,先把鱼的宽高存入一个提前量好的数组FISH_SIZE中,再根据不同种类的鱼来获取不同的宽高 var w=FISH_SIZE[this.type].w; var h=FISH_SIZE[this.type].h; //开始画鱼 注意画鱼先要save,结束以后restore 鱼的出场时可以改变方向的,所以画鱼的时候注意提前准备好角度 gd.save(); gd.translate(this.x,this.y); gd.rotate(d2a(this.rotate)); //鱼是有阴影的,当鱼从左面出来的时候,阴影是在鱼的右下面,当鱼从左面出来的时候,阴影是在鱼的左下面,在旋转完角度后鱼可能从左面出来也可能从右面出来,当鱼从右面出来的时候,需要scale图片,使图片翻转来修正阴影的位置 if(this.rotate>90 && this.rotate<270){ gd.scale(1,-1); } //利用drawImage这个函数来画鱼,JSON['fish'+this.type]可以选择不同种类的鱼,注意鱼的rotate是根据头部的位置来改变的 gd.drawImage(JSON['fish'+this.type], //使用this.cur 来变换鱼的图片的位置 0,h*this.cur,w,h, -w/2,-h/2,w,h ); gd.restore(); }; //鱼的move的方法 Fish.prototype.move=function(){ //鱼的运动分为向前游动和尾巴的摆动两个运动 //向前游动 向前游动是改变的鱼的this.x 和 this.y 的值 不停的往前游动需要配合定时器的实现 //注意事件中套定时器,定时器的this的指向不准确,解决办法是 提前存储一个正确的this var _this=this; setInterval(function(){ //将this.x this.y分解到斜线坐标上 _this.x+=Math.cos(d2a(_this.rotate))*_this.iSpeed; _this.y+=Math.sin(d2a(_this.rotate))*_this.iSpeed; },30); //鱼尾巴的摆动 鱼尾巴的摆动是在不停的更换鱼的图片来实现的 相同的是定时器配合this.cur的加减 setInterval(function(){ //图片的位置是改变鱼的精灵图上的鱼的起始位置x y来实现 这里用这个cur的值来关联这组坐标 _this.cur++; //循环这组数字 if(_this.cur==4){ _this.cur=0; } },200); }; //传入的两个参数x y是子弹的实时位置 Fish.prototype.isIn=function(x,y){ var a=this.x-x; var b=this.y-y; var c=Math.sqrt(a*a+b*b); //这里需要提前在鱼的构造函数中添加this.collR的属性this.collR=FISH_SIZE[this.type].collR; if(c<=this.collR){ return true; }else{ return false; } };
bullet.js
/** * Created by Jason on 2016/11/3. */ //开始构造炮弹 //炮弹具体尺寸 var BULLET_SIZE=[ null, {x: 86, y: 0, w: 24, h: 26}, {x: 62, y: 0, w: 25, h: 29}, {x: 30, y: 0, w: 31, h: 35}, {x: 32, y: 35, w: 27, h: 31}, {x: 30, y: 82, w: 29, h: 33}, {x: 0, y: 82, w: 30, h: 34}, {x: 0, y: 0, w: 30, h: 44} ]; //炮弹的构造函数,同样先在resource.js中加载炮弹的资源, 炮弹的属性有 type 位置x y rotate iSpeed move function Bullet(type){ this.type=type; this.x=0; this.y=0; this.rotate=0; this.iSpeed=this.type*2; this.move(); } //暂时想到的炮弹原型上的方法有draw move ,先写,后面出现其他的再补充 Bullet.prototype.draw=function(gd){ //同样的炮弹的尺寸数据表中已经量好并且给出 var w=BULLET_SIZE[this.type].w; var h=BULLET_SIZE[this.type].h; //这里与前面不同的是需要定义不同尺寸炮弹的起始位置,数据表中已经给出,直接获取 var x=BULLET_SIZE[this.type].x; var y=BULLET_SIZE[this.type].y; //开始画炮弹, gd.save(); gd.translate(this.x,this.y); gd.rotate(d2a(this.rotate)); gd.drawImage(JSON['bullet'], x,y,w,h, -w/2,-h/2,w,h ); gd.restore(); }; //添加炮弹move的方法,和fish运动的思路相同 Bullet.prototype.move=function(){ //开启定时器不停的改变炮弹的位置并且重绘,同样,注意事件中的定时器里的this有问题,需要提前存正确的this的指向 var _this=this; setInterval(function(){ //和鱼的move有些不同的是炮弹的y轴的方向不同炮弹都是是向上射出的 _this.x+=Math.sin(d2a(_this.rotate))*_this.iSpeed; _this.y-=Math.cos(d2a(_this.rotate))*_this.iSpeed; },30); }; cannon.js /** * Created by Jason on 2016/11/2. */ //炮 var CANNON_SIZE=[ null, {w: 74, h: 74}, {w: 74, h: 76}, {w: 74, h: 76}, {w: 74, h: 83}, {w: 74, h: 85}, {w: 74, h: 90}, {w: 74, h: 94} ]; //构建炮的构造函数 炮弹的属性有位置x y type rotate 添加的同时注意在resource.js文件中添加需要加载的资源 function Cannon(type){ this.type=type; this.x=0; this.y=0; this.cur=0; this.rotate=0; this.timer=null; } //构建炮的原型,炮的原型上面有draw的方法 Cannon.prototype.draw=function(gd){ //和鱼的原型相同,先要设置炮台的尺寸w h,同样的再2中的尺寸表中 var w=CANNON_SIZE[this.type].w; var h=CANNON_SIZE[this.type].h; //开始画炮台,注意先save最后再restore,炮台是可以旋转的 gd.save(); gd.translate(this.x,this.y); gd.rotate(d2a(this.rotate)); gd.drawImage(JSON['cannon'+this.type], 0,this.cur*h,w,h, -w/2,-h/2,w,h ); gd.restore(); }; //构建炮的后坐力方法 使用this.cur和draw的时候图片的位置相关联,通过更换图片来实现炮的运动, Cannon.prototype.emitChange=function(){ var _this=this; //注意当图片更换一轮的时候,需要清除定时器来保证大炮不是一直在那里动,所以大炮的有个定时器的属性需要添加 clearInterval(_this.timer); _this.timer=setInterval(function(){ _this.cur++; if(_this.cur==5){ _this.cur=0; clearInterval(_this.timer); } },30); };
coin.js
/** * Created by Jason on 2016/11/3. */ //添加金币的构造含函数 function Coin(type){ this.type=type; this.x=0; this.y=0; this.cur=0; this.move(); } //Coin的方法有draw move ,暂时想到这两个 Coin.prototype.draw=function(gd){ gd.save(); gd.translate(this.x,this.y); if(this.type<3){ gd.drawImage(JSON['coinAni1'], 0,this.cur*60,60,60, -30,-30,60,60 ); }else{ gd.drawImage(JSON['coinAni2'], 0,this.cur*60,60,60, -30,-30,60,60 ); } gd.restore(); }; //coin是从碰撞的点移动到画布左下角的金币存储中 就是想办法让金币的x移动到0,y的位置移动到oC.height Coin.prototype.move=function(){ var _this=this; setInterval(function(){ //这里速度的减小使用了一种很巧妙的方法,让this.x(碰撞的x)分10次减小到0,同样的y的值也相同 _this.x+=-_this.x/10; _this.y+=(630-_this.y)/10; //在运动的过程中还要旋转金币,也就是更换金币的图片,设置一个cur,使cur++,同时关联draw方法中金币图片的高度来更换图片 _this.cur++; if(_this.cur==10){ _this.cur=0; } },30); };
com.js
/** * Created by Jason on 2016/11/2. */ function d2a(n){ return n*Math.PI/180; } function a2d(n){ return n*180/Math.PI; } function rnd(m,n){ return parseInt(Math.random()*(n-m)+m); } var JSON={}; function loadImage(arr,fnSucc){ //为了测试是否加载完成,设置count,在加载的时候使count++,判断count的值来判断是否加载完成 var count=0; for(var i=0;i<arr.length;i++){ var oImg=new Image(); //加载所有鱼的资源 (function(index){ oImg.onload=function(){ //把所有资源加载,并且存到JSON中,注意,循环中有事件,事件里的i值不对,解决用封闭空间 JSON[arr[index]]=this; count++; //当count的值和需要加载的资源的数组相同的时候,资源加载完毕 if(count==arr.length){ fnSucc && fnSucc(); } }; })(i); oImg.src='img/'+arr[i]+'.png'; } }
diefish.js
/** * Created by Jason on 2016/11/3. */ //构造死鱼的构造函数 死鱼的属性有type x y cur翻转 rotate move function DieFish(type){ this.type=type; this.x=0; this.y=0; this.rotate=0; this.cur=0; this.move(); } //死鱼的方法有draw move 注意,鱼死的时候的方向和隐形需要修正,如果不修正则都会成为默认方向 DieFish.prototype.draw=function(gd){ var w=FISH_SIZE[this.type].w; var h=FISH_SIZE[this.type].h; gd.save(); gd.translate(this.x,this.y); gd.rotate(d2a(this.rotate)); //修复阴影 if(this.rotate>90&&this.rotate<270){ gd.scale(1,-1); } gd.drawImage(JSON['fish'+this.type], 0,(this.cur+4)*h,w,h, -w/2,-h/2,w,h ); gd.restore(); }; //死鱼的move方法 是更换图片翻肚皮,思路和鱼摆尾巴相似 DieFish.prototype.move=function(){ var _this=this; setInterval(function(){ _this.cur++; //当死鱼的图片换一遍以后,停止更换图片,以便后面死鱼消失,注意定时器的时间,控制图片的更换 if(_this.cur==4){ _this.cur=0; } },250); };
resource.js
/** * Created by Jason on 2016/11/2. */ //把所有资源放到一个resource的数组中,方便加载资源的函数loadImage调用 var resource=['fish1','fish2','fish3','fish4','fish5','bottom','cannon1','cannon2','cannon3','cannon4','cannon5','cannon6','cannon7','bullet','coinAni1','coinAni2','web'];
web.js
/** * Created by Jason on 2016/11/4. */ //网的尺寸 var WEB_SIZE=[ null, {x:332,y:373,w:87,h:86}, {x:13,y:413,w:108,h:106}, {x:177,y:369,w:125,h:124}, {x:252,y:179,w:149,h:149}, {x:1,y:244,w:160,h:154}, {x:21,y:22,w:198,h:199}, {x:241,y:0,w:180,h:179} ]; //构造渔网的构造函数 思路和死鱼的相同 渔网的方法有type x y scal move function Web(type){ this.type=type; this.x=0; this.y=0; this.scale=1; } //渔网的方法有 draw 思路和子弹相同,因为都是在一张图上,所以需要先把长宽位置x y获取 Web.prototype.draw=function(gd){ var x=WEB_SIZE[this.type].x; var y=WEB_SIZE[this.type].y; var w=WEB_SIZE[this.type].w; var h=WEB_SIZE[this.type].h; gd.save(); gd.translate(this.x,this.y); gd.scale(this.scale,this.scale); gd.drawImage(JSON['web'], x,y,w,h, -w/2,-h/2,w,h ); gd.restore(); };
init.js
/** * Created by Jason on 2016/11/4. */ document.addEventListener('DOMContentLoaded',function(){ var oC=document.getElementById('c1'); var gd=oC.getContext('2d'); var rule=0.05; //开始画炮台 画炮台需要先加载资源,然后再画,这里没有使用面向对象的概念 loadImage(resource,function(){ //设置炮的初始位置,初始位置在资源文件中已经写明 var c=new Cannon(rnd(1,8)); c.x=431; c.y=570; //存放炮弹的数组 var arrBullet=[]; //存放鱼的数组 var arrFish=[]; //存放金币的数组 var arrCoin=[]; //存放死鱼的数组 var arrDieFish=[]; //放渔网的数组 var arrWeb=[]; setInterval(function(){ gd.clearRect(0,0,oC.width,oC.height); //画死鱼 for(var i=0;i<arrDieFish.length;i++){ arrDieFish[i].draw(gd); } //画渔网 for(var i=0;i<arrWeb.length;i++){ arrWeb[i].draw(gd); } //画金币 for(var i=0;i<arrCoin.length;i++){ arrCoin[i].draw(gd); } //画鱼 鱼从左右两边同时随机出现 实现这个的原理是Math.random()是0-1的数,定时器的触发时间是30ms一秒钟30多条鱼的诞生有些多,所以在这里我们需要修改规则rule来降低鱼出现的概率,当rule=0.05(概率为原来的20%),再加入一个参数decoration,然后用Math.random()-0.5得出的这个值的范围时0.5到-0.5,这样正负的概率分别为50%,这样我们就能继续进行鱼诞生的方向了; var decoration=Math.random()-0.5; if(Math.random()<rule){ if(decoration<0){ var f1=new Fish(rnd(1,6)); f1.x=oC.width+50; f1.y=rnd(0,oC.height); // f1.rotate=rnd(91,269); }else{ var f1=new Fish(rnd(1,6)); f1.x=-50; f1.y=rnd(0,oC.height); // f1.rotate=rnd(-89,89); } arrFish.push(f1); } for(var i=0;i<arrFish.length;i++){ arrFish[i].draw(gd); } //和炮弹一样,对鱼进行性能优化,再鱼游出屏幕一定范围之后,便将鱼从鱼的数组中清除 for(var i=0;i<arrFish.length;i++){ if(arrFish[i].x<-50 || arrFish[i].x>(oC.width+50) || arrFish[i].y<0 || arrFish[i].y>(oC.height+50)){ arrFish.splice(i,1); i--; } } //炮是在炮台上的,可以在画炮台的时候一起画出来,画之前为了避免重绘,需要先清除画布 gd.drawImage(JSON['bottom'], 0,0,765,70, 0,532,765,70 ); //调用炮的方法draw来画炮 和鱼的转动相同,当点击画布的时候,炮需要跟随鼠标的指向来转动,这里在转动的时候我们改改变炮的转动角度,然后重新不停的删除,再画炮 这个效果思路和画鱼相同,需要配合定时器来实现 c.draw(gd); //将当次点击所产生的炮弹画出来 for(var i=0;i<arrBullet.length;i++){ arrBullet[i].draw(gd); } //这里由于炮弹不停的被创造,数组中也变得越来越大,当炮弹到达一定位置(移出屏幕)的时候,应该清除前面没用的炮弹,避免性能的浪费。注意,我们需要检测每个鱼和炮弹的位置(外层循环鱼,内层循环子弹),有我就是整个数组所有参数身上的x y for(var i=0;i<arrBullet.length;i++){ if(arrBullet[i].x<0 || arrBullet[i].x>oC.width || arrBullet[i].y>oC.height || arrBullet[i].y<0){ arrBullet.splice(i,1); i--; } } //进行碰撞检测,这里只做了简单的碰撞检测,我们把每个模型(鱼和子弹)都考虑成了原型,当两个物体的距离小于两个物体的半径之和的时候表明两个物体碰撞,注意,需要循环检测存在的所有的鱼,我们可以提前做一个函数来判断这个距离,当得到碰撞距离的时候函数返回true,当得到没有碰撞的距离的时候,函数返回的是false,这个函数是每条鱼身上的一个方法,每条鱼在游动的时候都在不停的计算这个值,并不停的返回真或者假来供我们判断是否和子弹碰撞 for(var i=0;i<arrFish.length;i++){ for(var j=0;j<arrBullet.length;j++){ if(arrFish[i].isIn(arrBullet[j].x, arrBullet[j].y)){ //碰撞的时候添加金币的声音文件 var oA=new Audio(); oA.src='snd/coin.wav'; oA.play(); //金币生成的起始坐标就是碰撞时候的鱼的坐标,注意代码位置,不能放到删除鱼之后 var x=arrFish[i].x; var y=arrFish[i].y; //鱼的类型关系到金币的类型,在这里存鱼的类型,下面创造金币的时候用 var type=arrFish[i].type; //在鱼碰到了之后,我们需要做的是让相互碰撞的鱼和子弹都消失,也就是从鱼和子弹的数组中删除 //鱼死的时候的rotate也要存一下,供后面死鱼使用,来确保活鱼和死鱼的方向相同 var rotate=arrFish[i].rotate; arrFish.splice(i,1); i--; arrBullet.splice(j,1); j--; //碰撞之后生成渔网 var web=new Web(type); web.x=x; web.y=y; arrWeb.push(web); //和死鱼相同,渔网生成一段时间后也需要消失 setTimeout(function(){ arrWeb.shift(); i--; },1000); //碰撞之后生成金币 var coin=new Coin(type); coin.x=x; coin.y=y; arrCoin.push(coin); //碰撞之后生成死鱼 var dieFish=new DieFish(type); dieFish.x=x; dieFish.y=y; dieFish.rotate=rotate; arrDieFish.push(dieFish); //清除死鱼,注意死鱼不是一直在清除,使用的一次性定时器,每隔一段时间便清除最前面的一条死鱼 setTimeout(function(){ arrDieFish.shift(); i--; },1000) } } } },30); //当点击画布的时候炮的角度对着鼠标点击的位置,并进行重绘 oC.onclick=function(ev){ //当点击的时候发射炮弹,所以在点击的时候创造炮弹发射声音文件 var oA=new Audio(); oA.src='snd/cannon.mp3'; oA.play(); //这里需要梳理鼠标点击的位置和炮旋转角度之间的关系(附图说明--炮的旋转角度.png) var x=ev.clientX-oC.offsetLeft- c.x; var y= c.y-(ev.clientY-oC.offsetTop); //计算角度,注意角度的公式tan是临边比对边,和数学公式的有所不同 Math.atan2(y,x);并且这里是弧度转角度,需要在com.js中添加a2d的函数 var d=90-a2d(Math.atan2(y,x)); c.rotate=d; //炮的后坐力方法存入 c.emitChange(); //当点击的时候生成炮弹,所以在点击事件中添加炮弹 var bullet=new Bullet(c.type); //炮弹的位置和旋转角度和炮的位置和旋转角度相同, bullet.x= c.x; bullet.y= c.y; bullet.rotate = c.rotate; //注意炮弹不能画在这里,如果画在这里会被画炮和炮台时所清空,当然潘丹并不是只画一个,可以用一个数组来存储所画出来的炮弹,然后在炮旋转重绘的时候同时添加炮弹,为了让点击事件和定时器都能用到这个数组,这个数组应该写到事件和定时器的父级的变量空间中 //讲当次点击画布所创建的炮弹存入arrBullet中 arrBullet.push(bullet); }; }); },false);