上一篇: SimpleApplication, 下一篇: 加载资产.

在这个教程,我们来学习3d场景的创建。

在创建一个3d游戏

  1. 要创景一些场景个体,比如玩家,建筑等。

  2. 要把这些个体添加到场景

  3. 要给予这些个体:移动,旋转,缩放,上色和动画

在这里你会学到:场景是如何表达一个3d世界, rooNode的重要性。你会学到:如何创建简单的个体,如何让它们携带自定义数据(比如血量)和如何“变换”它们(移动,旋转,缩放)。你会学到两个空间物体的不同:Nodes(节点)和Geometries(几何体)。

示例

package jme3test.helloworld;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;

/** Sample 2 - How to use nodes as handles to manipulate objects in the scene.
 * You can rotate, translate, and scale objects by manipulating their parent nodes.
 * The Root Node is special: Only what is attached to the Root Node appears in the scene.
 * 你可以通过变换个体的父节点来变换个体。
 * 根节点(rootNode)是特别的:这有附着在根节点的个体才会被渲染到画布。*/
public class HelloNode extends SimpleApplication {

    public static void main(String[] args){
        HelloNode app = new HelloNode();
        app.start();
    }

    @Override
    public void simpleInitApp() {

        /** create a blue box at coordinates (1,-1,1)
            在坐标(1,-1,1)创建蓝方块*/
        Box box1 = new Box(1,1,1);
        Geometry blue = new Geometry("Box", box1);
        blue.setLocalTranslation(new Vector3f(1,-1,1));
        Material mat1 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.Blue);
        blue.setMaterial(mat1);

        /** create a red box straight above the blue one at (1,3,1)
            在坐标(1,3,1)创建红方块*/
        Box box2 = new Box(1,1,1);
        Geometry red = new Geometry("Box", box2);
        red.setLocalTranslation(new Vector3f(1,3,1));
        Material mat2 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat2.setColor("Color", ColorRGBA.Red);
        red.setMaterial(mat2);

        /** Create a pivot node at (0,0,0) and attach it to the root node
            根节点坐标(0,0,0)附着上新的节点*/
        Node pivot = new Node("pivot");
        rootNode.attachChild(pivot); // put this node in the scene

        /** Attach the two boxes to the *pivot* node. (And transitively to the root node.)
            在这个节点附着上方块*/
        pivot.attachChild(blue);
        pivot.attachChild(red);
        /** Rotate the pivot node: Note that both boxes have rotated!
            选择节点: 注意方块也跟着旋转*/
        pivot.rotate(.4f,.4f,0f);
    }
}

编译和运行代码。 你应该看到两个方块,而且它们都有一样的旋转方向。

理解术语

在这里,你会学到新的术语:

你想怎么做?如何用JME3的术语表达?

布置场景。

填充个体到场景。

创建场景个体。

创景空间物体(比如创建几何体)

使个体在画布上显示。

附着空间物体到rootNode。

使个体在画布上消失。

解除物体在rootNode的绑定。

移动,选择和缩放

移动,选择和缩放 = 变换个体。

所有JME3应用有一个rootNode: 你的游戏从SimpleApplica自动继承了 rootNode 。所有绑定在rootNode的个体是场景的一部分。场景的组成是空间物体。

  • 空间物体包括物体的位移,旋转和大小。

  • 空间物体可以读取,变换和保存。

  • 有两种空间物体:Nodes(节点)和Geometries(几何体)。

Geometry(几何体)Node(节点)

可见性:

几何体是可见的物体。

节点是不可见的,负责管理场景个体。

目的:

几何体包含个体的形状信息。

节点被几何体和其他节点绑定,从而组成一个群体。

示例:

方块,球体,玩家,建筑,地形,汽车,导弹,NPCs等

根节点,节点包含几个地形,节点包含汽车和乘客,节点包含玩家和武器,音频的节点等

了解代码

