注:本系列教程全部翻譯完之后可能會(huì)以 PDF 的形式發(fā)布。
如果有什么錯(cuò)誤可以留言或 EMAIL : kakashi9bi@gmail.com 給我。
jME 版本 : jME_2.0.1_Stable
開(kāi)發(fā)工具: MyEclipse8.5
操作系統(tǒng): Window7/Vista
這個(gè)向?qū)е校覀儗? Flag Rush 構(gòu)建基礎(chǔ)。我們將通過(guò)自己實(shí)現(xiàn)繼承 BaseGame 。我們將使用 BaseGame 做為父類,但之后可能改為其它的游戲類型,因?yàn)? BaseGame 簡(jiǎn)單地盡可能快地進(jìn)行 update 和 render 。我們或許不必或不想使用這種類型的循環(huán)。然而,現(xiàn)在 BaseGame 是一個(gè)循環(huán)無(wú)關(guān)的類。在以后,改變 BaseGame 將不是重點(diǎn),因?yàn)橹皇莻魅? update 和 render 方法的值不同而已。
我們將開(kāi)始創(chuàng)建一個(gè)繼承自 BaseGame 的新類。你會(huì)注意到有 6 個(gè)需要實(shí)現(xiàn)的方法: update 、 render 、 initSystem 、 initGame 和 reinit 。現(xiàn)在,只需要為它們創(chuàng)建一個(gè)存根方法,我們將在后面將自己的邏輯填充進(jìn)去。
import com.jme.app.BaseGame;
public class Lesson2 extends BaseGame{
public static void main(String[] args) {
}
protected void cleanup() {
}
protected void initGame() {
}
protected void initSystem() {
}
protected void reinit() {
}
protected void render( float arg0) {
}
protected void update( float arg0) {
}
}
2.1 、 Main
那么,讓我們從最初開(kāi)始。我們?cè)谶@里將再次創(chuàng)建 main 方法。它很像前一個(gè)向?qū)У? main 方法,除了一個(gè)關(guān)鍵的地方不同。這次我們將顯示 FlagRush 的迷人的新 logo 。 AbstractGame 定義了一對(duì) setConfigShowMode 方法,其中的一個(gè)接受一個(gè) URL 類用于加載 Image 。因此,我們將加載 FlagRush.png (迷人的 logo )并把它傳給這個(gè)方法。現(xiàn)在,當(dāng) PropertiesDialog 被顯示時(shí),它將顯示新的 Logo 。
public static void main(String[] args) {
Lesson2 app = new Lesson2();
java.net.URL url =
app.getClass().getClassLoader()
.getResource( "jmetest/data/images/FlagRush.png" );
app.setConfigShowMode(ConfigShowMode. AlwaysShow ,url);
app.start();
}
現(xiàn)在,當(dāng) PropertiesDialog 出現(xiàn)時(shí),它將像下面這個(gè)一樣(你應(yīng)該在項(xiàng)目中新建一個(gè) package —— jmetest.data.images ,然后里面有一張叫 FlagRush.png 的圖片):
2.2 、 InitSystem
現(xiàn)在,你能運(yùn)行你的應(yīng)用程序,但它僅僅是顯示 PropertiesDialog ,除此之外不會(huì)做更多的工作。我們下一步將實(shí)現(xiàn) initSystem 方法。這個(gè)方法在進(jìn)入主循環(huán)之前由 BaseGame 調(diào)用。這正是我們?cè)O(shè)置 window 和 display 的地方。我們將保存 width , height , depth , frequency 和 fullscreen 標(biāo)志。我們將在后面使用這些值,假如用戶想改變分辨率的時(shí)候。所以,首先,讓我們創(chuàng)建變量去保存這些值:
public class Lesson2 extends BaseGame{
private int width , height ;
private int freq , depth ;
private boolean fullscreen ;
……………………….
我們也需要在我們的程序中保存 Camera ,所以我們也應(yīng)該為那創(chuàng)建一個(gè)變量。
// 我們的 camera 對(duì)象,用于觀看 scene
private Camera cam ;
最后將初始化的一項(xiàng)是 Timer , Timer 將允許我們獲取我們的幀率。所以,同樣的,這將是一個(gè)實(shí)例變量。
protected Timer timer ;
現(xiàn)在我們已經(jīng)準(zhǔn)備好我們的實(shí)例變量,并且我們將在 initSystem 中初始化它們。
protected void initSystem() {
// 保存屬性信息
width = settings .getWidth();
height = settings .getHeight();
depth = settings .getDepth();
freq = settings .getFrequency();
fullscreen = settings .isFullscreen();
try {
display = DisplaySystem. getDisplaySystem (
settings .getRenderer()
);
display .createWindow(
width , height , depth , freq , fullscreen
);
cam = display .getRenderer().createCamera( width , height );
} catch (JmeException e){
e.printStackTrace();
System. exit (-1);
}
// 設(shè)置背景為黑色
display .getRenderer().setBackgroundColor(ColorRGBA. black );
// 初始化攝像機(jī)
cam .setFrustumPerspective(
45.0f,
( float ) width /( float ) height ,
1f,
1000f
);
Vector3f loc = new Vector3f(0.0f,0.0f,25.0f);
Vector3f left = new Vector3f(-1.0f,0.0f,0.0f);
Vector3f up = new Vector3f(0.0f,1.0f,0.0f);
Vector3f dir = new Vector3f(0.0f,0.0f,-1.0f);
// 將攝像機(jī)移到正確位置和方向
cam .setFrame(loc, left, up, dir);
// 我們改變自己的攝像機(jī)位置和視錐的標(biāo)志
cam .update();
// 獲取一個(gè)高分辨率用于 FPS 更新
timer = Timer. getTimer ();
display .getRenderer().setCamera( cam );
KeyBindingManager. getKeyBindingManager ().set(
"exit" ,
KeyInput. KEY_ESCAPE
);
}
這是一個(gè)長(zhǎng)的方法,所以,我們將一點(diǎn)一點(diǎn)討論它。
// 保存屬性信息
width = settings .getWidth();
height = settings .getHeight();
depth = settings .getDepth();
freq = settings .getFrequency();
fullscreen = settings .isFullscreen();
首先,我們保存從 properties 對(duì)象( properties 是由 AbstractGame 創(chuàng)建的)獲取的值。通過(guò)保存這些值,當(dāng)用戶以后從系統(tǒng)菜單改變屏幕設(shè)置的時(shí)候,我們可以很容易地修改它們中的一個(gè)或全部值。
try {
display = DisplaySystem. getDisplaySystem (
settings .getRenderer()
);
display .createWindow(
width , height , depth , freq , fullscreen
);
cam = display .getRenderer().createCamera( width , height );
} catch (JmeException e){
e.printStackTrace();
System. exit (-1);
}
下一步,我們獲取新的 DisplaySystem ,并通過(guò)先前獲得的屏幕參數(shù)創(chuàng)建一個(gè)本地窗口。我們接著使用 DisplaySystem 去創(chuàng)建一個(gè) Camera 對(duì)象。你將注意到那用一個(gè) try/catch 塊包圍。如果我們嘗試創(chuàng)建一個(gè)系統(tǒng)沒(méi)能力繪制的窗口,異常將在這里出現(xiàn)。目前,它只會(huì)退出,但之后,我們將讓這個(gè)顯示得更友好,并通知用戶。
// 設(shè)置背景為黑色
display .getRenderer().setBackgroundColor(ColorRGBA. black );
我們接著設(shè)置了窗口的背景顏色。當(dāng)沒(méi)有其它數(shù)據(jù)被渲染的時(shí)候,這是顯示的默認(rèn)顏色。我選擇黑色,這是因?yàn)樗臀覀兒竺鎸⑹褂玫娜魏挝谋拘纬甚r明的對(duì)比。不管怎樣,這都不是重點(diǎn),因?yàn)楫?dāng)一切正常工作時(shí),屏幕上通常覆蓋其它的數(shù)據(jù)。
// 初始化攝像機(jī)
cam .setFrustumPerspective(
45.0f,
( float ) width /( float ) height ,
1f,
1000f
);
Vector3f loc = new Vector3f(0.0f,0.0f,25.0f);
Vector3f left = new Vector3f(-1.0f,0.0f,0.0f);
Vector3f up = new Vector3f(0.0f,1.0f,0.0f);
Vector3f dir = new Vector3f(0.0f,0.0f,-1.0f);
// 將攝像機(jī)移到正確位置和方向
cam .setFrame(loc, left, up, dir);
// 我們改變自己的攝像機(jī)位置和視錐的標(biāo)志
cam .update();
display .getRenderer().setCamera( cam );
下一步,我設(shè)置了 camera 。我想要一個(gè)標(biāo)準(zhǔn)的 camera ,正常情況下是右手坐標(biāo)系統(tǒng)(向上是正 Y ,向右是正 X 和向屏幕里面是 -Z )。我同時(shí)設(shè)置了透視圖為 45 度視角。這個(gè)是大多數(shù)游戲里面的公認(rèn)標(biāo)準(zhǔn),而它將應(yīng)用于 Flag Rush 。在 camera 數(shù)據(jù)設(shè)置之后,我們調(diào)用 update ,這將設(shè)置所有的 OpenGL 組件,例如視點(diǎn)(下文以 ViewPort 代替)和 Frustum 。
// 獲取一個(gè)高分辨率用于 FPS 更新
timer = Timer. getTimer ();
這里只是初始化 Timer ,從本地 Timer 獲取(例如 LWJGLTimer )。
KeyBindingManager. getKeyBindingManager ().set(
"exit" ,
KeyInput. KEY_ESCAPE
);
最后,我們創(chuàng)建一個(gè)新的 InputSystem ,將它綁定到我們的 KeyBindingManager 并設(shè)置一個(gè)輸入行為( Input action )。在這個(gè)框架中我們只關(guān)心一個(gè)按鍵—— Escape 。在這個(gè)例子中,我們?cè)O(shè)置 action “ exit ”給 Escape 鍵。 KeyBindingManager 是一個(gè)單例類,它使用單一的 get 調(diào)用,關(guān)注了所有 InputSystem 組件的初始化。
現(xiàn)在,如果你運(yùn)行系統(tǒng)你將真正獲得一個(gè)屏幕顯示。它將充滿黑色(我們?cè)O(shè)置的背景顏色),沒(méi)有任何東西。
2.3 、 InitGame
現(xiàn)在,我們擁有一個(gè)窗口和 OpenGL 上下文環(huán)境,我們將加載我們的游戲數(shù)據(jù)(如上面前個(gè)向?qū)У? Sphere )
protected void initGame() {
scene = new Node( "Scene Graph Node" );
// 創(chuàng)建我們的球體
Sphere s = new Sphere( "sphere" , 30, 30, 25);
s.setLocalTranslation( new Vector3f(0, 0, -40));
s.setModelBound( new BoundingBox());
s.updateModelBound();
ts = display .getRenderer().createTextureState();
ts . setEnabled ( true );
ts .setTexture(
TextureManager. loadTexture (
Lesson2. class .getClassLoader()
.getResource( "res/logo.png" ),
Texture.MinificationFilter. Trilinear ,
Texture.MagnificationFilter. Bilinear
)
);
s.setRenderState( ts );
scene .attachChild(s);
// 更新 scene 用于渲染
scene .updateGeometricState(0.0f, true );
scene .updateRenderState();
}
我們現(xiàn)在保存我們自己的 Scene Graph 結(jié)點(diǎn),我已經(jīng)選擇把它命名為 scene ,但實(shí)際上怎樣命名都是沒(méi)關(guān)系。因?yàn)檫@是 scene 的根節(jié)點(diǎn),它也是一個(gè)實(shí)例變量而它和其他實(shí)例變量一樣被聲明:
private Node scene ;
這個(gè) Node 接著被實(shí)例化。接著我們創(chuàng)建了一個(gè) Sphere 和 TextureState (就像上一個(gè)的向?qū)В? Sphere 接著被 attach 到 scene 。這個(gè)看起來(lái)將和我們上一個(gè)向?qū)龅暮芟嗨啤H欢F(xiàn)在,我們還調(diào)用 updateGeometricState 和 updateRenderState 。這些方法為 SceneGraph updates 調(diào)用。 updateGeometricState 是必須的,不管場(chǎng)景圖( Scene Graph )結(jié)構(gòu)在何時(shí)改變(設(shè)置一個(gè)新的,改變另一個(gè)的參數(shù),等等),在我們的例子中,我增加了一個(gè) sphere 到到 scene 。不管 RenderState 在什么時(shí)候以何種方式發(fā)生改變, updateRenderState 都應(yīng)該被調(diào)用(比如創(chuàng)建一個(gè)新的 RenderState 、改變它的參數(shù)等等),在我們的例子中,我們?cè)黾恿? TextureState 。
我們現(xiàn)在擁有游戲中的數(shù)據(jù),但它仍然沒(méi)被渲染到屏幕。
2.4 、 Render 和 update
既然我們已經(jīng)初始化了窗口并加載了數(shù)據(jù),如果能看到它將更好。那就是 render 方法的到來(lái)。 BaseGame 調(diào)用 update 并根據(jù)它的能力盡可能快地 render 。 render 的調(diào)用需要處理所有繪畫調(diào)用,而 update 應(yīng)該處理任何的游戲邏輯。在我們的例子中,我們想要 update 做一點(diǎn)游戲邏輯,退出游戲。為了簡(jiǎn)單退出游戲,我們將設(shè)置 finished 布爾值為 true 。
/*
* 在 update 期間,我們只需尋找 Escape 按鈕
* 并更新 timer 去獲取幀率
*/
protected void update( float interpolation) {
// 更新 timer 去獲取幀率
timer .update();
interpolation = timer .getTimePerFrame();
// 當(dāng) Escape 被按下時(shí),我們退出游戲
if (KeyBindingManager. getKeyBindingManager ()
.isValidCommand( "exit" )
){
finished = true ;
}
}
你也將注意到 update 獲取最新的 timer 讀數(shù)并為此設(shè)置插值( interpolation )。 BaseGame 通常在調(diào)用 update 時(shí)發(fā)送 -1 ,所以我們將繼續(xù)并重用這個(gè)值去保存每幀真正的時(shí)間。
接下來(lái),我們將渲染。
/*
* 繪制場(chǎng)景圖
*/
protected void render( float interpolation) {
// 清除屏幕
display .getRenderer().clearBuffers();
display .getRenderer().draw( scene );
}
這個(gè)直截了當(dāng)。我們使用 clearBuffers 清除屏幕。我們接著畫了 scene ,這是包含我們 Sphere 的樹(shù)。
你現(xiàn)在能運(yùn)行程序并看到:
正是和前一課的顯示一樣,只不過(guò)沒(méi)了燈光。
2.5 、 reinit 和 cleanup
最后我們將覆蓋的 2 個(gè)方法是 reinit 和 cleanup 。當(dāng)窗口需要重建時(shí), Reinit 應(yīng)該被調(diào)用,就像參數(shù)發(fā)生了變化。而在關(guān)閉的時(shí)候調(diào)用 cleanup 。
/*
* 如果分辨率改變將被調(diào)用
*/
protected void reinit() {
display .recreateWindow( width , height , depth , freq , fullscreen );
}
我們?cè)谶@里所做的就只是傳遞新的值給 DisplaySystem 處理。僅此而已。
/*
* 清除 texture
*/
protected void cleanup() {
ts .deleteAll();
}
這簡(jiǎn)單確保了 texture 被刪除。這不是特別必須的,因?yàn)? OpenGL 在它退出時(shí)將處理這個(gè)。但“寧可事先謹(jǐn)慎有余,切莫事后追悔莫及”。
2.6 、總結(jié)
很好,就是那樣。我們現(xiàn)在有一個(gè)很基本、可工作的框架。通過(guò)創(chuàng)建我們自己的應(yīng)用程序類型,我們能完全保持對(duì)我們場(chǎng)景中一切的控制。隨著向?qū)У睦^續(xù),我們將很明確地增強(qiáng)并構(gòu)建基于這個(gè)類的程序。
2.7 、源碼
import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
public class Lesson2 extends BaseGame{
private int width , height ;
private int freq , depth ;
private boolean fullscreen ;
// 我們的 camera 對(duì)象,用于觀看 scene
private Camera cam ;
protected Timer timer ;
private Node scene ;
private TextureState ts ;
public static void main(String[] args) {
Lesson2 app = new Lesson2();
java.net.URL url = app.getClass().getClassLoader().getResource( "res/logo.png" );
app. setConfigShowMode (ConfigShowMode. AlwaysShow ,url);
app.start();
}
/*
* 清除 texture
*/
protected void cleanup() {
ts .deleteAll();
}
protected void initGame() {
scene = new Node( "Scene Graph Node" );
// 創(chuàng)建我們的球體
Sphere s = new Sphere( "sphere" , 30, 30, 25);
s.setLocalTranslation( new Vector3f(0, 0, -40));
s.setModelBound( new BoundingBox());
s.updateModelBound();
ts = display .getRenderer().createTextureState();
ts .setEnabled( true );
ts .setTexture(
TextureManager. loadTexture (
Lesson2. class .getClassLoader().getResource( "res/logo.png" ),
Texture.MinificationFilter. Trilinear ,
Texture.MagnificationFilter. Bilinear
)
);
s.setRenderState( ts );
scene .attachChild(s);
// 更新 scene 用于渲染
scene .updateGeometricState(0.0f, true );
scene .updateRenderState();
}
protected void initSystem() {
// 保存屬性信息
width = settings .getWidth();
height = settings .getHeight();
depth = settings .getDepth();
freq = settings .getFrequency();
fullscreen = settings .isFullscreen();
try {
display = DisplaySystem. getDisplaySystem (
settings .getRenderer()
);
display .createWindow(
width , height , depth , freq , fullscreen
);
cam = display .getRenderer().createCamera( width , height );
} catch (JmeException e){
e.printStackTrace();
System. exit (-1);
}
// 設(shè)置背景為黑色
display .getRenderer().setBackgroundColor(ColorRGBA. black );
// 初始化攝像機(jī)
cam .setFrustumPerspective(
45.0f,
( float ) width /( float ) height ,
1f,
1000f
);
Vector3f loc = new Vector3f(0.0f,0.0f,25.0f);
Vector3f left = new Vector3f(-1.0f,0.0f,0.0f);
Vector3f up = new Vector3f(0.0f,1.0f,0.0f);
Vector3f dir = new Vector3f(0.0f,0.0f,-1.0f);
// 將攝像機(jī)移到正確位置和方向
cam .setFrame(loc, left, up, dir);
// 我們改變自己的攝像機(jī)位置和視錐的標(biāo)志
cam .update();
// 獲取一個(gè)高分辨率用于 FPS 更新
timer = Timer. getTimer ();
display .getRenderer().setCamera( cam );
KeyBindingManager. getKeyBindingManager ().set(
"exit" ,
KeyInput. KEY_ESCAPE
);
}
/*
* 如果分辨率改變將被調(diào)用
*/
protected void reinit() {
display .recreateWindow( width , height , depth , freq , fullscreen );
}
/*
* 繪制場(chǎng)景圖
*/
protected void render( float interpolation) {
// 清除屏幕
display .getRenderer().clearBuffers();
display .getRenderer().draw( scene );
}
/*
* 在 update 期間,我們只需尋找 Escape 按鈕
* 并更新 timer 去獲取幀率
*/
protected void update( float interpolation) {
// 更新 timer 去獲取幀率
timer .update();
interpolation = timer .getTimePerFrame();
// 當(dāng) Escape 被按下時(shí),我們退出游戲
if (KeyBindingManager. getKeyBindingManager ()
.isValidCommand( "exit" )
){
finished = true ;
}
}
}
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(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ì)您有幫助就好】元
