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

第二章又拖到現在才發佈,話說本次更新離上次已經很久了。不知道大家還記得上一章講的內容否?

在上一章中,我們創建了各式各樣的地形,今天我們就在這個地形之上,創建一輛自行車,並讓它受到我們的控制。首先放上一張截圖:


沒看過上一章的同學可以先移步到上一章。在瞭解上一章的內容之後,讀起本章方可容易一些。

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

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


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


一,基礎知識

首先我們來科普幾個知識,只有瞭解這些知識之後,讀起下面的內容纔不會感覺頭疼。

1,如何使剛體移動

在box2dweb中,要想使剛體移動,不能單純地改變x、y座標。其一,這樣會使你的遊戲失去了物理運動的效果;其二,在box2dweb中,直接調整剛體的位置是一個十分不好的方法,會違揹物理運動原理,只有在剛體創建前的時候才用這個方法。所以在我們的自行車創建出來以後,想要移動剛體,最好的方法就是給剛體施加一個力。

在box2dweb中,施加力的方法有:ApplyForce、ApplyImpulse、SetLinearVelocity。本次使用的只有ApplyForce。其餘的幾種可以暫且不管。如果你確實想了解的話,可以看看ladeng6666的這篇文章:讓剛體聽我的——ApplyForce、ApplyImpulse、SetLinearVelocity

ApplyForce的用法如下:

ApplyForce(vec, pos)

這個函數的第個一個參數是給物體施加力的向量(b2Vec2);第二個pos是施加的位置,一般不確定位置的時候就取物體的重心(可以用b2Body的GetWorldCenter獲取)。


2,如何構造像自行車這樣形狀複雜的物體

如何構造像自行車這樣形狀複雜的物體確實是一個值得思考的話題。對我們而言,第一個出現在腦子裏的想法就是創建一個多邊形剛體。當然這樣實現起來極其麻煩,而且不僅僅是麻煩,做到最後更掃興的是使用這種方法弄出來的剛體碰撞檢測有問題,換言之,一個原本是凹凸多邊形的剛體,突然就成了柔體,碰在其他物體上就會滲入其他物體裏,so qipa~

那到底應該怎麼辦呢?我們不妨假設自己就真正地在製作一輛自行車。首先,我們要把材料和工具準備好。材料就是幾個木塊,工具就是一個錘子,一把鐵釘。接下來,我們要做的就是用錘子鐵釘把幾個木塊組裝起來就ok了。這樣看起來雖然so easy,但是有朋友會問,在box2dweb裏到哪裏去找錘子鐵釘呢?錘子鐵釘在box2dweb裏倒是真沒有,不過有一個更先進的東西——關節(joint)。

在這裏主要就是需要兩種關節:旋轉關節(用於把輪子和支架綁起來),焊接關節(用於把各個支架固定起來)。

這兩個關節在box2dweb裏的使用方法依然不是那麼簡單,因此同樣用到了lufylegend.js的封裝。接下來就對這幾個關節的用法進行說明。

■setRevoluteJoint(b2BodyA, b2BodyB, limits, motors)

b2BodyA:表示物體A (b2Body對象,可以用LSprite的box2dBody屬性獲取)
b2BodyB:表示物體B(b2Body對象,可以用LSprite的box2dBody屬性獲取)
limits:表示旋轉角度限制數組,這個數組的內容是:[最小角度,最大角度],它在這裏可以限制旋轉關節旋轉的角度(可以不傳)
motors:表示馬達數組,這個數組的內容是:[力度,速度],馬達可以有很多用途,在這裏,它可以是關節自動進行旋轉(可以不傳)

示例:

