HTML5物理遊戲開發 - 越野山地自行車(三)粉碎自行車

自上一章發佈到現在已時隔四月,實在對不住大家,讓大家久等了~話說不是我不關注我的博客,而是事情一多起來寫博客的時間就少了。待到今日有空了,回頭看了看自己以前寫的文章,猛得發現已經四個月不曾寫文章了,便只得叫聲:“苦也~”,我害怕本系列文章會拖得更久,於是立刻提筆,也好爲本系列文章留個鳳尾。

首先,大家來溫習一下前面兩篇裏的內容吧:

HTML5物理遊戲開發 - 越野山地自行車(二)創建一輛可操控的自行車

http://blog.csdn.net/yorhomwang/article/details/21300253

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

http://blog.csdn.net/yorhomwang/article/details/19710537


今天我們要實現的內容就是——當我們自行車的關鍵部分碰壁後,立刻使其粉碎,通俗點講就是“自行車碎一地”。Ok,閒話何苦多說呢,我們就開始咯~

先放上兩張截圖吧:





※再次聲明,本次開發用到了lufylegend.js開源html5遊戲引擎和box2dweb物理引擎,請到官方網站下載使用。官方網站地址已在第一章中說過了。


一,粉碎原理

用過錘子的人都知道(如果你沒用過,而且也不知道怎麼用,建議你去問問雷神索爾),要砸碎一個自行車該怎麼砸呢?如果你不會,我教你三招吧:

法一:使勁砸;這種方法適用於你想換把錘子

法二:到阿斯嘉找雷神大哥去,這個速率最快,估計不到抽完一根菸的工夫,你的自行車就只剩原子了

法三:去某個地方把錘子換成螺絲刀等工具,然後把你那自行車零件一塊一塊地給卸下來

顯然,這三種方法各有所長,不過既然我們的自行車是一塊一塊地拼起來的,那麼還是一塊一塊地給拆了好,於是,我選擇了3(實際上是因爲Box2dWeb沒有錘子這玩意,也認不得雷神)。我們在上一章中提到過如何把零件拼起來,原理是運用了Box2dWeb裏的關節,這些關節把零件們連在了一起,那麼如果這些關節一銷燬,那麼這些零件就會散落。但是如何銷燬關節呢?Box2dWeb的b2World裏有一個DestroyJoint函數,參數就是你要銷燬的b2Joint對象。我們來看看在lufylegend裏如何銷燬關節吧。

首先,在lufylegend裏通過LBox2d關鍵關節的函數都會返回創建出的b2Joint對象,也就是說:

var j = LStage.box2d.setRevoluteJoint(a.box2dBody, b.box2dBody);
LStage.box2d.world.DestroyJoint(j);
我們可以把調用setRevoluteJoint創建出的旋轉關節保存在變量j裏,然後銷燬時就直接調用LStage.box2d.world.DestroyJoint函數,參數則是j。

如果我們有多個關節怎麼辦呢?放數組裏唄,想必聰明的你在我說這話之前就已經想到了這點吧。
原理搞定,那麼我們開始看代碼吧。


二,更新自行車類和Main類

上一章中我們着重講了如何實現自行車類,這次因爲要粉碎它,所以要先在它身上動手術,安放幾個炸彈再說。裝炸彈前要做好準備,以免把自己給炸over了,所以得先搞個盒子把要炸燬的地方裝起來。恰巧Main類路過(關於Main請看第一章),我把它一手提了過來,唱聲喏,道:“閣下暫且替我裝兩個東西,如何?”,Main類尋思道:“這廝可以掌管所有程序,萬一這廝火了,一把delete把我等刪個乾淨卻不是個好事。”,於是他無可奈何地替我擔負了這個重任。卻說看官欲知是哪兩個東西呢?原來是一個名叫jointList的數組和一個喚作gameOverController的bool型變量;這兩廝一個管裝所有生成的關節,一個管遊戲是否結束。於是Main的構造器改造後如下:

function Main(){
	var s = this;
	base(s,LSprite,[]);

	/**設置場景大小*/
	s.sceneWidth = 8500;
	s.sceneHeight = LStage.height+1000;

	/**關節列表*/
	s.jointList = new Array();
	/**遊戲結束控制器*/
	s.gameOverController = false;
}

卻說上面那段明顯有些水滸的風格,不好意思,近期《水滸傳》看多了,望各位包含包含。

