在上一篇文章中,我們研究了一下Box2dWeb的鎖鏈效果,當我研究出來以後,便突發奇想地想用可以這一效果製作一個越野自行車小遊戲,讓一個小自行車在各種地形之間來回顛簸。出於興趣便對此研究了一番。今天就先來看看越野自行車裏的地形是如何實現的。
一,準備工作
首先你需要下載lufylegend和box2dweb 這兩個引擎。
box2dweb可以到這裏下載:
http://code.google.com/p/box2dweb/downloads/list
lufylegend可以到這裏:
http://lufylegend.com/lufylegend
關於lufylegend怎麼用,可以到這裏看看API文檔:
http://lufylegend.com/lufylegend/api
Box2dWeb怎麼用?其實我也不太清楚,這次主要用lufylegend封裝的API,所以掌握lufylegend對box2dweb的操作就可以了。
在此之後,我們創建一個項目,就叫box2dBicycle吧,然後在裏面分別建立data,lib,main文件夾和index.html。如下:
box2dBicycle
|---data
|---lib
|---main
|---index.html
接下來,我們來介紹一下這些文件夾是用來裝什麼的。data文件夾是用來裝遊戲中的關卡數據的,我準備把遊戲中的關卡用json裝起來,這些json就放在這個文件夾裏。lib文件夾是裝引擎用的,也就是box2dweb和lufylegend.js。main就是放我們源代碼文件的。好了,介紹完了準備工作,我們來看看具體的代碼。
二,初始遊戲
既然是html5遊戲,那麼一定要有html代碼,這些代碼就在index.html中放着好了,代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>box2d demo</title>
<script type="text/javascript" src="./lib/Box2dWeb-2.1.a.3.min.js"></script>
<script type="text/javascript" src="./lib/lufylegend-1.8.7.min.js"></script>
<script type="text/javascript">
init(50,"mylegend",800,450,gameInit);
var world;
var JS_FILE_PATH = "./main/";
var LEVEL_FILE_PATH = "./data/"
var loadData = [
{path:JS_FILE_PATH+"Main.js",type:"js"},
{path:JS_FILE_PATH+"Road.js",type:"js"},
{path:JS_FILE_PATH+"BridgeGround.js",type:"js"},
{path:JS_FILE_PATH+"SmoothGround.js",type:"js"},
{path:JS_FILE_PATH+"HillGround.js",type:"js"},
{path:JS_FILE_PATH+"SlopeGround.js",type:"js"},
{path:LEVEL_FILE_PATH+"level01.js",type:"js"}
];
function gameInit(){
LStage.setDebug(true);
LStage.box2d = new LBox2d();
if(LStage.canTouch == true){
document.body.style.margin = "0px";
document.body.style.padding = "0px";
LStage.stageScale = LStageScaleMode.SHOW_ALL;
LSystem.screen(LStage.FULL_SCREEN);
}
LLoadManage.load(loadData,null,function(){
world = new Main();
addChild(world);
world.init();
});
}
</script>
</head>
<body>
<div id="mylegend"></div>
</body>
</html>
中間有一大段是js代碼,這些代碼就是遊戲中的數據加載和lufylegend初始化,遊戲全屏設置等。接下來逐句解釋一下這個js裏的代碼。首先是
init(50,"mylegend",800,450,gameInit);
也許你在納悶這個init函數是啥,讓我來告訴大家好了,這個函數是初始化lufylegend,也就是說是lufylegend中定義的。我這麼寫一行就說明要在id爲"mylegend"的div中創建一個寬800,高450的canvas,遊戲界面刷新次數爲50ms一次,當界面初始完成後,調用gameInit函數。當然,具體用法還是看lufylegend.js api文檔吧。接着是一些變量:
var world;
var JS_FILE_PATH = "./main/";
var LEVEL_FILE_PATH = "./data/"
var loadData = [
{path:JS_FILE_PATH+"Main.js",type:"js"},
{path:JS_FILE_PATH+"Road.js",type:"js"},
{path:JS_FILE_PATH+"BridgeGround.js",type:"js"},
{path:JS_FILE_PATH+"SmoothGround.js",type:"js"},
{path:JS_FILE_PATH+"HillGround.js",type:"js"},
{path:JS_FILE_PATH+"SlopeGround.js",type:"js"},
{path:LEVEL_FILE_PATH+"level01.js",type:"js"}
];
這些變量各有所用,打頭的那個world是用於保存整個界面對象用的,現在暫時沒有賦值,但在後面大家會看到他是一個Main類的實例,Main類是啥?在後面慢慢講吧。然後JS_FILE_PATH和LEVEL_FILE_PATH是用來保存公共路徑用的。loadData是裝有需要加載資源的數組,這個數組裏的內容寫法有點特殊,不過是個lufylegend的固定寫法,所以必須遵循庫件規範。其中,每一條就是一個資源,可以是圖片,可以是js文件,txt文件,mp3文件……在這裏我用的是加載js,爲什麼不加載圖片呢?因爲沒有用到圖片唄……加載js文件時,需要設置path屬性(也就是js文件路徑),type屬性(用於識別加載方式,加載js則填寫爲"js",txt就是"txt")。當然加載圖片時的寫法就不一樣了,可以參考我以前寫的文章,裏面有加載圖片時的資源列表的寫法。好了,這些變量解釋完了,就來看gameInit部分了:
function gameInit(){
LStage.setDebug(true);
LStage.box2d = new LBox2d();
if(LStage.canTouch == true){
document.body.style.margin = "0px";
document.body.style.padding = "0px";
LStage.stageScale = LStageScaleMode.SHOW_ALL;
LSystem.screen(LStage.FULL_SCREEN);
}
LLoadManage.load(loadData,null,function(){
world = new Main();
addChild(world);
world.init();
});
}
首先,因爲我們的遊戲中暫時沒有圖片,所以爲了看到物理剛體我們就要設置debug模式,也就是通過LStage.setDebug(true)來實現。然後初始lufylegend中的box2d,所以用到了LStage.box2d = new LBox2d()。再來看看接下來的這段代碼:if(LStage.canTouch == true){
document.body.style.margin = "0px";
document.body.style.padding = "0px";
LStage.stageScale = LStageScaleMode.SHOW_ALL;
LSystem.screen(LStage.FULL_SCREEN);
}
這些代碼有啥用呢?還是讓我來告訴你吧,首先開頭的if是爲了限制平臺用的,LStage這個靜態類有一個canTouch屬性,用來判斷是否是移動端。如果是(true),那麼就進入if中的代碼,if中的代碼是爲了設置全屏用的,前兩行是把body元素位置歸0,然後通過LSystem.screen(LStage.FULL_SCREEN)來設置全屏,改LStage.stageScale屬性是爲了設置全屏模式,除了LStageScaleMode.SHOW_ALL之外,還有其他的幾種,具體的還是看API文檔吧。最後到了加載資源這一步驟:
LLoadManage.load(loadData,null,function(){
world = new Main();
addChild(world);
world.init();
});
LLoadManage這個類功能挺強大的,主要是可以將多個數據一起加載到遊戲中(這裏所說的“多個數據”就是上面我們寫的那個名叫loadData的數組,也就是傳給LLoadManage.load的第一個參數)。詳細用法請移步API文檔。這裏只介紹最後一個參數,這個參數是一個function,這個function是在所有資源被加載完成之後調用的。所以說我們遊戲一切的開始都應該是在這個函數中調用的,要不然加載的圖片和js文件就使用不上了呢。當然在這裏這個函數裏的內容就是實例一個Main類出來,然後用addChild這個lufylegend中的方法把他加入到界面中,並調用init這個成員函數來初始化遊戲,這個Main類會在下文作詳細解釋。關於addChild(obj),這個函數是用來添加顯示對象到底層用的,也有對應的spriteObj.addChild(obj),也就是把obj添加到spriteObj裏,這個spriteObj必須是一個LSprite,具體的解釋還是看API文檔吧,講得絕對比我詳細。
ok,遊戲代碼中的index.html講完了。接下來切換主題,蹦到很基礎的lufylegend添加剛體講解。
三,基礎講解
1,什麼是剛體
說實話,這個剛體是什麼我也不是很清楚,我們不妨把它當成一個現實生活中的物體?剛體其實還有一個比較詳細的解釋,這個解釋來自《HTML5 Canvas遊戲開發實戰》一書:剛體表示十分堅硬的物質,它上面任意兩點的位置都是完全不變的,它就像“鑽石”那樣“堅硬”。
2,在lufylegend中,如何創建剛體
在lufylegend中,可以通過box2dweb創建圓形,方形,凸多邊形(凹多邊形也可以,但是box2dweb中對凹多邊形碰撞處理有一些問題,所以不推薦使用)這幾種剛體。
創建圓形剛體
var cLayer = new LSprite();
cLayer.x = 50 + Math.random()*700;
cLayer.y = 50;
addChild(cLayer);
//給LSprite加入圓形剛體
cLayer.addBodyCircle(50,50,50,1,0.5,0.4,0.5);
addBodyCircle(radius, cx, cy, type, density, friction, restitution)■參數:
radius:半徑
cx:圓心座標x
cy:圓心座標y
type:是否動態(1或0)
density:密度
friction:摩擦
restitution:彈性
創建方形剛體
cLayer = new LSprite();
cLayer.x = 50 + Math.random()*700;
cLayer.y = 50;
addChild(cLayer);
//給LSprite加入方形剛體
cLayer.addBodyPolygon(bitmap.getWidth(),bitmap.getHeight(),1,5,.4,.2);
addBodyPolygon(width, height, type, density, friction, restitution)■參數:
width:矩形寬
height:矩形高
type:是否動態(1或0)
density:密度
friction:摩擦
restitution:彈性
創建多邊形剛體
cLayer = new LSprite();
cLayer.x = 50 + Math.random()*700;
cLayer.y = 50;
addChild(cLayer);
//設置多邊形頂點數組
var shapeArray = [
[[0,54],[27,0],[54,54]]
];
//給LSprite添加多邊形剛體
cLayer.addBodyVertices(shapeArray,27,27,1,.5,.4,.5);
addBodyVertices(v, cx, cy, type, density, friction, restitution)
v:頂點數組
cx:中心座標x
cy:中心座標y
type:是否動態(1或0)
density:密度
friction:摩擦
restitution:彈性
ok,有了上面的介紹就可以進入下一個研究環節了。
四,建立各式各樣的地形
1,Main類詳解
首先來看看類的構造器。
function Main(){
var s = this;
base(s,LSprite,[]);
/**設置場景大小*/
s.sceneWidth = 8500;
s.sceneHeight = LStage.height+1000;
}
打頭的那句var s = this;可能就會懵住許多同學,疑問不在於var的使用,而在於爲什麼要這麼寫。其實在這裏不這麼寫,把s換成this應該也可以,但是出於習慣,我還是加上了,原因在於如果以後在構造器裏面出現了多個對象的使用,那麼用this的指向就會出錯。比如說我想在構造器裏面加一個調用事件的函數,然後在這個調用的函數裏面想訪問類的一個屬性,那麼在這個事件監聽函數中用this,指向的應該事件監聽函數,而取不到類這個對象。這時候,用s把this代換掉,在事件監聽函數中用s就可以來訪問類了。第一行代碼解釋了這麼久,相信熟悉js中class的朋友都看得不耐煩了吧。ok,咱們繼續看下一行代碼。類裏面第二行代碼是base函數的運用。這個函數也是lufylegend裏的全局函數,用於繼承某個類。這裏我選擇繼承了LSprite類。說實話,一般在lufylegend中用到繼承,通常都是用的繼承LSprite。繼承了LSprite之後,我們就可以用到LSprite的一系列函數了,包括添加剛體的函數。
接下來的幾行代碼就是添加用於控制場景大小用的屬性。這裏顯得它們很醬油,不過在後面還是用到了的。這兩個屬性顧名思義+註釋,所以,不解釋……(猛然看到LStage.height這個屬性,這個是取canvas大小的屬性,當然,還有LStage.width)
很簡單的構造器代碼就這樣地解釋完畢了。剛纔我們說到Main有個init函數,看看吧?
Main.prototype.init = function(){
var s = this;
/**加入邊框*/
s.addBorder();
/**加入路面*/
s.addRoad();
/**加入自行車*/
s.addBicycle();
/**加入循環事件*/
s.addEventListener(LEvent.ENTER_FRAME,s.loop);
};
在這個函數裏面,全部都是函數的調用。前三個是我自己拓展的,最後一個addEventListener是lufylegend中LSprite擁有的函數。(按理說應該是LInteractiveObject裏纔有addEventListener函數,但是LSprite繼承自這個類,所以我才說到LSprite擁有這個函數)。這裏的addEventListener和原始js中的不同,不過都是加事件用的,這個是仿照ActionScript設計的,參數依此是:[ 事件名稱, 事件觸發函數 ]。在這裏觸發的是loop這個成員函數,這個函數在現在沒有任何作用,不過,過一陣子後,就必須要在裏面做一些處理。接下來我們來到addBorder這個函數:
Main.prototype.addBorder = function(){
var s = this;
/**創建邊框*/
//設置邊框尺寸
var borderSize = 10;
//頂部邊框
var topBorder = new LSprite();
topBorder.x = s.sceneWidth/2;
topBorder.y = 5;
topBorder.addBodyPolygon(s.sceneWidth,borderSize,0);
s.addChild(topBorder);
//右部邊框
var rightBorder = new LSprite();
rightBorder.x = s.sceneWidth-5;
rightBorder.y = s.sceneHeight/2;
rightBorder.addBodyPolygon(borderSize,s.sceneHeight,0);
s.addChild(rightBorder);
//底部邊框
var bottomBorder = new LSprite();
bottomBorder.x = s.sceneWidth/2;
bottomBorder.y = s.sceneHeight-5;
bottomBorder.addBodyPolygon(s.sceneWidth,borderSize,0);
s.addChild(bottomBorder);
//左部邊框
var leftBorder = new LSprite();
leftBorder.x = 5;
leftBorder.y = s.sceneHeight/2;
leftBorder.addBodyPolygon(borderSize,s.sceneHeight,0);
s.addChild(leftBorder);
};
從這裏開始,我們就已經開始用到lufylegend加物理box2d剛體了。在上面的基礎講解中,我們都已經瞭解了這些,所以再加上註釋,這些代碼就不難看懂了。不過也許有朋友會問,加入這些邊框是幹什麼用的?解釋起來很簡單,如果不加邊框,當你加入動態的剛體時,剛體會落到顯示範圍之外,所以爲了防止這一點發生,我們給這個世界加一個限制,把剛體“關”在裏面。然後是addRoad這個函數:
Main.prototype.addRoad = function(){
var s = this;
/**創建路面*/
var roadObj = new Road(0,450);
s.addChild(roadObj);
};
在這裏面我們又用到了Road類,這個類一樣是以後講解,先把Main講完再說吧。然後是addBicycle這個函數
Main.prototype.addBicycle = function(){
var s = this;
//創建自行車對象
s.bicycleObj = new LSprite();
s.bicycleObj.x = 50;
s.bicycleObj.y = 385;
s.bicycleObj.addBodyCircle(30,30,30,1);
s.bicycleObj.setBodyMouseJoint(true);
s.addChild(s.bicycleObj);
};
由於暫時沒有寫關於自行車方面的代碼,所以就先搞個圓形剛體給大家玩玩吧,當然本章重點不在自行車,在地形呢……上面的代碼有一行沒有介紹過,那就是setBodyMouseJoint這個函數。這個函數是讓剛體可以進行鼠標拖拽用的。因爲沒有加入按鍵控制,所以就先來個鼠標操控。最後到了loop函數。這個函數是在講addEventListener時提到過的,忘記了的朋友不妨回憶一下那段代碼。可以看到,我給的事件名稱是LEvent.ENTER_FRAME,這個是哪裏定義的呢?其實你知道了也沒用,我是不會告訴你這個LEvent.ENTER_FRAME的值只是一個字符傳而已。這個LEvent.ENTER_FRAME是lufylegend中的事件軸事件,時間軸事件是個啥?這個雖然問得很好,但是我在很多文章中都講過,所以這裏不講……想了解更多的朋友不妨移步到《HTML5遊戲引擎Lufylegend.js深入淺出》-【基礎篇】-引擎介紹&原理 裏面有詳細的解釋呢。迴歸正題,看看loop裏的代碼吧:
Main.prototype.loop = function(event){
var s = event.target;
var bo = s.bicycleObj;
/**設置場景位置*/
s.x = LStage.width*0.5 - (bo.x + bo.getWidth()*0.5);
s.y = LStage.height*0.5 - (bo.y + bo.getHeight()*0.5);
/**處理位置*/
if(s.x > 0){
s.x = 0;
}else if(s.x < LStage.width - s.sceneWidth){
s.x = LStage.width - s.sceneWidth;
}
if(s.y > 0){
s.y = 0;
}else if(s.y < LStage.height - s.sceneHeight){
s.y = LStage.height - s.sceneHeight;
}
//計算剛體座標
LStage.box2d.synchronous();
};
首先我們通過時間軸事件觸發時傳給監聽函數的event參數的target成員獲取到自身這個對象(Main對象),然後,接下來的處理就是爲了達到跟隨鏡頭的效果。我們知道,在這種同類型的遊戲中,場景通常都很大,如果不把鏡頭跟隨自行車,那麼,自行車開跑了,那就會直接跑到屏幕外去了,看不着了,所以在這裏要處理一下。中間部分代碼很簡單,就是設置一些座標罷了。只有最後一行LStage.box2d.synchronous()可能使大家有點懵。這個是lufylegend中用來重新計算剛體座標用的。我們知道,把剛體加入到界面上後,剛體會進行物理運動,所以我們把剛體所在的LSprite改變了,剛體位置是會不變的,所以我們要把它們的位置重新計算一下好了。好了,Main講完了,該講Road類了。
2,Road類
這個類顧名思義,就是用來創建各種地形的。先看構造器中的代碼:
function Road(sx,sy){
var s = this;
base(s,LSprite,[]);
//設置起始位置
s.sx = sx;
s.sy = sy;
//設置新對象出現位置
s.newObjPosX = s.sx;
s.newObjPosY = s.sy;
/**設置路面數據*/
s.roadData = level01
//初始化
s.init();
}
同樣是一些屬性的設置,很簡單。值得注意的是,roadData這個屬性是賦值level01,這個前面所說的地形數據文件,也就是在data文件夾下的文件。我們先創建一個簡單的地形,所以在level01.js中加入如下代碼:var level01 = [
{
type:Road.TYPE.Ground,
groundWidth:200,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
},
{
type:Road.TYPE.Bridge,
plankWidth:50,
plankHeight:25,
plankAmount:10
},
{
type:Road.TYPE.Ground,
groundWidth:600,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
}
];
這個很明顯是一個數組套json的數據格式,其中數組中每一條都是一個地形區,比如說Road.TYPE.Ground就是代表地面,然後地面又分地形,比如說Road.TERRAIN.Smooth代表平地。每一種不同的地形就會有不同的數據名稱,比如說平地中有groundWidth和groundHeight,而吊橋(Road.TYPE.Bridge)中有plankWidth,plankHeight,plankAmount。至於其他的地形,我們以後再說吧。回到Road類中,我們在上面的level01.js中看到了Road.XXX.xxx,這是在哪裏定義的呢?看這裏吧:
Road.TYPE = {
//鎖鏈橋
Bridge:"bridge",
//地面
Ground:"ground",
//空隙
Spacing:"spacing"
};
Road.TERRAIN = {
//平地
Smooth:"smooth",
//斜坡
Slope:"slope",
//山坡
Hill:"hill"
};
我們暫時先定義這麼多,以後有其他的地形再拓展吧。在Road類的構造器中調用過init這個函數,這個函數的代碼如下:Road.prototype.init = function(){
var s = this;
for(var key in s.roadData){
var item = s.roadData[key];
switch(item.type){
/**路地*/
case Road.TYPE.Ground:
s.addGround(item);
break;
/**鎖橋*/
case Road.TYPE.Bridge:
s.addBridge(item);
break;
/**空隙*/
case Road.TYPE.Spacing:
s.addSpacing(item);
break;
}
}
};
在這個函數中,我們用到for和switch來加入不同的地形。接下來是addGround和addBridge以及addSpacing裏的代碼:Road.prototype.addSpacing = function(data){
var s = this;
//設置新對象出現位置
s.newObjPosX += data.spacingWidth;
s.newObjPosY += data.spacingHeight;
};
Road.prototype.addBridge = function(data){
var s = this;
//加入鎖鏈橋
var bridgeGroundObj = new BridgeGround(
s.newObjPosX,
s.newObjPosY,
data.plankAmount,
data.plankWidth,
data.plankHeight
);
world.addChild(bridgeGroundObj);
//設置新對象出現位置
s.newObjPosX += bridgeGroundObj.getGroundWidth();
};
Road.prototype.addGround = function(data){
var s = this;
switch(data.terrain){
case Road.TERRAIN.Smooth:
//加入平地路面
var smoothGroundObj = new SmoothGround(
s.newObjPosX,
s.newObjPosY,
data.groundWidth,
data.groundHeight
);
world.addChild(smoothGroundObj);
//設置新對象出現位置
s.newObjPosX += smoothGroundObj.getGroundWidth();
break;
case Road.TERRAIN.Hill:
//加入山坡路面
var hillGroundObj = new HillGround(
s.newObjPosX,
s.newObjPosY,
data.slopeAmount,
data.hillWidth,
data.hillHeight,
data.groundWidth,
data.groundHeight
);
world.addChild(hillGroundObj);
//設置新對象出現位置
s.newObjPosX += hillGroundObj.getGroundWidth();
break;
case Road.TERRAIN.Slope:
//加入斜坡路面
var slopeGroundObj = new SlopeGround(
s.newObjPosX,
s.newObjPosY,
data.groundWidth,
data.groundHeight,
data.angle
);
world.addChild(slopeGroundObj);
//設置新對象出現位置
s.newObjPosX += slopeGroundObj.getGroundWidth();
s.newObjPosY += slopeGroundObj.getGroundHeight();
break;
}
};
這看上去是一大串很長的代碼,其實結構相似,主要是需要注意newObjPosX和newObjPosY這個屬性的變換。當這個屬性是用於確定下一個物體出現時的位置。在addSpacing時,沒有其他的什麼物體要加入,所以直接把newObjPosX和newObjPosY改變了就能達到效果。在addBridge中,出現這個類:BridgeGround。這個類就是在上一篇文章中所說的鐵鎖橋類。具體實現過程就看上一篇文章吧:【HTML5物理小Demo】用Box2dWeb實現鎖鏈+彈簧效果介紹完addSpacing和addBridge,我們來到了addGround函數,這個函數和上面兩個有點區別,因爲地面會分很多種地形,所以我們首先要用switch來區分到底添加哪個地形。我打算分成平地路面、斜坡路面、山坡路面。在平地路面中,我們用到了SmoothGround類,在斜坡路面用到了SlopeGround類,山坡路面用的是HillGround。接下來我們依此來看看這些類中的代碼,先是SmoothGround:
function SmoothGround(sx,sy,w,h){
var s = this;
base(s,LSprite,[]);
//保存路面尺寸
s.groundW = w;
s.groundH = h;
//設置位置
s.x = sx+w/2;
s.y = sy;
//初始化
s.init();
}
SmoothGround.prototype.init = function(){
var s = this;
//加入剛體
s.addBodyPolygon(s.groundW,s.groundH,0);
};
SmoothGround.prototype.getGroundWidth = function(){
return this.groundW;
};
SmoothGround.prototype.getGroundHeight = function(){
return this.groundH;
};
這個類是最簡單的,所以就不加以分析了。接下來輪到SlopeGround類了:
function SlopeGround(sx,sy,w,h,angle){
var s = this;
base(s,LSprite,[]);
//保存路面尺寸
s.groundW = w;
s.groundH = h;
//保存坡度
s.angle = angle;
//設置位置
s.x = sx+s.getGroundWidth()/2-(Math.sin(s.angle*Math.PI/180)*s.groundH*0.5);
s.y = sy+s.getGroundHeight()/2;
//初始化
s.init();
}
SlopeGround.prototype.init = function(){
var s = this;
//加入剛體
s.addBodyPolygon(s.groundW,s.groundH,0);
//旋轉剛體
s.setRotate(s.angle*(Math.PI/180));
};
SlopeGround.prototype.getGroundWidth = function(){
return (Math.cos(this.angle*Math.PI/180)*this.groundW);
};
SlopeGround.prototype.getGroundHeight = function(){
return (Math.sin(this.angle*Math.PI/180)*this.groundW);
};
這個類和上一個類很類似,但是唯一不同的是我們在這個類中設置了剛體旋轉,並且在取尺寸用到的函數getGroundHeight、getGroundWidth都用了sin和cos進行處理。設置剛體旋轉用的是setRotate,參數是旋轉的弧度(角度a轉成弧度公式:a*Math.PI/180)。除了這幾點之外,其他就和第一個講的SmoothGround類沒有什麼區別了呢。最後是HillGround,這個類很特殊,需要詳細一點講:
function HillGround(x,y,a,hw,hh,gw,gh){
var s = this;
base(s,LSprite,[]);
//保存山坡尺寸
s.hillW = hw;
s.hillH = hh;
//保存山坡平地部分尺寸
s.groundW = gw;
s.groundH = gh;
//保存山坡數量
s.hillAmount = a;
//設置初始位置
s.sx = x;
s.sy = y;
//初始化
s.init();
}
HillGround.prototype.init = function(){
var s = this;
//加入山坡下方的平地
var ground = new LSprite();
ground.x = s.sx+s.getGroundWidth()/2;
ground.y = s.sy;
ground.addBodyPolygon(s.getGroundWidth(),s.groundH,0);
world.addChild(ground);
//設置山坡頂點初始位置
var toX = 0;
var toY = 0;
//循環添加山坡
for(var i=0; i<s.hillAmount; i++){
//設置山坡頂點數組
var shapeArray = new Array();
shapeArray.push(new Array());
shapeArray[0].push([toX,toY]);
shapeArray[0].push(
[toX+=s.hillW,toY-=s.hillH],
[toX+=s.hillW,toY+=s.hillH]
);
var hill = new LSprite();
hill.x = s.sx+s.groundW;
hill.y = s.sy-s.hillH/2-s.groundH/2;
//繪畫多邊形剛體
hill.addBodyVertices(shapeArray,0,s.hillH/2,0);
//加入顯示列表
world.addChild(hill);
}
};
HillGround.prototype.getGroundWidth = function(){
return this.groundW*2+this.hillAmount*2*this.hillW;
};
HillGround.prototype.getGroundHeight = function(){
return this.groundH+this.hillH;
};
這個類最特別的是init函數部分。在這個函數中,我們爲了添加小山丘,所以要畫一個三角形剛體,實現三角形剛體可以用畫多邊形剛體用的函數addBodyVertices(),這個函數在上面的基礎講解部分提到過,參數需要一個頂點數組,所以我們在init中計算了每個頂點的座標,然後把它們加入到頂點數組中。至於爲什麼要用for,那是因爲考慮到會有連續的小山丘添加的情況。好了,到了這一步算是成功了一大半。
最後拓展level01.js:
var level01 = [
{
type:Road.TYPE.Ground,
groundWidth:200,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
},
{
type:Road.TYPE.Bridge,
plankWidth:50,
plankHeight:25,
plankAmount:10
},
{
type:Road.TYPE.Ground,
groundWidth:600,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
},
{
type:Road.TYPE.Ground,
groundWidth:400,
groundHeight:50,
angle:20,
terrain:Road.TERRAIN.Slope
},
{
type:Road.TYPE.Ground,
groundWidth:400,
groundHeight:50,
angle:30,
terrain:Road.TERRAIN.Slope
},
{
type:Road.TYPE.Ground,
groundWidth:200,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
},
{
type:Road.TYPE.Ground,
hillWidth:230,
hillHeight:50,
groundWidth:40,
groundHeight:50,
slopeAmount:2,
terrain:Road.TERRAIN.Hill
},
{
type:Road.TYPE.Ground,
groundWidth:300,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
},
{
type:Road.TYPE.Ground,
groundWidth:400,
groundHeight:50,
angle:-30,
terrain:Road.TERRAIN.Slope
},
{
type:Road.TYPE.Ground,
groundWidth:200,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
},
{
type:Road.TYPE.Bridge,
plankWidth:50,
plankHeight:25,
plankAmount:20
},
{
type:Road.TYPE.Ground,
groundWidth:600,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
},
{
type:Road.TYPE.Spacing,
spacingWidth:200,
spacingHeight:150,
},
{
type:Road.TYPE.Ground,
groundWidth:600,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
},
{
type:Road.TYPE.Ground,
hillWidth:430,
hillHeight:150,
groundWidth:40,
groundHeight:50,
slopeAmount:1,
terrain:Road.TERRAIN.Hill
},
{
type:Road.TYPE.Ground,
groundWidth:800,
groundHeight:50,
angle:-30,
terrain:Road.TERRAIN.Slope
},
{
type:Road.TYPE.Ground,
groundWidth:600,
groundHeight:50,
terrain:Road.TERRAIN.Smooth
}
];
運行效果:
測試地址:http://www.cnblogs.com/yorhom/articles/box2dweb_bike1.html
打開測試地址後,用鼠標拖動圓球,使鏡頭跟隨小球移動,看看我們創建的地形怎麼樣吧。
源代碼下載:
http://files.cnblogs.com/yorhom/box2dBicycle%281%29.rar
本章就先到這裏了。如果文章有任何疏漏之處,歡迎指正。當然,有不懂之處也歡迎各位在本文下方留言,我會盡力回覆大家的。下一章。我們來研究一下如何用box2dweb+lufylegend創建一個自行車,並讓這輛自行車能受到我們的控制,敬請期待~
----------------------------------------------------------------
歡迎大家轉載我的文章。
轉載請註明:轉自Yorhom's Game Box
http://blog.csdn.net/yorhomwang
歡迎繼續關注我的博客