5.4 、讓我們?cè)黾油婕?
對(duì)于這個(gè)向?qū)В覀儗⒅皇鞘褂靡粋€(gè)占位符代替交通工具。我們將在之后載入模型,但那只是沒(méi)價(jià)值的工作,我們想要先讓游戲的核心能運(yùn)作。一個(gè) Box 是一個(gè)好的占位符,因?yàn)樗俏覀兘煌üぞ叩幕A(chǔ)模型。
所以,讓我們先增加一個(gè) buildPlayer 的方法并在 initGame 中調(diào)用它。我們將接著創(chuàng)建一個(gè) box 做為玩家的幾何體并把這個(gè) Box attach 到 node 。這個(gè)玩家 Node 將會(huì)是一個(gè)類變量,以便我們能在 update 期間訪問(wèn)它。我將創(chuàng)建一個(gè)中心為 (0,0,0) 和大小為 (0.35,0.25,0.5) ,讓它看起來(lái)長(zhǎng)和寬。 Node 接著被移到坐標(biāo) (100,0,100) 。我還沒(méi)設(shè)置它的高度,我將在之后才那么做。
private void buildPlayer() {
//box 代替
Box b = new Box( "box" , new Vector3f(), 0.35f,0.25f,0.5f);
b.setModelBound( new BoundingBox());
b.updateModelBound();
player = new Node( "Player Node" );
player .setLocalTranslation( new Vector3f(100,0, 100));
scene .attachChild( player );
player .attachChild(b);
player .updateWorldBound();
}
如果你現(xiàn)在運(yùn)行這個(gè),實(shí)際上不會(huì)看到 player ,因?yàn)樗钕菰? terrain 下面。我們?cè)? update 里面設(shè)置 height ,這是因?yàn)槲覀儗⒑芸熳尳煌üぞ咴谄矫嫔弦苿?dòng),并需要讓它保持在 terrain 上。所以,為了保持 box 在地面的頂部行駛,增加:
// 確保當(dāng)玩家離開(kāi)平面時(shí)我們不會(huì)墜落。
// 當(dāng)我們?cè)黾記_突時(shí), fence 將做它自己的工作并保持玩家在里面。
float characterMinHeight =
tb .getHeight( player .getLocalTranslation()) +
((BoundingBox) player .getWorldBound()). yExtent ;
if (
!Float. isInfinite (characterMinHeight) &&
!Float. isNaN (characterMinHeight)
)
player .getLocalTranslation(). y = characterMinHeight;
首先,我們獲取玩家當(dāng)前位置對(duì)應(yīng)的 terrain 的高度。接著加上 BoundingBix 的偏移,我們這么做是因?yàn)? Box 位置的點(diǎn)是 Box 的中心。如果我們沒(méi)加上偏移, box 將有一半沉入地下。我們使用包圍對(duì)象的 BoundingBox 去獲取對(duì)象的高度( yExtent )(但實(shí)際上如果你對(duì)模型了解得很好,你可以使用值代替)。最后,我們檢查獲取的高度去確認(rèn)沒(méi)有得到一些糟糕的值(非數(shù)字、無(wú)窮大等)。我們這么做是因?yàn)槟壳拔覀儧](méi)做任何事去阻止玩家駕駛出 terrain 。
我們現(xiàn)在已經(jīng)在 terrain 上擁有了玩家!
( 現(xiàn)在你可能還看不到這個(gè)畫(huà)面,別急,后面會(huì)看到的 )
5.5 、跟隨攝像機(jī)( ChaseCamera )
好了,現(xiàn)在我們有了玩家,我們應(yīng)該有能力移動(dòng)它。這將是一個(gè)第三人稱游戲,意味我們?cè)谕嬗螒虻臅r(shí)候能看到自己的玩家(而不是以 player 的眼睛去看)。所以我們想要攝像頭一直指向玩家并跟隨他。為了這么做,我們將使用 ChaseCamera 。 ChaseCamera 將通過(guò)定義它跟隨距離的參數(shù)一直追蹤一個(gè)給出的對(duì)象。 ChaseCamera 也定義一些值讓它平滑跟隨。那就是它不能突然轉(zhuǎn)向玩家。這種突然性的效果當(dāng)然也是可以定義的。
所以,當(dāng)我們使用 ChaseCamera ,視圖將一直對(duì)著玩家。鼠標(biāo)將允許 camera 在玩家四周旋轉(zhuǎn)并一直面向它。鼠標(biāo)滾輪將允許 camera 縮放(盡管在這個(gè)例子中縮放值很小)。
因此,創(chuàng)建一個(gè) buildChaseCamera 方法并從 initGame 中調(diào)用它。我們?cè)谶@里設(shè)置 ChaseCamera 的參數(shù)并創(chuàng)建它。 ChaseCamera 對(duì)象將成為一個(gè)類變量以致我們能 update 它(所以把它加到類的頂部)。
我們相對(duì) ChaseCamera 設(shè)置的參數(shù)有一些。首先,我們將設(shè)置 Camera 的目標(biāo)偏移玩家。我們通常想讓 camera 看起來(lái)在玩家上一點(diǎn)。所以我們?cè)O(shè)置偏移( offset )值為( 0 ,玩家的 Y*1.5 , 0 ) . 這將讓 camera 指向指向一個(gè)在玩家原始高度上面多一半的一個(gè)點(diǎn)。下一步,我們將設(shè)置滾動(dòng)( rollout )值。這些值決定了我們能拉近或推遠(yuǎn)攝像機(jī)多少。我這里不想給太多自由,因此這個(gè)級(jí)別實(shí)際上很小。所以我們?cè)O(shè)置最大為 6 個(gè)單元,而最小為 3 個(gè)單元。下一步我們將設(shè)置 camera 能向上轉(zhuǎn)動(dòng)多高,在這個(gè)例子中為 45 度,注意是弧度。最后,我們將為 camera 設(shè)置開(kāi)始起點(diǎn)的球形坐標(biāo), roll out 為 5 并升高 30 度。因?yàn)? camera 在一個(gè)“彈簧”系統(tǒng)中,如果交通工具行駛太快時(shí),它能延遲落后一定距離。因此,我們將增加 camera 能落后的最小和最大值。 8 和 2 應(yīng)該是可以的。
我們?cè)谝粋€(gè) hash map 中設(shè)置這些參數(shù)。
private void buildChaseCamera() {
Vector3f targetOffset = new Vector3f();
targetOffset. y =
((BoundingBox) player .getWorldBound()). yExtent *1.5f;
HashMap<String, Object> props = new HashMap<String, Object>();
props.put(ThirdPersonMouseLook. PROP_MAXROLLOUT , "6" );
props.put(ThirdPersonMouseLook. PROP_MINROLLOUT , "3" );
props.put(
ThirdPersonMouseLook. PROP_MAXASCENT ,
"" +45*FastMath. DEG_TO_RAD
);
props.put(
ChaseCamera. PROP_INITIALSPHERECOORDS ,
new Vector3f(5,0,30*FastMath. DEG_TO_RAD )
);
props.put(ChaseCamera. PROP_TARGETOFFSET , targetOffset);
chaser = new ChaseCamera( cam , player , props);
chaser .setMaxDistance(8);
chaser .setMinDistance(2);
}
我們現(xiàn)在已經(jīng)設(shè)置好了自己的 ChaseCamera ,但它在調(diào)用 update 方法之前不會(huì)產(chǎn)生任何作用。因此在 update 中加入:
chaser . update (interpolation);
現(xiàn)在,當(dāng)應(yīng)用程序運(yùn)行時(shí),你能看到 camera 在它初始化的位置并光滑地把鏡頭拉近直到最大的 6 個(gè)單元。你能接著移動(dòng)鼠標(biāo)去圍繞 box 旋轉(zhuǎn) camera ,也能滾動(dòng)鼠標(biāo)滑輪去將 camera 拉近或推遠(yuǎn)一點(diǎn)。那就是所激動(dòng)的,但沒(méi)有什么東西可以追蹤,因?yàn)? box 只是停在那里。讓我們糾正那個(gè)。
這里補(bǔ)充一點(diǎn)是作者漏掉的,我們還需要在 game 的 update 方面里面加入下面代碼以保證 camera 的位置一直在 terrain 上面:
// 我們不想 chase camera 走到世界下面,因此讓它一直在水平面上 2 個(gè)單元。
if ( cam .getLocation(). y < ( tb .getHeight( cam .getLocation())+2)) {
cam .getLocation(). y = tb .getHeight( cam .getLocation()) + 2;
cam .update();
}
5.6 、我們自定義的輸入處理
我們將創(chuàng)建自己的輸入處理器( InputHandler )從而允許我們駕駛交通工具。這個(gè) handler 的目標(biāo)是允許我們行駛向前、向后和轉(zhuǎn)向。我想這些控制的鍵被設(shè)置為: WASD 。幸運(yùn)的是,為了做到這個(gè),我們將能使用在 jME 中構(gòu)建的 action 。名字是, KeyNodeForwardAction , KeyNodeBackwardAction , KeyNodeRotateRightAction 和 KeyNodeRotateLeftAction 。這些 action 處理一個(gè) node 的旋轉(zhuǎn)和移動(dòng),這些都基于速度和傳入的時(shí)間。
InputAction 很直觀。你簡(jiǎn)單將觸發(fā)器( trigger )賦給 action ,并把鍵( key )賦給這些觸發(fā)器。然后在每次 update 期間它將檢查是否有任何鍵被按下,如果它們是 trigger 賦予的按鍵,那么則讓 trigger 去調(diào)用相應(yīng)的 action 。
創(chuàng)建一個(gè)新的叫做 FlagRushInputHandler 的類,它繼承自 InputHandler 。這個(gè)類將只有 2 個(gè)方法, setKeyBindings 和 setActions 。 setKeyBindings 將創(chuàng)建 KeyBindingManager 并賦予 W,A,S,D 到相應(yīng)的 trigger 名字,而 setActions 將為每個(gè) trigger 創(chuàng)建 InputAction 。
FlagRushInputHandler.java
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.action.KeyNodeBackwardAction;
import com.jme.input.action.KeyNodeForwardAction;
import com.jme.input.action.KeyNodeRotateLeftAction;
import com.jme.scene.Spatial;
/**
* 游戲的 InputHnadler 。這控制了一個(gè)給出的 Spatial
* 允許我們?nèi)グ阉耙啤⑼笠坪妥笥倚D(zhuǎn)。
* @author John
*
*/
public class FlagRushInputHandler extends InputHandler {
/**
* 提供用于控制的 node 。 api 將處理 input 的創(chuàng)建
* @param node 我們想移動(dòng)的那個(gè) node
* @param api library 將處理 input 的創(chuàng)建
*/
public FlagRushInputHandler(Spatial node, String api){
setKeyBindings(api);
setActions(node);
}
/**
* 將 action 類賦給 trigger 。這些 action 處理結(jié)點(diǎn)前移、后移和旋轉(zhuǎn)
* @param node 用于控制的結(jié)點(diǎn)
*/
private void setActions(Spatial node) {
KeyNodeForwardAction forward =
new KeyNodeForwardAction(node,30f);
addAction(forward, "forward" , true );
KeyNodeBackwardAction backward =
new KeyNodeBackwardAction(node,15f);
addAction(backward, "backward" , true );
KeyNodeRotateLeftAction rotateLeft =
new KeyNodeRotateLeftAction(node,5f);
rotateLeft.setLockAxis(
node.getLocalRotation().getRotationColumn(1)
);
addAction(rotateLeft, "turnLeft" , true );
KeyNodeRotateRightAction rotateRight =
new KeyNodeRotateRightAction(node,5f);
rotateRight.setLockAxis(
node.getLocalRotation().getRotationColumn(1)
);
addAction(rotateRight, "turnRight" , true );
}
/**
* 創(chuàng)建 keyboard 對(duì)象,當(dāng)鍵被按下時(shí)允許我們獲取鍵盤(pán)的值。
* 它接著設(shè)置 action 作為觸發(fā)器的基礎(chǔ),如果確認(rèn)了鍵被按下( WASD )
* @param api
*/
private void setKeyBindings(String api) {
KeyBindingManager keyboard =
KeyBindingManager. getKeyBindingManager ();
keyboard.set( "forward" , KeyInput. KEY_W );
keyboard.set( "backward" , KeyInput. KEY_S );
keyboard.set( "turnLeft" , KeyInput. KEY_A );
keyboard.set( "turnRight" , KeyInput. KEY_D );
}
}
當(dāng)這個(gè)類真的寫(xiě)完后,我們?cè)谧约旱挠螒蛑惺褂谩?chuàng)建一個(gè) buildInput 方法,由 initGame 方法調(diào)用。這個(gè)方法將只有一行:
input = new FlagRushInputHandler(
player ,
settings . getRenderer ()
);
正如你所猜的,這里 input 也是類變量。為什么要在類里面呢?因?yàn)槟銓⒃谟螒虻? update 期間調(diào)用它的 update 。
就是那樣!不管相信與否,我們現(xiàn)在具有做游戲的條件。 Box 能被駕駛。現(xiàn)在試試看。注意 ChaseCamera 將會(huì)落后于 box 一點(diǎn)然后嘗試趕上,帶來(lái)更平滑和真實(shí)的感覺(jué)。
接下來(lái),我們將改進(jìn) box 的移動(dòng)以便它能加速和減速。我們也將讓它和環(huán)境交互得更好。繼續(xù)收看!
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

