jMonkeyEngine译文 FlagRush4——创建环境

今天,右眼有点痛,估计是耗太多了。还是坚持翻译了jME Flag Rush的第四篇。废话不多说,下面译文。完工,睡觉!

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

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

jME版本 jME_2.0.1_Stable

开发工具:MyEclipse8.5

操作系统:Window7/Vista

既然我们已经有了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中移除terrainattach代码,并把它放到buildTerrain中。

最后,但并不意味着不重要,我调整了terrainscale。由于我以非传统格式做这个游戏(向导),我也应该做一些非传统的事(这样以致我能展示底层细节)。就这一点,这要求我对对象做一些调整,以致它们能更适合彼此。在这个例子中,我的Fence的大小只是不能超出,所以我需要调整terrain对准他。新的terrain行是:

Vector3f terrainScale = new Vector3f(4, .0575f, 4);

是的,这些值的产生只是简单的尝试和误差。我减少高度的变化是因为terrain太崎岖了。现在那些改变应该让你的东西看起来像这样:

jMonkeyEngine译文 FlagRush4——创建环境

·现在,我们准备开始!

4.2、深度缓冲(Depth Buffer

我们现在准备增加多个对象给scene。如果我们一不小心,对象将画得覆盖其它它本不该被覆盖的对象。我们想要的是前面的对象覆盖后面的。我们将做2件事去处理这种render顺序,RenderQueueZBufferState。我会先讲下ZBufferState(或者说深度缓冲),而保留RenderQueue,直到我们处理透明度(transparency)的时候。Depth Buffer允许OpenGL为每个像素(pixel)保留深度(depth)信息。那就是,一个颜色值被赋给了pixelOpenGL能知道那个pixel距离camera多远,从而决定是否用其它对象的pixel覆盖它。在我们的例子中,我们想当即将到来的pixelcamera的距离小于或等于当前pixelcamera的距离时,当前pixel被覆盖。我们将通过创建一个ZBufferState并把它赋给scene graph的根(root)来这么做。

我们将把它增加到initGame方法。

ZBufferState buf = display.getRenderer().createZBufferState();

buf.setEnabled(true);

buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);

scene.setRenderState(buf);

就是这样。现在任何加到场景中的东西在它允许被render到缓冲区之前,都不得不有一个小于或等于当前pixel的距离。这个将能防止我们的fenceterrain后面。

4.3、创建环境(手动)

这是一个大的部分,我们将通过手写代码创建fence。通常,你可以简单加载一个描绘fence的模型并把它放在terrain上。然而,出于向导的目的,我想要展示怎样加载不同的图形(Shape),并把它们安排到一个单一的Node里面。

fence将有下面几个参数:

1、 方形的

2、 每个角落有守卫塔

3、 从一个塔到下一个之间有伸展的框架相连。

4、 一个force field从框架“坠下”

所有这些代码将放在一个叫buildEnvironment的方法里面并由initGame调用。

4.4、守卫塔和SharedMesh

我已经决定在fence的每个角落创建圆柱体的塔。这意味着有4个塔。我应该怎样创建圆柱体对象?很好,Cylinder听起来是个不错的选择。然而,替代创建4Cylinder并创建4份相同的数据,我只创建了一个Cylinder。我然后将旋转Cylinder让它的面垂直(默认上Cylinder是沿着边躺下的)。在这之后,我将使用SharedMesh创建它的四分副本。SharedMesh允许我们使用相同的Geometry数据但把它们放在scene中不同的区域。这同时节省了内存(只保留一份数据)和render时间(只渲染一次)

由于我正在使用SharedMesh,我应该增加原始的Cylinder Geometryscene。这是因为SharedMeshrender阶段操纵原始的本地数据。

所以,我判定fence每一个边有32单元长。为什么我选择32?没有特别的原因,只是在那个时候觉得合理。我然后让Cylinder10单元高。同样的,没有原因,只是尝试和误差。所以,将每个塔之间距离32单元,我设置它们的localTranslation到适当的点。

//这个圆柱体将扮演每个角落那4个主要的柱子

Cylinder postGeometry = new Cylinder("Cylinder", 10, 10, 10, 100);

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

我们现在已经有了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一次性操纵4tower(移动、缩放它们等等)。

4.5、剩余的框架

现在,我们准备构建fence中剩余的部分。我们将使用Cylinder构建一个框架,关于这些框架最有趣的一点是其中2个将需要旋转90度。为了做到这个,我们将使用Quaternion。我不会深入讲解Quaternion是什么(你可以看看用户向导)。但我们使用它的fromAngleAxis去旋转2个框架。同样的,这4个框架实际上是SharedMesh对象。而就像前面的塔,它们被attach到一个Node并拥有它们自己的texture

我调整框架的位置,把它们放在塔那么高的地方。同样的,这里没有什么特别的,只是尝试并找出看起来好的。这些任意的数字,正是你通过手动创建对象而不是加载模型所能获得的。

//这个圆柱体将是水平的框架

