jMonkeyEngine译文 FlagRush7(1)——拥抱大地让我们驾驶的不再是Box

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

如果有什么错误可以到http://blog.csdn.net/kakashi8841留言或EMAILkakashi9bi@gmail.com给我。

 

jME版本 jME_2.0.1_Stable

开发工具:MyEclipse8.5

操作系统:Window7/Vista

 

 

迄今为止,我们拥有一个带驾驶参数的box。允许我们创建不同性能类型的vehiclebox在地形上表现得很好。我们开始看一些新的。我们开始获得一些玩游戏所必需的基础。所以,让我们花这一节课来让游戏中的事物变得好看点。是时候增加一些炫的啦。我们将让terrain看起来更真实,用一辆酷的未来主义vehicle代替box,而且让这个酷的未来主义vehicle跟随terrain得更好。让我们开始!

7.1、优化

         就像之前的向导提到的一样,我们的action正在做一些相同的事,并重复很多代码。此刻,我们为了优化代码将做一些巩固。首先,AccelerateActionBrakeAction被融合进一个单一的类VehicleRotateRightAction。然而VehicleRotateLeftActionVehicleRotateRightAction被组合进VehicleRotateAction

 

         ForwardAndBackActionupdate方法决定了我们想要让vehicle前进的方向,并调用vehicle相应的方法。它不再update translation。我过一会将说下这个。

 

    /**

     * 这个action调用vehicle

     * accelerate brake 命令去调整它的速度

     *

     */

    @Override

    public void performAction(InputActionEvent evt) {

       if( direction == FORWARD){

           node.accerate(evt.getTime());

       }else if( direction == BACKWARD ){

           node.brake(evt.getTime());

       }

    }

 

         这里的FORWARDBACKWARD是类的常量,而一个int参数被加到构造方法去定义我们想要移动的方向。

 

         相似的,VehicleRotationAction被改为:

 

    /**

     * vehicle的转弯速度转弯。

     * 如果vehicle正在后退,方向相反。

     */

    @Override

    public void performAction(InputActionEvent evt) {

       //影响方向

        if(direction == LEFT) {

            modifier = 1;

        } else if(direction == RIGHT) {

            modifier = -1;

        }

       

        //我们想根据我们运动的方向转向不同的方向

        if(vehicle.getVelocity() < 0) {

            incr.fromAngleNormalAxis(

                 -modifier * vehicle.getTurnSpeed()

                     * evt.getTime(),

                 upAxis

            );

        } else {

            incr.fromAngleNormalAxis(

                 modifier * vehicle.getTurnSpeed()

                     * evt.getTime(),

                 upAxis

            );

        }

        vehicle.getLocalRotation().fromRotationMatrix(

        incr.mult(

                vehicle.getLocalRotation().toRotationMatrix(tempMa),

                tempMb

        )

        );

        vehicle.getLocalRotation().normalize();

    }

 

         RIGHTLEFT做为常量被添加,而direction做为构造参数。modifier值定义了我们将转的方向,它是根据direction决定的。

 

         最后,DriftAction被轻微更新了一下。

 

    @Override

    public void performAction(InputActionEvent evt) {

       vehicle.drift(evt.getTime());

    }

 

         正如你看到的,translation代码被移除了。它被移往哪里?注意,我们将讲一下它。

         既然action被修改了,我们需要修改一下handler使用它们。

 

               

       ForwardAndBackwardAction forward =

           new ForwardAndBackwardAction(

                  node,

                  ForwardAndBackwardAction.FORWARD

           );

       addAction(forward,"forward",true);

      

       ForwardAndBackwardAction backward =

           new ForwardAndBackwardAction(

                  node,

                  ForwardAndBackwardAction.BACKWARD

           );

       addAction(backward,"backward",true);

      

       VehicleRotateAction rotateLeft =

           new VehicleRotateAction(

                  node,

                  VehicleRotateAction.LEFT

           );

       addAction(rotateLeft,"turnLeft",true);

      

       VehicleRotateAction rotateRight =

           new VehicleRotateAction(

                  node,

                  VehicleRotateAction.RIGHT

           );

       addAction(rotateRight,"turnRight",true);

      

         所以,最后的改变是Vehicle怎样移动它的位置。这个问题之前都是AccelerateAction / BrakeActionDriftAction在应用移位的。这实际上导致vehicle每次移动2次。因此,增加一个update方法给Vehicle并在handlerupdate方法中调用vehicleupdate

 

         public void update(float time){

       this.localTranslation.addLocal(

              this.localRotation.getRotationColumn(2, tempVa)

              .mult(velocity*time)

       );

    }

 

 

        vehicle.update(time);

 

         增加到handlerupdate方法。

 

         现在,vehicle移动了它原来速度的一半,所以我将调整vehicle的性能。附加地,我也觉得它转得太快。所以,我也降低了转速。

 

        player.setTurnSpeed(2.5f);

        player.setMaxSpeed(25);

        player.setMinSpeed(15);

 

         就是那样。所以,让我们用好的素材继续做下去!

