首页 > 代码库 > js原生捕鱼达人(一)
js原生捕鱼达人(一)
捕鱼达人的游戏大家都很熟悉吧,接下来的两三天,我会将整个游戏的原生js写法详细的写出来,整个游戏应用了面向对象的写法:创建构造函数,在构造函数上面添加对象的属性,然后在构造函数的原型上添加方法,当然这个程序使用了canvas来绘制,每一步的我都已经分别写出来,详细的步骤我在写代码的过程中都已经标注了出来。
下面是捕鱼达人的素材库:
1》加载资源
<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> 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=http://www.mamicode.com/‘img/‘+arr[i]+‘.png‘;"c1" width="800" height="600"></canvas> </body>
/** * Created by Jason on 2016/11/2. */ 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=http://www.mamicode.com/‘img/‘+arr[i]+‘.png‘;>
resource.js文件
/** * Created by Jason on 2016/11/2. */ //把所有资源放到一个resource的数组中,方便加载资源的函数loadImage调用 var resource=[‘fish1‘,‘fish2‘,‘fish3‘,‘fish4‘,‘fish5‘];
index.html
<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="http://www.mamicode.com/js/resource.js"></script> <script src="http://www.mamicode.com/js/com.js"></script> <script> document.addEventListener(‘DOMContentLoaded‘,function(){ var oC=document.getElementById(‘c1‘); var gd=oC.getContext(‘2d‘); var oImg=new Image(); loadImage(resource,function(){ gd.drawImage(JSON[‘fish3‘],0,0); }) },false); </script> </head> <body> <canvas id="c1" width="800" height="600"></canvas> </body>
3》先画一条鱼(以下步骤js文件中未改变的都没有提到,大家自己把代码归类存放,最后我会给出所有最终版的多有文件的全部代码)
关于鱼游动的角度的计算我也画了图
以下尺寸经过ps测量:
//鱼
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}
];
--------------------------
炮台尺寸:
宽度:765, 高度:70
距离画布上方的距离是:532
--------------------------
炮的位置:
x=431;
y=570;
--------------------------
炮的尺寸:
//炮
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}
];
-----------------------------
//炮弹具体尺寸
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}
];
<script src="http://www.mamicode.com/js/resource.js"></script> <script src="http://www.mamicode.com/js/com.js"></script> <script> function d2a(n){ return n*Math.PI/180; } //各种不同的鱼的图片的数据 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=220; this.iSpeed=1; 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>45 && 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 _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); }; document.addEventListener(‘DOMContentLoaded‘,function(){ var oC=document.getElementById(‘c1‘); var gd=oC.getContext(‘2d‘); //调用面向对象方法中创造的鱼,并在画布上画出鱼 loadImage(resource,function(){ var f1=new Fish(5); //给出新创建出鱼的出事位置 f1.x=300; f1.y=300; //在画鱼的时候需要先清除一次画布 同样画之前需要先保存,结束以后再存储 //使鱼动起来需要不停的在画布上擦除上衣画的鱼并且不停的创建新的鱼,需要配合定时器来实现 setInterval(function(){ gd.clearRect(0,0,oC.width,oC.height); gd.save(); //画鱼的方法在面向对象中都已经创建,在这直接使用就可以 f1.draw(gd); gd.restore(); },30); }) },false); </script> </head> <body> <canvas id="c1" width="800" height="600"></canvas> </body>
4》画炮台 尺寸见3中的位置
document.addEventListener(‘DOMContentLoaded‘,function(){ var oC=document.getElementById(‘c1‘); var gd=oC.getContext(‘2d‘); //开始画炮台 画炮台需要先加载资源,然后再画,这里没有使用面向对象的概念 loadImage(resource,function(){ gd.drawImage(JSON[‘bottom‘], 0,0,765,70, 0,532,765,70 ) });
5》画炮,尺寸详见3中的位置,代码思路同画鱼
//炮 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.rotate=0; } //构建炮的原型,炮的原型上面有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,0,w,h, -w/2,-h/2,w,h ); gd.restore(); }; document.addEventListener(‘DOMContentLoaded‘,function(){ var oC=document.getElementById(‘c1‘); var gd=oC.getContext(‘2d‘); //开始画炮台 画炮台需要先加载资源,然后再画,这里没有使用面向对象的概念 loadImage(resource,function(){ gd.drawImage(JSON[‘bottom‘], 0,0,765,70, 0,532,765,70 ) //炮是在炮台上的,可以在画炮台的时候一起画出来 var c=new Cannon(4); //设置炮的初始位置,初始位置在资源文件中已经写明 c.x=431; c.y=570; //调用炮的方法draw来画炮 gd.save(); c.draw(gd); c.restore(); }); /* //调用面向对象方法中创造的鱼,并在画布上画出鱼 loadImage(resource,function(){ var f1=new Fish(1); //给出新创建出鱼的出事位置 f1.x=300; f1.y=300; //在画鱼的时候需要先清除一次画布 同样画之前需要先保存,结束以后再存储 //使鱼动起来需要不停的在画布上擦除上衣画的鱼并且不停的创建新的鱼,需要配合定时器来实现 setInterval(function(){ gd.clearRect(0,0,oC.width,oC.height); gd.save(); //画鱼的方法在面向对象中都已经创建,在这直接使用就可以 f1.draw(gd); gd.restore(); },30); });*/ },false); </script> </head> <body> <canvas id="c1" width="800" height="600"></canvas>
6》创建点击画布炮转向的功能 相同的再这一步完成之后,给炮单独建立一个js文件存放到js文件夹下(cannon.js文件);并把a2d的函数放到com.js文件中
关于炮的旋转角度的计算,请看我的图,其中写错了一个,懒得改了大家应该能看懂
<script src="http://www.mamicode.com/js/resource.js"></script> <script src="http://www.mamicode.com/js/com.js"></script> <script src="http://www.mamicode.com/js/fish.js"></script> <script> function a2d(n){ return n*180/Math.PI; } //炮 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.rotate=0; } //构建炮的原型,炮的原型上面有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,0,w,h, -w/2,-h/2,w,h ); gd.restore(); }; document.addEventListener(‘DOMContentLoaded‘,function(){ var oC=document.getElementById(‘c1‘); var gd=oC.getContext(‘2d‘); //开始画炮台 画炮台需要先加载资源,然后再画,这里没有使用面向对象的概念 loadImage(resource,function(){ var c=new Cannon(4); //设置炮的初始位置,初始位置在资源文件中已经写明 c.x=431; c.y=570; setInterval(function(){ //炮是在炮台上的,可以在画炮台的时候一起画出来,画之前为了避免重绘,需要先清除画布 gd.save(); gd.clearRect(0,0,oC.width,oC.height); gd.drawImage(JSON[‘bottom‘], 0,0,765,70, 0,532,765,70 ); //调用炮的方法draw来画炮 和鱼的转动相同,当点击画布的时候,炮需要跟随鼠标的指向来转动,这里在转动的时候我们改改变炮的转动角度,然后重新不停的删除,再画炮 这个效果思路和画鱼相同,需要配合定时器来实现 c.draw(gd); gd.restore(); },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; }; }); /* //调用面向对象方法中创造的鱼,并在画布上画出鱼 loadImage(resource,function(){ var f1=new Fish(1); //给出新创建出鱼的出事位置 f1.x=300; f1.y=300; //在画鱼的时候需要先清除一次画布 同样画之前需要先保存,结束以后再存储 //使鱼动起来需要不停的在画布上擦除上衣画的鱼并且不停的创建新的鱼,需要配合定时器来实现 setInterval(function(){ gd.clearRect(0,0,oC.width,oC.height); gd.save(); //画鱼的方法在面向对象中都已经创建,在这直接使用就可以 f1.draw(gd); gd.restore(); },30); });*/ },false); </script> </head> <body> <canvas id="c1" width="800" height="600"></canvas> </body>
7》构造炮弹 思路和前面都是一样一样的 注意构造函数中需要考虑的属性 和 构造函数的原型上面的方法
<script src="http://www.mamicode.com/js/resource.js"></script> <script src="http://www.mamicode.com/js/com.js"></script> <script src="http://www.mamicode.com/js/fish.js"></script> <script src="http://www.mamicode.com/js/cannon.js"></script> <script> //开始构造炮弹 //炮弹具体尺寸 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=1; 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(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); }; document.addEventListener(‘DOMContentLoaded‘,function(){ var oC=document.getElementById(‘c1‘); var gd=oC.getContext(‘2d‘); //开始画炮台 画炮台需要先加载资源,然后再画,这里没有使用面向对象的概念 loadImage(resource,function(){ //设置炮的初始位置,初始位置在资源文件中已经写明 var c=new Cannon(4); c.x=431; c.y=570; //存放炮弹的数组 var arrBullet=[]; setInterval(function(){ //炮是在炮台上的,可以在画炮台的时候一起画出来,画之前为了避免重绘,需要先清除画布 /* gd.save();*/ gd.clearRect(0,0,oC.width,oC.height); 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); } /*gd.restore();*/ },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); }; }); /* //调用面向对象方法中创造的鱼,并在画布上画出鱼 loadImage(resource,function(){ var f1=new Fish(1); //给出新创建出鱼的出事位置 f1.x=300; f1.y=300; //在画鱼的时候需要先清除一次画布 同样画之前需要先保存,结束以后再存储 //使鱼动起来需要不停的在画布上擦除上衣画的鱼并且不停的创建新的鱼,需要配合定时器来实现 setInterval(function(){ gd.clearRect(0,0,oC.width,oC.height); gd.save(); //画鱼的方法在面向对象中都已经创建,在这直接使用就可以 f1.draw(gd); gd.restore(); },30); });*/ },false); </script> </head> <body> <canvas id="c1" width="800" height="600"></canvas> </body>
其中炮弹中有个小bug,明天继续写
转载请注明‘转载于Jason齐齐的博客http://www.cnblogs.com/jasonwang2y60/’
js原生捕鱼达人(一)