var backLayer,cLayer;
    function main(){    
    LGlobal.setDebug(true); 
    backLayer = new LSprite();  
    addChild(backLayer);    

    LGlobal.box2d = new LBox2d();
    cLayer = new LSprite();
    cLayer.x = 300;
    cLayer.y = 390;
    backLayer.addChild(cLayer);
    cLayer.addBodyPolygon(600,10,0,5,0.4,0.2);
    //加入一個動態的圓形物體1
    box01 = new LSprite();
    box01.x = 250;
    box01.y = 200;
    backLayer.addChild(box01);
    box01.addBodyCircle(100,0,0,1,1,0.4,0.2);
    box01.setBodyMouseJoint(true);
    //加入一個靜態的圓形物體2
    box02 = new LSprite();
    box02.x = 250;
    box02.y = 150;
    backLayer.addChild(box02);
    box02.addBodyCircle(10,0,0,0,1,0.4,0.2);
    //加入一個旋轉關節
    LGlobal.box2d.setRevoluteJoint(box01.box2dBody, box02.box2dBody ,[-360,720*5],[450,2]);
}

■setWeldJoint (b2BodyA, b2BodyB)

b2BodyA:表示捆綁對象物體A(b2Body對象,可以用LSprite的box2dBody屬性獲取)
b2BodyB:表示捆綁對象物體B(b2Body對象,可以用LSprite的box2dBody屬性獲取)

示例:

var backLayer,cLayer;
function main(){    
    LGlobal.setDebug(true); 
    backLayer = new LSprite();  
    addChild(backLayer);    
 
    LGlobal.box2d = new LBox2d();
    cLayer = new LSprite();
    cLayer.x = 300;
    cLayer.y = 390;
    backLayer.addChild(cLayer);
    cLayer.addBodyPolygon(600,10,0,5,0.4,0.2);
    //加入一個動態的圓形物體1
    box01 = new LSprite();
    box01.x = 200;
    box01.y = 100;
    backLayer.addChild(box01);
    box01.addBodyCircle(50,0,0,1,1,0.4,0.2);
    box01.setBodyMouseJoint(true);
    //加入一個動態的圓形物體2
    box02 = new LSprite();
    box02.x = 250;
    box02.y = 100;
    backLayer.addChild(box02);
    box02.addBodyCircle(50,0,0,1,1,0.4,0.2);
    box02.setBodyMouseJoint(true);
    //加入一個焊接關節
    LGlobal.box2d.setWeldJoint(box01.box2dBody, box02.box2dBody);
}

ok,基礎知識差不多講完了。進入正題吧。


二,修改Main類

上一章中的Main類大家還記得否?上次我沒有加入真正的自行車,而是拿一個圓形小球在那裏充當着,主要是給大家看看鏡頭跟隨效果和各式各樣的地形。這次既然要實現一個自行車,那麼就要先把小球換掉,於是更改addBicycle函數,更新後如下:

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

	//創建自行車對象
	s.bicycleObj = new Bicycle(50,385);
	s.addChild(s.bicycleObj);
};
這裏明顯用到了Bicycle這個類。那麼,我就立刻把筆尖指向Bicycle類吧,這個類是本章的重點內容,前面的基礎講解就是爲這個類作鋪墊的。


三,自行車類(Bicycle)

先看看構造器:

function Bicycle(sx,sy){
	var s = this;
	base(s,LSprite,[]);

	/**初始座標*/
	s.sx = sx;
	s.sy = sy;

	/**剛體所屬LSprite對象列表*/
	s.bodyList = new Array();

	/**添加左右移動力度向量*/
	s.moveVec = new LStage.box2d.b2Vec2();
	/**添加拉壓操作力度向量*/
	s.tcVec = new LStage.box2d.b2Vec2();

	/**添加事件*/
	//鍵盤按下事件
	LEvent.addEventListener(window,LKeyboardEvent.KEY_DOWN,function(e){
		s.onKeydown(e,s);
	});
	//鍵盤松開事件
	LEvent.addEventListener(window,LKeyboardEvent.KEY_UP,function(e){
		s.onKeyup(e,s);
	});

	//初始化
	s.init();
}

