jMonkeyEngine译文 FlagRush3——创建地形

系统 2070 0

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

如果有什么错误可以留言或 EMAIL kakashi9bi@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 并应该在增加 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、 生成基于高度的纹理

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,

heightMap.getHeightMap(),

new Vector3f(0, 0, 0)

);

tb . setModelBound ( new BoundingBox());

tb .updateModelBound();

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

我们接着设置了 terrain BoundingVolume

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

jMonkeyEngine译文 FlagRush3——创建地形

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

3.3 、生成 Texture

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

你将注意到每个 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

// 将纹理赋予地形

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 就能正常工作了。你现在能运行游戏并看到类似下面的:

jMonkeyEngine译文 FlagRush3——创建地形

注意: 我一直说类似,因为我们使用的是随机方法去生成 terrain 。所以它每次都将不同。

3.4 、创建灯光( Light

尽管使用了 texture ,我们依然很难辨别出 terrain 。那是因为没有灯光和阴影帮助我们辨别 terrain 的部分。所以,让我们继续并增加一个“太阳”。增加一个 buildLighting 到你的 initGame 。我们将增加一个 DirectionalLight 去照耀 terrain 。增加 light 2 部分。首先,创建 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 增加了一些层次感,而你能更好辨认出地形特征。

jMonkeyEngine译文 FlagRush3——创建地形

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

}

/**

* 创建 heightmap terrainBlock

*/

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 ;

}

}

}

jMonkeyEngine译文 FlagRush3——创建地形


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

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

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

【本文对您有帮助就好】

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

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