HTML5物理遊戲開發 - 越野山地自行車(一)建立各式各樣的地形

在上一篇文章中,我們研究了一下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

歡迎繼續關注我的博客

發佈了67 篇原創文章 · 獲贊 45 · 訪問量 93萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章