PS:這個坦克大戰(zhàn)是在網(wǎng)上下的一段源碼之后,自己進(jìn)行的重寫。本身沒有太難的東西,這個案例將js面向?qū)ο笥玫谋容^好,可以作為js面向?qū)ο蟮娜腴T教程。
1. 創(chuàng)建基本對象,實(shí)現(xiàn)坦克簡單的移動
1.1 如何在地圖中繪制畫布?
考慮到瀏覽器兼容的問題,我們用操作dom的方式來實(shí)現(xiàn)游戲?qū)ο蟮睦L制和刷新。我們?nèi)绾未鎯ξ覀兊牡貓D呢? 我們應(yīng)該把地圖用一個二維數(shù)組來保存, js中沒有二維數(shù)組,但是可以通過在一維數(shù)組從存儲數(shù)組來實(shí)現(xiàn)。
1.2 代碼實(shí)現(xiàn)
我們將畫布設(shè)計為 13 * 13 的一個二維數(shù)組,每個元素在地圖中對應(yīng)的長和寬均為40px,可以把整個地圖看成由 40px*40p x大小的單元格組成的一個表格,那么我們整個畫布的大小為 520px * 520px ;
上代碼前先給大家來一張對象關(guān)系圖:

1.2.1 創(chuàng)建頂級對象
html代碼:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>坦克大戰(zhàn)</title>
<link rel=stylesheet href="css/main.css" />
<script src="js/Common.js"></script>
<script src="js/TankObject.js"></script>
<script src="js/Mover.js"></script>
<script src="js/Tank.js"></script>
<script src="js/Frame.js"></script>
<script>
window.onload = function () {
// 調(diào)用游戲裝載對象
var loader = new GameLoader();
loader.Begin();
}
</script>
</head>
<body>
<!--地圖容器-->
<div id="divMap">
</div>
<div id="debugInfo">
</div>
</body>
</html>
TankObject.js文件:
// 頂級對象
TankObject = function () {
this.XPosition = 0; // 對象在地圖(13*13)中的X的位置
this.YPosition = 0;
this.UI = null; // dom元素
}
// 更改UI靜態(tài)方法
TankObject.prototype.UpdateUI = function (battlFiled) { }
// 設(shè)置位置,參數(shù)是這樣:1*40,6*40
TankObject.prototype.SetPosition = function (leftPosition, topPosition) {
// 在地圖的位置 Math.round四舍五入
this.XPosition = Math.round(leftPosition / 40);
this.YPosition = Math.round(topPosition / 40);
// 設(shè)置在窗體上的位置
if (this.UI != null && this.UI.style != null) {
this.UI.style.left = leftPosition + "px";
this.UI.style.top = topPosition + "px";
}
}
這里我們用X,Y坐標(biāo)表示對象在地圖上的位置。后面我們會將地圖中的每個對象都放入二維數(shù)組中,這時可以通過X,Y坐標(biāo)來取得對應(yīng)的對象。
然后用css中的left和top來控制我們對象在窗體中的位置。(可以移動的對象:坦克,子彈)
1.2.2 創(chuàng)建公用對象
我們還需要創(chuàng)建一個公共的對象,來寫入我們常用的一些方法。
Common.js:
// 坦克移動的四個方向
var EnumDirection = {
Up: "0",
Right: "1",
Down: "2",
Left: "3"
};
// 通用方法對象
var UtilityClass = {
// 創(chuàng)建dom元素到parentNode中,可指定id,className
CreateE: function (type, id, className, parentNode) {
var J = document.createElement(type);
if (id) { J.id = id };
if (className) { J.className = className };
return parentNode.appendChild(J);
}, // 移除元素
RemoveE: function (obj, parentNode) {
parentNode.removeChild(obj);
},
GetFunctionName: function (context, argumentCallee) {
for (var i in context) {
if (context[i] == argumentCallee) { return i };
}
return "";
}, // 綁定事件,返回func方法,this為傳入的obj
BindFunction: function (obj,func) {
return function () {
func.apply(obj, arguments);
};
}
};
1.2.3 創(chuàng)建移動對象
Mover.js
// 移動對象,繼承自頂層對象
Mover = function () {
this.Direction = EnumDirection.Up;
this.Speed = 1;
}
Mover.prototype = new TankObject();
Mover.prototype.Move = function () {
if (this.lock) {
return;/* 停用或者尚在步進(jìn)中,操作無效 */
}
// 根據(jù)方向設(shè)置坦克的背景圖片
this.UI.style.backgroundPosition = "0 -" + this.Direction * 40 + "px";
// 如果方向是上和下,vp就是top;如果方向是上和左,val就是-1
var vp = ["top", "left"][((this.Direction == EnumDirection.Up) || (this.Direction == EnumDirection.Down)) ? 0 : 1];
var val = ((this.Direction == EnumDirection.Up) || (this.Direction == EnumDirection.Left)) ? -1 : 1;
this.lock = true;/* 加鎖 */
// 把當(dāng)前對象保存到This
var This = this;
// 記錄對象移動起始位置
var startmoveP = parseInt(This.UI.style[vp]);
var xp = This.XPosition, yp = This.YPosition;
var subMove = setInterval(function () {
// 開始移動,每次移動5px
This.UI.style[vp] = parseInt(This.UI.style[vp]) + 5 * val + "px";
// 每次移動一個單元格 40px
if (Math.abs((parseInt(This.UI.style[vp]) - startmoveP)) >= 40) {
clearInterval(subMove);
This.lock = false;/* 解鎖,允許再次步進(jìn) */
// 記錄對象移動后在表格中的位置
This.XPosition = Math.round(This.UI.offsetLeft / 40);
This.YPosition = Math.round(This.UI.offsetTop / 40);
}
}, 80 - this.Speed * 10);
}
這里的移動對象繼承自我們的頂級對象 ,這里this就代表調(diào)用Move方法的對象。
Move對象的功能根據(jù)對象的方向和速度進(jìn)行移動,每次移動5px總共移動40px一個單元格。后面這個對象還會進(jìn)行擴(kuò)展,會加入碰撞檢測等功能。
1.2.4 創(chuàng)建坦克對象
Tank.js 文件:
//tank對象 繼承自Mover
Tank=function(){}
Tank.prototype = new Mover();
// 創(chuàng)建玩家坦克,繼承自tank對象
SelfTank = function () {
this.UI = UtilityClass.CreateE("div", "", "itank", document.getElementById("divMap"));
this.MovingState = false;
this.Speed = 4;
}
SelfTank.prototype = new Tank();
// 設(shè)置坦克的位置
SelfTank.prototype.UpdateUI = function () {
this.UI.className = "itank";
// 頂級對象方法,設(shè)置坦克的位置
this.SetPosition(this.XPosition * 40, this.YPosition * 40);
}
現(xiàn)在只創(chuàng)建了玩家坦克,后面我們還會往里添加敵人坦克。
1.2.5 創(chuàng)建游戲裝載對象(核心)
// 游戲載入對象 整個游戲的核心對象
GameLoader = function () {
this.mapContainer = document.getElementById("divMap"); // 存放游戲地圖的div
this._selfTank = null; // 玩家坦克
this._gameListener = null; // 游戲主循環(huán)計時器id
}
GameLoader.prototype = {
Begin: function () {
// 初始化玩家坦克
var selfT = new SelfTank();
selfT.XPosition = 4;
selfT.YPosition = 12;
selfT.UpdateUI();
this._selfTank = selfT;
// 添加按鍵事件
var warpper = UtilityClass.BindFunction(this, this.OnKeyDown);
window.onkeydown = document.body.onkeydown = warpper;
warpper = UtilityClass.BindFunction(this, this.OnKeyUp);
window.onkeyup = document.body.onkeyup = warpper;
// 游戲主循環(huán)
warpper = UtilityClass.BindFunction(this, this.Run);
/*長定時器監(jiān)聽控制鍵*/
this._gameListener = setInterval(warpper, 20);
}
// 鍵盤按下玩家坦克開始移動
, OnKeyDown: function (e) {
switch ((window.event || e).keyCode) {
case 37:
this._selfTank.Direction = EnumDirection.Left;
this._selfTank.MovingState = true;
break; //左
case 38:
this._selfTank.Direction = EnumDirection.Up;
this._selfTank.MovingState = true;
break; //上
case 39:
this._selfTank.Direction = EnumDirection.Right;
this._selfTank.MovingState = true;
break; //右
case 40:
this._selfTank.Direction = EnumDirection.Down;
this._selfTank.MovingState = true;
break; //下
}
}
// 按鍵彈起停止移動
, OnKeyUp: function (e) {
switch ((window.event || e).keyCode) {
case 37:
case 38:
case 39:
case 40:
this._selfTank.MovingState = false;
break;
}
}
/*游戲主循環(huán)運(yùn)行函數(shù),游戲的心臟,樞紐*/
, Run: function () {
if (this._selfTank.MovingState) {
this._selfTank.Move();
}
}
};
游戲裝載對象代碼看起來很多,其實(shí)就做了兩件事情:
1、創(chuàng)建玩家坦克對象。
2、添加按鍵監(jiān)聽事件,當(dāng)玩家按下移動鍵調(diào)用坦克Move方法移動坦克。
總結(jié):到這里我們的坦克可以通過按鍵自由的移動了。下一步我們需要完善地圖和碰撞檢測。