我們知道,炸彈都是要有點火線的,或者更高級一點的開關啊,反正就是一個引爆裝置,這個工作還是交給Main吧。修改Main的init方法:

Main.prototype.init = function(){
	var s = this;

	/**加入邊框*/
	s.addBorder();
	/**加入路面*/
	s.addRoad();
	/**加入自行車*/
	s.addBicycle();
	/**加入剛體碰撞事件*/
	LStage.box2d.setEvent(LEvent.POST_SOLVE,s.postSolve);
	/**加入循環事件*/
	s.addEventListener(LEvent.ENTER_FRAME,s.loop);
};

主要是加了個調用LStage.box2d.setEvent函數。這個函數是LBox2d類的一個方法(LStage.box2d是LBox2d的實例化對象),具體的使用方法是這樣子滴:

■setEvent(type, func)

參數介紹:

type box2d世界裏的碰撞事件類型

func 觸發事件時調度的函數

碰撞事件的類型可以爲:

LEvent.BEGIN_CONTACT:剛剛碰撞開始的時候會觸發這個函數
LEvent.END_CONTACT:碰撞結束的時候會觸發這個函數
LEvent.POST_SOLVE:碰撞後會處理這個函數
LEvent.PRE_SOLVE:碰撞前即將碰撞的時候


這裏我們選的是POST_SOLVE,也就是碰撞後會處理這個函數,其實選擇其他的事件類型,效果也應該是一樣的。事件觸發時調度的函數是postSolve,這個函數也交給Main吧~ [Main類:說好的裝兩個呢(T_T)]

Main.prototype.postSolve = function(contact){
	if(world.gameOverController)return;
	var l = world.jointList;
	if(l.length == 0)return;
	//獲取碰撞的LSprite對象
	var cA = contact.GetFixtureA().GetBody().GetUserData();
	var cB = contact.GetFixtureB().GetBody().GetUserData();
	//判斷是否摧毀自行車
	if(
		//--------------------------------------------
		//條件一:當自行車和牆碰撞時
		//--------------------------------------------
		(
			(cA.name=="wall" && cB.name=="bicycle")
			||
			(cA.name=="bicycle" && cB.name=="wall")
		)
		||
		//--------------------------------------------
		//條件二:當自行車的車把、車把到輪子的支架或者車座碰到其他物體時
		//--------------------------------------------
		(
			(cA.trigger=="destroy_bicycle" && cB.name!="bicycle")
			||
			(cA.name!="bicycle" && cB.trigger=="destroy_bicycle")
		)
	){
		//去掉自行車上的所有關節以達到催毀自行車
		for(var i in l){
			var jo = l[i];
			//去掉關節
			LStage.box2d.world.DestroyJoint(jo);
			//將遊戲結束控制器設置爲遊戲結束
			world.gameOverController = true;
		}
		//從自行車關節列表中移除所有關節
		l.length = 0;

		//添加遊戲結束提示
		var gameOverText = new LTextField();
		gameOverText.text = "Game Over";
		gameOverText.size = 50;
		gameOverText.alpha = 0;
		gameOverText.x = (LStage.width-gameOverText.getWidth())*0.5;
		gameOverText.y = (LStage.height-gameOverText.getHeight())*0.5;
		addChild(gameOverText);
		LTweenLite.to(gameOverText,5,{
			delay:1.5,
			alpha:1
		});
	}
};

該函數會接受一個參數,參數是個啥對象呢?其實我也不清楚,反正裏面有GetFixtureA和GetFixtureB這兩個函數。這兩個函數能取到正在碰撞的剛體的剛形,通過剛形的GetBody取到b2Body對象,然後用b2Body的GetUserData取到最終的LSprite對象。

參數介紹完了,我還是來介紹一下這個函數的執行邏輯吧。首先,如果遊戲已經結束或者jointList爲空的數組,則不再運行後面的代碼;如果繼續運行代碼,則首先把碰撞的兩個剛體所在的LSprite取出來,然後進行遊戲結束判斷,如果通過則執行遊戲結束的代碼。有人也許納悶那個world是個啥?如果你從第一章看起,就應該會明白了。主要是postSolve是個回調函數,裏面的this不是指向Main的。


