捕魚達人的游戲大家都很熟悉吧,接下來的兩三天,我會將整個游戲的原生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='img/'+arr[i]+'.png';
}
}
document.addEventListener('DOMContentLoaded',function(){
var oC=document.getElementById('c1');
var gd=oC.getContext('2d');
var oImg=new Image();
loadImage(['fish1','fish2','fish3','fish4','fish5'],function(){
gd.drawImage(JSON['fish3'],0,0);
})
},false);
</script>
</head>
<body>
<canvas id="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='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="js/resource.js"></script>
<script src="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="js/resource.js"></script>
<script src="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="js/resource.js"></script>
<script src="js/com.js"></script>
<script src="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="js/resource.js"></script>
<script src="js/com.js"></script>
<script src="js/fish.js"></script>
<script src="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/’
