JME 3 入门教程 2 – Hello Node
原文链接:http://jmonkeyengine.org/wiki/doku.php/jme3:beginner:hello_node
制作3D游戏时,一开始就得创建好场景和一些对象。把这些对象(玩家、障碍物等)放置在场景当中,移动、放缩、上色,然后加上动画。
本节教程我们将看到一个简单的3D场景。你将会了解到,3D世界是以场景图(scene graph)来描绘的,以及为何rootNode如此重要。你还将学会创建简单的对象,对其进行变换——移动、放缩、旋转。你还将理解场景图中两种类型的Spatial,Node和Geometry。如果你想直观地了解有关场景图的基础知识,请看Scene Graph for Dummies(为小鸟们准备的场景图课程)。
【注:鄙人认为Spatial可译为“空间元”,Node可译为“结点”,Geometry可译为“几何体”】
样例代码
1: package jme3test.helloworld;
2:
3: import com.jme3.app.SimpleApplication;
4: import com.jme3.material.Material;
5: import com.jme3.math.Vector3f;
6: import com.jme3.scene.Geometry;
7: import com.jme3.scene.shape.Box;
8: import com.jme3.math.ColorRGBA;
9: import com.jme3.math.FastMath;
10: import com.jme3.math.Matrix3f;
11: import com.jme3.scene.Node;
12:
13: /** Sample 2 - How to use nodes as handles to manipulate objects in the scene graph.
14: * You can rotate, translate, and scale objects by manipulating their parent nodes.
15: * The Root Node is special: Only what is attached to the Root Node appears in the scene. */
16: public class HelloNode extends SimpleApplication {
17:
18: public static void main(String[] args){
19: HelloNode app = new HelloNode();
20: app.start();
21: }
22:
23: @Override
24: public void simpleInitApp() {
25:
26: // create a blue box at coordinates (1,-1,1)
27: Box box1 = new Box( new Vector3f(1,-1,1), 1,1,1);
28: Geometry blue = new Geometry("Box", box1);
29: Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
30: mat1.setColor("Color", ColorRGBA.Blue);
31: blue.setMaterial(mat1);
32:
33: // create a red box straight above the blue one at (1,3,1)
34: Box box2 = new Box( new Vector3f(1,3,1), 1,1,1);
35: Geometry red = new Geometry("Box", box2);
36: Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
37: mat2.setColor("Color", ColorRGBA.Red);
38: red.setMaterial(mat2);
39:
40: // create a pivot node at (0,0,0) and attach it to root
41: Node pivot = new Node("pivot");
42: rootNode.attachChild(pivot);
43:
44: // attach the two boxes to the *pivot* node!
45: pivot.attachChild(blue);
46: pivot.attachChild(red);
47:
48: // attach the two boxes to the *pivot* node!
49: pivot.attachChild(blue);
50: pivot.attachChild(red);
51: // rotate pivot node: Both boxes have rotated!
52: pivot.rotate( 0.4f , 0.4f , 0.4f );
53: }
54: }
Build并运行此样例代码。你将看到两个立方体以同样的角度倾斜着。
理解术语
这一节,你将会学到一些新的术语:
1. 场景图——用于描述3D world
2. 场景图中的对象(如本例中的立方体)称为Spatial。
3. Spatial分为两类,Node和Geometry。
4. 要将一个Spatial添加到场景图中,就得将Spatial附加到rootNode。
5. 附加于rootNode的一切对象都属于场景图的一部分。
理解代码
上面的代码片段里究竟发生了些什么呢?请注意,我们用到了第一节教程中介绍的simpleInitApp()方法。
1. 创建一个立方体Geometry
Box box1 = new Box( new Vector3f(1,-1,1), 1,1,1);
Geometry blue = new Geometry("Box", box1);
Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat1.setColor("Color", ColorRGBA.Blue);
blue.setMaterial(mat1);
2. 创建另一个立方体Geometry
Box box2 = new Box( new Vector3f(1,3,1), 1,1,1);
Geometry red = new Geometry("Box", box2);
Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat2.setColor("Color", ColorRGBA.Red);
red.setMaterial(mat2);
3. 创建一个Node
- 默认情况下,Node坐标为(0, 0, 0)。
- 把这个Node附加到rootNode。
- Node在场景中是不可见的。
Node pivot = new Node("pivot");
rootNode.attachChild(pivot);
4. 注意,我们还没把这两个立方体附加于任何东西
- 如果我们现在就运行,将看到场景图中什么也没有。
5. 将两个立方体附加到Node
- 现在运行的话,就可以观察到一上一下两个立方体了。
pivot.attachChild(blue);
pivot.attachChild(red);
6. 现在,旋转Node
运行一下,我们看到,还是一上一下两个立方体,但是都以相同的角度倾斜着。
pivot.rotate( 0.4f , 0.4f , 0.0f );
上述步骤进行了什么操作呢?我们把两个立方体Geometry附加到一个Node上,接着,利用这个Node作为“句柄”,同时对两个立方体进行变换(旋转)。这是一种常见的操作,在创建游戏移动游戏人物时,你将频繁使用这种操作。
定义:Geometry vs Node
在场景图中,你会使用到两种类型的Spatial:Node和Geometry。以下是二者的区别:
Geometry |
Node |
|
可见性 | 可见的3-D对象 | 不可见的“句柄” |
用途 | 存储对象外形 | 用于将多个Geometry和Node组织在一起 |
举例 | 立方体,球体,玩家,建筑物,地形,车辆,导弹,NPC等等 | 默认的rootNode,guiNode(用于屏幕文字显示),地面Node,自定义的载有乘客的车辆node,声音Node等等 |
FAQ:怎样填充场景图
任务 |
解决方案 |
创建Spatial | 创建一个几何形,设定材质。比如一个立方体的例子:
Box mesh = new Box(Vector3f.ZERO, 1, 1, 1);
Geometry thing = new Geometry("thing", mesh); Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); thing.setMaterial(mat); |
让对象显示在场景中 |
将Spatial附加到rootNode或者任何附加于rootNode的Node。 rootNode.attachChild(thing); |
从场景中移除对象 |
将Spatial从rootNode或任何附加于rootNode的Node上分离下来。 rootNode.detachChild(thing); rootNode.detachAllChildren(); |
以对象名称或ID在场景中查找某Spatial |
查找Node的孩子 Spatial thing = rootNode.getChild("thing");
Spatial twentythird = rootNode.getChild(22); |
指定初始化场景时加载内容 | 只要在simpleInitApp()方法中初始化对象,并附加于rootNode,该对象就会显示在游戏初始场景中。 |
怎样变换对象?
有三种类型的3D变换:位移(平移),放缩(调整大小),旋转(转动)
任务 |
解决方案 |
x |
Y |
Z |
坐标以及移动对象 |
Translation(位移):指定对象在三维坐标系中的新坐标:右/左、上/下、前/后。 例1. 将对象移动到指定坐标,如(0, 40.2f, -2),代码如下: thing.setLocalTranslation( new Vector3f( 0.0f, 40.2f, -2.0f ) );
例2. 将对象移动一段距离,如上移(y = 40.2f)后移(z = -2.0f): thing.move( 0.0f, 40.2f, -2.0f ); |
右/左 | 上/下 | 前/后 |
调整对象大小 |
放缩(Scaling):调整Spatial大小,指定各坐标轴方向上的放缩系数:长度、高度、宽度。放缩系数介于0.0f和1.0f之间时,对象将缩小;放缩系数大于1.0f时,对象将放大;放缩系数为1.0f时将保持原来的体积不变。若各坐标轴方向上的放缩系数相同,则对象将保持原有形状成倍放缩,否则对象将走样【注:走样指长高宽比例变化】。 例:长度增至原来10倍,高度缩小至原来的1/10,宽度不变: thing.setLocalScale( 10.0f, 0.1f, 1.0f ); thing.scale( 10.0f, 0.1f, 1.0f ); |
长 | 高 | 宽 |
转动对象 |
旋转(Rotation):3-D旋转有些棘手(了解更多)。简而言之,可以绕着三个坐标轴旋转,【注:数学上,关于右手笛卡尔坐标系的x-, y- 和z-轴的旋转分别叫做roll, pitch 和yaw 旋转。参见维基百科。但在jME中坐标系是这样(这是3D基础知识介绍,稍后我会把翻译出来)的,这是3D游戏引擎中惯用的坐标系统,与数学中的有所不同,以Z轴作为深度的坐标轴,这也就是为什么Z-Buffer算法也叫深度缓冲算法。】 注意:不要使用角度制,角度范围0°~360°;请使用弧度制,弧度范围0.0f~6.28f(FastMath.PI*2)。 例:将对象绕Z轴旋转180°。 thing.rotate( 0f , 0f , FastMath.PI ); 提示:如果游戏中涉及大量旋转,最好使用quaternion。Quaternion是一种数据结构,它可以高效地存储和组合多个旋转。 thing.setLocalRotation( new Quaternion(). fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0))); |
X轴旋转角度(pitch) | Y轴旋转角度(yaw) | Z轴旋转角度(roll) |
有关Node的故障排除
问题 |
解决方案 |
场景中看不见创建的Geometry |
把Geometry附加到rootNode没?或者附加到某个附加于rootNode的Node没? 有没有设定材质? 坐标是多少?是不是被别的Geometry遮挡了? |
Spatial旋转错误 |
使用的是弧度还是角度? 是不是旋转了正确的Node? 旋转轴是否正确? |
Geometry材质异常 | 是不是用了别的Geometry的材质?或者不小心修改了材质的属性? |
小结
你已经了解到3D场景就是一张“画”满各种Spatial的场景图:可见的Geometry和不可见的Node。你可以对Spatial进行变换,或者将其附加到Node,然后变换Node。
类似球体、立方体等标准几何形是老古董了,接着学习下一节,你将学会如何加载3-D模型等资源。
posted on 2011-04-23 11:31 sailingbird 阅读(2593) 评论(1) 编辑 收藏 举报