使用 simpleInitApp() 函数来初始化场景,这在第一个教程里有介绍。

  1. 创建一个方块几何体

    • 创建方块形状,运用(1,1,1)的向外延伸。这样会创建一个2x2x2单位长度的方块。

    • 使用 setLocalTranslation() 函数把方块放到(1,-1,1)的位置。

    • 运用方块形状创建几何体。

    • 创建蓝色材质

    • 把材质应用到方块几何体上。 .

        Box box1 = new Box(1,1,1);
        Geometry blue = new Geometry("Box", box1);
        blue.setLocalTranslation(new Vector3f(1,-1,1));
        Material mat1 = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.Blue);
        blue.setMaterial(mat1);
  2. 创建第二个方块几何体

    • 用同样的大小创建第二个方块形状

    • 把第二个方块放在(1,3,1),在第一个方块的正上方,2个单位长度的间隔。

    • 运用方块形状创建几何体。

    • 创建红色材质

    • 把材质应用到方块几何体上。

          Box box2 = new Box(1,1,1);
          Geometry red = new Geometry("Box", box2);
          red.setLocalTranslation(new Vector3f(1,3,1));
          Material mat2 = new Material(assetManager,
            "Common/MatDefs/Misc/Unshaded.j3md");
          mat2.setColor("Color", ColorRGBA.Red);
          red.setMaterial(mat2);
  3. 创建节点

    • 给节点命名 pivot

    • 节点默认位置在(1,1,1)。

    • 把节点绑定的根节点上。

    • 节点在场景上是不可见的。

          Node pivot = new Node("pivot");
          rootNode.attachChild(pivot);

      如果你的应用只运行到这里,场景是不会有东西显示的。因为节点是不可见的,而且可见的几何体也没有绑定到rootNode上。

  4. 绑定两个方块几何体到pivot节点上.

            pivot.attachChild(blue);
            pivot.attachChild(red);

    如果你的应用只运行到这里,你会看到两个方块:红色的方块在蓝色的上面。

  5. 旋转pivot节点。

            pivot.rotate( 0.4f , 0.4f , 0.0f );

如果你的应用只运行到这里,你会看到两个方块都向同一个方向旋转。。

什么是 “Pivot” 节点?

你可以相对于“几何体的中心”或者“用户定义的中心”变换(比如,旋转)几何体。这个用户定义的中心就是“Pivor”节点。用户可以用它自定以一个或多个几何体的中心。

在这个实例里,有两个几何体绑定在一个“pivot”节点。通过变换“pivot”节点,同时变换两个几何体。旋转“pivot”节点会同时旋转所有被绑定的几何体。“pivot”节点的中心就是旋转的中心。在绑定其他几何体时,先确定“pivot”节点在坐标(0,0,0)。变换父节点会同时变换所有的子节点。你将会经常用到这个方法。

例子: 一辆汽车和它的驾驶员同时移动;一个带有卫星的行星围绕恒星旋转。

这个实例里,如果你不创建“pivot”节点而是只变换几何体,那么所有变换都是相对于几何体自己的中心。

例子: 如果旋转每一个方块(用 red.rotate(0.1f , 0.2f , 0.3f);blue.rotate(0.5f , 0.0f , 0.25f);),那么每个方块都会相对于自身的中心旋转。这就像行星的自转。

如何布置场景?

目标…?解决方法!

创建一个空间体(Spatial).

创建一个网格,把它包装成几何体然后给予它材质。比如:

Box mesh = new Box(Vector3f.ZERO, 1, 1, 1); // a cuboid default mesh
Geometry thing = new Geometry("thing", mesh);
Material mat = new Material(assetManager,
   "Common/MatDefs/Misc/ShowNormals.j3md");
thing.setMaterial(mat);

在画布显示物体。

绑定空间体到 rootNode ,或者任何绑定在 rootNode 的节点。

rootNode.attachChild(thing);

从画布移除物体。

解除空间体在 rootNode 和任何绑定在 rootNode 的节点的绑定。

rootNode.detachChild(thing);
rootNode.detachAllChildren();

通过个体名字,ID或父子关系找到空间体。

通过节点的父子找:

Spatial thing = rootNode.getChild("thing");
Spatial twentyThird = rootNode.getChild(22);
Spatial parent = myNode.getParent();

决定什么会在初始时加载。

所有你初始化了同时绑定在 rootNode 的个体将是游戏初始场景的一部分。

如何变换空间体

变换有3种: 位移,旋转和缩放

位移来移动空间体X轴Y轴Z轴