構造器代碼加入了詳細的註釋,所以我們直接進入講解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.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.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.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和輪子支架的旋轉關節
	LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelAObj.box2dBody);
	//輪子B和輪子支架的旋轉關節
	LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelBObj.box2dBody);
	//車把到輪子的支架和輪子支架的焊接關節
	LStage.box2d.setWeldJoint(frameAObj.box2dBody, frameBObj.box2dBody);
	//車把到輪子的支架和車把的焊接關節
	LStage.box2d.setWeldJoint(handlebarObj.box2dBody, frameBObj.box2dBody);
	//輪子的支架和座椅的焊接關節
	LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, frameAObj.box2dBody);
	//座椅的支架和座椅的焊接關節
	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;
};
這個函數看上去超長,但是邏輯卻異常簡單,大部分是重複的代碼,再通過上面的基礎講解外加註釋,看上去應該是無壓力了吧,當然如果有不懂的,歡迎在本文下方留言。其中,我爲了大家測試時的簡化操作,給每一塊剛體都設置了鼠標拖動,但是又由於剛體有很多,所以我把它們統統裝在bodyList這個數組了,然後最後用遍歷的方式給每一個剛體都設置了鼠標拖動。
最後來看看如何實現鍵盤操作。不難發現,在構造器裏有這麼一段代碼:

/**添加事件*/
//鍵盤按下事件
LEvent.addEventListener(window,LKeyboardEvent.KEY_DOWN,function(e){
	s.onKeydown(e,s);
});
//鍵盤松開事件
LEvent.addEventListener(window,LKeyboardEvent.KEY_UP,function(e){
	s.onKeyup(e,s);
});
在段代碼裏,我們還用到了onKeydown和onKeyup這兩個函數。代碼如下:

Bicycle.prototype.onKeydown = function(e,s){
	var force = 50;
	switch(e.keyCode){
		//向右
		case 39:
			s.moveVec.x = force;
			break;
		//向左
		case 37:
			s.moveVec.x = -force;
			break;
		//向上
		case 38:
			s.tcVec.y = -force;
			break;
		//向下
		case 40:
			s.tcVec.y = force;
			break;
		default:
			return;
	}
	/**施加移動的力*/
	s.mainBody.ApplyForce(s.moveVec,s.mainBody.GetWorldCenter());
	/**施加拉壓的力*/
	s.tcBody.ApplyForce(s.tcVec,s.tcBody.GetWorldCenter());
};
Bicycle.prototype.onKeyup = function(e,s){
	/**清空力度相量*/
	s.moveVec.SetZero();
	s.tcVec.SetZero();
};

在這裏我們用到了之前講過的ApplyForce。之前講的時候,沒有具體講第一個參數是因爲沒有實際用到講了等於徒勞。現在可以看到,這個b2Vec2有x,y屬性,這兩個屬性可以控制力的方向。x<0向左,x>0向右,y>0向下,y<0向上。

當然,tcVec和moveVec是兩個不同的向量,也就是說控制的不是同一個效果。moveVec是控制移動自行車的,這個不用多說。而tcVec是用於拉、壓自行車的,我們知道,當在遊戲中要開翻車的時候,往往可以用拉、壓的方式來操控自行車,使其重新平衡,這個tcVec就是幹這活兒的。

BTW,如果你不知道tcBody和mainBody是什麼,可以看看Bicycle裏init函數的代碼。

添加完鍵盤事件後,我們就可以通過方向鍵來控制自行車了。操作方式爲:上-拉,下-壓,左-後退,右-前進。大家可以打開本文最下方的測試鏈接進行測試。

ok,至此,運行一下代碼,得到的就是本文最上方圖片所示的效果了。(什麼?太醜了?你指的是我還是這個遊戲?這個遊戲啊,沒貼圖當然醜呢。)


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

測試地址:http://www.cnblogs.com/yorhom/articles/box2dweb_bike2.html


下一章預告:目前,咋們的自行車是金剛不壞之身,你怎麼摔它,它就是摔不碎(除非你把顯示器抱起來,然後使勁往地上摔)。但是這不科學對吧……所以在下一章,我們就來看看,如何把這個自行車摔得支離破碎,體無完膚。哈哈,盡情期待~


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

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

歡迎大家轉載我的文章。

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

http://blog.csdn.net/yorhomwang

歡迎繼續關注我的博客

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