在那個長達19行的判斷條件裏,我設定了兩個條件,滿足這兩個條件之一,便進行銷燬關節:條件一:當自行車和牆碰撞時;條件二:當自行車的車把、車把到輪子的支架或者車座碰到其他物體時。

銷燬部分的代碼主要是注意,在box2d裏,銷燬關節用DestroyJoint這個函數,這一點在“粉碎原理”中就已經提到過了,這個函數是在b2World類中的,LBox2d的world屬性就是b2World的實例化對象。

能不能結束遊戲關鍵要看碰撞的b2Body所在的LSprite對象的name和trigger(英文翻譯過來是“觸發器”的意思),這些屬性在哪裏設置的呢?當然是在構造自行車的類Bicycle類裏呢。接下來就來看看Bicycle類里加入&改進的代碼。

首先來看Bicycle的init函數的變化:

Bicycle.prototype.init = function(){
	var s = this;

	var sx = s.sx;
	var sy = s.sy; 

	/**輪子半徑*/
	var wheelR = 20;
	/**輪子之間的距離*/
	var gapBetweenWheelAndWheel = 100;
	/**車手柄到輪子的距離*/
	var gapBetweenWheelAndHandlebar = 50;
	/**車把尺寸*/
	var handlebarWidth=20,handlebarHeight=5;
	/**座椅到輪子支架的距離*/
	var gapBetweenWheelFrameAndSeat = 30;
	/**座椅尺寸*/
	var seatWidth=30,seatHeight=5;
	/**支架尺寸*/
	var frameSize = 10;

	/**加入支架*/
	//輪子上的支架
	var frameAObj = new LSprite();
	frameAObj.x = sx+gapBetweenWheelAndWheel/2;
	frameAObj.y = sy+frameSize/2;
	frameAObj.addBodyPolygon(gapBetweenWheelAndWheel,frameSize,1,5);
	world.addChild(frameAObj);
	s.bodyList.push(frameAObj);
	//車把到輪子的支架
	var frameBObj = new LSprite();
	frameBObj.trigger = "destroy_bicycle";
	frameBObj.x = sx+gapBetweenWheelAndWheel-frameSize/2;
	frameBObj.y = sy-gapBetweenWheelAndHandlebar/2;
	frameBObj.addBodyPolygon(frameSize,gapBetweenWheelAndHandlebar,1,2);
	world.addChild(frameBObj);
	s.bodyList.push(frameBObj);

	/**加入車把*/
	var handlebarObj = new LSprite();
	handlebarObj.trigger = "destroy_bicycle";
	handlebarObj.x = sx+gapBetweenWheelAndWheel-handlebarWidth/2-frameSize;
	handlebarObj.y = sy-gapBetweenWheelAndHandlebar+handlebarHeight/2;
	handlebarObj.addBodyPolygon(handlebarWidth,handlebarHeight,1,.5);
	world.addChild(handlebarObj);
	s.bodyList.push(handlebarObj);

	/**加入座椅*/
	//座椅到輪子支架的支架
	var seatFrameObj = new LSprite();
	seatFrameObj.x = sx+30;
	seatFrameObj.y = sy-gapBetweenWheelFrameAndSeat/2;
	seatFrameObj.addBodyPolygon(frameSize,gapBetweenWheelFrameAndSeat,1,1);
	world.addChild(seatFrameObj);
	s.bodyList.push(seatFrameObj);
	//座椅
	var seatObj = new LSprite();
	seatObj.trigger = "destroy_bicycle";
	seatObj.x = sx+30;
	seatObj.y = sy-gapBetweenWheelFrameAndSeat-seatHeight/2;
	seatObj.addBodyPolygon(seatWidth,seatHeight,1,.5);
	world.addChild(seatObj);
	s.bodyList.push(seatObj);

	/**加入輪子*/
	//左邊輪子A
	var wheelAObj = new LSprite();
	wheelAObj.x = sx-wheelR;
	wheelAObj.y = sy;
	wheelAObj.addBodyCircle(wheelR,wheelR,wheelR,1,2.5,.2,.4);
	world.addChild(wheelAObj);
	s.bodyList.push(wheelAObj);
	//右邊輪子B
	var wheelBObj = new LSprite();
	wheelBObj.x = sx+gapBetweenWheelAndWheel-wheelR;
	wheelBObj.y = sy;
	wheelBObj.addBodyCircle(wheelR,wheelR,wheelR,1,2.5,.2,.4);
	world.addChild(wheelBObj);
	s.bodyList.push(wheelBObj);
	
	/**添加關節*/
	//輪子A和輪子支架的旋轉關節
	world.jointList.push(LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelAObj.box2dBody));
	//輪子B和輪子支架的旋轉關節
	world.jointList.push(LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelBObj.box2dBody));
	//車把到輪子的支架和輪子支架的焊接關節
	world.jointList.push(LStage.box2d.setWeldJoint(frameAObj.box2dBody, frameBObj.box2dBody));
	//車把到輪子的支架和車把的焊接關節
	world.jointList.push(LStage.box2d.setWeldJoint(handlebarObj.box2dBody, frameBObj.box2dBody));
	//輪子的支架和座椅的焊接關節
	world.jointList.push(LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, frameAObj.box2dBody));
	//座椅的支架和座椅的焊接關節
	world.jointList.push(LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, seatObj.box2dBody));
	
	/**遍歷所有自行車零件剛體*/
	for(var key in s.bodyList){
		var obj = s.bodyList[key];
		//加入鼠標拖動
		if(obj.box2dBody)obj.setBodyMouseJoint(true);
		//設置對象名稱
		obj.name = "bicycle";
	}

	/**設置主剛體*/
	s.mainBody = frameAObj.box2dBody;
	/**設置拉壓操作剛體*/
	s.tcBody = wheelBObj.box2dBody;
};
主要改的是這裏:

