jMonkeyEngine译文 FlagRush3——创建地形

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

如果有什么错误可以留言或EMAIL[email protected]给我。

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

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

jMonkeyEngine译文 FlagRush3——创建地形

这里并不能看到很多东西,因为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就能正常工作了。你现在能运行游戏并看到类似下面的:

jMonkeyEngine译文 FlagRush3——创建地形

注意:我一直说类似,因为我们使用的是随机方法去生成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增加了一些层次感,而你能更好辨认出地形特征。

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

}

/**

* 创建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;

}

}

}

你可能感兴趣的:(Engine)