7.2Detail Texture(细节纹理)和TextureCombining(纹理组合)

         我们将增加的第一个悦目的东西是detail Texture。一个detail Texture通常是由黑色和白色组合而成的图片,从而表现地面的特性。通常,黑色和白色的texture模拟了不光滑地面凹凸物形成的阴影。所以,这个texture被应用于terrain并和原始terraintexture组合。terrain已有的颜色texture和这个detail texture的组合允许颜色texture呈现出比它本身更多的细节。

         为了完成这个,我们将使用jMEmultitexturing特性。这包含将第二张texture放入绘图卡/显卡(Graphics Card)的第二Texture单元,并为texture定义参数,指明它们2个是怎样被组合的。

         首先,我们加载detail texture(在buildTerrain方法中进行):

             //加载细节纹理并为2terraintexture设置组合模型

        Texture t2 = TextureManager.loadTexture(

                Lesson7.class.getClassLoader()

                  .getResource("res/Detail.jpg"),

                Texture.MinificationFilter.Trilinear,

                Texture.MagnificationFilter.Bilinear

        );

        ts.setTexture(t2, 1);

 

         我们就像我们之前一样使用TextureManager加载一个Detail.jpg图像。我们接着设置它到我们的TextureState做为第二个textureunit 1 记住我们从0开始数)。这告诉TextureState ts去保存2Texturet1t2.

 

         下一步,由于这是一种细节纹理,我们想让它……很好,细节。创建一个高分辨率的texture图像不会有什么影响,由于这只是提供为地面基础texture。因此,我们将用一张相当低的分辨率并让它在地面上重复(repeat)很多次。实际上,detail纹理将在terrain上的一边到另一边repeat16次。为了允许这个repeat,我们需要设置texturewrap模式。

 

        t2.setWrap(Texture.WrapMode.Repeat);

 

         现在,我们需要定义这2texture是怎样被组合的。Texture定义了很多组合模式(combine mode)。首先,我们将设置每个textureApplyMode.Combine,这告诉TextureState,那2Texture的颜色将被Combine进一个输出颜色。第一个texture将是我们的基础texture,所以我们让它用它的颜色(RGB)乘以第二张texture以得到最后颜色。我们这么做,因为第二张texture是黑色和白色。因此,细节全白,我们将得到完整颜色,而当细节完全黑色,输出颜色将是黑色。我们接着告诉texture 1,所有的输入源将从01,而且只适用颜色(RGB),我们不必关心Alpha值。

所以,我们主要的texturet1)看起来将是这样:

 

        t1.setApply(Texture.ApplyMode.Combine);

        t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate);

        t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);

        t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);

        t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor);

        t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);

 

         接下来,我们需要修改t2去应用它的颜色值给之前的texturet1.一切都和t1一样(定义颜色来自哪里),但我们设置CombineFuncRGBAddSigned,这意味着它将把自己的加到其它的texture单元上,该texture单元在和原始texture相乘之前就存在。

 

        t2.setApply(Texture.ApplyMode.Combine);

        t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned);

        t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);

        t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);

        t2.setCombineSrc1RGB(Texture.CombinerSource.Previous);

        t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);

 

         所以,现在已经设置了这2texture,我们的render到屏幕的结果texture单元将是这2个美丽的结合。

 

         然而,我们仍然没定义怎样将texture应用到terrain本身。那就是,纹理坐标(texture coordinate)。我们本应该为TerrainBlock定义texture coordinate,但幸运的是,我们不必这么做。使用setDetailTexture方法,我们能让它为我们完成。所以,调用:

 

       tb.setDetailTexture(1, 16);

 

         这告诉terrain去设置texture单元1 repeat 16.那就是沿着texture的高度和宽度repeat 16次。

         现在,当你运行例子,我们的box将飞奔在一个看起来有更多细节的terrain上。

