jMonkeyEngine译文 FlagRush2——从你的应用程

系统 1633 0

注:本系列教程全部翻译完之后可能会以 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 的图片):

jMonkeyEngine译文 FlagRush2——从你的应用程序中移除SimpleGame

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 的树。

你现在能运行程序并看到:

正是和前一课的显示一样,只不过没了灯光。

jMonkeyEngine译文 FlagRush2——从你的应用程序中移除SimpleGame

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 ;

}

}

}

jMonkeyEngine译文 FlagRush2——从你的应用程序中移除SimpleGame


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论