注:本系列教程全部翻譯完之后可能會以 PDF 的形式發布。
如果有什么錯誤可以留言或 EMAIL : kakashi9bi@gmail.com 給我。
jME 版本 : jME_2.0.1_Stable
開發工具: MyEclipse8.5
操作系統: Window7/Vista
這個向導中,我們將為 Flag Rush 構建基礎。我們將通過自己實現繼承 BaseGame 。我們將使用 BaseGame 做為父類,但之后可能改為其它的游戲類型,因為 BaseGame 簡單地盡可能快地進行 update 和 render 。我們或許不必或不想使用這種類型的循環。然而,現在 BaseGame 是一個循環無關的類。在以后,改變 BaseGame 將不是重點,因為只是傳入 update 和 render 方法的值不同而已。
我們將開始創建一個繼承自 BaseGame 的新類。你會注意到有 6 個需要實現的方法: update 、 render 、 initSystem 、 initGame 和 reinit 。現在,只需要為它們創建一個存根方法,我們將在后面將自己的邏輯填充進去。
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
那么,讓我們從最初開始。我們在這里將再次創建 main 方法。它很像前一個向導的 main 方法,除了一個關鍵的地方不同。這次我們將顯示 FlagRush 的迷人的新 logo 。 AbstractGame 定義了一對 setConfigShowMode 方法,其中的一個接受一個 URL 類用于加載 Image 。因此,我們將加載 FlagRush.png (迷人的 logo )并把它傳給這個方法。現在,當 PropertiesDialog 被顯示時,它將顯示新的 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();
}
現在,當 PropertiesDialog 出現時,它將像下面這個一樣(你應該在項目中新建一個 package —— jmetest.data.images ,然后里面有一張叫 FlagRush.png 的圖片):
2.2 、 InitSystem
現在,你能運行你的應用程序,但它僅僅是顯示 PropertiesDialog ,除此之外不會做更多的工作。我們下一步將實現 initSystem 方法。這個方法在進入主循環之前由 BaseGame 調用。這正是我們設置 window 和 display 的地方。我們將保存 width , height , depth , frequency 和 fullscreen 標志。我們將在后面使用這些值,假如用戶想改變分辨率的時候。所以,首先,讓我們創建變量去保存這些值:
public class Lesson2 extends BaseGame{
private int width , height ;
private int freq , depth ;
private boolean fullscreen ;
……………………….
我們也需要在我們的程序中保存 Camera ,所以我們也應該為那創建一個變量。
// 我們的 camera 對象,用于觀看 scene
private Camera cam ;
最后將初始化的一項是 Timer , Timer 將允許我們獲取我們的幀率。所以,同樣的,這將是一個實例變量。
protected Timer timer ;
現在我們已經準備好我們的實例變量,并且我們將在 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);
}
// 設置背景為黑色
display .getRenderer().setBackgroundColor(ColorRGBA. black );
// 初始化攝像機
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);
// 將攝像機移到正確位置和方向
cam .setFrame(loc, left, up, dir);
// 我們改變自己的攝像機位置和視錐的標志
cam .update();
// 獲取一個高分辨率用于 FPS 更新
timer = Timer. getTimer ();
display .getRenderer().setCamera( cam );
KeyBindingManager. getKeyBindingManager ().set(
"exit" ,
KeyInput. KEY_ESCAPE
);
}
這是一個長的方法,所以,我們將一點一點討論它。
// 保存屬性信息
width = settings .getWidth();
height = settings .getHeight();
depth = settings .getDepth();
freq = settings .getFrequency();
fullscreen = settings .isFullscreen();
首先,我們保存從 properties 對象( properties 是由 AbstractGame 創建的)獲取的值。通過保存這些值,當用戶以后從系統菜單改變屏幕設置的時候,我們可以很容易地修改它們中的一個或全部值。
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 ,并通過先前獲得的屏幕參數創建一個本地窗口。我們接著使用 DisplaySystem 去創建一個 Camera 對象。你將注意到那用一個 try/catch 塊包圍。如果我們嘗試創建一個系統沒能力繪制的窗口,異常將在這里出現。目前,它只會退出,但之后,我們將讓這個顯示得更友好,并通知用戶。
// 設置背景為黑色
display .getRenderer().setBackgroundColor(ColorRGBA. black );
我們接著設置了窗口的背景顏色。當沒有其它數據被渲染的時候,這是顯示的默認顏色。我選擇黑色,這是因為它和我們后面將使用的任何文本形成鮮明的對比。不管怎樣,這都不是重點,因為當一切正常工作時,屏幕上通常覆蓋其它的數據。
// 初始化攝像機
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);
// 將攝像機移到正確位置和方向
cam .setFrame(loc, left, up, dir);
// 我們改變自己的攝像機位置和視錐的標志
cam .update();
display .getRenderer().setCamera( cam );
下一步,我設置了 camera 。我想要一個標準的 camera ,正常情況下是右手坐標系統(向上是正 Y ,向右是正 X 和向屏幕里面是 -Z )。我同時設置了透視圖為 45 度視角。這個是大多數游戲里面的公認標準,而它將應用于 Flag Rush 。在 camera 數據設置之后,我們調用 update ,這將設置所有的 OpenGL 組件,例如視點(下文以 ViewPort 代替)和 Frustum 。
// 獲取一個高分辨率用于 FPS 更新
timer = Timer. getTimer ();
這里只是初始化 Timer ,從本地 Timer 獲取(例如 LWJGLTimer )。
KeyBindingManager. getKeyBindingManager ().set(
"exit" ,
KeyInput. KEY_ESCAPE
);
最后,我們創建一個新的 InputSystem ,將它綁定到我們的 KeyBindingManager 并設置一個輸入行為( Input action )。在這個框架中我們只關心一個按鍵—— Escape 。在這個例子中,我們設置 action “ exit ”給 Escape 鍵。 KeyBindingManager 是一個單例類,它使用單一的 get 調用,關注了所有 InputSystem 組件的初始化。
現在,如果你運行系統你將真正獲得一個屏幕顯示。它將充滿黑色(我們設置的背景顏色),沒有任何東西。
2.3 、 InitGame
現在,我們擁有一個窗口和 OpenGL 上下文環境,我們將加載我們的游戲數據(如上面前個向導的 Sphere )
protected void initGame() {
scene = new Node( "Scene Graph Node" );
// 創建我們的球體
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();
}
我們現在保存我們自己的 Scene Graph 結點,我已經選擇把它命名為 scene ,但實際上怎樣命名都是沒關系。因為這是 scene 的根節點,它也是一個實例變量而它和其他實例變量一樣被聲明:
private Node scene ;
這個 Node 接著被實例化。接著我們創建了一個 Sphere 和 TextureState (就像上一個的向導)。 Sphere 接著被 attach 到 scene 。這個看起來將和我們上一個向導所做的很相似。然而,現在,我們還調用 updateGeometricState 和 updateRenderState 。這些方法為 SceneGraph updates 調用。 updateGeometricState 是必須的,不管場景圖( Scene Graph )結構在何時改變(設置一個新的,改變另一個的參數,等等),在我們的例子中,我增加了一個 sphere 到到 scene 。不管 RenderState 在什么時候以何種方式發生改變, updateRenderState 都應該被調用(比如創建一個新的 RenderState 、改變它的參數等等),在我們的例子中,我們增加了 TextureState 。
我們現在擁有游戲中的數據,但它仍然沒被渲染到屏幕。
2.4 、 Render 和 update
既然我們已經初始化了窗口并加載了數據,如果能看到它將更好。那就是 render 方法的到來。 BaseGame 調用 update 并根據它的能力盡可能快地 render 。 render 的調用需要處理所有繪畫調用,而 update 應該處理任何的游戲邏輯。在我們的例子中,我們想要 update 做一點游戲邏輯,退出游戲。為了簡單退出游戲,我們將設置 finished 布爾值為 true 。
/*
* 在 update 期間,我們只需尋找 Escape 按鈕
* 并更新 timer 去獲取幀率
*/
protected void update( float interpolation) {
// 更新 timer 去獲取幀率
timer .update();
interpolation = timer .getTimePerFrame();
// 當 Escape 被按下時,我們退出游戲
if (KeyBindingManager. getKeyBindingManager ()
.isValidCommand( "exit" )
){
finished = true ;
}
}
你也將注意到 update 獲取最新的 timer 讀數并為此設置插值( interpolation )。 BaseGame 通常在調用 update 時發送 -1 ,所以我們將繼續并重用這個值去保存每幀真正的時間。
接下來,我們將渲染。
/*
* 繪制場景圖
*/
protected void render( float interpolation) {
// 清除屏幕
display .getRenderer().clearBuffers();
display .getRenderer().draw( scene );
}
這個直截了當。我們使用 clearBuffers 清除屏幕。我們接著畫了 scene ,這是包含我們 Sphere 的樹。
你現在能運行程序并看到:
正是和前一課的顯示一樣,只不過沒了燈光。
2.5 、 reinit 和 cleanup
最后我們將覆蓋的 2 個方法是 reinit 和 cleanup 。當窗口需要重建時, Reinit 應該被調用,就像參數發生了變化。而在關閉的時候調用 cleanup 。
/*
* 如果分辨率改變將被調用
*/
protected void reinit() {
display .recreateWindow( width , height , depth , freq , fullscreen );
}
我們在這里所做的就只是傳遞新的值給 DisplaySystem 處理。僅此而已。
/*
* 清除 texture
*/
protected void cleanup() {
ts .deleteAll();
}
這簡單確保了 texture 被刪除。這不是特別必須的,因為 OpenGL 在它退出時將處理這個。但“寧可事先謹慎有余,切莫事后追悔莫及”。
2.6 、總結
很好,就是那樣。我們現在有一個很基本、可工作的框架。通過創建我們自己的應用程序類型,我們能完全保持對我們場景中一切的控制。隨著向導的繼續,我們將很明確地增強并構建基于這個類的程序。
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 對象,用于觀看 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" );
// 創建我們的球體
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);
}
// 設置背景為黑色
display .getRenderer().setBackgroundColor(ColorRGBA. black );
// 初始化攝像機
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);
// 將攝像機移到正確位置和方向
cam .setFrame(loc, left, up, dir);
// 我們改變自己的攝像機位置和視錐的標志
cam .update();
// 獲取一個高分辨率用于 FPS 更新
timer = Timer. getTimer ();
display .getRenderer().setCamera( cam );
KeyBindingManager. getKeyBindingManager ().set(
"exit" ,
KeyInput. KEY_ESCAPE
);
}
/*
* 如果分辨率改變將被調用
*/
protected void reinit() {
display .recreateWindow( width , height , depth , freq , fullscreen );
}
/*
* 繪制場景圖
*/
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();
// 當 Escape 被按下時,我們退出游戲
if (KeyBindingManager. getKeyBindingManager ()
.isValidCommand( "exit" )
){
finished = true ;
}
}
}
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