//它将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);

现在,我们已经花了一些心思在塔和框架上。现在这个fence是一个高科技的force field,而且它不允许任何东西穿越它。这个force field 看起来像是个挂在框架上的平面对象。我们将使用拥有未来主义textureBox作为force field。我不再深入讨论这部分的细节,因为它只是再次使用SharedMeshattach到它自己的Node。有趣的一点是,我没有像旋转框架一样去旋转2box。取而代之的是,我使用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

);

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

我们现在有三个包含所有fenceGeometrynode。为了后面更容易工作,我们将这三个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();

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

这允许texture的颜色和在它后面的其它颜色混合。因为我们的force field是由很多黑色组成的,黑色将会100%透明。那么,我们现在拥有悬停的点。

可是我们的程序仍然有问题。如果我们通过当前的设置能移动地图,我们可能有时候会看到在force field后面的对象,但不总是这样。那是因为透明对象取决于其他你能看到的在它后面的对象去决定是否绘制。但我们目前的render顺序不担保能这样做。RenderQueue的出现可以解决这个问题。我们能把scene中所有的元素放到这个queue中,而它将为我们正确排序。有3个主要的queue(虽然还有其它的,但对我们而言不是很重要):transparent queue正是我们放入force field的地方。Opaque是我们放入不透明对象的地方,而OrthoGUI元素(在后面章节将讲到)将前往的地方。RenderQueue在下面几个方式上优化顺序:

l Opaque queue最先被render(所以我们所有不需要透明的对象都在这个buffer中)

l 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 Fieldtexture运动

texture运动实际是很简单的。首先,我们将需要访问包含在force-field中的Texture对象。将它的引用移到类级别(使force-fieldtexture成为类变量)。

我们现在能在update方法中访问Texture了。

每个 texure有它自己的矩阵。这个texture矩阵定义了texture的坐标是怎样被应用的。你能旋转texture,缩放它们,或者移动。在我们的例子中,我们想要沿着Y轴移动texture。实际上做起来和听起来是一样容易的:

//我们将使用插值(interpolation)去保持forcefield

//texture运动速度和计算机保持一致

//我们更新texture矩阵的Y值去让

//force-field看起来像是在移动

t.getTranslation().y += 0.3f*interpolation;

//如果translation超过1,它被换行,因此回到开始

//并检查这个(防止VectorY值变得太大)

t.getTranslation().y =

t.getTranslation().y > 1 ? 0 : t.getTranslation().y;

一旦上面完成之后,我们将更真实看到一个强大的fence( 如果你看不到下面这个图,不要紧张,因为可能是你的fence field放的位置太低的,使用以下代码调整位置forceFieldFence.setLocalTranslation(200,100,200);这些数字只是简单的尝试得出的,看到效果一样之后再把这句去掉,因为后面会讲到怎样 调整force-fieldterrain)

jMonkeyEngine译文 FlagRush4——创建环境

4.8、将force-field放置在terrain

现在,我们想要放置这个fenceterrain上。我想要做到一些事。首先,我想要它在terrain里面一点,这样当玩家到达fence的时候它们不会看到它脱离了terrain。其次,它需要足够高和足够大去盖在terrain上。

我需要去玩这个东西直到找到我想要的结果。我发现将fence放大5倍正好是我想要的。我接着把它在X轴和Z轴移动25个单元。把它升到一个合适的高度,我获取TerrainBlock2525处的高度,并增加一些误差范围。

//我们将手工做一些调整去让它更好适应terrain

//首先我们将实体模型放大

forceFieldFence.setLocalScale(5);

//现在,让我们移动fenceterrain的高度并有一点陷入它里面

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移动。就是那样。很简单就增加了世界!我们现在有一个看起来像下面一样的完整的水平面:

jMonkeyEngine译文 FlagRush4——创建环境

4.10、总结

我们现在拥有一个完整的水平面,有terrainfencesky。这给我们一个可行的场地去放置我们的游戏对象并开始飞奔。

下一节课我将讨论怎样使用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;

//我们的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);

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

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

//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"),

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

//增加所有的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);

//我们将手工做一些调整去让它更好适应terrain

//首先我们将实体模型放大

forceFieldFence.setLocalScale(5);

//现在,让我们移动fenceterrain的高度并有一点陷入它里面

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

}

/**

* 创建heightmapterrainBlock

*/

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(

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

}

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更新

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,它被换行,因此回到开始

//并检查这个(防止VectorY值变得太大)

t.getTranslation().y =

t.getTranslation().y > 1 ? 0 : t.getTranslation().y;

//我们想让skybox一直在我们的视野内,所以让它和camera一起移动

skybox.setLocalTranslation(cam.getLocation());

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

if(KeyBindingManager.getKeyBindingManager()

.isValidCommand("exit")

){

finished = true;

}

//由于我们改变了场景(移动skybox),我们需要更新scene graph

scene.updateGeometricState(interpolation, true);

}

}

你可能感兴趣的:(Engine)