谁说Java不适合写游戏?

In the mood of java...

JME 3 入门教程 2 – Hello Node

原文链接:http://jmonkeyengine.org/wiki/doku.php/jme3:beginner:hello_node

制作3D游戏时,一开始就得创建好场景和一些对象。把这些对象(玩家、障碍物等)放置在场景当中,移动、放缩、上色,然后加上动画。

本节教程我们将看到一个简单的3D场景。你将会了解到,3D世界是以场景图(scene graph)来描绘的,以及为何rootNode如此重要。你还将学会创建简单的对象,对其进行变换——移动、放缩、旋转。你还将理解场景图中两种类型的SpatialNodeGeometry。如果你想直观地了解有关场景图的基础知识,请看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

  • Spatial是对象信息的集合,包括对象的坐标、旋转角度、以及缩放比例。
  • 可以加载、变换、保存Spatial。

    3. Spatial分为两类,NodeGeometry

    4. 要将一个Spatial添加到场景图中,就得将Spatial附加到rootNode

    5. 附加于rootNode的一切对象都属于场景图的一部分。

    理解代码

    上面的代码片段里究竟发生了些什么呢?请注意,我们用到了第一节教程中介绍的simpleInitApp()方法。

    1. 创建一个立方体Geometry

  • 立方体Geometry的延展度为(1, 1, 1),于是这个立方体的体积就为2x2x2 world unit(场景单位长度)。【注:延展是对称双向延展的,此处的(1, 1, 1)即在x, y, z轴正负方向上各延展1个world unit,就构成了一个2x2x2的立方体。】
  • 将立方体置于(1, -1, 1)处。
  • 设置立方体材质为纯蓝色。
    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

  • 这个立方体的体积就为2x2x2 world unit。
  • 将这个立方体置于(1, 3, 1)处。这个位置位于蓝色立方体的正上方,两者间距2 world unit。
  • 设置立方体材质为纯红色。
    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编辑  收藏  举报

    导航