花了一个小时研究了CullState,发现以前没学线性代数真是个错误。唉!不过这也和学校教育制度有关吧,如果学校能以2个方向培养人才:先理论后应用,先应用后理论,学生选择适合自己的方向这样多好!
话不多说,下面为译文。
注:本系列教程全部翻译完之后可能会以PDF的形式发布。
如果有什么错误可以留言或EMAIL:[email protected]给我。
jME版本 :jME_2.0.1_Stable
开发工具:MyEclipse8.5
操作系统:Window7/Vista
在这个向导中我们准备增加一个玩家和一些控制。我们将集中于交通工具的基本移动和跟随摄像机(下文以ChaseCamera代替)。所以,我们将为交通工具增加一个Box占位符,增加一个ChaseCamera,然后学习关于构建我们的自定义InputHandle(输入处理)。主要,我们将构建一个ThirdPersonHandler(第三人称控制器)。然而,jME包含一个预建的ThirdPersonHandle,它具有更多特性并值得研究。为了达到最好的教育效果,我们将创建自己的控制器。
同样地,我们这一节课将在前一节的基础上创建代码。每次迭代我们将清理现有的代码,为了达到展示怎样把事情做得更好的效果。
Lesson5首先最主要的改变是我们创建了一个ForceFieldFence的类。通过将所有force field的数据移到它自己的类里面,这将允许我们可以在之后心血来潮的时候改善它。首先,我创建了一个新的类继承自Node。我们继承Node所以我们能附加fence到这个类本身并和平常一样把fence增加到scene。我接着创建了buildFence方法,从我们游戏的buildEnvironment方法中移除代码(除了位置和缩放调用,它们是游戏相关的,和围栏没有关系)到这个新的方法。现在,我们也想要继续力场的动画,所以我们也将移动Texture t到这个类并创建update方法。游戏中的Texture代码将移动到这个方法。现在,在游戏代码中,buildEnvironment方法我们加入:
fence = new ForceFieldFence("forceFieldFence");
与此同时为游戏类增加一个类变量
private ForceFieldFence fence;
最后,我们需要告诉fence类在游戏update期间也update。在update方法中增加:
fence.update(interpolation);
将这么做。
现在,游戏将和之前运行的一样,但我们有个fence让一切更容易。
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));
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 = 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);
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)
);
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);
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;
}
}
最后的一点优化是在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并告诉它剔除三角形背后的面。够还不够简单?