jMonkeyEngine 中文翻译(1-10)


UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    jMonkeyEngine  Flag Rush  系列教程        注:本系列教程目前作者只出了前 10 篇,故本人翻译也只能到此。  如果有什么错误可以到http://blog.csdn.net/kakashi8841留言。您的关注将是我最大的动力。      jME 版本 :  jME_2.0.1_Stable(可在 Google Code 下载到)  开发工具:  MyEclipse8.5  (本人所使用的 IDE,不要求一致,你喜欢的话用记事本也行) 开发环境:  Window7/Vista(只是本人翻译时测试代码的环境,不要求一致)          jMonkeyEngine.org/wiki  著  蔡俊鸿  译  2010‐10‐18  到  2010‐11‐03      1    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  1、  通过SimpleGame创建你的第一个应用程序 .................................................................. 4  1.1、SimpleGame介绍 ............................................................................................................. 4  1.2、显示一些东西 .................................................................................................................. 5  1.3、源码 ................................................................................................................................. 6  2、  从你的应用程序中移除SimpleGame .............................................................................. 8  2.1、Main ................................................................................................................................. 8  2.2、InitSystem ......................................................................................................................... 9  2.3、InitGame ......................................................................................................................... 13  2.4、Render和update ............................................................................................................ 14  2.5、reinit和cleanup .............................................................................................................. 15  2.6、总结 ............................................................................................................................... 16  2.7、源码 ............................................................................................................................... 16  3、  加载地形 ......................................................................................................................... 20  3.1、创建一个heightmap ...................................................................................................... 21  3.2、生成Terrain Mesh .......................................................................................................... 21  3.3、生成Texture .................................................................................................................... 22  3.4、创建灯光(Light) ........................................................................................................ 24  3.5、总结 ............................................................................................................................... 25  3.6、源码 ............................................................................................................................... 26  4、  创建环境 ......................................................................................................................... 31  4.1、准备和代码修改 ............................................................................................................ 31  4.2、深度缓冲(Depth Buffer) ........................................................................................... 32  4.3、创建环境(手动) ........................................................................................................ 33  4.4、守卫塔和SharedMesh ................................................................................................... 33  4.5、剩余的框架 .................................................................................................................... 34  4.6、透明和RenderQueue ..................................................................................................... 37  4.7、让Force Field的texture运动 .......................................................................................... 38  4.8、将force‐field放置在terrain上 ........................................................................................ 39  4.9、环绕的Skybox ................................................................................................................ 40  4.10、总结 ............................................................................................................................. 41  4.11、源码 ............................................................................................................................. 42  5、  增加一个跟随摄像机(Chase Camera) ........................................................................... 53  5.1、清理和优化 .................................................................................................................... 53  5.2、ForceFieldFence.java ...................................................................................................... 54  5.3、剔除/挑选状态(CullState) ............................................................................................. 59  5.4、让我们增加玩家 ............................................................................................................ 59  5.5、跟随摄像机(ChaseCamera) ...................................................................................... 61  5.6、我们自定义的输入处理 ................................................................................................ 62  5.7、源码 ............................................................................................................................... 65  6、  控制交通工具 ................................................................................................................. 74  6.1、介绍 ............................................................................................................................... 74  6.2、增加一个交通工具 ........................................................................................................ 74  6.3、Vehicle.java .................................................................................................................... 76  6.4、Actions ............................................................................................................................ 79  2    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  6.5、FlagRushHandler.java ..................................................................................................... 81  6.6、AccelerateAction.java .................................................................................................... 83  6.7、BrakeAction.java ............................................................................................................. 84  6.8、VehicleRotateLeftAction.java ......................................................................................... 84  6.9、VehicleRotateRightAction.java ....................................................................................... 86  6.10、DriftAction.java ............................................................................................................ 87  6.11、总结 ............................................................................................................................. 87  7、拥抱大地让我们驾驶的不再是Box ......................................................................................... 88  7.1、优化 ............................................................................................................................... 88  7.2、Detail Texture(细节纹理)和TextureCombining(纹理组合) ................................ 91  7.3、Terrain法向(Normal)和朝向(Orientation) .......................................................... 92  7.4、加载模型(model) ..................................................................................................... 93  7.5、结论 ............................................................................................................................... 95  7.6、源码 ............................................................................................................................... 95  8、增加随机的Flag ...................................................................................................................... 106  8.1、介绍 ............................................................................................................................. 106  8.2、优化 ............................................................................................................................. 106  8.3、向转弯的方向倾斜 ...................................................................................................... 107  8.4、旋转轮胎 ...................................................................................................................... 109  8.5、增加一个Flag对象 ....................................................................................................... 111  8.6、为旗杆增加布 .............................................................................................................. 114  8.7、源码 ............................................................................................................................. 118  9、墙壁检测系统(Detection System) .................................................................................... 128  9.1、介绍 ............................................................................................................................. 128  9.2、冲突(Collision)接口 ................................................................................................ 129  9.3、墙壁Detection System ................................................................................................. 130  9.4、粒子 ............................................................................................................................. 132  9.5、Lesson9.java ................................................................................................................. 133  9.6、CollisionDetection.java ................................................................................................. 144  10、夺取Flag ................................................................................................................................ 148  10.1、介绍 ........................................................................................................................... 148  10.2、Flag夺取计算 ............................................................................................................. 148  10.3、移除Flag的计时随机位置 ......................................................................................... 149  10.4、增加检查去看player是否夺取了Flag ....................................................................... 149  10.5、Vehicle.java ................................................................................................................ 149  10.6、Lesson10.java ............................................................................................................. 155  „  附录: ................................................................................................................................... 167  A、摄像机简介 .................................................................................................................... 167  a、Camera .................................................................................................................... 168  b、Camera Node .......................................................................................................... 170    3    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  1、 通过SimpleGame创建你的第一个应 用程序  1.1、SimpleGame介绍  SimpleGame 是包含在 jME 包中默认的应用程序类型。SimpleGame 尝试为你关心所有的 事。这让它很容易构建起原型并运行。它设置了所有的元素,诸如:Camera、InputHandle、 基础 RenderState 等等。首先,我将运行一个由 SimpleGame 创建的简单的应用程序,它向 屏幕绘制了一个球体(下文使用 Sphere 代替),接着我们将创建自己的应用程序类型,让你 更好了解它和控制它。    首先,我们将创建一个新的类继承自 SimpleGame。在我的例子中,我创建类 Lesson1:    public class Lesson1 extends SimpleGame {}   SimpleGame 包含一个抽象方法 simpleInitGame。我们正是在这个方法中创建了 Sphere。 现在先增加这个方法,我们将在之后回来讨论 Sphere。首先,我们想讨论 main 方法。这是 jME 应用程序的入口点(就像其他 Java 应用程序)。在创建期间,你需要创建你的应用程序 并告诉它们开始执行游戏循环。主游戏循环执行 update/render 循环,直到被告知退出和清 理。为了开始这个循环需要调用 start。    为了允许使用者指定窗口参数(分辨率,全屏等等),我们将一直显示 PropertiesDialog。 为了做到这点,我们设置应用程序行为为 ConfigShowMode.AlwaysShow。    import com.jme.app.SimpleGame; public class Lesson1 extends SimpleGame { @Override protected void simpleInitGame() { } public static void main(String[] args) { Lesson1 app = new Lesson1(); app.setConfigShowMode(ConfigShowMode.AlwaysShow); app.start(); } }    上面代码能真正被编译和运行,创建一个空的窗口。  4    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  1.2、显示一些东西  现在,我们想为 simpleInitGame 增加一些东西去显示带纹理(下文使用 Texture 代替) 的 Sphere。为了这么做,我们需要:  z 加载 Sphere  z 加载图像  z 将图像应用到 Sphere 做为它的 Texture  z 增加 Sphere 到场景(下文使用 scene 代替)  Sphere 的创建和创建一个新的对象一样简单。    Sphere s = new Sphere("Sphere",30,30,25); s.setLocalTranslation(new Vector3f(0,0,-40)); s.setModelBound(new BoundingBox()); s.updateModelBound();       你定义了 Sphere 垂直和水平位置的截面数目(在这个例子中都为 30)和它的半径(25)。 就那样,现在我们有了 Sphere。我们能接着操纵 Sphere 的位置。在这个例子中,我们想让 它沿着 Z 轴负向移动(这和把它移向屏幕里面等价)。我们接着设置了 Sphere 的边界体积(下 文使用 BoundingVolume 代替)。这允许摄像机(下文使用 Camera 代替)的视锥剔除(下文 使用 Frustum  Culling)工作。这意味着,如果我们将 Camera 偏移 Sphere,它将不会被绘制 (而统计掉为 0)。      下一步,我们将加载 Monkey.jpg 图像并把它做为 Texture 应用到 Sphere。为了加载图像 并获取 Texture,我们使用 TextureManager 和它的 loadTexture 方法。我们将通过基础 Texture 的值加载图像。    Texture texture = TextureManager.loadTexture( Lesson1.class.getClassLoader().getResource( "jmetest/data/images/Monkey.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear );    我们接着创建了 TextureState 并把这个 texture 设置给它。为了创建 TextureState,我们 使用以工厂方法的形式使用 DisplaySystem 。 SimpleGame 已经有了一个指向当前 DisplaySystem 的实例:“display”。  TextureState ts = display.getRenderer().createTextureState();   这样设置,如果允许在渲染(下文使用 Render 代替)期间使用渲染状态(下文使用 Render  State 代替):    ts.setEnabled(true); 5    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  我们接着使用 setTexture 方法将 Monkey.jpg 图像的 texture 放入 TextureState 中。 TextureState 现在已经准备好被应用到 Sphere 中,调用 setRenderState 来这么做。现在这个 TextureState 被附加(下文使用 attach 代替)到 Sphere,不管 Sphere 在什么时候被 render, 它将使用它的纹理坐标(下文使用 texture coordinate)并将图像应用到自身。    ts.setTexture(texture); s.setRenderState(ts);    最后,我们将 Sphere  attach 到 scene。SimpleGame 提供一个对象叫 rootNode,用于描 述主场景(下文使用 main scene 代替),将 sphere attach 到这个结点(下文使用 Node 代替), 为它的 render 做好准备。为了 attach 它,简单加入:    rootNode.attachChild(s);    通过这些简单的调用,我们已经有了一个带 texture 的 sphere,它被 render 到屏幕上。        SimpleGame,正如它名字所暗示的,让一切简单。然而,为了创建一个非常成熟的游 戏,我们想要完全控制一切。下一章将通过创建我们自己的游戏类型展示怎样做到这样。    1.3、源码  6    import com.jme.app.SimpleGame; UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingSphere; import com.jme.image.Texture; import com.jme.math.Vector3f; import com.jme.scene.shape.Sphere; import com.jme.scene.state.TextureState; import com.jme.util.TextureManager; public class Lesson1 extends SimpleGame { @Override protected void simpleInitGame() { Sphere s = new Sphere("Sphere",30,30,25); s.setLocalTranslation(new Vector3f(0,0,-40)); s.setModelBound(new BoundingBox()); s.updateModelBound(); Texture texture = TextureManager.loadTexture( Lesson1.class.getClassLoader().getResource( "jmetest/data/images/Monkey.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); TextureState ts = display.getRenderer().createTextureState(); ts.setEnabled(true); ts.setTexture(texture); s.setRenderState(ts); rootNode.attachChild(s); } public static void main(String[] args) { Lesson1 app = new Lesson1(); app.setConfigShowMode(ConfigShowMode.AlwaysShow); app.start(); } }        7    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  2、  从你的应用程序中移除SimpleGame  这个向导中,我们将为 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 8    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  定义了一对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; 9    private boolean fullscreen; UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ……………………….    我们也需要在我们的程序中保存 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 10    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ); 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); 11    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  }    下一步,我们获取新的 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", 12    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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,但实际上 13    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  怎样命名都是没关系。因为这是 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,所以我们将继续并重用这个值去保存每帧真正的时间。      接下来,我们将渲染。    14    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  /* * 绘制场景图 */ 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); 15    }  UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  我们在这里所做的就只是传递新的值给 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; 16    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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); 17    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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); //将摄像机移到正确位置和方向 18    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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") ){ 19    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  finished = true; } } }    3、  加载地形  这个向导中我们涉及到一些好玩的,我们将为我们的游戏加载地形(下文将使用 Terrain 代替)。这里对于我想要的类型的 terrain 有一些要求:  z 每次随机  z 不需太多三角形  z 为了跳跃“崎岖”  z 对于快速的交通工具足够大  我们将在第二课中的框架上构建。首先,由清除 Sphere 渲染代码开始。我们不再需要 这个例子。你现在应该有相当干净的框架用于工作。现在,我们将创建的地形会相当大。所 以我想改变 Camera 的位置保证地形在视野里面。因此,在 initSystem 中作出如下改变:  Vector3f loc = new Vector3f(0.0f,0.0f,25.0f);    改为: Vector3f loc = new Vector3f(500f,150f,500f);    这向上、远、后移动,确保我们对地形有恰当的视野。    现在,在 initGame 方法里面我们将加入一个对新方法的调用,这为这个 scene 增加一 个TerrainBlock。这个TerrainBlock 叫做tb并应该在类顶部定义。这个新的方法叫做buildTerrain 并应该在增加 tb 到 scene 之前调用。你应该像下面一样:  protected void initGame() { scene = new Node("Scene Graph Node"); buildTerrain(); scene.attachChild(tb); //更新scene用于渲染 scene.updateGeometricState(0.0f, true); scene.updateRenderState(); }      这引导我们到这个向导的核心,buildTerrain。    这里有我们 terrain 创建的核心:  1、 创建一个 heightmap  2、 从 heightmap 生成网格(下文将以 Mesh 代替)  3、 生成基于高度的纹理  20    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  3.1、创建一个heightmap  AbstractHeightMap 定义了一个方法用于保存高度数据。在它的核心,主要是一个二维 矩阵的数据,任何一个点(X,Z)的高度 Y。然而这不允许创建复杂 terrain(窑洞、悬崖等等)。 它提供了很基础的方形 terrain,然而这正是我们 FlagRush 中所需要的。    我们将创建一个 MidPointHeightMap,它使用中点取代不规则碎片。这将允许地形足够 有趣和真实,为我们提供了一些颠簸和跳跃。    创建这个 heightmap 很直截了当,在我们 buildTerrain 方法中的第一行:  /** * 创建heightmap和terrainBlock */ private void buildTerrain() { //生成随机地形数据 MidPointHeightMap heightMap = new MidPointHeightMap(64,1f); …… }    我们调用 MidPointHeightMap 的构造方法创建一个新的 heightMap 对象。它只需要 2 个 参数:大小和粗糙程度。    MidPointHeightMap 的大小必须是 2 的幂。那就是 2、4、8、16、32、64 等等。在我们 的例子中,我们选择 64。这正好符合我们的需要(我们的行为将被局限在一个相当小的舞 台)。粗糙程度才是有趣的东西。这个值越低,则 terrain 越粗糙,反之越平滑。我们先选择 它为 1,让 terrain 看起来像地狱般凹凸还带着尖刺。然而,我们还没设置完,这些尖刺将被 调下来。    我们将定义一个 terrain 缩放因数。这将简单拉伸或挤压 mesh 以满足我们的需求。所以, 增加:      //缩放数据 Vector3f terrainScale = new Vector3f(20, .5f, 20);      到 buildTerrain 方法。这意味着:我们将拉伸 terrain 的 X 和 Z 的值 20。这将让 terrain 感觉更大(实际上大了 20 倍)。然而与此同时,我们让 Y 值减少了一半。这将得到我们想要 的凹凸感,但让它们处于一个合理的值(不会太突然)。  3.2、生成Terrain Mesh    现在,我们已经设置好了数据,我们能真正创建 mesh。我们将创建一个 TerrainBlock, 它是一个简单的 Geometry。这个将增加到 scene 里,就像我们之前增加 Sphere 那样。  //创建一个terrain block tb = new TerrainBlock( "terrain", heightMap.getSize(), terrainScale, 21    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  heightMap.getHeightMap(), new Vector3f(0, 0, 0) ); tb.setModelBound(new BoundingBox()); tb.updateModelBound();      TerrainBlock 接受一些参数,大多数都很直接。首先,是 terrain 的名字。heightMap 的 大小,接着是我们之前所设的 terrain 的缩放值。接着给出 heightMap 真正的数据。下一个 参数定义了 terrain 的起点。我们这里没有理由设置一些奇怪的值,因此设置了基本的(0, 0,0)。    我们接着设置了 terrain 的 BoundingVolume。    你现在或许能继续并运行游戏,看到类似下面的一些东西:    这里并不能看到很多东西,因为 terrain 仅是一大块白色。我们需要应用 texture 去让它 有一点层次感。  3.3、生成Texture    创建一个 Texture 将通过使用 ProceduralTextureGenerator。这个类将生成一个基于 heightmap 的高度的纹理,并在多个 texture 间混合。一个 texture 被指定到一个高度区域, 而它们之后混合进单一的 texture  map。这允许我们很容易创建一个看起来相当真实的 Terrain。在我们的例子中,我们将使用 3 张 texture,一个用于低区域的草地 texture,中部 的岩石和高处的雪。  //通过三个纹理生成地形纹理 22    ProceduralTextureGenerator pt = UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  new ProceduralTextureGenerator(heightMap); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/grassb.png") ), -128, 0, 128 ); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/dirt.jpg") ), 0, 128, 256 ); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/highest.jpg") ), 128, 256, 374 ); pt.createTexture(32);    你将注意到每个 Texture 有 3 个值。这描述了这个 texture 将被应用到低的,最佳的和高 的海拔。例如(dirt.jpg)将混合从海拔 0‐256。heightmap 生成从 0‐256 的值。所以这意味着 dirt 在 128 将更强烈(看得更多),然后向 0 和 256 混合其它的 texture。同时其它的 2 个 texture 被填充在低和高的区域。  addTexture 接受 ImageIcon 对象去定义 texture 数据。在这个例子中,我们通过我们的类 的 getResource 方法获取到的 URL 创建 ImageIcon。这个在 classpath 里面搜索 images。这当 然不是一定要这么做,ImageIcon 能在其它某个地方被创建,它将适用于你应用程序。    createTexture 真正创建了我们需要使用的 texture。在这个例子中,我让它生成一个 32X32 像素的 texture。虽然这个看起来很小,但是我并不需要它的细节。这只是用于基础颜色, 之后我们将创建更详细的 texture 和对象。    例如:在运行游戏期间,我保存了一个生成的 texture。它看起来像这样:    你能看到三个 texture(grassb,dirt,highest)是怎样被混合为一个单一的 texture。白 色的区域将会是 terrain 的高点,而 grass 将是 terrain 的低点。      现在我们已经生成了 Terrain,我们把它放入一个 TextureState 并把它应用到 terrain。  23    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //将纹理赋予地形 ts = display.getRenderer().createTextureState(); Texture t1 = TextureManager.loadTexture( pt.getImageIcon().getImage(), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true ); ts.setTexture(t1, 0); tb.setRenderState(ts);      通过这样,terrain 就能正常工作了。你现在能运行游戏并看到类似下面的:      注意:我一直说类似,因为我们使用的是随机方法去生成 terrain。所以它每次都将不同。  3.4、创建灯光(Light)  尽管使用了 texture,我们依然很难辨别出 terrain。那是因为没有灯光和阴影帮助我们 辨别 terrain 的部分。所以,让我们继续并增加一个“太阳”。增加一个 buildLighting 到你的 initGame。我们将增加一个 DirectionalLight 去照耀 terrain。增加 light 有 2 部分。首先,创建 DirectionalLight,然后把它增加到 LightState。    24    private void buildLighting() { UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  /* 设置一个基础、默认灯光 */ DirectionalLight light = new DirectionalLight(); light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); light.setDirection(new Vector3f(1, -1, 0)); light.setEnabled(true); LightState lightState = display.getRenderer().createLightState(); lightState.attach(light); scene.setRenderState(lightState); }        这个 DirectionalLight 被设置于照耀(1, ‐1,  0)那个方向(向下和向右)。它接着被 增加到 LightState 并应用到 scene。    这为 terrain 增加了一些层次感,而你能更好辨认出地形特征。      3.5、总结  我们现在拥有了一个可以在上面奔跑的平面。然而,那还是存在令人讨厌的黑色背景。 25    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  下一节课我们将适当关注个问题。  3.6、源码  import javax.swing.ImageIcon; 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.light.DirectionalLight; import com.jme.math.Vector3f; import com.jme.renderer.Camera; import com.jme.renderer.ColorRGBA; import com.jme.scene.Node; import com.jme.scene.state.LightState; 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; import com.jmex.terrain.TerrainBlock; import com.jmex.terrain.util.MidPointHeightMap; import com.jmex.terrain.util.ProceduralTextureGenerator; public class Lesson3 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; private TerrainBlock tb; public static void main(String[] args) { 26    Lesson3 app = new Lesson3(); UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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"); buildTerrain(); buildLighting(); scene.attachChild(tb); //更新scene用于渲染 scene.updateGeometricState(0.0f, true); scene.updateRenderState(); } private void buildLighting() { /* 设置一个基础、默认灯光 */ DirectionalLight light = new DirectionalLight(); light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); light.setDirection(new Vector3f(1, -1, 0)); light.setEnabled(true); LightState lightState = display.getRenderer().createLightState(); lightState.attach(light); scene.setRenderState(lightState); } /** * 创建heightmap和terrainBlock */ private void buildTerrain() { 27    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //生成随机地形数据 MidPointHeightMap heightMap = new MidPointHeightMap(64,1f); //缩放数据 Vector3f terrainScale = new Vector3f(20, .5f, 20); //创建一个terrain block tb = new TerrainBlock( "terrain", heightMap.getSize(), terrainScale, heightMap.getHeightMap(), new Vector3f(0, 0, 0) ); tb.setModelBound(new BoundingBox()); tb.updateModelBound(); //通过三个纹理生成地形纹理 ProceduralTextureGenerator pt = new ProceduralTextureGenerator(heightMap); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/grassb.png") ), -128, 0, 128 ); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/dirt.jpg") ), 0, 128, 256 ); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/highest.jpg") ), 128, 256, 374 ); pt.createTexture(32); 28    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //将纹理赋予地形 ts = display.getRenderer().createTextureState(); Texture t1 = TextureManager.loadTexture( pt.getImageIcon().getImage(), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true ); ts.setTexture(t1, 0); tb.setRenderState(ts); } 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(500f,150f,500f); 29    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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(); 30    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //当Escape被按下时,我们退出游戏 if(KeyBindingManager.getKeyBindingManager() .isValidCommand("exit") ){ finished = true; } } }  4、 创建环境  既然我们已经有了 terrain(或者说是我们即将交互的真实平面),我们需要其他“没用” 的对象去让它看起来像个足够大的世界包围着我们。因为地形是有限的,我们需要一些方法 去保持玩家包含在我们狭小的空间内,而且是以一种合理的方式。所以,这个向导,我们将 创建一个力场(Force‐Field)舞台(Fence),那定义了游戏的区域。可能我们的玩家是被监 禁的,然后被迫为腐败的典狱长提供消遣?这将创建一个感觉,那就是我们永远也不能超出 那个 terrain 的原因。再者,我们想让它看起来像是在 Fence 外有个世界。我们将使用 Skybox 来这么做。Skybox 将创建大世界的幻觉,让我们开始吧。  4.1、准备和代码修改  有些和环境无关的代码需要修改,我将会提到。由于,我正在潦草地写一个游戏,并为 此写一个向导,那将会有一些东西随着时间推进需要调整。放心,我将提及所有的改变。    首先,我为这个测试改变 camera 的设置(以后将使用 input,所以这将是最后一次)。 不管怎样,由于我正在添加一个非常大的 Geometry,我想让它看得更清楚。为了做到这一 点,我将 camera 往后移动,往下移,并往下调整角度。你将注意到这些改变:  Vector3f loc = new Vector3f(250f,100f,250f); Vector3f left = new Vector3f(-0.5f,0.0f,0.5f); Vector3f up = new Vector3f(0.0f,1.0f,0.0f); Vector3f dir = new Vector3f(-0.5f,0.0f,-0.5f);      接着,我们忘记给窗口加标题。我们当然想在屏幕上显示我们的标题!为了这么做,我 们告诉 DisplaySystem 我们的标题将会是什么。这个被加在 initGame 的顶部。    display.setTitle("Flag Rush");     另一个小清除是我从 initGame 中移除 terrain 的 attach 代码,并把它放到 buildTerrain 中。    最后,但并不意味着不重要,我调整了terrain的scale。由于我以非传统格式做这个游戏 (向导),我也应该做一些非传统的事(这样以致我能展示底层细节)。就这一点,这要求我 对对象做一些调整,以致它们能更适合彼此。在这个例子中,我的Fence的大小只是不能超 出,所以我需要调整terrain对准他。新的terrain行是: 31    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  Vector3f terrainScale = new Vector3f(4, .0575f, 4);     是的,这些值的产生只是简单的尝试和误差。我减少高度的变化是因为 terrain 太崎岖 了。现在那些改变应该让你的东西看起来像这样:    ·现在,我们准备开始!  4.2、深度缓冲(Depth Buffer)  我们现在准备增加多个对象给 scene。如果我们一不小心,对象将画得覆盖其它它本不 该被覆盖的对象。我们想要的是前面的对象覆盖后面的。我们将做 2 件事去处理这种 render 顺序,RenderQueue 和 ZBufferState。我会先讲下 ZBufferState(或者说深度缓冲),而保留 RenderQueue,直到我们处理透明度(transparency)的时候。Depth  Buffer 允许 OpenGL 为 每个像素(pixel)保留深度(depth)信息。那就是,一个颜色值被赋给了 pixel,OpenGL 能知道那个 pixel 距离 camera 多远,从而决定是否用其它对象的 pixel 覆盖它。在我们的例 子中,我们想当即将到来的 pixel 和 camera 的距离小于或等于当前 pixel 与 camera 的距离时, 当前 pixel 被覆盖。我们将通过创建一个 ZBufferState 并把它赋给 scene graph 的根(root)来 这么做。    我们将把它增加到 initGame 方法。  ZBufferState buf = display.getRenderer().createZBufferState(); buf.setEnabled(true); buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); 32    scene.setRenderState(buf);  UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    就是这样。现在任何加到场景中的东西在它允许被 render 到缓冲区之前,都不得不有 一个小于或等于当前 pixel 的距离。这个将能防止我们的 fence 在 terrain 后面。  4.3、创建环境(手动)    这是一个大的部分,我们将通过手写代码创建 fence。通常,你可以简单加载一个描绘 fence 的模型并把它放在 terrain 上。然而,出于向导的目的,我想要展示怎样加载不同的图 形(Shape),并把它们安排到一个单一的 Node 里面。    fence 将有下面几个参数:  1、 方形的  2、 每个角落有守卫塔  3、 从一个塔到下一个之间有伸展的框架相连。  4、 一个 force field 从框架“坠下”  所有这些代码将放在一个叫 buildEnvironment 的方法里面并由 initGame 调用。  4.4、守卫塔和SharedMesh    我已经决定在 fence 的每个角落创建圆柱体的塔。这意味着有 4 个塔。我应该怎样创建 圆柱体对象?很好,Cylinder 听起来是个不错的选择。然而,替代创建 4 个 Cylinder 并创建 4 份相同的数据,我只创建了一个 Cylinder。我然后将旋转 Cylinder 让它的面垂直(默认上 Cylinder 是沿着边躺下的)。在这之后,我将使用 SharedMesh 创建它的四分副本。SharedMesh 允许我们使用相同的 Geometry 数据但把它们放在 scene 中不同的区域。这同时节省了内存 (只保留一份数据)和 render 时间(只渲染一次)    由于我正在使用 SharedMesh,我应该增加原始的 Cylinder  Geometry 到 scene。这是因 为 SharedMesh 在 render 阶段操纵原始的本地数据。    所以,我判定 fence 每一个边有 32 单元长。为什么我选择 32?没有特别的原因,只是 在那个时候觉得合理。我然后让 Cylinder10 单元高。同样的,没有原因,只是尝试和误差。 所以,将每个塔之间距离 32 单元,我设置它们的 localTranslation 到适当的点。  //这个圆柱体将扮演每个角落那4个主要的柱子 Cylinder postGeometry = new Cylinder("Cylinder", 10, 10, 1, 10); Quaternion q = new Quaternion(); //将圆柱体转为垂直 q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0)); postGeometry.setLocalRotation(q); postGeometry.setModelBound(new BoundingBox()); postGeometry.updateModelBound(); //我们将共享柱子4次(每个柱子一次) //增加原始的圆柱体不是一种好的方法 //因为sharedmesh将修改它的本地值 //我们然后将调整柱子到它的位置 //使用神奇的数字是不好的,但帮助我们声明它的位置^_^ 33    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  SharedMesh post1 = new SharedMesh("post1",postGeometry); post1.setLocalTranslation(new Vector3f(0,0.5f,0)); SharedMesh post2 = new SharedMesh("post2",postGeometry); post2.setLocalTranslation(new Vector3f(32,0.5f,0)); SharedMesh post3 = new SharedMesh("post3",postGeometry); post3.setLocalTranslation(new Vector3f(0,0.5f,32)); SharedMesh post4 = new SharedMesh("post4",postGeometry); post4.setLocalTranslation(new Vector3f(32,0.5f,32));    我们现在已经有了 4 个塔,而且他们都在正确的位置。现在我想要组织这些塔。它们将 使用相同的纹理,因此,我把它们移动到一个单一的组并为这一组的父亲应用 TextureState。    //将所有的柱子放入一个tower Node Node towerNode = new Node("tower"); towerNode.attachChild(post1); towerNode.attachChild(post2); towerNode.attachChild(post3); towerNode.attachChild(post4);    //将towerNode放入不透明队列(Opaque queue),我们不必看穿它 //而我们想穿过forcefield看到它 towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); //为towerNode加载纹理 TextureState ts2 = display.getRenderer().createTextureState(); Texture t2 = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/post.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts2.setTexture(t2); towerNode.setRenderState(ts2);      我现在能通过控制 towerNode 一次性操纵 4 个 tower(移动、缩放它们等等)。  4.5、剩余的框架  现在,我们准备构建 fence 中剩余的部分。我们将使用 Cylinder 构建一个框架,关于这 些框架最有趣的一点是其中 2 个将需要旋转 90 度。为了做到这个,我们将使用 Quaternion。 我不会深入讲解 Quaternion 是什么(你可以看看用户向导)。但我们使用它的 fromAngleAxis 去旋转 2 个框架。同样的,这 4 个框架实际上是 SharedMesh 对象。而就像前面的塔,它们 被 attach 到一个 Node 并拥有它们自己的 texture。  34    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    我调整框架的位置,把它们放在塔那么高的地方。同样的,这里没有什么特别的,只是 尝试并找出看起来好的。这些任意的数字,正是你通过手动创建对象而不是加载模型所能获 得的。    //这个圆柱体将是水平的框架 //它将field限制在一个地方 Cylinder strutsGeometry = new Cylinder( "struts",10,10,0.125f,32 ); strutsGeometry.setModelBound(new BoundingBox()); strutsGeometry.updateModelBound(); //同样的,我们将共享mesh SharedMesh strut1 = new SharedMesh("strut1",strutsGeometry); Quaternion rotate90 = new Quaternion(); rotate90.fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0)); strut1.setLocalRotation(rotate90); strut1.setLocalTranslation(new Vector3f(16,3f,0)); SharedMesh strut2 = new SharedMesh("strut2",strutsGeometry); strut2.setLocalTranslation(new Vector3f(0,3f,16)); SharedMesh strut3 = new SharedMesh("strut3",strutsGeometry); strut3.setLocalTranslation(new Vector3f(32,3f,16)); SharedMesh strut4 = new SharedMesh("strut4",strutsGeometry); strut4.setLocalRotation(rotate90); strut4.setLocalTranslation(new Vector3f(16,3f,32)); //将所有框架放入一个结点 Node strutNode = new Node("strutNode"); strutNode.attachChild(strut1); strutNode.attachChild(strut2); strutNode.attachChild(strut3); strutNode.attachChild(strut4); //为框架加载纹理 TextureState ts3 = display.getRenderer().createTextureState(); Texture t3 = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/rust.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts3.setTexture(t3); strutNode.setRenderState(ts3);    35    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  现在,我们已经花了一些心思在塔和框架上。现在这个 fence 是一个高科技的 force field, 而且它不允许任何东西穿越它。这个 force field  看起来像是个挂在框架上的平面对象。我们 将使用拥有未来主义 texture 的 Box 作为 force field。我不再深入讨论这部分的细节,因为它 只是再次使用 SharedMesh 并 attach 到它自己的 Node。有趣的一点是,我没有像旋转框架一 样去旋转 2 个 box。取而代之的是,我使用 Box 的构造方法去创建 2 个符合我需求的形状。 这没有真实的原因,只是想向你展示 Box 形状能不同。    //创建真实的forcefield //第一个box控制着X轴,而第二个控制着z轴 //作为示例,我们没有旋转box,而是展示box能被不同地创建 Box forceFieldX = new Box( "forceFieldX", new Vector3f(-16, -3f, -0.1f), new Vector3f(16, 3f, 0.1f) ); forceFieldX.setModelBound(new BoundingBox()); forceFieldX.updateModelBound(); //我们也将共享这些box SharedMesh forceFieldX1 = new SharedMesh( "forceFieldX1", forceFieldX ); forceFieldX1.setLocalTranslation(new Vector3f(16,0,0)); SharedMesh forceFieldX2 = new SharedMesh( "forceFieldX2", forceFieldX ); forceFieldX2.setLocalTranslation(new Vector3f(16,0,32)); //另一个box,控制z轴的那个 Box forceFieldZ = new Box( "forceFieldY", new Vector3f(-0.1f, -3f, -16f), new Vector3f(0.1f, 3f, 16f) ); forceFieldZ.setModelBound(new BoundingBox()); forceFieldZ.updateModelBound(); //我们也将共享这些box SharedMesh forceFieldZ1 = new SharedMesh( "forceFieldZ1", forceFieldZ ); forceFieldZ1.setLocalTranslation(new Vector3f(0,0,16)); SharedMesh forceFieldZ2 = new SharedMesh( "forceFieldZ2", forceFieldZ ); 36    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  forceFieldZ2.setLocalTranslation(new Vector3f(32,0,16)); //增加所有的forceField到一个单一的Node Node forceFieldNode = new Node("forceFieldNode"); forceFieldNode.attachChild(forceFieldX1); forceFieldNode.attachChild(forceFieldX2); forceFieldNode.attachChild(forceFieldZ1); forceFieldNode.attachChild(forceFieldZ2); //为force field元素增加texture TextureState ts = display.getRenderer().createTextureState(); Texture t = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/reflector.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); t.setWrap(Texture.WrapMode.Repeat); t.setTranslation(new Vector3f()); ts.setTexture(t); forceFieldNode.setRenderState(ts);      我们现在有三个包含所有 fence 中 Geometry 的 node。为了后面更容易工作,我们将这 三个 node 增加到一个单一的 Node。我们能随着将这个 node attach  到 scene 并移动它的位 置。    Node forceFieldFence = new Node("forceFieldFence"); forceFieldFence.attachChild(towerNode); forceFieldFence.attachChild(strutNode); forceFieldFence.attachChild(forceFieldNode);      一切都还好,但是 force field 确实看起来不像一个 force field。我想做 2 件事情来改进它: 透明和动画。  4.6、透明和RenderQueue  我想让 ForceField 透明。那就是,我想穿过它看到另一边,但仍然能辨别出粒子。为了 做到这个,我们为包含 box 的构成 force field 的结点设置 AlphaState。我们将附加 AlphaState:      //为transparent结点增加Alpha值 BlendState as1 = display.getRenderer().createBlendState(); 37    as1.setSourceFunction( UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  BlendState.SourceFunction.SourceAlpha ); as1.setDestinationFunction( BlendState.DestinationFunction.One ); as1.setTestFunction(BlendState.TestFunction.GreaterThan); as1.setBlendEnabled(true); as1.setTestEnabled(true); as1.setEnabled(true); forceFieldNode.setRenderState(as1);      这允许 texture 的颜色和在它后面的其它颜色混合。因为我们的 force field 是由很多黑色 组成的,黑色将会 100%透明。那么,我们现在拥有悬停的点。      可是我们的程序仍然有问题。如果我们通过当前的设置能移动地图,我们可能有时候会 看到在 force field 后面的对象,但不总是这样。那是因为透明对象取决于其他你能看到的在 它后面的对象去决定是否绘制。但我们目前的 render 顺序不担保能这样做。RenderQueue 的出现可以解决这个问题。我们能把 scene 中所有的元素放到这个 queue 中,而它将为我们 正确排序。有 3 个主要的 queue(虽然还有其它的,但对我们而言不是很重要):transparent  queue 正是我们放入 force field 的地方。Opaque 是我们放入不透明对象的地方,而 Ortho 是 GUI 元素(在后面章节将讲到)将前往的地方。RenderQueue 在下面几个方式上优化顺序:  z Opaque queue 最先被 render(所以我们所有不需要透明的对象都在这个 buffer 中)  z Transparency queue 接着被 render。与 camera 相距最远的透明对象最先被 render。 这确保其它透明对象能通过另一个透明对象看到。  所以,我们将把我们的 Geometry 放入这个 queue 中:  forceFieldNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); strutNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);      我们能设置实际 Geometry 对象的父亲 node,因为默认上它们将继承 mode。现在,当 我们 render 时,透明的 forcefield 将能从其它面正确地看到。    force‐field 看起来还是不够完美。我想要点看上去像是从框架下坠落一样,就像它们不 断被产生。我们让 texture 运动!  4.7、让Force Field的texture运动    让 texture 运动实际是很简单的。首先,我们将需要访问包含在 force‐field 中的 Texture 对象。将它的引用移到类级别(使 force‐field 的 texture 成为类变量)。    我们现在能在 update 方法中访问 Texture 了。    每个  texure 有它自己的矩阵。这个 texture 矩阵定义了 texture 的坐标是怎样被应用的。 你能旋转 texture,缩放它们,或者移动。在我们的例子中,我们想要沿着 Y 轴移动 texture。 实际上做起来和听起来是一样容易的:  38    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //我们将使用插值(interpolation)去保持forcefield //texture运动速度和计算机保持一致 //我们更新texture矩阵的Y值去让 //force-field看起来像是在移动 t.getTranslation().y += 0.3f*interpolation; //如果translation超过1,它被换行,因此回到开始 //并检查这个(防止Vector的Y值变得太大) t.getTranslation().y = t.getTranslation().y > 1 ? 0 : t.getTranslation().y;     一旦上面完成之后,我们将更真实看到一个强大的fence(  如果你看不到下面这个图,不 要紧张,因为可能是你的fence field放的位置太低的,使用以下代码调整位置 forceFieldFence.setLocalTranslation(200,100,200);这些数字只是简单的尝试得 出的,看到效果一样之后再把这句去掉,因为后面会讲到怎样  调整force‐field到terrain上)。    4.8、将force­field放置在terrain上    现在,我们想要放置这个 fence 在 terrain 上。我想要做到一些事。首先,我想要它在 terrain 里面一点,这样当玩家到达 fence 的时候它们不会看到它脱离了 terrain。其次,它需要足够 高和足够大去盖在 terrain 上。    我需要去玩这个东西直到找到我想要的结果。我发现将 fence 放大 5 倍正好是我想要的。 我接着把它在 X 轴和 Z 轴移动 25 个单元。把它升到一个合适的高度,我获取 TerrainBlock 39    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  在 25,25 处的高度,并增加一些误差范围。  //我们将手工做一些调整去让它更好适应terrain //首先我们将实体“模型”放大 forceFieldFence.setLocalScale(5); //现在,让我们移动fence到terrain的高度并有一点陷入它里面 forceFieldFence.setLocalTranslation( new Vector3f(25,tb.getHeight(25,25)+15,25) );    4.9、环绕的Skybox    OK,我们现在已经有一个很好的 fence 阻止我们的玩家跳出世界并掉落导致死亡。我们 仍然看到非 terrain 部分是黑色,然而,这破坏了我们对这个大世界的感觉。我们所能做的 是使用 Skybox 给人们一个巨大的世界的错觉。这个将通过一个有描述世界的图像的 box 包 围 camera。由于你不应该靠近地平线,这个 box 应该随着 camera 移动。    创建一个新的叫 createSkybox 并创建个类变量 skybox。我想要这成为一个类变量,因为 我们在 camera 移动的时候准备更新它。    Skybox 处理它自己所有的内部状态。这让它和其它大多数 scene 元素有点不同。所以, 在这个例子中我们所要做的是创建一个新的 Skybox 并设置它的 textures。我们将使用 texture 包含在 jmetest 部分并用于其它测试。我能寻找自己的 texture 并创建一个原创的 FlagRush 向导,但我有点懒,之后再说吧。       我们接着通过设置它的 localTranslation 告诉 Skybox 去初始化 texture。最后把它作为 scene 的孩子添加到 scene。      基本上我们做完了,下一步我们需要设置它的 localTranslation 为我们 cam 对象的位置。 到 update 方法中增加:  //我们想让skybox一直在我们的视野内,所以让它和camera一起移动 skybox.setLocalTranslation(cam.getLocation()); //由于我们改变了场景(移动skybox),我们需要更新scene graph scene.updateGeometricState(interpolation, true);      这将让 Skybox 随着 camera 移动。就是那样。很简单就增加了世界!我们现在有一个看 起来像下面一样的完整的水平面:  40    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841      注意:由于 Skybox 离我们的游戏 fence 比较远,可能导致 camera 观看范围不足,因此 可以改变:  //初始化摄像机 cam.setFrustumPerspective( 45.0f, (float)width/(float)height, 1f, 1000f ); 为: //初始化摄像机 cam.setFrustumPerspective( 45.0f, (float)width/(float)height, 1f, 5000f );  4.10、总结    我们现在拥有一个完整的水平面,有 terrain,fence 和 sky。这给我们一个可行的场地去 放置我们的游戏对象并开始飞奔。  41    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    下一节课我将讨论怎样使用 ThirdPersonHandler 去控制一辆交通工具。我们离飞奔更近 了。    4.11、源码    import javax.swing.ImageIcon; 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.light.DirectionalLight; import com.jme.math.FastMath; import com.jme.math.Quaternion; import com.jme.math.Vector3f; import com.jme.renderer.Camera; import com.jme.renderer.ColorRGBA; import com.jme.renderer.Renderer; import com.jme.scene.Node; import com.jme.scene.SharedMesh; import com.jme.scene.Skybox; import com.jme.scene.shape.Box; import com.jme.scene.shape.Cylinder; import com.jme.scene.state.BlendState; import com.jme.scene.state.LightState; import com.jme.scene.state.TextureState; import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySystem; import com.jme.system.JmeException; import com.jme.util.TextureManager; import com.jme.util.Timer; import com.jmex.terrain.TerrainBlock; import com.jmex.terrain.util.MidPointHeightMap; import com.jmex.terrain.util.ProceduralTextureGenerator; public class Lesson4 extends BaseGame{ private int width,height; private int freq,depth; private boolean fullscreen; 42    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //我们的camera对象,用于观看scene private Camera cam; protected Timer timer; private Node scene; private TextureState ts; private TerrainBlock tb; private Texture t; private Skybox skybox; public static void main(String[] args) { Lesson4 app = new Lesson4(); 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() { display.setTitle("Flag Rush"); scene = new Node("Scene Graph Node"); ZBufferState buf = display.getRenderer().createZBufferState(); buf.setEnabled(true); buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); scene.setRenderState(buf); buildTerrain(); buildLighting(); buildEnvironment(); createSkybox(); //更新scene用于渲染 scene.updateGeometricState(0.0f, true); 43    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  scene.updateRenderState(); } private void createSkybox() { skybox = new Skybox("skybox",10,10,10); Texture north = TextureManager.loadTexture( Lesson4.class.getClassLoader() .getResource("res/texture/north.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture south = TextureManager.loadTexture( Lesson4.class.getClassLoader() .getResource("res/texture/south.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture east = TextureManager.loadTexture( Lesson4.class.getClassLoader() .getResource("res/texture/east.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture west = TextureManager.loadTexture( Lesson4.class.getClassLoader() .getResource("res/texture/west.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture up = TextureManager.loadTexture( Lesson4.class.getClassLoader() .getResource("res/texture/top.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture down = TextureManager.loadTexture( Lesson4.class.getClassLoader() .getResource("res/texture/bottom.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); skybox.setTexture(Skybox.Face.North, north); skybox.setTexture(Skybox.Face.West, west); 44    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  skybox.setTexture(Skybox.Face.South, south); skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down); skybox.preloadTextures(); scene.attachChild(skybox); } private void buildEnvironment() { //这个圆柱体将扮演每个角落那4个主要的柱子 Cylinder postGeometry = new Cylinder("Cylinder", 10, 10, 1, 10); Quaternion q = new Quaternion(); //将圆柱体转为垂直 q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0)); postGeometry.setLocalRotation(q); postGeometry.setModelBound(new BoundingBox()); postGeometry.updateModelBound(); //我们将共享柱子4次(每个柱子一次) //增加原始的圆柱体不是一种好的方法 //因为sharedmesh将修改它的本地值 //我们然后将调整柱子到它的位置 //使用神奇的数字是不好的,但帮助我们声明它的位置^_^ SharedMesh post1 = new SharedMesh("post1",postGeometry); post1.setLocalTranslation(new Vector3f(0,0.5f,0)); SharedMesh post2 = new SharedMesh("post2",postGeometry); post2.setLocalTranslation(new Vector3f(32,0.5f,0)); SharedMesh post3 = new SharedMesh("post3",postGeometry); post3.setLocalTranslation(new Vector3f(0,0.5f,32)); SharedMesh post4 = new SharedMesh("post4",postGeometry); post4.setLocalTranslation(new Vector3f(32,0.5f,32)); //将所有的柱子放入一个tower Node Node towerNode = new Node("tower"); towerNode.attachChild(post1); towerNode.attachChild(post2); towerNode.attachChild(post3); towerNode.attachChild(post4); //将towerNode放入不透明队列(Opaque queue),我们不必看穿它 //而我们想穿过forcefield看到它 towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); 45    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //为towerNode加载纹理 TextureState ts2 = display.getRenderer().createTextureState(); Texture t2 = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/post.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts2.setTexture(t2); towerNode.setRenderState(ts2); //这个圆柱体将是水平的建筑 //它将field限制在一个地方 Cylinder strutsGeometry = new Cylinder("struts",10,10,0.125f,32); strutsGeometry.setModelBound(new BoundingBox()); strutsGeometry.updateModelBound(); //同样的,我们将共享mesh SharedMesh strut1 = new SharedMesh("strut1",strutsGeometry); Quaternion rotate90 = new Quaternion(); rotate90.fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0)); strut1.setLocalRotation(rotate90); strut1.setLocalTranslation(new Vector3f(16,3f,0)); SharedMesh strut2 = new SharedMesh("strut2",strutsGeometry); strut2.setLocalTranslation(new Vector3f(0,3f,16)); SharedMesh strut3 = new SharedMesh("strut3",strutsGeometry); strut3.setLocalTranslation(new Vector3f(32,3f,16)); SharedMesh strut4 = new SharedMesh("strut4",strutsGeometry); strut4.setLocalRotation(rotate90); strut4.setLocalTranslation(new Vector3f(16,3f,32)); //将所有建筑放入一个结点 Node strutNode = new Node("strutNode"); strutNode.attachChild(strut1); strutNode.attachChild(strut2); strutNode.attachChild(strut3); strutNode.attachChild(strut4); //为建筑加载纹理 TextureState ts3 = display.getRenderer().createTextureState(); Texture t3 = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/rust.jpg"), 46    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts3.setTexture(t3); strutNode.setRenderState(ts3); //创建真实的forcefield //第一个box控制着X轴,而第二个控制着z轴 //作为示例,我们没有旋转box,而是展示box能被不同地创建 Box forceFieldX = new Box( "forceFieldX", new Vector3f(-16, -3f, -0.1f), new Vector3f(16, 3f, 0.1f) ); forceFieldX.setModelBound(new BoundingBox()); forceFieldX.updateModelBound(); //我们也将共享这些box SharedMesh forceFieldX1 = new SharedMesh( "forceFieldX1", forceFieldX ); forceFieldX1.setLocalTranslation(new Vector3f(16,0,0)); SharedMesh forceFieldX2 = new SharedMesh( "forceFieldX2", forceFieldX ); forceFieldX2.setLocalTranslation(new Vector3f(16,0,32)); //另一个box,控制z轴的那个 Box forceFieldZ = new Box( "forceFieldY", new Vector3f(-0.1f, -3f, -16f), new Vector3f(0.1f, 3f, 16f) ); forceFieldZ.setModelBound(new BoundingBox()); forceFieldZ.updateModelBound(); //我们也将共享这些box SharedMesh forceFieldZ1 = new SharedMesh( "forceFieldZ1", forceFieldZ ); forceFieldZ1.setLocalTranslation(new Vector3f(0,0,16)); SharedMesh forceFieldZ2 = new SharedMesh( "forceFieldZ2", forceFieldZ ); forceFieldZ2.setLocalTranslation(new Vector3f(32,0,16)); 47    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //增加所有的forceField到一个单一的Node Node forceFieldNode = new Node("forceFieldNode"); forceFieldNode.attachChild(forceFieldX1); forceFieldNode.attachChild(forceFieldX2); forceFieldNode.attachChild(forceFieldZ1); forceFieldNode.attachChild(forceFieldZ2); //为force field元素增加texture TextureState ts = display.getRenderer().createTextureState(); t = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/reflector.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); t.setWrap(Texture.WrapMode.Repeat); t.setTranslation(new Vector3f()); ts.setTexture(t); //为transparent结点增加Alpha值 BlendState as1 = display.getRenderer().createBlendState(); as1.setSourceFunction( BlendState.SourceFunction.SourceAlpha ); as1.setDestinationFunction( BlendState.DestinationFunction.One ); as1.setTestFunction(BlendState.TestFunction.GreaterThan); as1.setBlendEnabled(true); as1.setTestEnabled(true); as1.setEnabled(true); forceFieldNode.setRenderState(as1); forceFieldNode.setRenderState(ts); forceFieldNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); strutNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); Node forceFieldFence = new Node("forceFieldFence"); forceFieldFence.attachChild(towerNode); forceFieldFence.attachChild(strutNode); forceFieldFence.attachChild(forceFieldNode); 48    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //我们将手工做一些调整去让它更好适应terrain //首先我们将实体“模型”放大 forceFieldFence.setLocalScale(5); //现在,让我们移动fence到terrain的高度并有一点陷入它里面 forceFieldFence.setLocalTranslation( new Vector3f(25,tb.getHeight(25,25)+15,25) ); scene.attachChild(forceFieldFence); } private void buildLighting() { /* 设置一个基础、默认灯光 */ DirectionalLight light = new DirectionalLight(); light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); light.setDirection(new Vector3f(1, -1, 0)); light.setEnabled(true); LightState lightState = display.getRenderer().createLightState(); lightState.setEnabled(true); lightState.attach(light); scene.setRenderState(lightState); } /** * 创建heightmap和terrainBlock */ private void buildTerrain() { //生成随机地形数据 MidPointHeightMap heightMap = new MidPointHeightMap(64,1f); //缩放数据 Vector3f terrainScale = new Vector3f(4, .0575f, 4); //创建一个terrain block tb = new TerrainBlock( "terrain", heightMap.getSize(), terrainScale, heightMap.getHeightMap(), 49    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  new Vector3f(0, 0, 0) ); tb.setModelBound(new BoundingBox()); tb.updateModelBound(); //通过三个纹理生成地形纹理 ProceduralTextureGenerator pt = new ProceduralTextureGenerator(heightMap); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/grassb.png") ), -128, 0, 128 ); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/dirt.jpg") ), 0, 128, 256 ); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/highest.jpg") ), 128, 256, 374 ); pt.createTexture(32); //将纹理赋予地形 ts = display.getRenderer().createTextureState(); Texture t1 = TextureManager.loadTexture( pt.getImageIcon().getImage(), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true ); ts.setTexture(t1, 0); tb.setRenderState(ts); scene.attachChild(tb); 50    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  } 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, 5000f ); Vector3f loc = new Vector3f(250f,100f,250f); Vector3f left = new Vector3f(-0.5f,0.0f,0.5f); Vector3f up = new Vector3f(0.0f,1.0f,0.0f); Vector3f dir = new Vector3f(-0.5f,0.0f,-0.5f); //将摄像机移到正确位置和方向 cam.setFrame(loc, left, up, dir); //我们改变自己的摄像机位置和视锥的标志 cam.update(); //获取一个高分辨率用于FPS更新 51    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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(); //我们将使用插值(interpolation)去保持forcefield //texture运动速度和计算机保持一致 //我们更新texture矩阵的Y值去让 //force-field看起来像是在移动 t.getTranslation().y += 0.3f*interpolation; //如果translation超过1,它被换行,因此回到开始 //并检查这个(防止Vector的Y值变得太大) t.getTranslation().y = t.getTranslation().y > 1 ? 0 : t.getTranslation().y; 52    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //我们想让skybox一直在我们的视野内,所以让它和camera一起移动 skybox.setLocalTranslation(cam.getLocation()); //当Escape被按下时,我们退出游戏 if(KeyBindingManager.getKeyBindingManager() .isValidCommand("exit") ){ finished = true; } //由于我们改变了场景(移动skybox),我们需要更新scene graph scene.updateGeometricState(interpolation, true); } }    5、 增加一个跟随摄像机(Chase Camera)  在这个向导中我们准备增加一个玩家和一些控制。我们将集中于交通工具的基本移动和 跟随摄像机(下文以 ChaseCamera 代替)。所以,我们将为交通工具增加一个 Box 占位符, 增加一个 ChaseCamera,然后学习关于构建我们的自定义 InputHandle(输入处理)。主要, 我们将构建一个 ThirdPersonHandler(第三人称控制器)。然而,jME 包含一个预建的 ThirdPersonHandle,它具有更多特性并值得研究。为了达到最好的教育效果,我们将创建自 己的控制器。  5.1、清理和优化  同样地,我们这一节课将在前一节的基础上创建代码。每次迭代我们将清理现有的代码, 为了达到展示怎样把事情做得更好的效果。    Lesson5 首先最主要的改变是我们创建了一个 ForceFieldFence 的类。通过将所有 force  field 的数据移到它自己的类里面,这将允许我们可以在之后心血来潮的时候改善它。首先, 我创建了一个新的类继承自 Node。我们继承 Node 所以我们能附加 fence 到这个类本身并和 平常一样把 fence 增加到 scene。我接着创建了 buildFence 方法,从我们游戏的 buildEnvironment 方法中移除代码(除了位置和缩放调用,它们是游戏相关的,和围栏没有 关系)到这个新的方法。现在,我们也想要继续力场的动画,所以我们也将移动 Texture t 到这个类并创建 update 方法。游戏中的 Texture 代码将移动到这个方法。现在,在游戏代码 中,buildEnvironment 方法我们加入:  fence = new ForceFieldFence("forceFieldFence");    与此同时为游戏类增加一个类变量  53    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  private ForceFieldFence fence;      最后,我们需要告诉 fence 类在游戏 update 期间也 update。在 update 方法中增加:  fence.update(interpolation);      将这么做。    现在,游戏将和之前运行的一样,但我们有个 fence 让一切更容易。  5.2、ForceFieldFence.java  package lesson5; import com.jme.bounding.BoundingBox; import com.jme.image.Texture; import com.jme.math.FastMath; import com.jme.math.Quaternion; import com.jme.math.Vector3f; import com.jme.renderer.Renderer; import com.jme.scene.Node; import com.jme.scene.SharedMesh; import com.jme.scene.shape.Box; import com.jme.scene.shape.Cylinder; import com.jme.scene.state.BlendState; import com.jme.scene.state.TextureState; import com.jme.system.DisplaySystem; import com.jme.util.TextureManager; public class ForceFieldFence extends Node { private Texture t; public ForceFieldFence(String name){ super(name); buildFence(); } public void buildFence(){ //这个圆柱体将扮演每个角落那4个主要的柱子 Cylinder postGeometry = new Cylinder("Cylinder", 10, 10, 1, 10); Quaternion q = new Quaternion(); //将圆柱体转为垂直 q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0)); 54    postGeometry.setLocalRotation(q); UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  postGeometry.setModelBound(new BoundingBox()); postGeometry.updateModelBound(); //我们将共享柱子4次(每个柱子一次) //增加原始的圆柱体不是一种好的方法 //因为sharedmesh将修改它的本地值 //我们然后将调整柱子到它的位置 //使用神奇的数字是不好的,但帮助我们声明它的位置^_^ SharedMesh post1 = new SharedMesh("post1",postGeometry); post1.setLocalTranslation(new Vector3f(0,0.5f,0)); SharedMesh post2 = new SharedMesh("post2",postGeometry); post2.setLocalTranslation(new Vector3f(32,0.5f,0)); SharedMesh post3 = new SharedMesh("post3",postGeometry); post3.setLocalTranslation(new Vector3f(0,0.5f,32)); SharedMesh post4 = new SharedMesh("post4",postGeometry); post4.setLocalTranslation(new Vector3f(32,0.5f,32)); //将所有的柱子放入一个tower Node Node towerNode = new Node("tower"); towerNode.attachChild(post1); towerNode.attachChild(post2); towerNode.attachChild(post3); towerNode.attachChild(post4); //将towerNode放入不透明队列(Opaque queue),我们不必看穿它 //而我们想穿过forcefield看到它 towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); //为towerNode加载纹理 TextureState ts2 = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); Texture t2 = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/post.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts2.setTexture(t2); towerNode.setRenderState(ts2); //这个圆柱体将是水平的建筑 //它将field限制在一个地方 Cylinder strutsGeometry = new Cylinder("struts",10,10,0.125f,32); 55    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  strutsGeometry.setModelBound(new BoundingBox()); strutsGeometry.updateModelBound(); //同样的,我们将共享mesh SharedMesh strut1 = new SharedMesh("strut1",strutsGeometry); Quaternion rotate90 = new Quaternion(); rotate90.fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0)); strut1.setLocalRotation(rotate90); strut1.setLocalTranslation(new Vector3f(16,3f,0)); SharedMesh strut2 = new SharedMesh("strut2",strutsGeometry); strut2.setLocalTranslation(new Vector3f(0,3f,16)); SharedMesh strut3 = new SharedMesh("strut3",strutsGeometry); strut3.setLocalTranslation(new Vector3f(32,3f,16)); SharedMesh strut4 = new SharedMesh("strut4",strutsGeometry); strut4.setLocalRotation(rotate90); strut4.setLocalTranslation(new Vector3f(16,3f,32)); //将所有建筑放入一个结点 Node strutNode = new Node("strutNode"); strutNode.attachChild(strut1); strutNode.attachChild(strut2); strutNode.attachChild(strut3); strutNode.attachChild(strut4); //为建筑加载纹理 TextureState ts3 = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); Texture t3 = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/rust.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts3.setTexture(t3); strutNode.setRenderState(ts3); //创建真实的forcefield //第一个box控制着X轴,而第二个控制着z轴 //作为示例,我们没有旋转box,而是展示box能被不同地创建 Box forceFieldX = new Box( "forceFieldX", new Vector3f(-16, -3f, -0.1f), new Vector3f(16, 3f, 0.1f) ); 56    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  forceFieldX.setModelBound(new BoundingBox()); forceFieldX.updateModelBound(); //我们也将共享这些box SharedMesh forceFieldX1 = new SharedMesh("forceFieldX1", forceFieldX); forceFieldX1.setLocalTranslation(new Vector3f(16,0,0)); SharedMesh forceFieldX2 = new SharedMesh("forceFieldX2", forceFieldX); forceFieldX2.setLocalTranslation(new Vector3f(16,0,32)); //另一个box,控制z轴的那个 Box forceFieldZ = new Box( "forceFieldY", new Vector3f(-0.1f, -3f, -16f), new Vector3f(0.1f, 3f, 16f) ); forceFieldZ.setModelBound(new BoundingBox()); forceFieldZ.updateModelBound(); //我们也将共享这些box SharedMesh forceFieldZ1 = new SharedMesh("forceFieldZ1", forceFieldZ); forceFieldZ1.setLocalTranslation(new Vector3f(0,0,16)); SharedMesh forceFieldZ2 = new SharedMesh("forceFieldZ2", forceFieldZ); forceFieldZ2.setLocalTranslation(new Vector3f(32,0,16)); //增加所有的forceField到一个单一的Node Node forceFieldNode = new Node("forceFieldNode"); forceFieldNode.attachChild(forceFieldX1); forceFieldNode.attachChild(forceFieldX2); forceFieldNode.attachChild(forceFieldZ1); forceFieldNode.attachChild(forceFieldZ2); //为force field元素增加texture TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); t = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/reflector.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); t.setWrap(Texture.WrapMode.Repeat); 57    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  t.setTranslation(new Vector3f()); ts.setTexture(t); //为transparent结点增加Alpha值 BlendState as1 = DisplaySystem.getDisplaySystem().getRenderer().createBlendState(); as1.setSourceFunction( BlendState.SourceFunction.SourceAlpha ); as1.setDestinationFunction( BlendState.DestinationFunction.One ); as1.setTestFunction(BlendState.TestFunction.GreaterThan); as1.setBlendEnabled(true); as1.setTestEnabled(true); as1.setEnabled(true); forceFieldNode.setRenderState(as1); forceFieldNode.setRenderState(ts); forceFieldNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); strutNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); Node forceFieldFence = new Node("forceFieldFenceNode"); forceFieldFence.attachChild(towerNode); forceFieldFence.attachChild(strutNode); forceFieldFence.attachChild(forceFieldNode); attachChild(forceFieldFence); } public void update(float interpolation){ //我们将使用插值(interpolation)去保持forcefield //texture运动速度和计算机保持一致 //我们更新texture矩阵的Y值去让 //force-field看起来像是在移动 t.getTranslation().y += 0.3f*interpolation; //如果translation超过1,它被换行,因此回到开始 //并检查这个(防止Vector的Y值变得太大) t.getTranslation().y = t.getTranslation().y > 1 ? 0 : t.getTranslation().y; } } 58    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  5.3、剔除/挑选状态(CullState)    最后的一点优化是在 game 中添加了 CullState。这将是我们的首次提速。CullState 处理 三角形的剔除,这和视锥挑选(Frustum Culling)是不同的,因为它只和单个三角形的 winding 有关。顶点的 winding 定义了三角形的法向并接着告诉了我们三角形是否面对我们。jME 使 用和 OpenGL 一样的右手坐标系。这意味着,如果一个三角形的 winding 是逆时针(CCW) 的,那么它面向屏幕。    我们没理由看到三角形的背后,因此,也没理由去绘制它们。所以,我们将为整个 scene 应用 CullState 去移除所有三角形后面的面。这很直接,你只需要在 initGame 方法中加入:  CullState cs = display.getRenderer().createCullState(); cs.setCullFace(CullState.Face.Back); scene.setRenderState(cs);      耶,那很容易。我简单创建了一个新的 state 并告诉它剔除三角形背后的面。这还不够 简单?  5.4、让我们增加玩家  对于这个向导,我们将只是使用一个占位符代替交通工具。我们将在之后载入模型,但 那只是没价值的工作,我们想要先让游戏的核心能运作。一个 Box 是一个好的占位符,因为 它是我们交通工具的基础模型。    所以,让我们先增加一个 buildPlayer 的方法并在 initGame 中调用它。我们将接着创建 一个 box 做为玩家的几何体并把这个 Box  attach 到 node。这个玩家 Node 将会是一个类变 量,以便我们能在 update 期间访问它。我将创建一个中心为(0,0,0)和大小为(0.35,0.25,0.5), 让它看起来长和宽。Node 接着被移到坐标(100,0,100)。我还没设置它的高度,我将在之后 才那么做。  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(); }     如果你现在运行这个,实际上不会看到 player,因为它深陷在 terrain 下面。我们在 update 里面设置 height,这是因为我们将很快让交通工具在平面上移动,并需要让它保持在 terrain 59    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  上。所以,为了保持 box 在地面的顶部行驶,增加:  //确保当玩家离开平面时我们不会坠落。 //当我们增加冲突时,fence将做它自己的工作并保持玩家在里面。 float characterMinHeight = tb.getHeight(player.getLocalTranslation()) + ((BoundingBox)player.getWorldBound()).yExtent; if( !Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight) ) player.getLocalTranslation().y = characterMinHeight;     首先,我们获取玩家当前位置对应的 terrain 的高度。接着加上 BoundingBix 的偏移,我 们这么做是因为 Box 位置的点是 Box 的中心。如果我们没加上偏移,box 将有一半沉入地下。 我们使用包围对象的 BoundingBox 去获取对象的高度(yExtent)(但实际上如果你对模型了 解得很好,你可以使用值代替)。最后,我们检查获取的高度去确认没有得到一些糟糕的值 (非数字、无穷大等)。我们这么做是因为目前我们没做任何事去阻止玩家驾驶出 terrain。    我们现在已经在 terrain 上拥有了玩家!    (现在你可能还看不到这个画面,别急,后面会看到的)  60    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  5.5、跟随摄像机(ChaseCamera)    好了,现在我们有了玩家,我们应该有能力移动它。这将是一个第三人称游戏,意味我 们在玩游戏的时候能看到自己的玩家(而不是以 player 的眼睛去看)。所以我们想要摄像头 一直指向玩家并跟随他。为了这么做,我们将使用 ChaseCamera。ChaseCamera 将通过定义 它跟随距离的参数一直追踪一个给出的对象。ChaseCamera 也定义一些值让它平滑跟随。那 就是它不能突然转向玩家。这种突然性的效果当然也是可以定义的。      所以,当我们使用 ChaseCamera,视图将一直对着玩家。鼠标将允许 camera 在玩家四 周旋转并一直面向它。鼠标滚轮将允许 camera 缩放(尽管在这个例子中缩放值很小)。      因此,创建一个 buildChaseCamera 方法并从 initGame 中调用它。我们在这里设置 ChaseCamera 的参数并创建它。ChaseCamera 对象将成为一个类变量以致我们能 update 它(所 以把它加到类的顶部)。      我们相对 ChaseCamera 设置的参数有一些。首先,我们将设置 Camera 的目标偏移玩家。 我们通常想让 camera 看起来在玩家上一点。所以我们设置偏移(offset)值为(0,玩家的 Y*1.5,0).这将让 camera 指向指向一个在玩家原始高度上面多一半的一个点。下一步,我 们将设置滚动(rollout)值。这些值决定了我们能拉近或推远摄像机多少。我这里不想给太 多自由,因此这个级别实际上很小。所以我们设置最大为 6 个单元,而最小为 3 个单元。下 一步我们将设置 camera 能向上转动多高,在这个例子中为 45 度,注意是弧度。最后,我们 将为 camera 设置开始起点的球形坐标,roll out 为 5 并升高 30 度。因为 camera 在一个“弹 簧”系统中,如果交通工具行驶太快时,它能延迟落后一定距离。因此,我们将增加 camera 能落后的最小和最大值。8 和 2 应该是可以的。    我们在一个 hash map 中设置这些参数。  private void buildChaseCamera() { Vector3f targetOffset = new Vector3f(); targetOffset.y = ((BoundingBox)player.getWorldBound()).yExtent*1.5f; HashMap props = new HashMap(); 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); 61    chaser = new ChaseCamera(cam, player, props); UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  chaser.setMaxDistance(8); chaser.setMinDistance(2); }     我们现在已经设置好了自己的 ChaseCamera,但它在调用 update 方法之前不会产生任 何作用。因此在 update 中加入:  chaser.update(interpolation);     现在,当应用程序运行时,你能看到 camera 在它初始化的位置并光滑地把镜头拉近直 到最大的 6 个单元。你能接着移动鼠标去围绕 box 旋转 camera,也能滚动鼠标滑轮去将 camera 拉近或推远一点。那就是所激动的,但没有什么东西可以追踪,因为 box 只是停在 那里。让我们纠正那个。    这里补充一点是作者漏掉的,我们还需要在 game 的 update 方面里面加入下面代码以 保证 camera 的位置一直在 terrain 上面:  //我们不想chase camera走到世界下面,因此让它一直在水平面上2个单元。 if(cam.getLocation().y < (tb.getHeight(cam.getLocation())+2)) { cam.getLocation().y = tb.getHeight(cam.getLocation()) + 2; cam.update(); }      注意:由于我们将 camera 赋予了 ChaseCamera,因此 camera 自动将目标锁定在对象身 上,故可以不设置 Camera 的朝向。即将:  Vector3f loc = new Vector3f(200f,1000f,200f); Vector3f left = new Vector3f(-0.5f,0.0f,0.5f); Vector3f up = new Vector3f(0.0f,1.0f,0.0f); Vector3f dir = new Vector3f(-0.5f,0.0f,-0.5f); //将摄像机移到正确位置和方向 cam.setFrame(loc, left, up, dir); 改为: Vector3f loc = new Vector3f(200f,1000f,200f); cam.setLocation(loc);  5.6、我们自定义的输入处理  我们将创建自己的输入处理器(InputHandler)从而允许我们驾驶交通工具。这个 handler 的目标是允许我们行驶向前、向后和转向。我想这些控制的键被设置为:WASD。幸运的是, 为了做到这个,我们将能使用在 jME 中构建的 action。名字是,KeyNodeForwardAction, KeyNodeBackwardAction,KeyNodeRotateRightAction 和 KeyNodeRotateLeftAction。这 些 action 处理一个 node 的旋转和移动,这些都基于速度和传入的时间。    InputAction 很直观。你简单将触发器(trigger)赋给 action,并把键(key)赋给这些触 发器。然后在每次 update 期间它将检查是否有任何键被按下,如果它们是 trigger 赋予的按 键,那么则让 trigger 去调用相应的 action。  62    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    创建一个新的叫做 FlagRushInputHandler 的类,它继承自 InputHandler。这个类将只有 2 个方法,setKeyBindings和setActions。setKeyBindings将创建KeyBindingManager并赋予W,A,S,D 到相应的 trigger 名字,而 setActions 将为每个 trigger 创建 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。这控制了一个给出的Spatial * 允许我们去把它往前移、往后移和左右旋转。 * @author John * */ public class FlagRushInputHandler extends InputHandler { /** * 提供用于控制的node。api将处理input的创建 * @param node 我们想移动的那个node * @param api library将处理input的创建 */ public FlagRushInputHandler(Spatial node, String api){ setKeyBindings(api); setActions(node); } /** * 将action类赋给trigger。这些action处理结点前移、后移和旋转 * @param node 用于控制的结点 */ 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); 63    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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); } /** * 创建keyboard对象,当键被按下时允许我们获取键盘的值。 * 它接着设置action作为触发器的基础,如果确认了键被按下(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); } }    当这个类真的写完后,我们在自己的游戏中使用。创建一个 buildInput 方法,由 initGame 方法调用。这个方法将只有一行:  input = new FlagRushInputHandler( player, settings.getRenderer() );      正如你所猜的,这里 input 也是类变量。为什么要在类里面呢?因为你将在游戏的 update 期间调用它的 update。    就是那样!不管相信与否,我们现在具有做游戏的条件。Box 能被驾驶。现在试试看。 注意 ChaseCamera 将会落后于 box 一点然后尝试赶上,带来更平滑和真实的感觉。    接下来,我们将改进 box 的移动以便它能加速和减速。我们也将让它和环境交互得更好。 继续收看!  64    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    5.7、源码  import java.util.HashMap; import javax.swing.ImageIcon; import com.jme.app.BaseGame; import com.jme.bounding.BoundingBox; import com.jme.image.Texture; import com.jme.input.ChaseCamera; import com.jme.input.InputHandler; import com.jme.input.KeyBindingManager; import com.jme.input.KeyInput; import com.jme.input.thirdperson.ThirdPersonMouseLook; import com.jme.light.DirectionalLight; import com.jme.math.FastMath; import com.jme.math.Vector3f; import com.jme.renderer.Camera; import com.jme.renderer.ColorRGBA; import com.jme.scene.Node; import com.jme.scene.Skybox; import com.jme.scene.shape.Box; import com.jme.scene.state.CullState; import com.jme.scene.state.LightState; import com.jme.scene.state.TextureState; import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySystem; import com.jme.system.JmeException; import com.jme.util.TextureManager; import com.jme.util.Timer; import com.jmex.terrain.TerrainBlock; import com.jmex.terrain.util.MidPointHeightMap; import com.jmex.terrain.util.ProceduralTextureGenerator; public class Lesson5 extends BaseGame{ private int width,height; private int freq,depth; private boolean fullscreen; 65    //我们的camera对象,用于观看scene UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  private Camera cam; protected Timer timer; private Node scene; private TextureState ts; private TerrainBlock tb; private ForceFieldFence fence; private Skybox skybox; private Node player; private ChaseCamera chaser; private InputHandler input; public static void main(String[] args) { Lesson5 app = new Lesson5(); 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() { display.setTitle("Flag Rush"); scene = new Node("Scene Graph Node"); ZBufferState buf = display.getRenderer().createZBufferState(); buf.setEnabled(true); buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); scene.setRenderState(buf); buildTerrain(); buildLighting(); buildEnvironment(); createSkybox(); 66    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  buildPlayer(); buildChaseCamera(); buildInput(); CullState cs = display.getRenderer().createCullState(); cs.setCullFace(CullState.Face.Back); scene.setRenderState(cs); //更新scene用于渲染 scene.updateGeometricState(0.0f, true); scene.updateRenderState(); } private void buildInput() { input = new FlagRushInputHandler( player, settings.getRenderer() ); } private void buildChaseCamera() { Vector3f targetOffset = new Vector3f(); targetOffset.y = ((BoundingBox)player.getWorldBound()).yExtent*1.5f; HashMap props = new HashMap(); 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); } private void buildPlayer() { //box 代替 67    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  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)); player.attachChild(b); player.updateWorldBound(); scene.attachChild(player); } private void createSkybox() { skybox = new Skybox("skybox",10,10,10); Texture north = TextureManager.loadTexture( Lesson5.class.getClassLoader() .getResource("res/texture/north.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture south = TextureManager.loadTexture( Lesson5.class.getClassLoader() .getResource("res/texture/south.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture east = TextureManager.loadTexture( Lesson5.class.getClassLoader() .getResource("res/texture/east.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture west = TextureManager.loadTexture( Lesson5.class.getClassLoader() .getResource("res/texture/west.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture up = TextureManager.loadTexture( Lesson5.class.getClassLoader() .getResource("res/texture/top.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); Texture down = TextureManager.loadTexture( 68    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  Lesson5.class.getClassLoader() .getResource("res/texture/bottom.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear ); skybox.setTexture(Skybox.Face.North, north); skybox.setTexture(Skybox.Face.West, west); skybox.setTexture(Skybox.Face.South, south); skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down); skybox.preloadTextures(); scene.attachChild(skybox); } private void buildEnvironment() { fence = new ForceFieldFence("forceFieldFence"); //我们将手工做一些调整去让它更好适应terrain //首先我们将实体“模型”放大 fence.setLocalScale(5); //现在,让我们移动fence到terrain的高度并有一点陷入它里面 fence.setLocalTranslation( new Vector3f(25,tb.getHeight(25,25)+10,25) ); scene.attachChild(fence); } private void buildLighting() { /* 设置一个基础、默认灯光 */ DirectionalLight light = new DirectionalLight(); light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); light.setDirection(new Vector3f(1, -1, 0)); light.setEnabled(true); LightState lightState = display.getRenderer().createLightState(); lightState.setEnabled(true); lightState.attach(light); 69    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  scene.setRenderState(lightState); } /** * 创建heightmap和terrainBlock */ private void buildTerrain() { //生成随机地形数据 MidPointHeightMap heightMap = new MidPointHeightMap(64,1f); //缩放数据 Vector3f terrainScale = new Vector3f(4, .0575f, 4); //创建一个terrain block tb = new TerrainBlock( "terrain", heightMap.getSize(), terrainScale, heightMap.getHeightMap(), new Vector3f(0, 0, 0) ); tb.setModelBound(new BoundingBox()); tb.updateModelBound(); //通过三个纹理生成地形纹理 ProceduralTextureGenerator pt = new ProceduralTextureGenerator(heightMap); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/grassb.png") ), -128, 0, 128 ); pt.addTexture( new ImageIcon( getClass().getClassLoader() .getResource("res/dirt.jpg") ), 0, 128, 256 ); pt.addTexture( new ImageIcon( 70    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  getClass().getClassLoader() .getResource("res/highest.jpg") ), 128, 256, 384 ); pt.createTexture(32); //将纹理赋予地形 ts = display.getRenderer().createTextureState(); Texture t1 = TextureManager.loadTexture( pt.getImageIcon().getImage(), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true ); ts.setTexture(t1, 0); tb.setRenderState(ts); scene.attachChild(tb); } 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); 71    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //初始化摄像机 cam.setFrustumPerspective( 45.0f, (float)width/(float)height, 1f, 5000f ); Vector3f loc = new Vector3f(250f,1000f,250f); //将摄像机移到正确位置 cam.setLocation(loc); //我们改变自己的摄像机位置和视锥的标志 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去获取帧率 72    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  */ protected void update(float interpolation) { //更新timer去获取帧率 timer.update(); interpolation = timer.getTimePerFrame(); input.update(interpolation); chaser.update(interpolation); fence.update(interpolation); //我们想让skybox一直在我们的视野内,所以让它和camera一起移动 skybox.setLocalTranslation(cam.getLocation()); //我们不想chase camera走到世界下面,因此让它一直在水平面上2个单元。 if(cam.getLocation().y < (tb.getHeight(cam.getLocation())+2)) { cam.getLocation().y = tb.getHeight(cam.getLocation()) + 2; cam.update(); } //确保当玩家离开平面时我们不会坠落。 //当我们增加冲突时,fence将做它自己的工作并保持玩家在里面。 float characterMinHeight = tb.getHeight(player.getLocalTranslation()) + ((BoundingBox)player.getWorldBound()).yExtent; if( !Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight) ) player.getLocalTranslation().y = characterMinHeight; //当Escape被按下时,我们退出游戏 if(KeyBindingManager.getKeyBindingManager() .isValidCommand("exit") ){ finished = true; } //由于我们改变了场景(移动skybox),我们需要更新scene graph scene.updateGeometricState(interpolation, true); } } 73    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  6、 控制交通工具  6.1、介绍  这节课我们将改善我们上一次的控制模式。由于我们正驾驶一辆交通工具,我们假设它 遵循物理定律,它不应不经加速就移动,或者突然就停下来。  为了做到这个,我们增加一些模仿物理的东西。这将需要一个类保存交通工具的性能, 就和传统的 InputAction 一样。  6.2、增加一个交通工具    我们将封装所有交通工具的信息到一个单一的类叫 Vehicle。这将继承自 node,为这个 node 在 scene 中移动的方式增加信息。Vehicle 类将使用一个 Spatial,那将会是个模型来代 表我们的玩家。这是一个 Spatial,因此允许我们增加 Node 或 TriMesh 做为 Vehicle。    而定义 Vehicle 还有一些好处。这允许我们创建具有不同性能的 Vehicle。我们可能有一 个缓慢、笨重、火力强大的 Vehicle,或者是轻便、敏捷、疲弱的 Vehicle。当然,没有武器 或敌人,在大部分 scene 都不会选择一个缓慢的 Vehicle。这个将在以后改变。    我决定加入以下属性:    属性 描述 Velocity vehicle 当前的速度 Acceleration vehicle 的加速度 (加速的快慢) Braking vehicle 的减速度 (减速的快慢) Turn Speed vehicle 转动的速度 Max Speed 能达到的最大速度 Min Speed 能达到的最大负的速度(提供的时候采用正值,但速度将在负值达 到它) Weight vehicle 有多重,通常定义了摩擦力     所以我们最初对 Vehicle 类的缩减是简单继承 Node 并为这些值增加 getter 和 setter。我 将加入一些 public 方法,这允许我们去调整速度。  public void accerate(float time){ velocity = (velocity+=acceleration*time) > maxSpeed ? maxSpeed : velocity; }      足够简单。我不关心物理的精确性,只是看起来好就行了。减速很像这个:  public void brake(float time){ 74    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  velocity = (velocity-=braking*time) < -minSpeed ? -minSpeed : velocity; }      我接着增加一个漂移(drifting)方法,它将会在每次反向的时候被调用,这个描述了由 摩擦力引起 Vehicle 停下来。在科学天才的尝试下,我决定它由值(weight/2)调整。我不会 深入证明这个选择只多么完美…只需知道没有什么理由让我选择这个,我只是使用其它不同 的值玩了玩。    我们现在有一个 Vehicle 类,它描述了一个 Node,能被作为汽车一样被运行。现在我们 只需把它加载进我们的游戏。这将包含在 buildPlayer 方法中。  private void buildPlayer() { //box 代替 Box b = new Box("box", new Vector3f(), 0.35f,0.25f,0.5f); b.setModelBound(new BoundingBox()); b.updateModelBound(); //设置Vehicle的属性(这些数字能被认为是单元/秒 Unit/S) player = new Vehicle("Player Node",b); player.setAcceleration(15); player.setBraking(25); player.setTurnSpeed(5); player.setWeight(25); player.setMaxSpeed(25); player.setMinSpeed(15); player.setLocalTranslation(new Vector3f(100,0, 100)); player.attachChild(b); player.updateWorldBound(); scene.attachChild(player); player.setRenderQueueMode(Renderer.QUEUE_OPAQUE); }      通常,我设置的值是这样获取的,通过一直玩,一直尝试知道我发现我喜欢它。由于现 在 player 对象需要用到 Vehicle 中自己定义的方法,而这些方法在父类 node 中不存在,因此, 我们需要修改 player 定义的那一行:  private Vehicle player;      现在,我们有了自己的 vehicle,我们需要控制它。    75    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  6.3、Vehicle.java  import com.jme.scene.Node; import com.jme.scene.Spatial; /** * Vehicle将会是一个node,处理了vehicle在游戏中的移动。 * 它拥有定义它的加速度acceleration、速度velocity * 和减速度barking的参数。 * 转向速度turnSpeed定义了它有怎样的处理。 * 而重量weight定义了漂移时的摩擦力,它下降多快等等。 * @author Mark Powell */ public class Vehicle extends Node { private Spatial model; private float velocity; private float maxSpeed = 30; private float minSpeed = 10; private float acceleration; private float braking; private float turnSpeed; private float weight; /** * 基础构造方法,获取模型作为vehicle的外观 * @param id vehicle的id * @param model 表达vehicle绘图外观的模型 */ public Vehicle(String id, Spatial model){ super(id); setModel(model); } /** * 构造方法,获取模型作为vehicle的外观 * @param id vehicle的id * @param model 表达vehicle绘图外观的模型 * @param maxSpeed vehicle能达到的最大速度(Unit/Sec) 76    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  * @param minSpeed vehicle的反向最大速度(Unit/Sec) * @param accerlation vehicle多快能达到最大速度 * @param braking vehicle减速的速度以及按多久反向 * @param weight vehicle的重量 * @param turnSpeed vehicle转向转得多块 */ public Vehicle(String id,Spatial model, float maxSpeed, float minSpeed, float accerlation, float braking, float weight, float turnSpeed){ super(id); setModel(model); this.maxSpeed = maxSpeed; this.minSpeed = minSpeed; this.acceleration = acceleration; this.braking = braking; this.weight = weight; this.turnSpeed = turnSpeed; } public Spatial getModel() { return model; } public void setModel(Spatial model) { this.detachChild(this.model); this.model = model; this.attachChild(this.model); } public float getVelocity() { return velocity; } public void setVelocity(float velocity) { this.velocity = velocity; } public float getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(float maxSpeed) { this.maxSpeed = maxSpeed; } public float getMinSpeed() { return minSpeed; } public void setMinSpeed(float minSpeed) { 77    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  this.minSpeed = minSpeed; } public float getAcceleration() { return acceleration; } public void setAcceleration(float acceleration) { this.acceleration = acceleration; } public float getBraking() { return braking; } public void setBraking(float braking) { this.braking = braking; } public float getTurnSpeed() { return turnSpeed; } public void setTurnSpeed(float turnSpeed) { this.turnSpeed = turnSpeed; } public float getWeight() { return weight; } public void setWeight(float weight) { this.weight = weight; } public void accerate(float time){ velocity = (velocity+=acceleration*time) > maxSpeed ? maxSpeed : velocity; } public void brake(float time){ velocity = (velocity-=braking*time) < -minSpeed ? -minSpeed : velocity; } public void drift(float time) { velocity = velocity < 0 ? (velocity += ((weight/5) * time)): (velocity -= ((weight/5) * time)); } }   78    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  6.4、Actions    在第五课,我们创建一个 InputHandler ,它调用了 4 个分开的 InputAction : KeyNodeForwardAction,KeyNodeBackwardAction,KeyTurnLeftAction,KeyTurnRightAction。 我们现在将编写我们自定义的 Action 去使用新的 Vehicle 类。    首先,我们想用一个加速 vehicle 的 action 替换 KeyNodeForwardAction。我决定创建 5 个新的 InputAction(比之前多了一个)。所以,为了替换 keyNodeForwardAction,我们将创建 AccelerateAction。    我们不再需要关心 action 的速度,因为这是由 vehicle 决定的。我们只需要在 action 执 行的时候更新它的速度,然后基于这个新的速度移动 vehicle。  public void performAction(InputActionEvent e) { node.accerate(e.getTime()); Vector3f loc = node.getLocalTranslation(); loc.addLocal( node.getLocalRotation().getRotationColumn(2,tempVa) .multLocal(node.getVelocity()*e.getTime()) ); node.setLocalTranslation(loc); }      正如你看到的,我们仅仅调用 Vehicle 的 accelerate 方法(AccelerateAction 也在构造期 间接收一个 Vehicle 对象,而不是 Node),然后改变它的移动。    BrakeAction 也一样,除了我们叫它 brake 之外。  public void performAction(InputActionEvent e) { node.brake(e.getTime()); Vector3f loc = node.getLocalTranslation(); loc.addLocal( node.getLocalRotation().getRotationColumn(2,tempVa) .multLocal(node.getVelocity()*e.getTime()) ); node.setLocalTranslation(loc); }      这些 action 现在将允许我们加速和停止 vehicle。现在我们需要允许它转弯。正如你可 能猜到它和 KeyNodeTurn*Action 类是一样的,除了我们使用 Vehicle 的转弯速度代替 action 的速度。一点不同的是我加入了判断我们的速度是正的还是负的。如果它是正的,那么我们 正常工作,但如果它是负的,Vehicle 正在后退,所以转弯的效果将相反。  public void performAction(InputActionEvent evt) { //我们想转不同的方向,这取决于我们往哪个方向行驶 if(vehicle.getVelocity() < 0) { 79    incr.fromAngleNormalAxis( UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  -vehicle.getTurnSpeed() * evt.getTime() , upAxis ); } else { incr.fromAngleNormalAxis( vehicle.getTurnSpeed() * evt.getTime(), upAxis ); } vehicle.getLocalRotation().fromRotationMatrix( incr.mult( vehicle.getLocalRotation() .toRotationMatrix(tempMa), tempMb ) ); vehicle.getLocalRotation().normalize(); } 和  public void performAction(InputActionEvent evt) { if (vehicle.getVelocity() < 0) { incr.fromAngleNormalAxis( vehicle.getTurnSpeed() * evt.getTime(), upAxis ); } else { incr.fromAngleNormalAxis( -vehicle.getTurnSpeed() * evt.getTime(), upAxis ); } vehicle.getLocalRotation().fromRotationMatrix( incr.mult( vehicle.getLocalRotation() .toRotationMatrix(tempMa), tempMb ) ); vehicle.getLocalRotation().normalize(); }      我们最后的 InputAction 将处理 vehicle 的漂移。这个 action 是不同的,因为它不会由 key 80    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  触发,但在每次 update 都发生。这将在下一节讲到。现在,我们调用 Vehicle 的 drift 方法并 更新位置。  public void performAction(InputActionEvent evt) { vehicle.drift(evt.getTime()); Vector3f loc = vehicle.getLocalTranslation(); loc.addLocal( vehicle.getLocalRotation() .getRotationColumn(2, tempVa) .multLocal( vehicle.getVelocity() * evt.getTime() ) ); vehicle.setLocalTranslation(loc); }      你可能注意到一堆重复的代码。相当于大多类都做相同的事情,只是调用 vehicle 不同 的方法。下一节,我们将清理并优化。      既然我们现在已经有了我们 5 个新的 aciton,我们需要在 FlagRushHandler 中调用它们。 我们将赋予相同的键(WASD)。      所以在 setActions 方法中我们只是创建 4 个移动 action,传入 vehicle 对象作为参数。      Drift 也被实例化而并没有赋给一个触发器。这是因为我们现在覆盖了 update 方法。 update 调用 super 去检查其他常规 action,然后调用 drift action。这确保了当没有键被按下 时 drift 漂移。然而,这个逻辑有点瑕疵,这就给读者作为练习去弄清那是什么。一点暗示, 当玩家按下 W 或 S 的时候发生了什么?我将在下一节课修复和讨论这个瑕疵。是的,我现 在给出作业了。  6.5、FlagRushHandler.java  import lesson6.actions.AccelerateAction; import lesson6.actions.DriftAction; import lesson6.actions.VehicleRotateLeftAction; 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.KeyNodeRotateRightAction; /** 81    * 游戏的InputHnadler。这控制了一个给出的Spatial UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  * 允许我们去把它往前移、往后移和左右旋转。 * @author John * */ public class FlagRushInputHandler extends InputHandler { private DriftAction drift; /** * 提供用于控制的node。api将处理input的创建 * @param node 我们想移动的那个node * @param api library将处理input的创建 */ public FlagRushInputHandler(Vehicle node, String api){ setKeyBindings(api); setActions(node); } /** * 将action类赋给trigger。这些action处理结点前移、后移和旋转 * @param node 用于控制的结点 */ private void setActions(Vehicle node) { AccelerateAction forward = new AccelerateAction(node); addAction(forward,"forward",true); KeyNodeBackwardAction backward = new KeyNodeBackwardAction(node,15f); addAction(backward,"backward",true); VehicleRotateLeftAction rotateLeft = new VehicleRotateLeftAction(node); addAction(rotateLeft,"turnLeft",true); KeyNodeRotateRightAction rotateRight = new KeyNodeRotateRightAction(node,5f); rotateRight.setLockAxis( node.getLocalRotation().getRotationColumn(1) ); addAction(rotateRight,"turnRight",true); //不由key触发 drift = new DriftAction(node); } 82    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  /** * 创建keyboard对象,当键被按下时允许我们获取键盘的值。 * 它接着设置action作为触发器的基础,如果确认了键被按下(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); } @Override public void update(float time) { if(!isEnabled()) return; super.update(time); //我们通常想让摩擦力控制漂移 drift.performAction(event); } } 6.6、AccelerateAction.java  import lesson6.Vehicle; import com.jme.input.action.InputAction; import com.jme.input.action.InputActionEvent; import com.jme.math.Vector3f; public class AccelerateAction extends InputAction { private Vehicle node; private Vector3f tempVa=new Vector3f(); public AccelerateAction(Vehicle node){ this.node = node; } @Override public void performAction(InputActionEvent e) { node.accerate(e.getTime()); 83    Vector3f loc = node.getLocalTranslation(); UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  loc.addLocal( node.getLocalRotation().getRotationColumn(2,tempVa) .multLocal(node.getVelocity()*e.getTime()) ); node.setLocalTranslation(loc); } } 6.7、BrakeAction.java  import lesson6.Vehicle; import com.jme.input.action.InputAction; import com.jme.input.action.InputActionEvent; import com.jme.math.Vector3f; public class BrakeAction extends InputAction { private Vehicle node; private Vector3f tempVa=new Vector3f(); public BrakeAction(Vehicle node){ this.node = node; } @Override public void performAction(InputActionEvent e) { node.brake(e.getTime()); Vector3f loc = node.getLocalTranslation(); loc.addLocal( node.getLocalRotation().getRotationColumn(2,tempVa) .multLocal(node.getVelocity()*e.getTime()) ); node.setLocalTranslation(loc); } } 6.8、VehicleRotateLeftAction.java  import lesson6.Vehicle; import com.jme.input.action.InputAction; import com.jme.input.action.InputActionEvent; 84    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  import com.jme.math.Matrix3f; import com.jme.math.Vector3f; public class VehicleRotateLeftAction extends InputAction { //处理旋转的临时变量 private static final Matrix3f incr = new Matrix3f(); private static final Matrix3f tempMa = new Matrix3f(); private static final Matrix3f tempMb = new Matrix3f(); //我们使用Y轴作为上 private Vector3f upAxis = new Vector3f(0,1,0); //操纵的结点 private Vehicle vehicle; public VehicleRotateLeftAction(Vehicle vehicle){ this.vehicle = vehicle; } @Override public void performAction(InputActionEvent evt) { //我们想转不同的方向,这取决于我们往哪个方向行驶 if(vehicle.getVelocity() < 0) { incr.fromAngleNormalAxis( -vehicle.getTurnSpeed() * evt.getTime() , upAxis ); } else { incr.fromAngleNormalAxis( vehicle.getTurnSpeed() * evt.getTime(), upAxis ); } vehicle.getLocalRotation().fromRotationMatrix( incr.mult( vehicle.getLocalRotation() .toRotationMatrix(tempMa), tempMb ) ); vehicle.getLocalRotation().normalize(); } } 85    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  6.9、VehicleRotateRightAction.java  import lesson6.Vehicle; import com.jme.input.action.InputAction; import com.jme.input.action.InputActionEvent; import com.jme.math.Matrix3f; import com.jme.math.Vector3f; public class VehicleRotateRightAction extends InputAction { //用于处理旋转的临时变量 private static final Matrix3f incr = new Matrix3f(); private static final Matrix3f tempMa = new Matrix3f(); private static final Matrix3f tempMb = new Matrix3f(); //用于操作的结点 private Vehicle vehicle; private Vector3f upAxis = new Vector3f(0, 1, 0); public VehicleRotateRightAction(Vehicle vehicle){ this.vehicle = vehicle; } @Override public void performAction(InputActionEvent evt) { if (vehicle.getVelocity() < 0) { incr.fromAngleNormalAxis( vehicle.getTurnSpeed() * evt.getTime(), upAxis ); } else { incr.fromAngleNormalAxis( -vehicle.getTurnSpeed() * evt.getTime(), upAxis ); } vehicle.getLocalRotation().fromRotationMatrix( incr.mult( vehicle.getLocalRotation() .toRotationMatrix(tempMa), tempMb ) 86    ); UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  vehicle.getLocalRotation().normalize(); } } 6.10、DriftAction.java  import lesson6.Vehicle; import com.jme.input.action.InputAction; import com.jme.input.action.InputActionEvent; import com.jme.math.Vector3f; public class DriftAction extends InputAction { private Vehicle vehicle; private Vector3f tempVa = new Vector3f(); public DriftAction(Vehicle vehicle) { this.vehicle = vehicle; } @Override public void performAction(InputActionEvent evt) { vehicle.drift(evt.getTime()); Vector3f loc = vehicle.getLocalTranslation(); loc.addLocal( vehicle.getLocalRotation() .getRotationColumn(2, tempVa) .multLocal( vehicle.getVelocity() * evt.getTime() ) ); vehicle.setLocalTranslation(loc); } }  6.11、总结    就那样,我们现在已经创建自己的 action 去允许我们的 vehicle 以一种更真实的方式运 行。这将给我们一种控制玩家执行特性的能力,包括后面的敌人。    下一步,我们将看看改善 terrain 和图形外观。  87    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  7、拥抱大地让我们驾驶的不再是Box    迄今为止,我们拥有一个带驾驶参数的 box。允许我们创建不同性能类型的 vehicle。box 在地形上表现得很好。我们开始看一些新的。我们开始获得一些玩游戏所必需的基础。所以, 让我们花这一节课来让游戏中的事物变得好看点。是时候增加一些炫的啦。我们将让 terrain 看起来更真实,用一辆酷的未来主义 vehicle 代替 box,而且让这个酷的未来主义 vehicle 跟 随 terrain 得更好。让我们开始!  7.1、优化    就像之前的向导提到的一样,我们的 action 正在做一些相同的事,并重复很多代码。此 刻,我们为了优化代码将做一些巩固。首先,AccelerateAction 和 BrakeAction 被融合进一个 单一的类 VehicleRotateRightAction。然而 VehicleRotateLeftAction 和 VehicleRotateRightAction 被组合进 VehicleRotateAction。      ForwardAndBackAction 的 update 方法决定了我们想要让 vehicle 前进的方向,并调用 vehicle 相应的方法。它不再 update translation。我过一会将说下这个。    /** * 这个action调用vehicle的 * accelerate 或 brake 命令去调整它的速度 * */ @Override public void performAction(InputActionEvent evt) { if( direction == FORWARD){ node.accerate(evt.getTime()); }else if( direction == BACKWARD ){ node.brake(evt.getTime()); } }      这里的 FORWARD 和 BACKWARD 是类的常量,而一个 int 参数被加到构造方法去定义我 们想要移动的方向。      相似的,VehicleRotationAction 被改为:    /** * 由vehicle的转弯速度转弯。 * 如果vehicle正在后退,方向相反。 */ @Override 88    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  public void performAction(InputActionEvent evt) { //影响方向 if(direction == LEFT) { modifier = 1; } else if(direction == RIGHT) { modifier = -1; } //我们想根据我们运动的方向转向不同的方向 if(vehicle.getVelocity() < 0) { incr.fromAngleNormalAxis( -modifier * vehicle.getTurnSpeed() * evt.getTime(), upAxis ); } else { incr.fromAngleNormalAxis( modifier * vehicle.getTurnSpeed() * evt.getTime(), upAxis ); } vehicle.getLocalRotation().fromRotationMatrix( incr.mult( vehicle.getLocalRotation().toRotationMatrix(tempMa), tempMb ) ); vehicle.getLocalRotation().normalize(); }      RIGHT 和 LEFT 做为常量被添加,而 direction 做为构造参数。modifier 值定义了我们将转 的方向,它是根据 direction 决定的。      最后,DriftAction 被轻微更新了一下。    @Override public void performAction(InputActionEvent evt) { vehicle.drift(evt.getTime()); }     正如你看到的,translation 代码被移除了。它被移往哪里?注意,我们将讲一下它。    既然 action 被修改了,我们需要修改一下 handler 使用它们。    89    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    ForwardAndBackwardAction forward = new ForwardAndBackwardAction( node, ForwardAndBackwardAction.FORWARD ); addAction(forward,"forward",true); ForwardAndBackwardAction backward = new ForwardAndBackwardAction( node, ForwardAndBackwardAction.BACKWARD ); addAction(backward,"backward",true); VehicleRotateAction rotateLeft = new VehicleRotateAction( node, VehicleRotateAction.LEFT ); addAction(rotateLeft,"turnLeft",true); VehicleRotateAction rotateRight = new VehicleRotateAction( node, VehicleRotateAction.RIGHT ); addAction(rotateRight,"turnRight",true);     所以,最后的改变是 Vehicle 怎样移动它的位置。这个问题之前都是 AccelerateAction  /  BrakeAction 和 DriftAction 在应用移位的。这实际上导致 vehicle 每次移动 2 次。因此,增加 一个 update 方法给 Vehicle 并在 handler 的 update 方法中调用 vehicle 的 update。      public void update(float time){ this.localTranslation.addLocal( this.localRotation.getRotationColumn(2, tempVa) .mult(velocity*time) ); }    和  vehicle.update(time);    90    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    增加到 handler 的 update 方法。      现在,vehicle 移动了它原来速度的一半,所以我将调整 vehicle 的性能。附加地,我也 觉得它转得太快。所以,我也降低了转速。  player.setTurnSpeed(2.5f); player.setMaxSpeed(25); player.setMinSpeed(15);      就是那样。所以,让我们用好的素材继续做下去!  7.2、Detail Texture(细节纹理)和TextureCombining(纹 理组合)    我们将增加的第一个悦目的东西是 detail Texture。一个 detail Texture 通常是由黑色和白 色组合而成的图片,从而表现地面的特性。通常,黑色和白色的 texture 模拟了不光滑地面 凹凸物形成的阴影。所以,这个 texture 被应用于 terrain 并和原始 terrain 的 texture 组合。 terrain 已有的颜色 texture 和这个 detail texture 的组合允许颜色 texture 呈现出比它本身更多 的细节。    为了完成这个,我们将使用 jME 的 multitexturing 特性。这包含将第二张 texture 放入绘 图卡/显卡(Graphics Card)的第二 Texture 单元,并为 texture 定义参数,指明它们 2 个是怎 样被组合的。    首先,我们加载 detail texture(在 buildTerrain 方法中进行):    //加载细节纹理并为2个terrain的texture设置组合模型 Texture t2 = TextureManager.loadTexture( Lesson7.class.getClassLoader() .getResource("res/Detail.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts.setTexture(t2, 1);      我们就像我们之前一样使用 TextureManager 加载一个 Detail.jpg 图像。我们接着设置它 到我们的 TextureState 做为第二个 texture(unit 1,  记住我们从 0 开始数)。这告诉 TextureState  ts 去保存 2 张 Texture(t1 和 t2).      下一步,由于这是一种细节纹理,我们想让它……很好,细节。创建一个高分辨率的 texture 图像不会有什么影响,由于这只是提供为地面基础 texture。因此,我们将用一张相当低的 分辨率并让它在地面上重复(repeat)很多次。实际上,detail 纹理将在 terrain 上的一边到 另一边 repeat16 次。为了允许这个 repeat,我们需要设置 texture 的 wrap 模式。  t2.setWrap(Texture.WrapMode.Repeat);    91    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    现在,我们需要定义这 2 张 texture 是怎样被组合的。Texture 定义了很多组合模式 (combine  mode)。首先,我们将设置每个 texture 为 ApplyMode.Combine,这告诉 TextureState,那 2 张 Texture 的颜色将被 Combine 进一个输出颜色。第一个 texture 将是我 们的基础 texture,所以我们让它用它的颜色(RGB)乘以第二张 texture 以得到最后颜色。 我们这么做,因为第二张 texture 是黑色和白色。因此,细节全白,我们将得到完整颜色, 而当细节完全黑色,输出颜色将是黑色。我们接着告诉 texture 1,所有的输入源将从 0 到 1, 而且只适用颜色(RGB),我们不必关心 Alpha 值。  所以,我们主要的 texture(t1)看起来将是这样:  t1.setApply(Texture.ApplyMode.Combine); t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate); t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor); t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);      接下来,我们需要修改 t2 去应用它的颜色值给之前的 texture(t1).一切都和 t1 一样(定 义颜色来自哪里),但我们设置 CombineFuncRGB 为 AddSigned,这意味着它将把自己的加到 其它的 texture 单元上,该 texture 单元在和原始 texture 相乘之前就存在。  t2.setApply(Texture.ApplyMode.Combine); t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned); t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); t2.setCombineSrc1RGB(Texture.CombinerSource.Previous); t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);      所以,现在已经设置了这 2 个 texture,我们的 render 到屏幕的结果 texture 单元将是这 2 个美丽的结合。      然而,我们仍然没定义怎样将 texture 应用到 terrain 本身。那就是,纹理坐标(texture  coordinate)。我们本应该为 TerrainBlock 定义 texture  coordinate,但幸运的是,我们不必这 么做。使用 setDetailTexture 方法,我们能让它为我们完成。所以,调用:  tb.setDetailTexture(1, 16);      这告诉 terrain 去设置 texture 单元 1 repeat 16 次.那就是沿着 texture 的高度和宽度 repeat  16 次。    现在,当你运行例子,我们的 box 将飞奔在一个看起来有更多细节的 terrain 上。  7.3、Terrain法向(Normal)和朝向(Orientation)    既然我们的 terrain 看起来更有细节,让我们让 box 也在地面上跟得更好。box 保持它的 高度在 terrain 上以便它能跟着山的高度变化自己的高度,这很好,但是,它看起来却不是 92    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  很正确,因为它一直保持相同的朝向(Orientation)。    我们所要做的是使用 2 个工具方法:getSurfaceNormal 和 rotateUpTo。getSurfaceNormal 方法给我们沿着 TerrainBlock 上任何点的 normal。就像 getHeight 它将玩家的位置插入 height  map 最近的点并计算那点的 normal。这个 normal 是保存在一个提供的 Vector 对象中,所以 你可以增加:  //保存terrain的任何一个给出点的法向 private Vector3f normal = new Vector3f();    到应用程序的顶部。我们将于每次 update 循环期间把这个和玩家的位置做为输入传入 getSurfaceNormal 方法。这将在每帧给我们一个玩家当前位置的 normal。      现在,我们知道我们正处的 terrain 的角度,我们想要调整玩家对齐山。调用 rotateUpTo 允许我们这样做。简单在 player 上带着我们从 terrain 获取的 normal 调用方法,那么我们就 做完了。只是简单的处理就为游戏增加了更多的真实性。    //获取terrain在我们当前位置的normal。 //我们然后将它应用到player上面。 tb.getSurfaceNormal( player.getLocalTranslation(), normal ); if(normal != null) { player.rotateUpTo(normal); }    现在,驾驶着 box 并看它是怎样跟着 terrain 的。太激动了!现在,让我们处理掉那个 丑陋的 box。  7.4、加载模型(model)    那么,现在我们想真正驾驶一辆cool的未来主义vehicle。很好,就像很多没艺术的人一 样,我开始寻找免费的模型。3D Cafe  有一些未来主义的摩托车。它甚至在两边有漂亮的枪。    所以,我们需要做一些事去让这个进入我们的 scene。  1、 加载.3ds 文件(它是 3DS MAX 的格式,相当标准)  2、 转换为.jme(jME 二进制格式)  93    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  3、 加载二进制.jme 格式到 scene graph  很明显,每次都将.3ds 转为.jme 不是必须的。可以优化,我们将在发布游戏之前先这么 做, 一些被支持的格式 m 的 yte 。 scene  graph 的.jme 数据。使用 BinaryImporter 我们能调用它的 ad 大了。手动使用你 box 创建部分换成下面)。  Node model = null; e C1 = new MaxToJme(); redInputStream(maxFile.openStream()), l = (Node)BinaryImporter.getInstance().load( 放它,让它比原来更小 025f); ngBox()); } ca 最后,用它代替在 vehicle 的 box 位置: player = new Vehicle("Player Node",model); 我们现在有一个 cool 的 bike 用于驾驶。   那么以后只需加载.jme。这在下节课的优化将进行更深入的讨论。    加载模型的第一个任务是使用 jME 的 Model  Conversion 工具。有 ( d2、md3、milkshape、3ds、obj、ase),选择和你的格式对应的 Converter。这 个 converter 创建一个.jme 二进制文件。转换需要 2 件事:文件的 InputStream,用于保存.jme 的 OutputStream。所以,我们将创建一个 MaxToJme converter 和一个 ByteArrayOutputStream 对 象。我们接着创建一个指向我们模型的 URL(我们将使用这个 URL 去获取 InputStream)。    调用 converter 的 convert 方法将使用 jme 文件的数据填充我们 B ArrayOutputStream     我们现在拥有能加载进 lo 方法去创建一个 Node。这个 Node 将被用于替换 vehicle 中的 box。    如果你继续工作你可能会注意到,model 有点大…,事实上,它太巨 在网上找到的模型并没有精确为我们的游戏制作。所以,我们需要缩放它到适合我们的 terrain。事实上,我们将缩放很多(0.0025%)。    我们现在已经加载了 bike(将 buildPlayer 中的   try { MaxToJm ByteArrayOutputStream BO = new ByteArrayOutputStream(); URL maxFile = Lesson7.class.getClassLoader() .getResource("res/bike.3ds"); C1.convert( new Buffe BO ); mode new ByteArrayInputStream(BO.toByteArray()) ); //缩 model.setLocalScale(.0 model.setModelBound(new Boundi model.updateModelBound(); tch (IOException e) { e.printStackTrace(); }  94    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  另一个轻微的改变是我们将在增加 model 之后 update 实体 scene。这是因为 scene.updateGeometricState(0, true); 7.5、结论    我们已经确实地为我们的游戏添加了一些视觉上具有吸引力的东西。  Model 是由许多 Node(或 scene graph中的分支)组成的。由于这样,这个能影 响到实体树 BoundingVolumes 的合并。所以,我们将调用updateGeometricState 去重建 scene:       terrain 看起来更好,我们驾驶一辆真正的交通工具而且它精确紧跟着 terrain。下一步, 7.6、源码  port java.io.BufferedInputStream;   我们将再优化一点:保存一个.jme 文件并加载,从而不必每次都转化,让 bike 在转弯时倾 斜和车轮旋转。我们将介绍终点 Flag,所以我们将有一个目标!继续观看! im import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; 95    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  import java.net.URL; import java.util.HashMap; port javax.swing.ImageIcon; port com.jme.app.BaseGame; ngBox; a; er; .ThirdPersonMouseLook; ; im im import com.jme.bounding.Boundi import com.jme.image.Texture; import com.jme.input.ChaseCamer import com.jme.input.InputHandler; import com.jme.input.KeyBindingManag import com.jme.input.KeyInput; import com.jme.input.thirdperson import com.jme.light.DirectionalLight; import com.jme.math.FastMath; import com.jme.math.Vector3f; import com.jme.renderer.Camera; import com.jme.renderer.ColorRGBA import com.jme.renderer.Renderer; import com.jme.scene.Node; import com.jme.scene.Skybox; import com.jme.scene.shape.Box; ate; inary.BinaryImporter; ightMap; erator; blic class Lesson7 extends BaseGame{ private int width,height; n; 们的camera对象,用于观看scene import com.jme.scene.state.CullSt import com.jme.scene.state.LightState; import com.jme.scene.state.TextureState; import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySystem; import com.jme.system.JmeException; import com.jme.util.TextureManager; import com.jme.util.Timer; import com.jme.util.export.b import com.jmex.model.converters.MaxToJme; import com.jmex.terrain.TerrainBlock; import com.jmex.terrain.util.MidPointHe import com.jmex.terrain.util.ProceduralTextureGen pu private int freq,depth; private boolean fullscree //我 96    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  private Camera cam; protected Timer timer; ts; ate TerrainBlock tb; ate ForceFieldFence fence; ate Vehicle player; r; ate InputHandler input; 存terrain的任何一个给出点的法向 tor3f(); ic static void main(String[] args) { ().getResource("res/logo.png"); /* texture ed void cleanup() { protected void initGame() { sh"); "); ferState buf = display.getRenderer().createZBufferState(); State.TestFunction.LessThanOrEqualTo); dTerrain(); private Node scene; private TextureState priv priv private Skybox skybox; priv private ChaseCamera chase priv //保 private Vector3f normal = new Vec publ Lesson7 app = new Lesson7(); java.net.URL url = app.getClass().getClassLoader app.setConfigShowMode(ConfigShowMode.AlwaysShow,url); app.start(); } * 清除 */ protect ts.deleteAll(); } display.setTitle("Flag Ru scene = new Node("Scene Graph Node ZBuf buf.setEnabled(true); buf.setFunction(ZBuffer scene.setRenderState(buf); buil 97    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  buildLighting(); buildEnvironment(); createSkybox(); buildPlayer(); buildChaseCamera(); State cs = display.getRenderer().createCullState(); 新scene用于渲染 icState(0.0f, true); private void buildInput() { Handler( tRenderer() private void buildChaseCamera() { tor3f(); er.getWorldBound()).yExtent*1.5f; (); rsonMouseLook.PROP_MAXASCENT, put( mera.PROP_INITIALSPHERECOORDS, ) put(ChaseCamera.PROP_TARGETOFFSET, targetOffset); er = new ChaseCamera(cam, player, props); buildInput(); Cull cs.setCullFace(CullState.Face.Back); scene.setRenderState(cs); //更 scene.updateGeometr scene.updateRenderState(); } input = new FlagRushInput player, settings.ge ); } Vector3f targetOffset = new Vec targetOffset.y = ((BoundingBox)play HashMap props = new HashMap props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6"); props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3"); props.put( ThirdPe ""+45*FastMath.DEG_TO_RAD ); props. ChaseCa new Vector3f(5,0,30*FastMath.DEG_TO_RAD ); props. chas chaser.setMaxDistance(8); chaser.setMinDistance(2); } 98    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  private void buildPlayer() { e C1 = new MaxToJme(); redInputStream(maxFile.openStream()), l = (Node)BinaryImporter.getInstance().load( 放它,让它比原来更小 025f); ox()); //设置Vehicle的属性(这些数字能被认为是单元/秒 Unit/S) ); ayer.setLocalTranslation(new Vector3f(100,0, 100)); nderer.QUEUE_OPAQUE); ene.attachChild(player); , true); private void createSkybox() { ",10,10,10); e( Node model = null; try { MaxToJm ByteArrayOutputStream BO = new ByteArrayOutputStream(); URL maxFile = Lesson7.class.getClassLoader() .getResource("res/bike.3ds"); C1.convert( new Buffe BO ); mode new ByteArrayInputStream(BO.toByteArray()) ); //缩 model.setLocalScale(.0 model.setModelBound(new BoundingB model.updateModelBound(); } catch (IOException e) { e.printStackTrace(); } player = new Vehicle("Player Node",model); player.setAcceleration(15); player.setBraking(25); player.setTurnSpeed(2.5f player.setWeight(25); player.setMaxSpeed(25); player.setMinSpeed(15); pl player.updateWorldBound(); player.setRenderQueueMode(Re sc scene.updateGeometricState(0 } skybox = new Skybox("skybox Texture north = TextureManager.loadTextur Lesson7.class.getClassLoader() 99    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  .getResource("res/texture/north.jpg"), ap, e south = TextureManager.loadTexture( uth.jpg"), ap, e east = TextureManager.loadTexture( st.jpg"), Map, e west = TextureManager.loadTexture( st.jpg"), Map, e up = TextureManager.loadTexture( p.jpg"), pMap, e down = TextureManager.loadTexture( ttom.jpg"), p, ox.setTexture(Skybox.Face.North, north); ); e.attachChild(skybox); Texture.MinificationFilter.BilinearNearestMipM Texture.MagnificationFilter.Bilinear ); Textur Lesson7.class.getClassLoader() .getResource("res/texture/so Texture.MinificationFilter.BilinearNearestMipM Texture.MagnificationFilter.Bilinear ); Textur Lesson7.class.getClassLoader() .getResource("res/texture/ea Texture.MinificationFilter.BilinearNearestMip Texture.MagnificationFilter.Bilinear ); Textur Lesson7.class.getClassLoader() .getResource("res/texture/we Texture.MinificationFilter.BilinearNearestMip Texture.MagnificationFilter.Bilinear ); Textur Lesson7.class.getClassLoader() .getResource("res/texture/to Texture.MinificationFilter.BilinearNearestMi Texture.MagnificationFilter.Bilinear ); Textur Lesson7.class.getClassLoader() .getResource("res/texture/bo Texture.MinificationFilter.BilinearNearestMipMa Texture.MagnificationFilter.Bilinear ); skyb skybox.setTexture(Skybox.Face.West, west); skybox.setTexture(Skybox.Face.South, south); skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down skybox.preloadTextures(); scen 100    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  } private void buildEnvironment() { rceFieldFence"); //我们将手工做一些调整去让它更好适应terrain in的高度并有一点陷入它里面 t(25,25)+10,25) e.attachChild(fence); private void buildLighting() { DirectionalLight(); .0f)); tState lightState = tate(); e.setRenderState(lightState); /** eightmap fence = new ForceFieldFence("fo //首先我们将实体“模型”放大 fence.setLocalScale(5); //现在,让我们移动fence到terra fence.setLocalTranslation( new Vector3f(25,tb.getHeigh ); scen } /* 设置一个基础、默认灯光 */ DirectionalLight light = new light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1 light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); light.setDirection(new Vector3f(1, -1, 0)); light.setEnabled(true); Ligh display.getRenderer().createLightS lightState.setEnabled(true); lightState.attach(light); scen } * 创建h 和terrainBlock void buildTerrain() { heightMap = new MidPointHeightMap(64,1f); 放数据 rrainScale = new Vector3f(4, .0575f, 4); 建一个terrain block */ private //生成随机地形数据 MidPointHeightMap //缩 Vector3f te //创 tb = new TerrainBlock( 101    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  "terrain", heightMap.getSize(), htMap(), ModelBound(new BoundingBox()); 过三个纹理生成地形纹理 or pt = ightMap); ddTexture( on( ).getClassLoader() ") , 128 Texture( on( ).getClassLoader() 256 Texture( on( ).getClassLoader() g") 6, 384 ateTexture(32); 纹理赋予地形 Renderer().createTextureState(); ar, terrainScale, heightMap.getHeig new Vector3f(0, 0, 0) ); tb.set tb.updateModelBound(); //通 ProceduralTextureGenerat new ProceduralTextureGenerator(he pt.a new ImageIc getClass( .getResource("res/grassb.png ), -128, 0 ); pt.add new ImageIc getClass( .getResource("res/dirt.jpg") ), 0, 128, ); pt.add new ImageIc getClass( .getResource("res/highest.jp ), 128, 25 ); pt.cre //将 ts = display.get Texture t1 = TextureManager.loadTexture( pt.getImageIcon().getImage(), Texture.MinificationFilter.Triline Texture.MagnificationFilter.Bilinear, true ); 102    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ts.setTexture(t1, 0); 载细节纹理并为2个terrain的texture设置组合模型 ear, setTexture(t2, 1); .setWrap(Texture.WrapMode.Repeat); t1.setApply(Texture.ApplyMode.Combine); tionRGB.Modulate); t2.setApply(Texture.ApplyMode.Combine); tionRGB.AddSigned); or); tb.setRenderState(ts); ; tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE); protected void initSystem() { ettings.getWidth(); //加 Texture t2 = TextureManager.loadTexture( Lesson7.class.getClassLoader() .getResource("res/Detail.jpg"), Texture.MinificationFilter.Trilin Texture.MagnificationFilter.Bilinear ); ts. t2 t1.setCombineFuncRGB(Texture.CombinerFunc t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor); t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor); t2.setCombineFuncRGB(Texture.CombinerFunc t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); t2.setCombineSrc1RGB(Texture.CombinerSource.Previous); t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceCol tb.setDetailTexture(1, 16) scene.attachChild(tb); } //保存属性信息 width = s height = settings.getHeight(); depth = settings.getDepth(); freq = settings.getFrequency(); fullscreen = settings.isFullscreen(); 103    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  try{ splay = DisplaySystem.getDisplaySystem( y.createWindow( th, freq, fullscreen display.getRenderer().createCamera(width, height); 置背景为黑色 rer().setBackgroundColor(ColorRGBA.black); 始化摄像机 Perspective( h/(float)height, 3f loc = new Vector3f(200f,1000f,200f); r3f(-0.5f,0.0f,0.5f); di settings.getRenderer() ); displa width, height, dep ); cam = }catch(JmeException e){ e.printStackTrace(); System.exit(-1); } //设 display.getRende //初 cam.setFrustum 45.0f, (float)widt 1f, 5000f ); Vector cam.setLocation(loc); // Vector3f left = new Vecto // Vector3f up = new Vector3f(0.0f,1.0f,0.0f); // Vector3f dir = new Vector3f(-0.5f,0.0f,-0.5f); // //将摄像机移到正确位置和方向 // cam.setFrame(loc, left, up, dir); 们改变自己的摄像机位置和视锥的标志 取一个高分辨率用于FPS更新 lay.getRenderer().setCamera(cam); set( _ESCAPE //我 cam.update(); //获 timer = Timer.getTimer(); disp KeyBindingManager.getKeyBindingManager(). "exit", KeyInput.KEY ); } 104    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  /* 分辨率改变将被调用 ed void reinit() { width, height, depth, freq, fullscreen); /* 场景图 ed void render(float interpolation) { Renderer().clearBuffers(); /* date期间,我们只需寻找Escape按钮 ed void update(float interpolation) { er.getTimePerFrame(); t.update(interpolation); fence.update(interpolation); 们想让skybox * 如果 */ protect display.recreateWindow( } * 绘制 */ protect //清除屏幕 display.get display.getRenderer().draw(scene); } * 在up * 并更新timer去获取帧率 */ protect //更新timer去获取帧率 timer.update(); interpolation = tim inpu chaser.update(interpolation); //我 一直在我们的视野内,所以让它和camera一起移动 们不想chase camera走到世界下面,因此让它一直在水平面上2个单元。 { 保当玩家离开平面时我们不会坠落。 工作并保持玩家在里面。 calTranslation()) + skybox.setLocalTranslation(cam.getLocation()); //我 if(cam.getLocation().y < (tb.getHeight(cam.getLocation())+2)) cam.getLocation().y = tb.getHeight(cam.getLocation()) + 2; cam.update(); } //确 //当我们增加冲突时,fence将做它自己的 float characterMinHeight = tb.getHeight(player.getLo ((BoundingBox)player.getWorldBound()).yExtent; 105    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  if( !Float.isInfinite(characterMinHeight) && ayer.getLocalTranslation().y = characterMinHeight; 取terrain在我们当前位置的normal。 anslation(), mal != null) { ormal); Escape被按下时,我们退出游戏 dingManager() ished = true; 于我们改变了场景(移动skybox !Float.isNaN(characterMinHeight) ) pl //获 //我们然后将它应用到player上面。 tb.getSurfaceNormal( player.getLocalTr normal ); if(nor player.rotateUpTo(n } //当 if(KeyBindingManager.getKeyBin .isValidCommand("exit") ){ fin } //由 ),我们需要更新scene graph 8、增加随机的Flag  8.1、介绍  在这个向导中,我们将在平面上增加 flag。这将给我们自己的“目标”,我们想捕获的 对象 8.2、优化    这节课我们将有几个优化要做。首先,加载一个 3DS  模型文件,将它转换为 jme 格式 scene.updateGeometricState(interpolation, true); } } 。为了这么做,我们将讨论 Cloth  System(织物系统),它是 jME 中用于 render 看起来 真实的 flag。我们也将做一些优化和一些对 bike 动画的改进,让它看起来更好。  106    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  然后把它加载进 我把转化的文件保 scene,这没有多大的意义。我们已经只是加载一个.jme 文件。所以,首先, 存为bike.jme然后改变buildPlayer方法加载这个文件。这将提高一点速度。 为了保存为.jme,我简单使用先前的向导并将转化的 buffer 写入文件。  try { naryImporter.getInstance().load( el.setModelBound(new BoundingBox()); OException e1) 漂移到停止(尤其在低的 FrameRate)的时候。 于 drift 的算法引起的。我们不应该认为如果 高就行,bike 能 drift 超过 0。因此,它应该 drift 到停,然后往后退一点,再 前,等等。所以,我增加检查,如果它超过 0,把它设置为 0。  //我们将漂移到停止,所以不能超过0 0 : velocity; N){ 移除从 buildChaseCamera target offset property。这将让 camera 低一点,并让它更容易看到平面上的东西。  是这次的优化。下面我们将改进游戏。    我对现在 bike 当我们做一个转弯时它的僵硬看起来很不爽。为了让 bike 看起来更流畅 和更像一个真正的 bike,我想增加倾斜以便 bike 能在转弯的时候倾斜。这里需要注意的一 个概念是我将倾斜 Vehicle 包含的模型的 node,而不是 Vehicle 它自己。这是因为 Vehicle 的 Node model = null; URL maxFile = Lesson8.class.getClassLoader() .getResource("res/bike.jme"); model = (Node)Bi maxFile.openStream() ); mod model.updateModelBound(); model.setLocalScale(0.0025f); } catch (I { e1.printStackTrace(); }      当驾驶时,我注意到一点问题,那是在我 这个 bike 将停止得看起来像往后震动。那是由 frame rate 足够 往 public void drift(float time) { if(velocity < -FastMath.FLT_EPSILON) { velocity += ((weight/5) * time); velocity = velocity > 0 ? } else if(velocity > FastMath.FLT_EPSILO velocity -= ((weight/5) * time); //我们将漂移到停止,所以不能低于0 velocity = velocity < 0 ? 0 : velocity; } }     我对于 chase  camera 一直跟在 bike 上面很不满意,所以,我 方法中移除   以上就 8.3、向转弯的方向倾斜  107    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  责任是保存 bike 在 terrain 上的方向,   我们要做的第一件事是为 vehicle 这个倾斜只是纯粹的视觉效果。  增加一个方式以便我们告诉它我们是否需要倾斜。在 VehicleRotateAction 中,我们在 performAction 方法下面增加以下这一行。  给 Vehicle。  lean = modifier; 向。  倾斜调整方向。如果 lean 是 0, 们看 bike 的 leanAngle 是否是其它的而不是 0,如果是, 让 bike 从右到直。这给出一种效果,做一个转弯,然后把 bike 带到正确的位置 。如果 lean 不是 0,那么我们根据 lean 的值调整 leanAngle。我们将把它锁定为‐1 到 1 因 b Vector3f(0,0,1))设置 bike 的角度为一个新的 leanAngle。将 lean 设置回 0 很重要,这样使 vehicle.setRotateOn(modifier);      这将告诉 Vehicle 我们将转左或右。而当然,我们需要增加一个方法 public void setRotateOn(int modifier) { }      这将为 modifier 设置一个新的类变量。Lean 包含在一个给出的帧下 bike 倾斜的方     既然我们知道 bike 是怎样倾斜的,我们需要为 bike 的 我们则不必倾斜。在这个例子中,我 我们将缓慢 上 此 ike 不会倒下(我们驾驶我们的驾驶员知道怎么骑一个 bike)。我们然后根据 leanAxis ( 我们能正确驾驶 bike,如果玩家停止转弯。    /** * processlean将基于一个lean因素调整bike模型的角度。 * 我们调整bike而不是Vehicle, * 因为Vehicle关心的是bike在terrain上的位置 * @param time the time between frames */ void processLean(float time) private { else if(lean == 1 && leanAngle > 0) { //查查我们是否需要倾斜 if(lean != 0) { if(lean == -1 && leanAngle < 0) { leanAngle += -lean * 4 * time; } leanAngle += -lean * 4 * time; } else { leanAngle += -lean * 2 * time; leanAngle < -1) { } //最大倾斜为-1到1 if(leanAngle > 1) { leanAngle = 1; } else if( leanAngle = -1; } 108    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  } else { //我们不需要倾斜,让它直立 UFFER && leanAngle > -LEAN_BUFFER) { -FastMath.FLT_EPSILON) { leanAngle += time * 4; .FLT_EPSILON) { leanAngle = 0; nAxis); lRotation(q); 0; 候更新 bike。所以,update 调用。 n(time); 就像那样,现在我们的 bike 当转弯的时候倾斜!真正解决了 bike 的视觉! if(leanAngle < LEAN_B leanAngle = 0; } else if(leanAngle < } else if(leanAngle > FastMath leanAngle -= time * 4; } else { } } q.fromAngleAxis(leanAngle, lea model.setLoca lean = } 现在,我们需要在每帧调用这个方法去在需要的时 方法被修改为包含一个 processLean 的 processLea     8.4、旋转轮胎    我们想增加的另一个移动的可视提示是轮胎的旋转。这个是微妙的增加,因为车轮没有 多变化去表示它们正在旋转,但是它对于那些细心观察的人是可 被注意到的。    首先,我们需要获取模型部件的参考才能知道哪里是车轮。这个简单要求你对模型结构 有一点了解。在 bike.jme 这个例子中,结构像下面:    很 以 109    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841        所以,我们需要获取 Geometry 的后轮(backwheel)和前轮(frontwheel)。我偶然知道 这些是索引为 1 和 5 的孩子。实际上我通过名字获取。    //获取前轮和后轮的引用 backwheel = ((Node)model).getChild("backwheel"); frontwheel = ((Node)model).getChild("frontwheel");    下一步,在 Vehicle 中,我们将增加一个方法处理这些轮子的旋转。首先,我们通过调 float time){ //前进 gle < -360) { angle += 360; (time) * velocity * .5f);   用一个 vehicleIsMoving 决定 Vehicle 是否正在移动。如果是,我们通过 1/2 速度修改轮子的 旋转角度(由时间缩放)。    private void rotateWheels( //当vehicle移动的时候旋转轮子 if(vehicleIsMoving()){ if(velocity > FastMath.FLT_EPSILON){ angle = angle - ((time) * velocity * 0.5f); if (an } }else{ angle = angle + ( 110    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  if(angle > 360){ angle -= 360; } } rotQuat.fromAngleAxis(angle, wheelAxis); frontwheel.getLocalRotation().multLocal(rotQuat); l.setLocalRotation(frontwheel.getLocalRotation());     面 到 vehicleIsMoving 只是决定了 velocity 是否足够接近 0 到应该停止。  度 * turn true 当vehicle正在移动,否则为false */ private boolean vehicleIsMoving() { velocity < -FastMath.FLT_EPSILON; hicle 的新方法 rotateWheels(time);  得很厉害,这可能是作者的疏 经过本人对比,发现作者对程序 Lesson8.java 做了以下修改。  nd())).yExtent; protected void update(float interpolation)中的: rldBound()).yExtent; at characterMinHeight = agl; 戏的同时想想为什么? 现在,我们将增加一面旗。如果你记起这个游戏的目的是快速收集旗帜。很好,这讲的 backwhee } }  正如上 提 的, /** * 用于判断vehicle是否移动的便利方法。 * 当速 velocity接近0时为真。 @re return velocity > FastMath.FLT_EPSILON || }      我们然后简单在在 update 中调用 ve   这时候你运行程序,会发现 vehicle 行驶的时候“颠簸” 忽。后面 1、 增加了类变量private float agl; 2、 在private void buildPlayer() 中增加: agl = ((BoundingBox)(player.getWorldBou 3、将 float characterMinHeight = tb.getHeight(player.getLocalTranslation()) + ((BoundingBox)player.getWo 改为: flo tb.getHeight(player.getLocalTranslation()) + OK了,现在平滑地运行吧。在享受高质量游 8.5、增加一个Flag对象    111    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  将是那旗帜。flag 的工作是处理它自己的 timer 和在 terrain 上随机的位置。  import com.jme.math.Quaternion; f; com.jme.renderer.Renderer; import com.jme.scene.Node; 为了分数尝试获取Flag。 y, 己的位置   所以,我们将创建一个新的 Flag 类从 Node 继承。  import com.jme.math.FastMath; import com.jme.math.Vector3 import import com.jme.scene.shape.Cylinder; import com.jmex.terrain.TerrainBlock; /** 保存了游戏中的“goal”。驾驶员 * Flag 将 这个类的主要工作是创建flag geometr * 还有在一段时间后在平面内随机移动自 * @author Mark Powell * * */ public class Flag extends Node { / p /生命10秒 rivate static final int LIFT_TIME = 10; E; tb; 杆 Quaternion(); omAngleAxis(FastMath.PI/2, new Vector3f(1,0,0)); ctor3f(-12.5f,-12.5f,0)); derer.QUEUE_OPAQUE); 0.25f); //从满生命开始 private float countdown = LIFT_TIM 指向用于放置的地形// private TerrainBlock public Flag(TerrainBlock tb){ super("flag"); this. tb = tb; 创建一个旗 // Cylinder c = new Cylinder( , 10, 10, "pole" 0.5f, 50 ); this attachChild(c); . Quaternion q = new //选择旗杆为垂直 q.fr c.set tLocalRo ation(q); c.setLocalTranslation(new Ve this.setRenderQueueMode(Ren this.setLocalScale( } 112    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  /** * 在update期间,我们减少time。 * 当它到了0,我们重设flag * @param time 间隔2帧的时间 */ public void update(float time){ untdown-=time; (countdown<=0){ reset(); ) { down = LIFT_TIME; aceFlag(); } flag。 在(45和175)之间,这在force平面里面 + FastMath.nextRandomFloat()*130; oat z = 45 + FastMath.nextRandomFloat()*130; y = tb.getHeight(x, z)+7.5f; lTranslation.x = x;   。它包含一个 upda 通过 placeFlag 方法 0+45 或 45‐175。这个数字是我 决定       去创建 Flag 并 update 它。首先增加 一个 uildFlag。    co if } } /** * 设置生命时间为10, * 然后随机在terrain上放置flag */ private void reset( count pl /** * 在terrain上选择一个随机点并在那放置 * 设置值 */ public void placeFlag() { float x = 45 fl float loca localTranslation.y = y; localTranslation.z = z; } } 这相当直观。它获取了我们 TerrainBlock 类的引用,以便旗能放在平面上 te 方法去处理减少时间,计算它什么时候需要重设自己的位置。而那是 来完成的。你将注意平面上的 flag 的随机值将是 0 到 13 将 flag 放在 fence 里面并使用大部分的区域。 我们使用一个简单的 cylinder 作为旗杆。  现在,为了真正使用这个 Flag,我们修改 Lesson8 新的方法 b /** 113    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  * 我们创建一个新的Flag类, * 因此我们将使用这个把Flag加到世界中。 * 这个flag是我们相要获取的那个。 */ private void buildFlag() { flag = new Flag(tb); scene.attachChild(flag); .placeFlag(); 到 scene 并随机设置位置。 们将在 initGame 中加入对 buildFlag 的调用,在 buildTerrain 下 是因为我们使用 terrain 去放置 Flag。 tdown。因此在 Lesson8 的 update 方法 flag.update(interpolation); .6 jME 的 个向导创建了我 自己的风力,而我们将在下面讨论。 类。 flag } 这将创建新的 Flag,将它 attach 接下来我 调用。我们在 buildTerrain 后调用 我们也需要 update flag去更新 coun 中增加 flag 的 update 方法: 通过那样,我们现在将有一个旗杆随机放置在 terrain 上,然后每 10 秒移 动一次。然而,看着旗杆实在没趣,所以让我们增加旗帜。 8 、为旗杆增加布  我想要让 Flag 看起来像…很好,像 Flag。为了这么做,我们需要模拟一个 的 Flag,attach 到旗杆。有什么更好的方式完成这件事,还是使用布 ClothPatch 功能。这将允许我们去创建一个弹簧(spring)点的 matrix,它们 由不同方向的外力(force)调整(引力和风力)。我已经为这 首先,增加对象到 Flag //用于制作Flag的Cloth private ClothPatch cloth; //风的参数 private float windStrength = 15f; private Vector3f windDirection = new Vector3f(0.8f, 0, 0.2f); private SpringPointForce gravity ,wind;  g 的构造参数中,我们将创建一个 ClothPatch。这个 Cloth 将是 25*25 的 matrix, 上的点越多,它运行得越慢,所以你应该在 flag 的视觉外 *25 给我一个可接受的性能和外观的比例。在创建 增加我们的 force。我们增加的第一个 force 是我们为这个向导创建的 我们也将增加一个默认的重力它是由     在 Fla 给它一个相当详细的 cloth。cloth 观和它对游戏的影响之间选个平衡点。25 cloth 之后,我们将 自定义的 force,叫做 RandomFlagWindForce。 114    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  Clo Utils 创建的,这将在风减小的时候把 Flag 拉下来。 th //创建一个cloth patch 将处理我们flag ty(); cloth.addForce(gravity); 们将为 Flag 增加 让 monkey 的头在你脑海留下烙印的企图。  ture,一起推进jME发展! getDisplaySystem() TextureManager.loadTexture( Texture.MinificationFilter.Trilinear, ar State(ts); ,因为 flag 大部分时间都是阴 g 移动,并且根据 你对待一个 light 们将使用一个LightNode去给Flag增加light,使用Node //首先创建light .0f)); dr.setLocation(new Vector3f(0.5f, -0.5f, 0)); aySystem.getDisplaySystem() createLightState cloth = new ClothPatch("cloth", 25, 25, 1f, 10); //将我们自定义的风力增加到cloth wind = new RandomFlagWindForce(windStrength, windDirection); cloth.addForce(wind); //增加一个简单的重力 gravity = ClothUtils.createBasicGravi     由于 cloth 是标准的 jME Spatial,我们和平常一样应用 RenderState。我 texture,使用 jME monkey 的 logo。这个是一个 //创建一个将flag显示的tex TextureState ts = DisplaySystem. .getRenderer().createTextureState(); ts.setTexture( Flag.class.getClassLoader() .getResource("res/logo.png"), Texture.MagnificationFilter.Biline ) ); cloth.setRender     当我开始增加一个 flag 到场景时,我对 light 非常不满意 影。因此,我决定增加一个 light,它只影响 flag。这个 light 应该跟着 Fla Flag 定位自己。因此,使用一个 LightNode 是最好的解决方案。LightNode 允许 就像 scene 中的其它元素一样,通过移动 light 的父亲 node 移动它。  //我 //是因为它允许light随着FLag移动 PointLight dr = new PointLight(); dr.setEnabled(true); dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); dr.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1 //接下来是state LightState lightState = Displ .getRenderer(). (); lightState.setEnabled(true); 115    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  lightState.setTwoSidedLighting(true); ode = new LightNode("light"); ctor3f(15,0,0)); 它所有面的三角形。前面,我们设置了 scene 后的三角形。因此,我们将增加另一个 CUllState 给 cloth,让它不 tem.getDisplaySystem() ); cs.setCullFace(CullState.Face.None); ,我们需要为 cloth 创建一些点去固定它。如果我们不这么做,整个 cloth 将在 一些点去保住 cloth。我们通过 模拟这个方法,一 将被 attach,下面的也是。 ach 点(0,1,2,3,4)到旗杆, 们也调整这些点在 y 的位置让它们偏移,从而 lightState.attach(dr); setRenderState(lightState); //最后是结点 LightNode lightN lightNode.setLight(dr); lightNode.setLocalTranslation(new Ve attachChild(lightNode); cloth.setRenderState(lightState);     Flag 有一点和前面不同,那就是我们想看到 的 CullState 去剔除所有背 要剔除任何三角形。它将看到所有的三角形。  //我们想看flag所有的面,所以我们将关闭Cull CullState cs = DisplaySys .getRenderer().createCullState( cloth.setRenderState(cs); this.attachChild(cloth);      下一步 force 应用于它身上时被“吹走”。我们需要在旗杆上 attach 设置点的质量为无穷大来这么做。因此,没有任何 force 能移走它。为了 个 flag 被 attach 到一个旗杆,flag 上边缘的一些和旗杆接触的点 这些点被设置在一个基础的二维 matrix 中,所以我们将 att 点(500,525,550,575,600)也一样。我 让 cloth 形成一点褶皱。    //我们需要attach一些点到旗杆,这些点不应该被移动。 //因此,我们将attach顶部和底部的5个点去让它们足够高而 //且没有任何forece能移动它。我也稍微移动这些点的位置去 //形成褶皱让它更真实。 for(int i=0; i<5; i++){ cloth.getSystem().getNode(i*25).position.y *= .8f; cloth.getSystem().getNode(i*25) .setMass(Float.POSITIVE_INFINITY); = .8f; .getNode(i*25) ITIVE_INFINITY); } for(int i=24; i>19; i--){ cloth.getSystem().getNode(i*25).position.y * cloth.getSystem() .setMass(Float.POS }    116    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    最后,我创建自定义风力。为了模仿旗杆上 flag 的效果,我们也随机化风向。风向将基 位置  * 度、 blic void apply(float dt, SpringPoint node) { windDirection.x += dt * (FastMath.nextRandomFloat() - .5f); dDirection.normalize(); float tStr = FastMath.nextRandomFloat() * strength; node.acceleration.addLocal( windDirection.y * tStr, 游戏中 于它之前的   /* * 在每次update cloth时调用。将轻微调整风向和强 */ pu windDirection.z += dt * (FastMath.nextRandomFloat() - .5f); win windDirection.x * tStr, windDirection.z * tStr ); }      通过那样,我们现在有一个看起来真实的 flag,而且我们以自己的方式去为我们 添加元素。下一课,我们只需能获取 flag。      117    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  8.7、源码  import java.io.IOException; import java.net.URL; import java.util.HashMap; import javax.swing.ImageIcon; import com.jme.app.BaseGame; import com.jme.bounding.BoundingBox; import com.jme.image.Texture; import com.jme.input.ChaseCamera; import com.jme.input.InputHandler; import com.jme.input.KeyBindingManager; import com.jme.input.KeyInput; import com.jme.input.thirdperson.ThirdPersonMouseLook; import com.jme.light.DirectionalLight; import com.jme.math.FastMath; import com.jme.math.Vector3f; e.renderer.Camera; import com.jme.renderer.Renderer; port com.jme.scene.Node; hape.Box import com.jm import com.jme.renderer.ColorRGBA; im import com.jme.scene.Skybox; import com.jme.scene.s ; CullState; port com.jme.scene.state.LightState; ureState; port com.jme.scene.state.ZBufferState; port com.jme.system.DisplaySystem; tion; inaryImporter; ToJme import com.jme.scene.state. im import com.jme.scene.state.Text im im import com.jme.system.JmeExcep import com.jme.util.TextureManager; import com.jme.util.Timer; import com.jme.util.export.binary.B import com.jmex.model.converters.Max ; PointHeightMap; seGame{ import com.jmex.terrain.TerrainBlock; import com.jmex.terrain.util.Mid import com.jmex.terrain.util.ProceduralTextureGenerator; public class Lesson8 extends Ba 118    private int width,height; UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  private int freq,depth; private boolean fullscreen; //我们的camera对象,用于观看scene fence; private InputHandler input; //保存terrain的任何一个给出点的法向 Vector3f(); args) { Lesson8(); ava.net.URL url = p er().getResource("res/logo.png"); Mode(ConfigShowMode.AlwaysShow,url); ected void cleanup() { .setTitle("Flag Rush"); Node"); private Camera cam; protected Timer timer; private Node scene; private TextureState ts; private TerrainBlock tb; private ForceFieldFence private Skybox skybox; private Vehicle player; private ChaseCamera chaser; private Vector3f normal = new private float agl; private Flag flag; public static void main(String[] Lesson8 app = new j ap .getClass().getClassLoad app.setConfigShow app.start(); } /* * 清除texture */ prot ts.deleteAll(); } protected void initGame() { display scene = new Node("Scene Graph 119    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ZBufferState buf = display.getRenderer().createZBufferState(); rue); uf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); (); eateSkybox(); buildPlayer(); ldChaseCamera(); ); etRenderer().createCullState(); State.Face.Back); ene.setRenderState(cs); rue); .placeFlag(); ) { utHandler( tRenderer() shMap(); k.PROP_MAXROLLOUT, "6"); s.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3"); T, AD buf.setEnabled(t b scene.setRenderState(buf); buildTerrain(); buildFlag(); buildLighting(); buildEnvironment cr bui buildInput( CullState cs = display.g cs.setCullFace(Cull sc //更新scene用于渲染 scene.updateGeometricState(0.0f, t scene.updateRenderState(); } private void buildFlag() { flag = new Flag(tb); scene.attachChild(flag); flag } private void buildInput( input = new FlagRushInp player, settings.ge ); } private void buildChaseCamera() { HashMap props = new Ha props.put(ThirdPersonMouseLoo prop props.put( ThirdPersonMouseLook.PROP_MAXASCEN ""+45*FastMath.DEG_TO_R ); 120    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  props.put( INITIALSPHERECOORDS, 0,30*FastMath.DEG_TO_RAD) era(cam, player, props); aser.setMaxDistance(8); chaser.setMinDistance(2); er() { xFile = Lesson8.class.getClassLoader() etResource("res/bike.jme"); try { .getInstance().load( eModelBound(); printStackTrace(); = new Vehicle("Player Node",model); ayer.setWeight(25); player.setMaxSpeed(25); 100)); QUEUE_OPAQUE); teGeometricState(0, true); rldBound())).yExtent; ChaseCamera.PROP_ new Vector3f(5, ); chaser = new ChaseCam ch } private void buildPlay Node model = null; URL ma .g model = (Node)BinaryImporter maxFile.openStream() ); model.setModelBound(new BoundingBox()); model.updat model.setLocalScale(0.0025f); } catch (IOException e1) { e1. } //设置Vehicle的属性(这些数字能被认为是单元/秒 Unit/S) player player.setAcceleration(15); player.setBraking(25); player.setTurnSpeed(2.5f); pl player.setMinSpeed(15); player.setLocalTranslation(new Vector3f(100,0, player.updateWorldBound(); player.setRenderQueueMode(Renderer. scene.attachChild(player); scene.upda agl = ((BoundingBox)(player.getWo } private void createSkybox() { 121    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  skybox = new Skybox("skybox",10,10,10); Texture north = TextureManager.loadTexture( pg"), ilter.BilinearNearestMipMap, ationFilter.Bilinear eManager.loadTexture( ClassLoader() ), Texture.MinificationFilter.BilinearNearestMipMap, Lesson8.class.getClassLoader() /texture/east.jpg"), inearNearestMipMap, Texture.MagnificationFilter.Bilinear xture west = TextureManager.loadTexture( Lesson8.class.getClassLoader() /texture/west.jpg"), rNearestMipMap, ), Texture.MinificationFilter.BilinearNearestMipMap, pg"), Texture.MinificationFilter.BilinearNearestMipMap, .setTexture(Skybox.Face.South, south); Lesson8.class.getClassLoader() .getResource("res/texture/north.j Texture.MinificationF Texture.Magnific ); Texture south = Textur Lesson8.class.get .getResource("res/texture/south.jpg" Texture.MagnificationFilter.Bilinear ); Texture east = TextureManager.loadTexture( .getResource("res Texture.MinificationFilter.Bil ); Te .getResource("res Texture.MinificationFilter.Bilinea Texture.MagnificationFilter.Bilinear ); Texture up = TextureManager.loadTexture( Lesson8.class.getClassLoader() .getResource("res/texture/top.jpg" Texture.MagnificationFilter.Bilinear ); Texture down = TextureManager.loadTexture( Lesson8.class.getClassLoader() .getResource("res/texture/bottom.j Texture.MagnificationFilter.Bilinear ); skybox.setTexture(Skybox.Face.North, north); skybox.setTexture(Skybox.Face.West, west); skybox skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down); 122    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  skybox.preloadTextures(); scene.attachChild(skybox); 手工做一些调整去让它更好适应terrain ) t(); A(1.0f, 1.0f, 1.0f, 1.0f)); t.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); f(1, -1, 0)); ght.setEnabled(true); lightState.setEnabled(true); tate); htmap } private void buildEnvironment() { fence = new ForceFieldFence("forceFieldFence"); //我们将 //首先我们将实体“模型”放大 fence.setLocalScale(5); //现在,让我们移动fence到terrain的高度并有一点陷入它里面 fence.setLocalTranslation( new Vector3f(25,tb.getHeight(25,25)+10,25 ); scene.attachChild(fence); } private void buildLighting() { /* 设置一个基础、默认灯光 */ DirectionalLight light = new DirectionalLigh light.setDiffuse(new ColorRGB ligh light.setDirection(new Vector3 li LightState lightState = display.getRenderer().createLightState(); lightState.attach(light); scene.setRenderState(lightS } /** * 创建heig 和terrainBlock 生成随机地形数据 MidPointHeightMap heightMap = new MidPointHeightMap(64,1f); ; */ private void buildTerrain() { // //缩放数据 Vector3f terrainScale = new Vector3f(4, .0575f, 4) 123    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //创建一个terrain block tb = new TerrainBlock( "terrain", heightMap.getSize(), etModelBound(new BoundingBox()); //通过三个纹理生成地形纹理 eduralTextureGenerator pt = erator(heightMap); con( .getResource("res/grassb.png") ().getClassLoader() ) Texture( etClassLoader() .getResource("res/highest.jpg") reateTexture(32); (); mageIcon().getImage(), tionFilter.Trilinear, terrainScale, heightMap.getHeightMap(), new Vector3f(0, 0, 0) ); tb.s tb.updateModelBound(); Proc new ProceduralTextureGen pt.addTexture( new ImageI getClass().getClassLoader() ), -128, 0, 128 ); pt.addTexture( new ImageIcon( getClass .getResource("res/dirt.jpg" ), 0, 128, 256 ); pt.add new ImageIcon( getClass().g ), 128, 256, 384 ); pt.c //将纹理赋予地形 ts = display.getRenderer().createTextureState Texture t1 = TextureManager.loadTexture( pt.getI Texture.Minifica 124    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  Texture.MagnificationFilter.Bilinear, ain的texture设置组合模型 e t2 = TextureManager.loadTexture( s.getClassLoader() s/Detail.jpg"), e.Repeat); ure.ApplyMode.Combine); ulate); e.CurrentTexture); ndRGB.SourceColor); imaryColor); .SourceColor); Apply(Texture.ApplyMode.Combine); re.CombinerFunctionRGB.AddSigned); etCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); etCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); evious); dRGB.SourceColor); enderer.QUEUE_OPAQUE); cene.attachChild(tb); true ); ts.setTexture(t1, 0); //加载细节纹理并为2个terr Textur Lesson8.clas .getResource("re Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts.setTexture(t2, 1); t2.setWrap(Texture.WrapMod t1.setApply(Text t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Mod t1.setCombineSrc0RGB(Texture.CombinerSourc t1.setCombineOp0RGB(Texture.CombinerOpera t1.setCombineSrc1RGB(Texture.CombinerSource.Pr t1.setCombineOp1RGB(Texture.CombinerOperandRGB t2.set t2.setCombineFuncRGB(Textu t2.s t2.s t2.setCombineSrc1RGB(Texture.CombinerSource.Pr t2.setCombineOp1RGB(Texture.CombinerOperan tb.setRenderState(ts); tb.setDetailTexture(1, 16); tb.setRenderQueueMode(R s } protected void initSystem() { //保存属性信息 width = settings.getWidth(); height = settings.getHeight(); depth = settings.getDepth(); 125    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  freq = settings.getFrequency(); fullscreen = settings.isFullscreen(); width, height, depth, freq, fullscreen ); er().createCamera(width, height); e.printStackTrace(); //设置背景为黑色 ckgroundColor(ColorRGBA.black); 5000f eft = new Vector3f(-0.5f,0.0f,0.5f); 0f,1.0f,0.0f); try{ display = DisplaySystem.getDisplaySystem( settings.getRenderer() ); display.createWindow( cam = display.getRender }catch(JmeException e){ System.exit(-1); } display.getRenderer().setBa //初始化摄像机 cam.setFrustumPerspective( 45.0f, (float)width/(float)height, 1f, ); Vector3f loc = new Vector3f(200f,1000f,200f); cam.setLocation(loc); // Vector3f l // Vector3f up = new Vector3f(0. // Vector3f dir = new Vector3f(-0.5f,0.0f,-0.5f); // //将摄像机移到正确位置和方向 // cam.setFrame(loc, left, up, dir); //我们改变自己的摄像机位置和视锥的标志 取一个高分辨率用于FPS更新 tTimer(); lay.getRenderer().setCamera(cam); ger.getKeyBindingManager().set( Y_ESCAPE cam.update(); //获 timer = Timer.ge disp KeyBindingMana "exit", KeyInput.KE 126    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ); } /* * 如果分辨率改变将被调用 */ protected void reinit() { display.recreateWindow(width, height, depth, freq, fullscreen); 景图 (float interpolation) { 除屏幕 Buffers(); scene); 找Escape按钮 cted void update(float interpolation) { //更新timer去获取帧率 er.update(); er.getTimePerFrame(); ion); ag.update(interpolation); fence.update(interpolation); } /* * 绘制场 */ protected void render //清 display.getRenderer().clear display.getRenderer().draw( } /* * 在update期间,我们只需寻 * 并更新timer去获取帧率 */ prote tim interpolation = tim input.update(interpolat chaser.update(interpolation); fl //我们想让skybox一直在我们的视野内,所以让它和camera一起移动 ox.setLocalTranslation(cam.getLocation()); 走到世界下面,因此让它一直在水平面上2个单元。 .getLocation())+2)) { cam.getLocation()) + 2; cam.update(); } nce将做它自己的工作并保持玩家在里面。 skyb //我们不想chase camera if(cam.getLocation().y < (tb.getHeight(cam cam.getLocation().y = tb.getHeight( //确保当玩家离开平面时我们不会坠落。 //当我们增加冲突时,fe 127    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  float characterMinHeight = ()) + agl; inite(characterMinHeight) && y = characterMinHeight; 。 们然后将它应用到player上面。 normal p ) inished = true; 我们改变了场景(移动skybox tb.getHeight(player.getLocalTranslation if( !Float.isInf !Float.isNaN(characterMinHeight) ) player.getLocalTranslation(). //获取terrain在我们当前位置的normal //我 tb.getSurfaceNormal( player.getLocalTranslation(), ); if(normal != null) { layer.rotateUpTo(normal); } //当Escape被按下时,我们退出游戏 if(KeyBindingManager.getKeyBindingManager() .isValidCommand("exit" ){ f } //由于 ),我们需要更新scene graph De ection System)  ion Detection,也叫碰撞检测,碰撞检查, 证我们呆在一个 fence。我将通过为 Collision Detection 增加一个新的接口来 式,我们有几种不同的 Collision  Detection 系统。像反弹(bounce)墙和冲撞   y   scene.updateGeometricState(interpolation, true); } } 9、墙壁检测系统( t 9.1、介绍  在这个向导中,我们将通过冲突检测(Collis 冲突检查等)保 实现。这种方 (crash)墙。   而最 我不是会 mojomonke后, 巫术的猴子( ),所以这将和其它向导不同。 128    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  9.2、冲突(Collision)接口    首先,我们增加一个接口去知道我们需要注意哪里。所以我们需要知道 player 和我们寻 Collision Detector 需要知道下一步发生什么。我们也准备必要的例子用于产生 public CollisionDetection( Vehicle player, Node target, int nextMove){ this.player = player; this.nextMove = nextMove; this.zExtent = ((BoundingBox)(player.getWorldBound())).zExtent; Particles(); } player = player; this.zExtent = ((BoundingBox 找的目标。而 更好的效果。    我们先创建一个类:CollisionDetection,里面有 2 个方法:    this.target = target; create public CollisionDetection( Vehicle player, Spatial target, int nextMove){ this. this .target = (Node) target; this.nextMove = nextMove; )(player.getWorldBound())).zExtent; }  0; public static final int MOVE_STOP = 1;    bounce createParticles();   nextMove 现在只有 2 种类型:  public static final int MOVE_BOUNCE_OFF =     所以你现在可以输入这个如果你想要 vehicle 撞在 fence 后反弹的话:   增加一个类变量:    private CollisionDetection ; = new CollisionDetection( llisionDetection. MOVE_BOUNCE_OFF 定义。我将在之后再做。因为你将一个新 (fence)attach 到 Node walls。  nvironment 方法后)中加入:   在 initGame(buildPlayer 和 buildE     bounce player, walls, Co );       你现在看到那里有个 node(walls)还没有被 墙的 bounce 墙放入游戏中所需要做的就是,将 129    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841    增加一个类变量:     private Node walls;    walls = new Node("bouncing walls"); fence = new ForceFieldFence("forceFieldFence"); //我们将手工做一些调整去让它更好适应terrain fence   然后像这样:      private void buildEnvironment() { //这将是所有walls的主要结点 scene.attachChild(walls); //首先我们将实体“模型”放大 .setLocalScale(5); 并有一点陷入它里面 w Vector3f(25,tb.getHeight(25,25)+10,25) ; Child(fence); .3 tection System  中将 vehicle 弹回。而另一个 detection rontwheel() }else if( false) //现在,让我们移动fence到terrain的高度 fence.setLocalTranslation( ne ) //这里我们将它(fence)attach到walls Node walls.attach }    9 、墙壁De   这是我创建的一个小的 de tetection sys m。它在游戏 直接停止 vehicle。  你所有的冲突 //这里处理 public void processCollisions(){ if(nextMove == MOVE_STOP){ if(player.hasCollision(target,false)){ if( player.getF .hasCollision(target, false) ){ player.setMaxSpeed(0); player.setMinSpeed(15); 130    player.getBackwheel() .hasCollision(target, UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ){ player.setMaxSpeed(25); player.setMinSpeed(0); } player.setMinSpeed(15); (nextMove == MOVE_BOUNCE_OFF){ if(player.hasCollision(target,false)) heel() )) () ion().x, ; player.getBackwheel() , false) abs ( .x, nslation().y*0.0025f, }else{ player.setMaxSpeed(25); } }else if if ( player.getFrontw .hasCollision( target, false) ) { player.setVelocity( -Math.abs(player.getVelocity( ); particleGeom.setLocalTranslation( player.getFrontwheel .getLocalTranslat player.getFrontwheel() .getLocalTranslation().y*0.0025f, zExtent ); particleGeom.forceRespawn() }else if( .hasCollision(target ){ player.setVelocity( Math. (player.getVelocity()) ); particleGeom.setLocalTranslation player.getFrontwheel() .getLocalTranslation() player.getFrontwheel() .getLocalTra -zExtent ); particleGeom.forceRespawn(); } } }   131    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  上面的 particleGeom 为类变量:  private ParticleSystem particleGeom;    9.4、粒子    为了有一个好的冲突,你需要火花。我们将使用 ParticleSystem 创建这   些。我尝试几次 n", 300); true Vector3f(0.0f, 1.0f, 0.0f) (360f * FastMath.DEG_TO_RAD); ic particleGeom.recreate(FastMath.nextRandomInt(6, 20)); om.setStartSize(.05f); om.setEndSize(.02f); particleGeom.setStartColor( particleGeom.setEndColor( f, 0.5976f, 1.0f) roller().setRepeatType( (BlendState) particleGeom.getRenderState( RenderState.StateType.Blend as = DisplaySystem.getDisplaySystem().getRenderer() 得出这些数字让它看起来更好。   private void createParticles() { particleGeom = ParticleFactory.buildParticles("collsio particleGeom.addInfluence( SimpleParticleInfluenceFactory .createBasicGravity( Vector3f(0, -0.01f, 0), new ) ); eom.setEmissionDirection( particleG new ); particleGeom.setMaximumAngle leGeom.setMinimumLifeTime(2000.0f); part particleGeom.setMaximumLifeTime(4000.0f); particleGeom.setInitialVelocity(.004f); particleGe particleGe new ColorRGBA(1.0f, 0.796875f, 0.1992f, 1.0f) ); new ColorRGBA(1.0f, 1.0 ); particleGeom.getParticleCont Controller.RT_CLAMP ); BlendState as = ); if (as == null) { 132    .createBlendState(); UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  as.setBlendEnabled(true); as.setSourceFunction(BlendState.SourceFunction.SourceAlpha); han); ); te(); on.One); ureState ts = DisplaySystem.getDisplaySystem() xtureState(); TextureManager.loadTexture( ClassLoader().getResource( "res/flaresmall.jpg" ), Map, near m); on; input.InputHandler; r; com.jme.input.thirdperson.ThirdPersonMouseLook; as.setTestEnabled(true); as.setTestFunction(BlendState.TestFunction.GreaterT particleGeom.setRenderState(as particleGeom.updateRenderSta } as.setDestinationFunction(BlendState.DestinationFuncti Text .getRenderer().createTe ts.setTexture( CollisionDetection.class.get Texture.MinificationFilter.BilinearNearestMip Texture.MagnificationFilter.Bili ) ); particleGeom.setRenderState(ts); player.attachChild(particleGeo particleGeom.updateRenderState(); }  9.5、Lesson9.java  import java.io.IOException; import java.net.URL; shMap; import java.util.Ha ing.ImageIcimport javax.sw ame; import com.jme.app.BaseG import com.jme.bounding.BoundingBox; import com.jme.image.Texture; nput.ChaseCamera; import com.jme.i com.jme.import import com.jme.input.KeyBindingManage port com.jme.input.KeyInput; 133    im import UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  import com.jme.light.DirectionalLight; port com.jme.math.Vector3f; import com.jme.renderer.Camera; .ColorRGBA; com.jme.renderer.Renderer; import com.jme.scene.Node; llState; .LightState; TextureState; stem; port com.jme.system.JmeException; ryImporter; ck; ntHeightMap; extureGenerator; import com.jme.math.FastMath; im import com.jme.renderer import import com.jme.scene.Skybox; import com.jme.scene.state.Cu import com.jme.scene.state import com.jme.scene.state. import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySy im import com.jme.util.TextureManager; import com.jme.util.Timer; import com.jme.util.export.binary.Bina import Blo import com.jmex.terrain.util.MidPoi com.jmex.terrain.Terrain import com.jmex.terrain.util.ProceduralT public class Lesson9 extends BaseGame{ private int width,height; private int freq,depth; private boolean fullscreen; 们的camera对象,用于观看scene //我 private Camera cam; protected Timer timer; private Node scene; vate TextureState ts; pri private TerrainBlock tb; private ForceFieldFence fence; vate Skybox skybox; pri private Vehicle player; private ChaseCamera chaser; private InputHandler input; 134    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //保存terrain的任何一个给出点的法向 Vector3f(); e; String[] args) { Lesson9(); lass().getClassLoader().getResource("res/logo.png"); ConfigShowMode.AlwaysShow,url); pp.start(); ected void cleanup() { ("Flag Rush"); ("Scene Graph Node"); nderer().createZBufferState(); ue); uf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); (); eateSkybox(); buildPlayer(); ldChaseCamera(); ); tection( s, CollisionDetection.MOVE_BOUNCE_OFF private Vector3f normal = new private float agl; private Flag flag; private CollisionDetection bounc private Node walls; public static void main( Lesson9 app = new java.net.URL url = app.getC app.setConfigShowMode( a } /* * 清除texture */ prot ts.deleteAll(); } protected void initGame() { display.setTitle scene = new Node ZBufferState buf = display.getRe buf.setEnabled(tr b scene.setRenderState(buf); buildTerrain(); buildFlag(); buildLighting(); buildEnvironment cr bui buildInput( bounce = new CollisionDe player, wall ); 135    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  tRenderer().createCullState(); ); ate(0.0f, true); ) { tb); ag); ushInputHandler( player, shMap(); k.PROP_MAXROLLOUT, "6"); s.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3"); T, AD props.put( INITIALSPHERECOORDS, 0,30*FastMath.DEG_TO_RAD) era(cam, player, props); aser.setMaxDistance(8); chaser.setMinDistance(2); er() { xFile = Lesson9.class.getClassLoader() etResource("res/bike.jme"); CullState cs = display.ge cs.setCullFace(CullState.Face.Back scene.setRenderState(cs); //更新scene用于渲染 scene.updateGeometricSt scene.updateRenderState(); } private void buildFlag( flag = new Flag( scene.attachChild(fl flag.placeFlag(); } private void buildInput() { input = new FlagR settings.getRenderer() ); } private void buildChaseCamera() { HashMap props = new Ha props.put(ThirdPersonMouseLoo prop props.put( ThirdPersonMouseLook.PROP_MAXASCEN ""+45*FastMath.DEG_TO_R ); ChaseCamera.PROP_ new Vector3f(5, ); chaser = new ChaseCam ch } private void buildPlay Node model = null; URL ma .g 136    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  try { .getInstance().load( eModelBound(); printStackTrace(); = new Vehicle("Player Node",model); ayer.setWeight(25); player.setMaxSpeed(25); 100)); QUEUE_OPAQUE); teGeometricState(0, true); rldBound())).yExtent; x = new Skybox("skybox",10,10,10); Texture north = TextureManager.loadTexture( pg"), ilter.BilinearNearestMipMap, ationFilter.Bilinear eManager.loadTexture( ClassLoader() ), Texture.MinificationFilter.BilinearNearestMipMap, model = (Node)BinaryImporter maxFile.openStream() ); model.setModelBound(new BoundingBox()); model.updat model.setLocalScale(0.0025f); } catch (IOException e1) { e1. } //设置Vehicle的属性(这些数字能被认为是单元/秒 Unit/S) player player.setAcceleration(15); player.setBraking(25); player.setTurnSpeed(2.5f); pl player.setMinSpeed(15); player.setLocalTranslation(new Vector3f(100,0, player.updateWorldBound(); player.setRenderQueueMode(Renderer. scene.attachChild(player); scene.upda agl = ((BoundingBox)(player.getWo } private void createSkybox() { skybo Lesson9.class.getClassLoader() .getResource("res/texture/north.j Texture.MinificationF Texture.Magnific ); Texture south = Textur Lesson9.class.get .getResource("res/texture/south.jpg" Texture.MagnificationFilter.Bilinear ); 137    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  Texture east = TextureManager.loadTexture( Lesson9.class.getClassLoader() /texture/east.jpg"), nearNearestMipMap, Texture.MagnificationFilter.Bilinear xture west = TextureManager.loadTexture( Lesson9.class.getClassLoader() /texture/west.jpg"), rNearestMipMap, ), Texture.MinificationFilter.BilinearNearestMipMap, pg"), Texture.MinificationFilter.BilinearNearestMipMap, .setTexture(Skybox.Face.South, south); attachChild(skybox); attachChild(walls); Fence"); .getResource("res Texture.MinificationFilter.Bili ); Te .getResource("res Texture.MinificationFilter.Bilinea Texture.MagnificationFilter.Bilinear ); Texture up = TextureManager.loadTexture( Lesson9.class.getClassLoader() .getResource("res/texture/top.jpg" Texture.MagnificationFilter.Bilinear ); Texture down = TextureManager.loadTexture( Lesson9.class.getClassLoader() .getResource("res/texture/bottom.j Texture.MagnificationFilter.Bilinear ); skybox.setTexture(Skybox.Face.North, north); skybox.setTexture(Skybox.Face.West, west); skybox skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down); skybox.preloadTextures(); scene. } private void buildEnvironment() { //这将是所有walls的主要结点 walls = new Node("bouncing walls"); scene. fence = new ForceFieldFence("forceField //我们将手工做一些调整去让它更好适应terrain 138    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //首先我们将实体“模型”放大 fence.setLocalScale(5); 在,让我们移动fence到terrain的高度并有一点陷入它里面 5) 设置一个基础、默认灯光 */ DirectionalLight light = new DirectionalLight(); 1.0f, 1.0f, 1.0f, 1.0f)); RGBA(0.5f, 0.5f, 0.5f, 1.0f)); , 0)); splay.getRenderer().createLightState(); ; ightmap //现 fence.setLocalTranslation( new Vector3f(25,tb.getHeight(25,25)+10,2 ); //这里我们将它(fence)attach到walls Node walls.attachChild(fence); } private void buildLighting() { /* light.setDiffuse(new ColorRGBA( light.setAmbient(new Color light.setDirection(new Vector3f(1, -1 light.setEnabled(true); LightState lightState = di lightState.setEnabled(true); lightState.attach(light) scene.setRenderState(lightState); } /** * 创建he 和terrainBlock 生成随机地形数据 MidPointHeightMap heightMap = new MidPointHeightMap(64,1f); ; heightMap.getSize(), */ private void buildTerrain() { // //缩放数据 Vector3f terrainScale = new Vector3f(4, .0575f, 4) //创建一个terrain block tb = new TerrainBlock( "terrain", terrainScale, heightMap.getHeightMap(), new Vector3f(0, 0, 0) 139    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ); tb.setModelBound(new BoundingBox()); //通过三个纹理生成地形纹理 eduralTextureGenerator pt = erator(heightMap); con( .getResource("res/grassb.png") ().getClassLoader() ) Texture( etClassLoader() .getResource("res/highest.jpg") reateTexture(32); (); mageIcon().getImage(), tionFilter.Trilinear, Texture.MagnificationFilter.Bilinear, ain的texture设置组合模型 e t2 = TextureManager.loadTexture( tb.updateModelBound(); Proc new ProceduralTextureGen pt.addTexture( new ImageI getClass().getClassLoader() ), -128, 0, 128 ); pt.addTexture( new ImageIcon( getClass .getResource("res/dirt.jpg" ), 0, 128, 256 ); pt.add new ImageIcon( getClass().g ), 128, 256, 384 ); pt.c //将纹理赋予地形 ts = display.getRenderer().createTextureState Texture t1 = TextureManager.loadTexture( pt.getI Texture.Minifica true ); ts.setTexture(t1, 0); //加载细节纹理并为2个terr Textur 140    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  Lesson9.class.getClassLoader() s/Detail.jpg"), e.Repeat); ure.ApplyMode.Combine); ulate); e.CurrentTexture); ndRGB.SourceColor); imaryColor); .SourceColor); Apply(Texture.ApplyMode.Combine); re.CombinerFunctionRGB.AddSigned); etCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); etCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); evious); dRGB.SourceColor); enderer.QUEUE_OPAQUE); cene.attachChild(tb); fullscreen = settings.isFullscreen(); .getResource("re Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts.setTexture(t2, 1); t2.setWrap(Texture.WrapMod t1.setApply(Text t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Mod t1.setCombineSrc0RGB(Texture.CombinerSourc t1.setCombineOp0RGB(Texture.CombinerOpera t1.setCombineSrc1RGB(Texture.CombinerSource.Pr t1.setCombineOp1RGB(Texture.CombinerOperandRGB t2.set t2.setCombineFuncRGB(Textu t2.s t2.s t2.setCombineSrc1RGB(Texture.CombinerSource.Pr t2.setCombineOp1RGB(Texture.CombinerOperan tb.setRenderState(ts); tb.setDetailTexture(1, 16); tb.setRenderQueueMode(R s } protected void initSystem() { //保存属性信息 width = settings.getWidth(); height = settings.getHeight(); depth = settings.getDepth(); freq = settings.getFrequency(); try{ display = DisplaySystem.getDisplaySystem( settings.getRenderer() ); display.createWindow( 141    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  width, height, depth, freq, fullscreen ); er().createCamera(width, height); e.printStackTrace(); //设置背景为黑色 ckgroundColor(ColorRGBA.black); 5000f eft = new Vector3f(-0.5f,0.0f,0.5f); 0f,1.0f,0.0f); cam = display.getRender }catch(JmeException e){ System.exit(-1); } display.getRenderer().setBa //初始化摄像机 cam.setFrustumPerspective( 45.0f, (float)width/(float)height, 1f, ); Vector3f loc = new Vector3f(200f,1000f,200f); cam.setLocation(loc); // Vector3f l // Vector3f up = new Vector3f(0. // Vector3f dir = new Vector3f(-0.5f,0.0f,-0.5f); // //将摄像机移到正确位置和方向 // cam.setFrame(loc, left, up, dir); //我们改变自己的摄像机位置和视锥的标志 取一个高分辨率用于FPS更新 tTimer(); lay.getRenderer().setCamera(cam); ger.getKeyBindingManager().set( Y_ESCAPE freq, fullscreen); cam.update(); //获 timer = Timer.ge disp KeyBindingMana "exit", KeyInput.KE ); } /* * 如果分辨率改变将被调用 */ protected void reinit() { display.recreateWindow(width, height, depth, 142    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  } /* 景图 (float interpolation) { 除屏幕 Buffers(); scene); 找Escape按钮 cted void update(float interpolation) { //更新timer去获取帧率 er.update(); er.getTimePerFrame(); ion); ag.update(interpolation); fence.update(interpolation); ocessCollisions(); * 绘制场 */ protected void render //清 display.getRenderer().clear display.getRenderer().draw( } /* * 在update期间,我们只需寻 * 并更新timer去获取帧率 */ prote tim interpolation = tim input.update(interpolat chaser.update(interpolation); fl bounce.pr //我们想让skybox一直在我们的视野内,所以让它和camera一起移动 ocalTranslation(cam.getLocation()); 一直在水平面上2个单元。 (cam.getLocation().y < (tb.getHeight(cam.getLocation())+2)) { cam.getLocation().y = tb.getHeight(cam.getLocation()) + 2; cam.update(); 保当玩家离开平面时我们不会坠落。 家在里面。 ight = yer.getLocalTranslation()) + agl; !Float.isInfinite(characterMinHeight) && Height) skybox.setL //我们不想chase camera走到世界下面,因此让它 if } //确 //当我们增加冲突时,fence将做它自己的工作并保持玩 float characterMinHe tb.getHeight(pla if( !Float.isNaN(characterMin ) 143    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  player.getLocalTranslation().y = characterMinHeight; l。 们然后将它应用到player上面。 normal p ) inished = true; 我们改变了场景(移动skybox //获取terrain在我们当前位置的norma //我 tb.getSurfaceNormal( player.getLocalTranslation(), ); if(normal != null) { layer.rotateUpTo(normal); } //当Escape被按下时,我们退出游戏 if(KeyBindingManager.getKeyBindingManager() .isValidCommand("exit" ){ f } //由于 ),我们需要更新scene graph ture; jme.scene.Node; ySystem; anager; mex.effects.particles.ParticleFactory; mex.effects.parti mpleParticle ory; scene.updateGeometricState(interpolation, true); } } 9.6、CollisionDetection.java  import com.jme.bounding. e.image.Tex BoundingBox; import com.jm import com.jme.math.FastMath; p t im or com.jme.math.Vector3f; me.renderer.ColorRGBA; import com.j port com.jme.scene.Controller; im import com. import com.jme.scene.Spatial; import com.jme.scene.state.BlendState; import com.jme.scene.state.RenderState; e.scene.state.TextureState; import com.jm import com.jme.system.Displa me.util.TextureMimport com.j jimport com. 144    import com.jmex.effects.particles.ParticleSystem; import com.j cles.Si InfluenceFact UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  public class CollisionDetection { private Vehicle player; float E_BOUNCE_OFF = 0; E_STOP = 1; eom; int nextMove){ layer.getWorldBound())).zExtent; target, int nextMove){ ove; ndingBox)(player.getWorldBound())).zExtent; MOVE_STOP )){ if ed(0); etBackwheel() .hasCollision(target, false) private Node target; private int nextMove; private zExtent; public static final int MOV public static final int MOV private ParticleSystem particleG public CollisionDetectio n( Vehicle player, Node target, this.player = player; this.target = target; this.nextMove = nextMove; this.zExtent = ((BoundingBox)(p createParticles(); } public CollisionDetection( Vehicle player, Spatial this.player = player; this .target = (Node) target; this.nextMove = nextM . tent = ((Bo this zEx u t icles(); crea ePart } 这里处理你所有的冲突// public void processCollisions(){ if (nextMove == ){ if(player.hasCollision(target,false ( player.getFrontwheel() .hasCollision(target, false) ){ player.setMaxSpe player.setMinSpeed(15); }else if( player.g 145    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ){ player.setMaxSpeed(25); player.setMinSpeed(0); eed(15); } else if(nextMove == MOVE_BOUNCE_OFF){ ollision(target,false)) twheel() ) lation( () .getLocalTranslation().x, .0025f, zExtent ); if( player.getBackwheel() , false) player.setVelocity( ocity()) articleGeom.setLocalTranslation( x, player.getFrontwheel() slation().y*0.0025f, } }else{ player.setMaxSpeed(25); player.setMinSp } if(player.hasC if( player.getFron .hasCollision(target, false) ){ player.setVelocity( -Math.abs(player.getVelocity() ); particleGeom.setLocalTrans player.getFrontwheel player.getFrontwheel() .getLocalTranslation().y*0 ); particleGeom.forceRespawn( }else .hasCollision(target ){ Math.abs(player.getVel ); p player.getFrontwheel() .getLocalTranslation(). .getLocalTran -zExtent ); particleGeom.forceRespawn(); } } } 146    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  private void createParticles() { rticleFactory.buildParticles("collsion", 300); InfluenceFactory ue 0.0f, 1.0f, 0.0f) G_TO_RAD); xtRandomInt(6, 20)); tSize(.05f); StartColor( w ColorRGBA(1.0f, 0.796875f, 0.1992f, 1.0f) particleGeom.setEndColor( .5976f, 1.0f) roller().setRepeatType( (BlendState) particleGeom.getRenderState( RenderState.StateType.Blend as = DisplaySystem.getDisplaySystem().getRenderer() unction.SourceAlpha); han); ); te(); on.One); ureState ts = DisplaySystem.getDisplaySystem() xtureState(); particleGeom = Pa particleGeom.addInfluence( SimpleParticle .createBasicGravity( new Vector3f(0, -0.01f, 0), tr ) ); particleGeom.setEmissionDirection( new Vector3f( ); particleGeom.setMaximumAngle(360f * FastMath.DE particleGeom.setMinimumLifeTime(2000.0f); particleGeom.setMaximumLifeTime(4000.0f); particleGeom.setInitialVelocity(.004f); particleGeom.recreate(FastMath.ne particleGeom.setStar particleGeom.setEndSize(.02f); particleGeom.set ne ); new ColorRGBA(1.0f, 1.0f, 0 ); particleGeom.getParticleCont Controller.RT_CLAMP ); BlendState as = ); if (as == null) { .createBlendState(); as.setBlendEnabled(true); as.setSourceFunction(BlendState.SourceF as.setTestEnabled(true); as.setTestFunction(BlendState.TestFunction.GreaterT particleGeom.setRenderState(as particleGeom.updateRenderSta } as.setDestinationFunction(BlendState.DestinationFuncti Text .getRenderer().createTe 147    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ts.setTexture( TextureManager.loadTexture( () ce( "res/flaresmall.jpg" ), Map, near m); 夺取 的计时随 。  private int flagsGrabbed CollisionDetection.class.getClassLoader .getResour Texture.MinificationFilter.BilinearNearestMip Texture.MagnificationFilter.Bili ) ); particleGeom.setRenderState(ts); player.attachChild(particleGeo particleGeom.updateRenderState(); } }  10、 Flag  10.1、介绍    在这个向导,我将首次为玩家增加夺取 flag 数目的统计。那么我们将移除 flag 机位置,取代的是,我们将增加检查去看看玩家的 vehicle 是否“夺取”了 Flag 10.2、Flag夺取计算  首先,增加一个新的字段给 Vehicle  ;  增加 getter 和 setter 方法  turn flagsGrabbed; } public void setFlagsGrabbed(int flagsGrabbed) { this.flagsGrabbed = flagsGrabbed; }        public int getFlagsGrabbed() { re 148    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  10.3、移除Flag的计时随机位置    为了移除 Flag 计时,我们停止更新,注释 Flag 的 update。  // flag.update(interpolation);  决定了玩家能在多远获取一面 flag。  private float grabDistanceSquared 10.4、增加检查去看player是否夺取了Flag    我们首先通过增加一个新字段 //获取距离 = 64f;  我们 ate 时检查 Flag 和 Vehicle 的距离。直接在游戏的       这里使用的方法将在每次游戏 upd update 中加入:      //首先获取Flag和player的 float 距离 distanceSquared = player.getLocalTranslation().distanceSquared(     vehicle 和 flag 足够接近,我们将获取它并重设 flag 位置。    if(distanceSquared <= grabDistanceSquared){ //请到Flag.java里面将reset方法改为public player.setFlagsGrabbed(player.getFlagsGrabbed()+1); 、Vehicle.java  imp mport com.jme.math.Quaternion; .scene.Node; mport com.jme.scene.Spatial; ** flag.getLocalTranslation()   ); 如果   //检查能否获取flag //重设flag,如果reset不能使用 flag.reset(); }    10.5 ort com.jme.math.FastMath; i import com.jme.math.Vector3f; import com.jme i / 149    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ehicle将会是一个node,处理了vehic* V le在游戏中的移动。 速度velocity 义了漂移时的摩擦力,它下降多快等等。 * @author Mark Powell * 它拥有定义它的加速度acceleration、 * 和减速度barking的参数。 * 转向速度turnSpeed定义了它有怎样的处理。 * 而重量weight定 */ public class Vehicle extends Node { float minSpeed = 10; private float acceleration; g; float rivate float leanAngle; new Quaternion(); bed(int flagsGrabbed) { rabbed; ; ) { private Spatial model; private float velocity; private float maxSpeed = 30; private private float brakin private turnSpeed; private float weight; vate Vector3f tempVa; pri private int lean; p private static final float LEAN_BUFFER = 0.05f; private Vector3f leanAxis = new Vector3f(0,0,1); private Quaternion q = private int ; flagsGrabbed public int getFlagsGrabbed() { return flagsGrabbed; } public void setFlagsGrab this. Grabbed = f flags lagsG } private Spatial backwheel,frontwheel getFrontwheel(public Spatial return frontwheel; 150    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  } public void setFrontwheel(Spatial frontwheel) { twheel; } l() { ublic void setBackwheel(Spatial backwheel) { eel; } t = new Quaternion(); te Vector3f wheelAxis = new Vector3f(0, 1, 0); aram id vehicle的id * @param model 表达vehicle绘图外观的模型 model){ tModel(model); } 方法,获取模型作为vehicle的外观 * @param id vehicle的id 的模型 icle能达到的最大速度(Unit/Sec this.frontwheel = fron public Spatial getBackwhee return backwheel; } p this.backwheel = backwh private float angle; private Quaternion rotQua priva /** * 基础构造方法,获取模型作为vehicle的外观 * @p */ public Vehicle(String id, Spatial super(id); se /** * 构造 * @param model 表达vehicle绘图外观 * @param maxSpeed veh ) aram minSpeed vehicle的反向最大速度(Unit/Sec * @p ) * @param accerlation vehicle多快能达到最大速度 aram turnSpeed vehicle转向转得多块 */ id,Spatial model, float weight, float turnSpeed){ r(id); * @param braking vehicle减速的速度以及按多久反向 * @param weight vehicle的重量 * @p public Vehicle(String float maxSpeed, float minSpeed, float accerlation, float braking, supe 151    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  setModel(model); this.maxSpeed = maxSpeed; .acceleration = acceleration this.minSpeed = minSpeed; this ; = weight; = turnSpeed; Spatial getModel() { "backwheel"); = ((Node)model).getChild("frontwheel"); Velocity(float velocity) { city; xSpeed) { ic float getMinSpeed() { c void setMinSpeed(float minSpeed) { tion() { ion(float acceleration) { this.braking = braking; this.weight this.turnSpeed } public return model; } public void setModel(Spatial model) { this.detachChild(this.model); this.model = model; this.attachChild(this.model); //获取前轮和后轮的引用 backwheel = ((Node)model).getChild( frontwheel } public float getVelocity() { return velocity; } public void set this.velocity = velo } public float getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(float ma this.maxSpeed = maxSpeed; } publ return minSpeed; } publi this.minSpeed = minSpeed; } public float getAccelera return acceleration; } public void setAccelerat this.acceleration = acceleration; 152    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  } public float getBraking() { c void setBraking(float braking) { c float getTurnSpeed() { c void setTurnSpeed(float turnSpeed) { c float getWeight() { c void setWeight(float weight) { acceleration*time) > maxSpeed ? maxSpeed : velocity; c void brake(float time){ king*time) ed ? -minSpeed : velocity; ) { (velocity < -FastMath.FLT_EPSILON) { * time); ,所以不能超过0 velocity = velocity > 0 ? 0 : velocity; ){ time); //我们将漂移到停止,所以不能低于0 0 ? 0 : velocity; ime){ is.localTranslation.addLocal( return braking; } publi this.braking = braking; } publi return turnSpeed; } publi this.turnSpeed = turnSpeed; } publi return weight; } publi this.weight = weight; } public void accerate(float time){ velocity = (velocity+= } publi velocity = (velocity-=bra < -minSpe } public void drift(float time if velocity += ((weight/5) //我们将漂移到停止 } else if(velocity > FastMath.FLT_EPSILON velocity -= ((weight/5) * velocity = velocity < } } public void update(float t th 153    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  this.localRotation.getRotationColumn(2, tempVa) ocessLean(time); .mult(velocity*time) ); rotateWheels(time); pr } public void setRotateOn(int modifier) { lean = modifier; } /** * processlean将基于一个lean因素调整bike模型的角度。 我 调 位置 if(lean == -1 && leanAngle < 0) { leanAngle += -lean * 4 * time; } else if(lean == 1 && leanAngle > 0) { * time; 大倾斜为-1到1 } else if(leanAngle < -1) { leanAngle = -1; 斜,让它正直 if(leanAngle < LEAN_BUFFER && leanAngle > -LEAN_BUFFER) { leanAngle = 0; { 4; _EPSILON) { else { * 们 整bike而不是Vehicle, * 因为Vehicle关心的是bike在terrain上的 * @param time the time between frames */ private void processLean(float time) { //查查我们是否需要倾斜 if(lean != 0) { leanAngle += -lean * 4 } else { leanAngle += -lean * 2 * time; } //最 if(leanAngle > 1) { leanAngle = 1; } } else { //我们不需要倾 } else if(leanAngle < -FastMath.FLT_EPSILON) leanAngle += time * } else if(leanAngle > FastMath.FLT leanAngle -= time * 4; } leanAngle = 0; } 154    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  } q.fromAngleAxis(leanAngle, leanAxis); model.setLocalRotation(q); lean = 0; } private void rotateWheels(float time){ FLT_EPSILON){ gle = angle - ((time) * velocity * 0.5f); angle, wheelAxis); heel.getLocalRotation().multLocal(rotQuat); ckwheel.setLocalRotation(frontwheel.getLocalRotation()); le是否移动的便利方法。 velocity接近0时为真。 @return true 当vehicle正在移动,否则为false ing() { stMath.FLT_EPSILON || city < -FastMath.FLT_EPSILON; java.io.IOException; //当vehicle移动的时候旋转轮子 if(vehicleIsMoving()){ //前进 if(velocity > FastMath. an if (angle < -360) { angle += 360; } }else{ angle = angle + ((time) * velocity * .5f); if(angle > 360){ angle -= 360; } } rotQuat.fromAngleAxis( frontw ba } } /** * 用于判断vehic * 当速度 * */ private boolean vehicleIsMov return velocity > Fa velo } } 10.6、Lesson10.java  155    import UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  import java.net.URL; import java.util.HashMap; import javax.swing.ImageIcon; com.jme.bounding.BoundingBox; com.jme.image.Texture; port com.jme.input.ChaseCamera; com.jme.input.InputHandler; r; seLook; m.jme.renderer.ColorRGBA; ort com.jme.renderer.Renderer; import com.jme.scene.Node; ; com.jme.scene.state.CullState; import com.jme.scene.state.LightState; ufferState; laySystem; ception; port com.jme.util.export.binary.BinaryImporter; HeightMap; lTextureGenerator; import com.jme.app.BaseGame; import import im import import com.jme.input.KeyBindingManage import com.jme.input.KeyInput; import com.jme.input.thirdperson.ThirdPersonMou import com.jme.light.DirectionalLight; import com.jme.math.FastMath; import com.jme.math.Vector3f; import com.jme.renderer.Camera; import co imp import com.jme.scene.Skybox import import com.jme.scene.state.TextureState; import com.jme.scene.state.ZB import com.jme.system.Disp import com.jme.system.JmeEx import com.jme.util.TextureManager; import com.jme.util.Timer; im import com.jmex.terrain.TerrainBlock; com.jmex.terrain.util.Mimport idPoint import com.jmex.terrain.util.Procedura public class Lesson10 extends BaseGame{ ene vate int width,height; pri private int freq,depth; private boolean fullscreen; 我们的camera对象,用于观看sc // vate Camera cam; pri 156    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  protected Timer timer; private Node scene; private TextureState ts; private TerrainBlock tb; private ForceFieldFence fence; private Skybox skybox; private Vehicle player; private ChaseCamera chaser; private Vector3f normal = new Vector3f(); private Flag flag; n bounce; tanceSquared = 64f; String[] args) { w Lesson10(); lass().getClassLoader().getResource("res/logo.png"); ConfigShowMode.AlwaysShow,url); pp.start(); private InputHandler input; //保存terrain的任何一个给出点的法向 private float agl; private CollisionDetectio private Node walls; //获取距离 private float grabDis public static void main( Lesson10 app = ne java.net.URL url = app.getC app.setConfigShowMode( a } /* * 清除texture */ protected void cleanup() { ts.deleteAll(); } protected void initGame() { display.setTitle("Flag Rush"); ("Scene Graph Node"); scene = new Node 157    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  ZBufferState buf = display.getRenderer().createZBufferState(); ue); buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); etRenderState(buf); buildTerrain(); (); ildInput(); nce = new CollisionDetection( er, walls, CollisionDetection.MOVE_BOUNCE_OFF lay.getRenderer().createCullState(); .setCullFace(CullState.Face.Back); scene.setRenderState(cs); e.updateRenderState(); e.attachChild(flag); hInputHandler( erer() p props = new HashMap(); s.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6"); buf.setEnabled(tr scene.s buildFlag(); buildLighting(); buildEnvironment(); createSkybox(); buildPlayer(); buildChaseCamera bu bou play ); CullState cs = disp cs //更新scene用于渲染 scene.updateGeometricState(0.0f, true); scen } private void buildFlag() { flag = new Flag(tb); scen flag.placeFlag(); } private void buildInput() { input = new FlagRus player, settings.getRend ); } private void buildChaseCamera() { HashMa prop props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3"); 158    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  props.put( ThirdPersonMouseLook.PROP_MAXASCENT, ""+45*FastMath.DEG_TO_RAD LSPHERECOORDS, new Vector3f(5,0,30*FastMath.DEG_TO_RAD) ); (cam, player, props); 8); private void buildPlayer() { ssLoader() bike.jme"); el = (Node)BinaryImporter.getInstance().load( maxFile.openStream() ); dingBox()); ckTrace(); ehicle的属性(这些数字能被认为是单元/秒 Unit/S) ehicle("Player Node",model); .setTurnSpeed(2.5f); player.setLocalTranslation(new Vector3f(100,0, 100)); eMode(Renderer.QUEUE_OPAQUE); pdateGeometricState(0, true); ); props.put( ChaseCamera.PROP_INITIA chaser = new ChaseCamera chaser.setMaxDistance( chaser.setMinDistance(2); } Node model = null; URL maxFile = Lesson10.class.getCla .getResource("res/ try { mod model.setModelBound(new Boun model.updateModelBound(); model.setLocalScale(0.0025f); } catch (IOException e1) { e1.printSta } //设置V player = new V player.setAcceleration(15); player.setBraking(25); player player.setWeight(25); player.setMaxSpeed(25); player.setMinSpeed(15); player.updateWorldBound(); player.setRenderQueu scene.attachChild(player); scene.u 159    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  agl = ((BoundingBox)(player.getWorldBound())).yExtent; ); er.loadTexture( sLoader() .getResource("res/texture/north.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, er.loadTexture( etClassLoader() /texture/south.jpg"), ationFilter.BilinearNearestMipMap, tionFilter.Bilinear exture east = TextureManager.loadTexture( /texture/east.jpg"), pMap, Texture.MagnificationFilter.Bilinear ure( Lesson10.class.getClassLoader() Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear .jpg"), ap, e down = TextureManager.loadTexture( tom.jpg"), ap, ); } private void createSkybox() { skybox = new Skybox("skybox",10,10,10 Texture north = TextureManag Lesson10.class.getClas Texture.MagnificationFilter.Bilinear ); Texture south = TextureManag Lesson10.class.g .getResource("res Texture.Minific Texture.Magnifica ); T Lesson10.class.getClassLoader() .getResource("res Texture.MinificationFilter.BilinearNearestMi ); Texture west = TextureManager.loadText .getResource("res/texture/west.jpg"), ); Texture up = TextureManager.loadTexture( Lesson10.class.getClassLoader() .getResource("res/texture/top Texture.MinificationFilter.BilinearNearestMipM Texture.MagnificationFilter.Bilinear ); Textur Lesson10.class.getClassLoader() .getResource("res/texture/bot Texture.MinificationFilter.BilinearNearestMipM Texture.MagnificationFilter.Bilinear ); skybox.setTexture(Skybox.Face.North, north); skybox.setTexture(Skybox.Face.West, west 160    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  skybox.setTexture(Skybox.Face.South, south); skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down); id buildEnvironment() { ); Vector3f(25,tb.getHeight(25,25)+10,25) ctionalLight light = new DirectionalLight(); (1.0f, 1.0f, 1.0f, 1.0f)); ght.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); light.setDirection(new Vector3f(1, -1, 0)); te(); tState.setEnabled(true); skybox.preloadTextures(); scene.attachChild(skybox); } private vo //这将是所有walls的主要结点 walls = new Node("bouncing walls"); scene.attachChild(walls); fence = new ForceFieldFence("forceFieldFence" //我们将手工做一些调整去让它更好适应terrain //首先我们将实体“模型”放大 fence.setLocalScale(5); //现在,让我们移动fence到terrain的高度并有一点陷入它里面 fence.setLocalTranslation( new ); //这里我们将它(fence)attach到walls Node walls.attachChild(fence); } private void buildLighting() { /* 设置一个基础、默认灯光 */ Dire light.setDiffuse(new ColorRGBA li light.setEnabled(true); LightState lightState = display.getRenderer().createLightSta ligh lightState.attach(light); scene.setRenderState(lightState); } 161    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  /** * 创建heightmap和terrainBlock */ private void buildTerrain() { 成随机地形数据 ntHeightMap(64,1f); 缩放数据 Vector3f terrainScale = new Vector3f(4, .0575f, 4); tMap(), new Vector3f(0, 0, 0) 过三个纹理生成地形纹理 new ProceduralTextureGenerator(heightMap); ddTexture( getClass().getClassLoader() ce("res/grassb.png") re( getClass().getClassLoader() source("res/dirt.jpg") assLoader() .getResource("res/highest.jpg") //生 MidPointHeightMap heightMap = new MidPoi // //创建一个terrain block tb = new TerrainBlock( "terrain", heightMap.getSize(), terrainScale, heightMap.getHeigh ); tb.setModelBound(new BoundingBox()); tb.updateModelBound(); //通 ProceduralTextureGenerator pt = pt.a new ImageIcon( .getResour ), -128, 0, 128 ); pt.addTextu new ImageIcon( .getRe ), 0, 128, 256 ); pt.addTexture( new ImageIcon( getClass().getCl ), 162    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  128, 256, 384 ); pt.createTexture(32); //将纹理赋予地形 ts = display.getRenderer().createTextureState(); reManager.loadTexture( ).getImage(), Texture(t1, 0); .class.getClassLoader() es/Detail.jpg"), Texture.MinificationFilter.Trilinear, ificationFilter.Bilinear ture.WrapMode.Repeat); Apply(Texture.ApplyMode.Combine); re.CombinerFunctionRGB.Modulate); etCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); RGB(Texture.CombinerOperandRGB.SourceColor); olor); dRGB.SourceColor); GB.AddSigned); Src0RGB(Texture.CombinerSource.CurrentTexture); CombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); re.CombinerSource.Previous); etCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor); Texture t1 = Textu pt.getImageIcon( Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true ); ts.set //加载细节纹理并为2个terrain的texture设置组合模型 Texture t2 = TextureManager.loadTexture( Lesson10 .getResource("r Texture.Magn ); ts.setTexture(t2, 1); t2.setWrap(Tex t1.set t1.setCombineFuncRGB(Textu t1.s t1.setCombineOp0 t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryC t1.setCombineOp1RGB(Texture.CombinerOperan t2.setApply(Texture.ApplyMode.Combine); t2.setCombineFuncRGB(Texture.CombinerFunctionR t2.setCombine t2.set t2.setCombineSrc1RGB(Textu t2.s tb.setRenderState(ts); tb.setDetailTexture(1, 16); 163    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE); scene.attachChild(tb); } protected void initSystem() { width = settings.getWidth(); display = DisplaySystem.getDisplaySystem( }catch(JmeException e){ e.printStackTrace(); ackgroundColor(ColorRGBA.black); //初始化摄像机 ; setLocation(loc); 3f left = new Vector3f(-0.5f,0.0f,0.5f); //保存属性信息 height = settings.getHeight(); depth = settings.getDepth(); freq = settings.getFrequency(); fullscreen = settings.isFullscreen(); try{ settings.getRenderer() ); display.createWindow( width, height, depth, freq, fullscreen ); cam = display.getRenderer().createCamera(width, height); System.exit(-1); } //设置背景为黑色 display.getRenderer().setB cam.setFrustumPerspective( 45.0f, (float)width/(float)height, 1f, 5000f ); Vector3f loc = new Vector3f(200f,1000f,200f) cam. // Vector // Vector3f up = new Vector3f(0.0f,1.0f,0.0f); // Vector3f dir = new Vector3f(-0.5f,0.0f,-0.5f); 位置和方向 // //将摄像机移到正确 // cam.setFrame(loc, left, up, dir); 像机位置和视锥的标志 //我们改变自己的摄 164    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  cam.update(); //获取一个高分辨率用于FPS更新 timer = Timer.getTimer(); lay.getRenderer().setCamera(cam); r.getKeyBindingManager().set( KeyInput.KEY_ESCAPE 用 disp KeyBindingManage "exit", ); } /* * 如果分辨率改变将被调 */ protected void reinit() { eq, fullscreen); 绘 / display.recreateWindow(width, height, depth, fr } /* * 制场景图 * protected void render(float interpolation) { 除屏幕 (); er().draw(scene); ate期间,我们只需寻找Escape按钮 //清 display.getRenderer().clearBuffers display.getRender } /* * 在upd * 并更新timer去获取帧率 */ protected void update(float interpolation) { update(); terpolation = timer.getTimePerFrame(); ut.update(interpolation); polation); .update(interpolation); ion); unce.processCollisions(); //更新timer去获取帧率 timer. in inp chaser.update(inter // flag fence.update(interpolat bo 165    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //我们想让skybox一直在我们的视野内,所以让它和camera一起移动 tLocalTranslation(cam.getLocation()); 在水平面上2个单元。 ocation().y < (tb.getHeight(cam.getLocation())+2)) { .getLocation()) + 2; 家离开平面时我们不会坠落。 作并保持玩家在里面。 eight = tb.getHeight(player.getLocalTranslation()) + agl; nite(characterMinHeight) && (characterMinHeight) .getLocalTranslation().y = characterMinHeight; mal。 etSurfaceNormal( on(), normal ayer.rotateUpTo(normal); 首 = ayer.getLocalTranslation().distanceSquared( flag.getLocalTranslation() tanceSquared){ /请到Flag.java里面将reset方法改为public sGrabbed()+1); skybox.se //我们不想chase camera走到世界下面,因此让它一直 if(cam.getL cam.getLocation().y = tb.getHeight(cam cam.update(); } //确保当玩 //当我们增加冲突时,fence将做它自己的工 float characterMinH if( !Float.isInfi !Float.isNaN ) player //获取terrain在我们当前位置的nor //我们然后将它应用到player上面。 tb.g player.getLocalTranslati ); if(normal != null) { pl } // 先获取Flag和player的距离 float distanceSquared pl ); //检查能否获取flag if(distanceSquared <= grabDis //重设flag,如果reset不能使用 / flag.reset(); player.setFlagsGrabbed(player.getFlag } 166    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  //当Escape被按下时,我们退出游戏 if(KeyBindingManager.getKeyBindingManager() skybox .isValidCommand("exit") ){ finished = true; } //由于我们改变了场景(移动 ),我们需要更新scene graph erpolation, true);   jME 使用 Camera 类去描述一些关键条件,这些关系到你的 scene graph 是怎样被渲染的。  就像一个真实摄像机的设置(例如位置和焦距)决定了 scene 中将有多少东西被记录一 jME 的摄像机决定了你的虚拟世界中有多少东西会被 render 然后显示给用户。  一个 Camera 在 scene graph 中能和其它对象一样被对待,例如作为一个 node 被 attach, 像你所创建的 model 和 shape。这需要你使用 CameraNode。  你的 camera 的位置和设置定义了一个 View  Frustum  ,这是一个你的用户可见空间体 scene.updateGeometricState(int } }                                   „ 附录: A、摄像机简介      样,   就   167    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  积的逻辑描述。如果你拿一个金字塔(角锥体/Pyramid),切开它的顶部,然后从顶往底部 ,你将得到一个 View  Frustum。你看穿的那个新形成的顶部是一个矩形,它正是描述了 户的屏幕。Pyramid 向外扩大了视野。所有在 Pyramid 内部的一切都将被 render,而外部 则不会。这允许 jME 去处理有很多对象的很复杂的 scene graph,与此同时保持高的 fps。  你 Camera 的设置将定义了 View Frustum 放在哪里,它在世界中的朝向,和它的边缘属 。边缘是否在高度上超出一定角度,给用户一个很大的视野?或者它们相对狭窄,为场景 道效应?你将在随后的页面学习怎样设置这些属性。  世界中有很多可见的对象,然而游戏中没有这么多,在大多数情况下,它超出了计算机 能同时显示的数目。我们因此需要一种方法去限制在任何给定时间渲染的数目。我们仅限于 渲染那些包含在视野体积(ViewVolume)中的对象。任何在这个体积之外的对象将被剔除 (Cull),而任何在其中的(甚至只有部分在其中)将被绘制。这个 volume 将被 ViewFrustum 定义。Camera 负责设置这个 volume 并保存它。    这个 ViewFrustum 用于 openGL  glFrustum 调用。这不仅设置用于剔除的 Camera,还 用 于设置 OpenGL 中的透视图(Perspective)模式。  此外,Camera 允许用户去控制 Frustum 的位置和朝向。一个 camera 是由坐标系统定义 的(事实上,游戏中 Camera 定义了坐标系统)。坐标轴是左、上和方向。这些轴必须创建 一个合法的右手系坐标系统。这个 camera 的坐标称为它的帧(frame)。有很多方法去设置 camera 的 frame:    1、 通过 setDirection、setLeft、setUp 设置单独的轴。  2、 通过 setAxes(接受总共 3 个轴或一个 Quaternion)一次性设置 3 个轴。  3、 通过 setFrame(同样能接受总共 3 个轴或一个 Quaternion)一次性设置 3 个轴。  4、 通过 lookAt 设置看向的点。    看 用 的   性 带来隧   a、Camera  定义  168    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  Camera 通常保存视点(Viewport)信息。视点定义了从标准化设备坐标到窗口坐标的仿 射变换。这允许渲染去填充一个屏幕而不必在意分辨率。默认上,变换设置(0,0)为左下 角。    视锥剔除(Frustum Culling)  边界体积,从而创建一个高效的边界 。调用 Camera 的 contains 方法和 Spatial 的 w S_FRUSTUM – BoundingVolumn 位于 Frustum 的一个平面并应该被进一步 处理。  tum 内并应该被绘制。  是个 node,我们不必检查它的孩子和 frustum 是否相 (由于父亲完全包含所有的孩子)。  有当 INTERSECTS_FRUSTUM 出现的时候我们需要做更多的工作。如果 Spatial 是一个 ode,那么所有的孩子被 Camera 的 contains 方法处理,如果 Spatial 是一个 Geometry,它 将被发送给显卡渲染。    当使用 FustumCulling,每个 scene 叶子(Geometry),应该被赋予一个模型边界 BoundingVolume。这将允许场景图从树的顶层检查 树。    在每一帧,每个 Spatial 被 Camera 检查,用于剔除 orldBound 将决定 Spatial 的处理与否。从 contains 方法的有效返回是:    z OUTSIDE_FRUSTUM – BoundingVolumn 完全在 Frustum 外面,不应该被进一步处理。  z INTERSECT z INSIDE_FRUSTUM – BoundingVolumn 完全位于 Frus   这带给我们一些优化。首先,如果给出的是 OUTSIDE_FRUSTUM,我们能继续并从处理 中抛出那个 Spatial。这意味着,如果那 交 其次,如果 INSIDE_FRUSTUM 被给出,我们知道 Spatial 将被绘制。如果这个是一个 node 我们能继续执行并告诉所有的孩子被绘制,而不用检查是否有孩子在 frustum 外。  只 N 169    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  创建  在 jME 中,你不应该直接创建一个 Camera,这将减少对于任意 render API 中的独立性。 因此从 Renderer 中请求一个 Camera。唯一的参数需求是用于绘制的分辨率。这允许对 Viewport 的精确变换。  CameraNode 允许你像一个 scene graph 元素一样去使用 Camera 对象。这样做的好处是 你能 attach Camera 到 scene 中的任何对象。例如,你能将 ch 到一辆奔跑中的汽 车,然后切换到另一辆汽车。CameraNode 的朝向决定了 Camera 的朝向。默认(没有进行 任何 local translation 的设置)朝向将以 left(1,0,0),up(0,1,0),direction(0,0,1)设置 Camera。  使用一个 CameraNode 是很容易的。像平常一样创建一个 Camera 对象。注意,由于 CameraNode 将覆盖 direction,up 和 left 向量,因此你不必再次进行设置。通过 setCamera 方法将 Camera 对象放入 CameraNode 里面。接着你就能像控制其它场景结点一样去控制 CameraNode。将它附加到其它的场景元素,直接控制它,等等。  用 updateWorldData 的期间,CameraNode 将关注更新 Camera 视图(View)的值。因 此,如果你在被 CameraNode 捕捉到的 scene 上调用 updateGeometricState,它的 View 将被 更新。  eraNode进行导航  display.getWidth(),display.getHeight() ); cam.setFrustum( 1.0f, -0.55f, 0.55f, 0.4125f, -0.4125f b、Camera Node  定义  camera atta 使用  在调 例子 1:使用Cam Camera cam = display.getRenderer().createCamera( 1000.0f, ); 170    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  cam.update(); CameraNode camNode = new CameraNode("Camera Node", cam); camNode.setLocalTranslation(new Vector3f(0, 250, -20)); camNode.updateWorldData(0); scene.attachChild(camNode);                                                  171    UBird3D 转载/复制/打印/出版  请注明来自 http://blog.csdn.net/kakashi8841  172           
还剩171页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

zhuwenfly

贡献于2014-08-01

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf