jMonkeyEngine译文 FlagRush3——创建地形

注:本系列教程全部翻译完之后可能会以PDF的形式发布。

如果有什么错误可以留言或EMAILkakashi9bi@gmail.com给我。

 

jME版本 jME_2.0.1_Stable

开发工具:MyEclipse8.5

操作系统:Window7/Vista

 

 

 

    这个向导中我们涉及到一些好玩的,我们将为我们的游戏加载地形(下文将使用Terrain代替)。这里对于我想要的类型的terrain有一些要求:

l  每次随机

l  不需太多三角形

l  为了跳跃“崎岖”

l  对于快速的交通工具足够大

我们将在第二课中的框架上构建。首先,由清除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并应该在增加tbscene之前调用。你应该像下面一样:

    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、  生成基于高度的纹理

3.1、创建一个heightmap

AbstractHeightMap定义了一个方法用于保存高度数据。在它的核心,主要是一个二维矩阵的数据,任何一个点(X,Z)的高度Y。然而这不允许创建复杂terrain(窑洞、悬崖等等)。它提供了很基础的方形terrain,然而这正是我们FlagRush中所需要的。

         我们将创建一个MidPointHeightMap,它使用中点取代不规则碎片。这将允许地形足够有趣和真实,为我们提供了一些颠簸和跳跃。

         创建这个heightmap很直截了当,在我们buildTerrain方法中的第一行:

    /**

     * 创建heightmapterrainBlock

     */

    private void buildTerrain() {

       //生成随机地形数据

       MidPointHeightMap heightMap = new MidPointHeightMap(64,1f);

       ……

    }

 

我们调用MidPointHeightMap的构造方法创建一个新的heightMap对象。它只需要2个参数:大小和粗糙程度。

         MidPointHeightMap的大小必须是2的幂。那就是248163264等等。在我们的例子中,我们选择64。这正好符合我们的需要(我们的行为将被局限在一个相当小的舞台)。粗糙程度才是有趣的东西。这个值越低,则terrain越粗糙,反之越平滑。我们先选择它为1,让terrain看起来像地狱般凹凸还带着尖刺。然而,我们还没设置完,这些尖刺将被调下来。

         我们将定义一个terrain缩放因数。这将简单拉伸或挤压mesh以满足我们的需求。所以,增加:

 

         //缩放数据

    Vector3f terrainScale = new Vector3f(20, .5f, 20);

 

         buildTerrain方法。这意味着:我们将拉伸terrainXZ的值20。这将让terrain感觉更大(实际上大了20倍)。然而与此同时,我们让Y值减少了一半。这将得到我们想要的凹凸感,但让它们处于一个合理的值(不会太突然)。

3.2、生成Terrain Mesh

         现在,我们已经设置好了数据,我们能真正创建mesh。我们将创建一个TerrainBlock,它是一个简单的Geometry。这个将增加到scene里,就像我们之前增加Sphere那样。

       //创建一个terrain block

       tb = new TerrainBlock(

              "terrain",

              heightMap.getSize(),

              terrainScale,

              heightMap.getHeightMap(),

              new Vector3f(0, 0, 0)

       );

       tb.setModelBound(new BoundingBox());

       tb.updateModelBound();

 

         TerrainBlock接受一些参数,大多数都很直接。首先,是terrain的名字。heightMap的大小,接着是我们之前所设的terrain的缩放值。接着给出heightMap真正的数据。下一个参数定义了terrain的起点。我们这里没有理由设置一些奇怪的值,因此设置了基本的(000)。

         我们接着设置了terrainBoundingVolume

         你现在或许能继续并运行游戏,看到类似下面的一些东西:

这里并不能看到很多东西,因为terrain仅是一大块白色。我们需要应用texture去让它有一点层次感。

3.3、生成Texture

         创建一个Texture将通过使用ProceduralTextureGenerator。这个类将生成一个基于heightmap的高度的纹理,并在多个texture间混合。一个texture被指定到一个高度区域,而它们之后混合进单一的texture map。这允许我们很容易创建一个看起来相当真实的Terrain。在我们的例子中,我们将使用3texture,一个用于低区域的草地texture,中部的岩石和高处的雪。

//通过三个纹理生成地形纹理

       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);

 

你将注意到每个Texture3个值。这描述了这个texture将被应用到低的,最佳的和高的海拔。例如(dirt.jpg)将混合从海拔0-256heightmap生成从0-256的值。所以这意味着dirt128将更强烈(看得更多),然后向0256混合其它的texture。同时其它的2texture被填充在低和高的区域。

addTexture接受ImageIcon对象去定义texture数据。在这个例子中,我们通过我们的类的getResource方法获取到的URL创建ImageIcon。这个在classpath里面搜索images。这当然不是一定要这么做,ImageIcon能在其它某个地方被创建,它将适用于你应用程序。

         createTexture真正创建了我们需要使用的texture。在这个例子中,我让它生成一个32X32像素的texture。虽然这个看起来很小,但是我并不需要它的细节。这只是用于基础颜色,之后我们将创建更详细的texture和对象。

         例如:在运行游戏期间,我保存了一个生成的texture。它看起来像这样:

 

 

 

你能看到三个texturegrassbdirthighest)是怎样被混合为一个单一的texture。白色的区域将会是terrain的高点,而grass将是terrain的低点。

 

         现在我们已经生成了Terrain,我们把它放入一个TextureState并把它应用到terrain

       //将纹理赋予地形

       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。增加light2部分。首先,创建DirectionalLight,然后把它增加到LightState

 

    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);

    }

 

 

         这个DirectionalLight被设置于照耀(1 -1 0)那个方向(向下和向右)。它接着被增加到LightState并应用到scene

         这为terrain增加了一些层次感,而你能更好辨认出地形特征。

 

 

3.5、总结

我们现在拥有了一个可以在上面奔跑的平面。然而,那还是存在令人讨厌的黑色背景。下一节课我们将适当关注个问题。

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) {

       Lesson3 app = new Lesson3();

       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);

    }

 

    /**

     * 创建heightmapterrainBlock

     */

    private void buildTerrain() {

       //生成随机地形数据

       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);

      

       //将纹理赋予地形

       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);

       Vector3f left = new Vector3f(-1.0f,0.0f,0.0f);

       Vector3f up = new Vector3f(0.0f,1.0f,0.0f);

       Vector3f dir = new Vector3f(0.0f,0.0f,-1.0f);

       //将摄像机移到正确位置和方向

       cam.setFrame(loc, left, up, dir);

      

       //我们改变自己的摄像机位置和视锥的标志

       cam.update();

      

       //获取一个高分辨率用于FPS更新

       timer = Timer.getTimer();

      

       display.getRenderer().setCamera(cam);

       KeyBindingManager.getKeyBindingManager().set(

              "exit",

              KeyInput.KEY_ESCAPE

       );

    }

 

    /*

     * 如果分辨率改变将被调用

     */

    protected void reinit() {

       display.recreateWindow(width, height, depth, freq, fullscreen);

    }

 

    /*

     * 绘制场景图

     */

    protected void render(float interpolation) {

       //清除屏幕

       display.getRenderer().clearBuffers();

       display.getRenderer().draw(scene);

    }

 

    /*

     * update期间,我们只需寻找Escape按钮

     * 并更新timer去获取帧率

     */

    protected void update(float interpolation) {

       //更新timer去获取帧率

       timer.update();

       interpolation = timer.getTimePerFrame();

      

       //Escape被按下时,我们退出游戏

       if(KeyBindingManager.getKeyBindingManager()

              .isValidCommand("exit")

       ){

           finished = true;

       }

    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值