用3个维度来表示新的位移: 在原点的位移向 右-上-前 3个方向上移动多少来移动到目标位移,比如(0,40.2f,-2):

thing.setLocalTranslation( new Vector3f( 0.0f, 40.2f, -2.0f ) );
在原来的位移移动一定的量,比如更高(y=40.2f)和更远(z=-2.0f):
thing.move( 0.0f, 40.2f, -2.0f );

+右 -左

+上 -下

+前 -后

缩放来改变空间体大小x轴y轴z轴

输入在每个维度的缩放比例:长度,高度,宽度。
0.0f到1.0f的数值把空间体变小;大于1.0f的数值把空间体变大;1.0f不缩放空间体。
用相同的数值缩放会保持空间体的比例,不同数值会相应的拉伸空间体。
比如,把空间体变成,10倍于原来的长度,0.1倍于原来的高度,保持高度不变:

thing.scale( 10.0f, 0.1f, 1.0f );

长度

高度

宽度

旋转来转动空间体X轴 俯仰角(Pitch)Y轴 偏航角(Yaw)Z轴 旋转角(Roll)

3D旋转可能会有点复杂 (详情请看这里)。简单来说:你可以绕3个轴旋转:俯仰角,偏航角和旋转角。你可以输入弧度或用角度乘以 FastMath.DEG_TO_RAD
比如,旋转物体的Z轴180度:

thing.rotate( 0f , 0f , 180*FastMath.DEG_TO_RAD );

小贴士:如果你的游戏运用到大量旋转,很值得查看 四元数(quaternions),一个数据结构可以效率的运算和保存。

thing.setLocalRotation(
  new Quaternion().fromAngleAxis(180*FastMath.DEG_TO_RAD, new Vector3f(1,0,0)));

点头

摇头

左右摆动头

我应该如何故障排除空间体?

如果你得到意料之外的结果,可以根据以下检查你的通常错误:

问题?解决方案!

创建的几何体没能在场景中显示。

是否有把这个几何体绑定到rootNode?
它有材质吗?
它的变换是什么(位置)?
它是在相机的后面还是被其他几何体挡住了?
它是否太小或太大了?
它是否离相机太远了? (尝试 cam.setFrustumFar(111111f); 来看得更远)

空间体的旋转不是所想要的。

用的是角度还是弧度?(如果用的是角度,用 FastMath.DEG_TO_RAD 乘以它们来得到弧度)
是否在移动它之前创建空间体到原点 (Vector.ZERO) ?
是否根据正确的枢轴(pivot)节点旋转还是别的节点?
是否在正确的轴旋转?

几何体的颜色或材质不是所想的。

你是否重用了其他几何体的材质和不经意地改动了它的属性?(是的话,考虑复制它: mat2 = mat.clone(); )

如何添加自定义数据到空间体?

很多空间体代表游戏的角色和其他互动的个体。比如,上面围绕共同枢轴旋转两个立方体的代码可以用在连接在轨道空间站的宇宙飞船。

根据你的游戏,游戏个体不仅位移,选装和缩放(你刚学到的“变换”)。游戏个体还有自定义的属性,比如生命值,携带物品,装备,或船体强度和空间站的所剩燃油。在Java,你用类的变量来代表对象, 比如,floats,Strings,和Arrays。

你可以直接添加自定义数据到节点和几何体。 你不需要extends节点的类(Node)来包含变量! 比如,给节点添加自定义id,你可以这样做:

pivot.setUserData( "pivot id", 42 );

要在别处读取节点的id,你可以这样做:

int id = pivot.getUserData( "pivot id" );

通过使用不同的Strings映射(keys)(这里的映射是 pivot id),你可以get和set任何空间体需要携带的数据。当你开始写你的游戏时,你可能添加燃油数值到汽车节点,速度数值到飞机节点,或金币数到玩家节点,等等。但是,需要注意的是只有implements了Savable类的自定义个体可以这样做。

结语

你学到了3d场景是一个带有空间体的场景图:可视的几何体和不可视的节点。你可以变换空间体,或者绑定它们到节点然后变换节点。你学会了最方便的方法来添加自定义个体数据(比如玩家生命和汽车速度)到几何体。

因为标准形状比如球体和立方体不经常用,在下一个章节你可以学到 导入3d模型等资源.