7.3Terrain法向(Normal)和朝向(Orientation

         既然我们的terrain看起来更有细节,让我们让box也在地面上跟得更好。box保持它的高度在terrain上以便它能跟着山的高度变化自己的高度,这很好,但是,它看起来却不是很正确,因为它一直保持相同的朝向(Orientation)。

         我们所要做的是使用2个工具方法:getSurfaceNormalrotateUpTogetSurfaceNormal方法给我们沿着TerrainBlock上任何点的normal。就像getHeight它将玩家的位置插入height map最近的点并计算那点的normal。这个normal是保存在一个提供的Vector对象中,所以你可以增加:

   

    //保存terrain的任何一个给出点的法向

    private Vector3f normal = new Vector3f();

 

到应用程序的顶部。我们将于每次update循环期间把这个和玩家的位置做为输入传入getSurfaceNormal方法。这将在每帧给我们一个玩家当前位置的normal

 

         现在,我们知道我们正处的terrain的角度,我们想要调整玩家对齐山。调用rotateUpTo允许我们这样做。简单在player上带着我们从terrain获取的normal调用方法,那么我们就做完了。只是简单的处理就为游戏增加了更多的真实性。

 

//获取terrain在我们当前位置的normal

       //我们然后将它应用到player上面。

       tb.getSurfaceNormal(

              player.getLocalTranslation(),

              normal

       );

       if(normal != null) {

          player.rotateUpTo(normal);

       }

         现在,驾驶着box并看它是怎样跟着terrain的。太激动了!现在,让我们处理掉那个丑陋的box

7.4、加载模型(model

         那么,现在我们想真正驾驶一辆cool的未来主义vehicle。很好,就像很多没艺术的人一样,我开始寻找免费的模型。3D Cafe 有一些未来主义的摩托车。它甚至在两边有漂亮的枪。

 

 

所以,我们需要做一些事去让这个进入我们的scene

1、  加载.3ds文件(它是3DS MAX的格式,相当标准)

2、  转换为.jmejME二进制格式)

3、  加载二进制.jme格式到scene graph

 

很明显,每次都将.3ds转为.jme不是必须的。可以优化,我们将在发布游戏之前先这么做,那么以后只需加载.jme。这在下节课的优化将进行更深入的讨论。

         加载模型的第一个任务是使用jMEModel Conversion工具。有一些被支持的格式(md2md3milkshape3dsobjase),选择和你的格式对应的Converter。这个converter创建一个.jme二进制文件。转换需要2件事:文件的InputStream,用于保存.jmeOutputStream。所以,我们将创建一个MaxToJme converter和一个ByteArrayOutputStream对象。我们接着创建一个指向我们模型的URL(我们将使用这个URL去获取InputStream)

         调用converterconvert方法将使用jme文件的数据填充我们的ByteArrayOutputStream

         我们现在拥有能加载进scene graph.jme数据。使用BinaryImporter我们能调用它的load方法去创建一个Node。这个Node将被用于替换vehicle中的box

         如果你继续工作你可能会注意到,model有点大,事实上,它太巨大了。手动使用你在网上找到的模型并没有精确为我们的游戏制作。所以,我们需要缩放它到适合我们的terrain。事实上,我们将缩放很多(0.0025%)。

         我们现在已经加载了bike(buildPlayer中的box创建部分换成下面)

 

       Node model = null;

        try {

        MaxToJme C1 = new MaxToJme();

        ByteArrayOutputStream BO = new ByteArrayOutputStream();

            URL maxFile = Lesson7.class.getClassLoader()

              .getResource("res/bike.3ds");

            C1.convert(

                 new BufferedInputStream(maxFile.openStream()),

                 BO

            );

            model = (Node)BinaryImporter.getInstance().load(

                 new ByteArrayInputStream(BO.toByteArray())

            );

            //缩放它,让它比原来更小

model.setLocalScale(.0025f);

             model.setModelBound(new BoundingBox());

model.updateModelBound();

        } catch (IOException e) {

        e.printStackTrace();

        }

 

最后,用它代替在vehiclebox位置:

 

        player = new Vehicle("Player Node",model);

 

    我们现在有一个coolbike用于驾驶。

    另一个轻微的改变是我们将在增加model之后update实体scene。这是因为Model是由许多Node(或scene graph中的分支)组成的。由于这样,这个能影响到实体树BoundingVolumes的合并。所以,我们将调用updateGeometricState去重建scene

   

        scene.updateGeometricState(0, true);

 

7.5、结论

         我们已经确实地为我们的游戏添加了一些视觉上具有吸引力的东西。

 

 

         terrain看起来更好,我们驾驶一辆真正的交通工具而且它精确紧跟着terrain。下一步,我们将再优化一点:保存一个.jme文件并加载,从而不必每次都转化,让bike在转弯时倾斜和车轮旋转。我们将介绍终点Flag,所以我们将有一个目标!继续观看!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值