/**添加關節*/
//輪子A和輪子支架的旋轉關節
world.jointList.push(LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelAObj.box2dBody));
//輪子B和輪子支架的旋轉關節
world.jointList.push(LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelBObj.box2dBody));
//車把到輪子的支架和輪子支架的焊接關節
world.jointList.push(LStage.box2d.setWeldJoint(frameAObj.box2dBody, frameBObj.box2dBody));
//車把到輪子的支架和車把的焊接關節
world.jointList.push(LStage.box2d.setWeldJoint(handlebarObj.box2dBody, frameBObj.box2dBody));
//輪子的支架和座椅的焊接關節
world.jointList.push(LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, frameAObj.box2dBody));
//座椅的支架和座椅的焊接關節
world.jointList.push(LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, seatObj.box2dBody));
我把所有的關節都加入到wolrd的jointList裏了,這樣一來我們就可以通過遍歷取出關節來,然後進行銷燬,這一點在Main的postSolve裏就已經實現了。

還有修改的就是:1,給所有的屬於自行車的剛體都加了一個name屬性,設定爲:“bicycle”;2,給關鍵部位的剛體(車把、車把到輪子的支架和車座)加了trigger屬性,設置爲“destroy_bicycle”,表示如果這些部位碰到了其他不屬於自行車的剛體,就結束遊戲。

至於name爲"wall"的剛體其實就只有一個(bottomBorder底部邊框,促使自行車跌到底部時結束遊戲):

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.name = "wall";
	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);
};
Ok,運行代碼,得到的就是本文最上方圖片所示的效果了。


奉上源代碼下載地址:http://files.cnblogs.com/yorhom/box2dBicycle%283%29.rar

測試地址:http://yuehaowang.github.io/demo/ridebike_box2d/

本系列教程就到此爲止了,其實如果要做一個真正的“越野山地自行車”這種遊戲,還需要對剛體進行貼圖,勝利判斷等。這些都很簡單,大家可以自己動手做一做吧~(目前我做的demo應該可以當作一種發泄工具吧,壓力大了,就來把這輛虛擬的自行車拿來狠狠地摔吧~ 哈哈)

還有同學(這會兒可是真正意義上的同學了...)問我如何操作,好吧,算我失誤,沒有在本章告訴大家(其實你看了第兩章就會知道的),在這裏補充一下。操作說明:上下左右鍵操作,至於這些按鍵對應的效果就自己摸索吧,實在總結不出來就去第二章慢慢找吧,我也就偷個懶吧~


本章就先到這裏了。如果文章有任何疏漏之處,歡迎指正。當然,有不懂之處也歡迎各位在本文下方留言,我會盡力回覆大家的。

----------------------------------------------------------------

歡迎大家轉載我的文章。

轉載請註明:轉自Yorhom's Game Box

http://blog.csdn.net/yorhomwang

歡迎繼續關注我的博客

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