自上一章發佈到現在已時隔四月,實在對不住大家,讓大家久等了~話說不是我不關注我的博客,而是事情一多起來寫博客的時間就少了。待到今日有空了,回頭看了看自己以前寫的文章,猛得發現已經四個月不曾寫文章了,便只得叫聲:“苦也~”,我害怕本系列文章會拖得更久,於是立刻提筆,也好爲本系列文章留個鳳尾。
首先,大家來溫習一下前面兩篇裏的內容吧:
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
歡迎繼續關注我的博客