jMonkeyEngine译文 FlagRush8(1)——增加随机的Flag

FlagRush系列教程快完了~~今天正在看第9篇,现在先发第八篇吧。

 

 

 

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

如果有什么错误可以到http://blog.csdn.net/kakashi8841留言或EMAIL[email protected]给我。

 

jME版本 jME_2.0.1_Stable

开发工具:MyEclipse8.5

操作系统:Window7/Vista

 

 

8.1、介绍

在这个向导中,我们将在平面上增加flag。这将给我们自己的“目标”,我们想捕获的对象。为了这么做,我们将讨论Cloth System(织物系统),它是jME中用于render看起来真实的flag。我们也将做一些优化和一些对bike动画的改进,让它看起来更好。

8.2、优化

         这节课我们将有几个优化要做。首先,加载一个3DS 模型文件,将它转换为jme格式然后把它加载进scene,这没有多大的意义。我们已经只是加载一个.jme文件。所以,首先,我把转化的文件保存为bike.jme然后改变buildPlayer方法加载这个文件。这将提高一点速度。为了保存为.jme,我简单使用先前的向导并将转化的buffer写入文件。

 

       Node model = null;

       URL maxFile = Lesson8.class.getClassLoader()

        .getResource("res/bike.jme");

       try {

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

                  maxFile.openStream()

           );

           model.setModelBound(new BoundingBox());

           model.updateModelBound();

           model.setLocalScale(0.0025f);

       } catch (IOException e1) {

           e1.printStackTrace();

       }

 

         当驾驶时,我注意到一点问题,那是在我漂移到停止(尤其在低的FrameRate)的时候。这个bike将停止得看起来像往后震动。那是由于drift的算法引起的。我们不应该认为如果frame rate足够高就行,bikedrift超过0。因此,它应该drift到停,然后往后退一点,再往前,等等。所以,我增加检查,如果它超过0,把它设置为0

 

    public void drift(float time) {

       if(velocity < -FastMath.FLT_EPSILON) {

           velocity += ((weight/5) * time);

            //我们将漂移到停止,所以不能超过0

            velocity = velocity > 0 ? 0 : velocity;

        } else if(velocity > FastMath.FLT_EPSILON){

            velocity -= ((weight/5) * time);

          //我们将漂移到停止,所以不能低于0

            velocity = velocity < 0 ? 0 : velocity;

        }

    }

   

         我对于chase camera一直跟在bike上面很不满意,所以,我移除从buildChaseCamera方法中移除target offset property。这将让camera低一点,并让它更容易看到平面上的东西。

         以上就是这次的优化。下面我们将改进游戏。

8.3、向转弯的方向倾斜

         我对现在bike当我们做一个转弯时它的僵硬看起来很不爽。为了让bike看起来更流畅和更像一个真正的bike,我想增加倾斜以便bike能在转弯的时候倾斜。这里需要注意的一个概念是我将倾斜Vehicle包含的模型的node,而不是Vehicle它自己。这是因为Vehicle的责任是保存biketerrain上的方向,这个倾斜只是纯粹的视觉效果。

         我们要做的第一件事是为vehicle增加一个方式以便我们告诉它我们是否需要倾斜。在VehicleRotateAction中,我们在performAction方法下面增加以下这一行。

 

vehicle.setRotateOn(modifier);

 

         这将告诉Vehicle我们将转左或右。而当然,我们需要增加一个方法给Vehicle

 

    public void setRotateOn(int modifier) {

       lean = modifier;

    }

 

         这将为modifier设置一个新的类变量。Lean包含在一个给出的帧下bike倾斜的方向。

 

         既然我们知道bike是怎样倾斜的,我们需要为bike的倾斜调整方向。如果lean0,我们则不必倾斜。在这个例子中,我们看bikeleanAngle是否是其它的而不是0,如果是,我们将缓慢让bike从右到直。这给出一种效果,做一个转弯,然后把bike带到正确的位置上。如果lean不是0,那么我们根据lean的值调整leanAngle。我们将把它锁定为-11因此bike不会倒下(我们驾驶我们的驾驶员知道怎么骑一个bike)。我们然后根据leanAxisVector3f(0,0,1))设置bike的角度为一个新的leanAngle。将lean设置回0很重要,这样使我们能正确驾驶bike,如果玩家停止转弯。

 

    /**

     * processlean将基于一个lean因素调整bike模型的角度。

     * 我们调整bike而不是Vehicle

     * 因为Vehicle关心的是biketerrain上的位置

     * @param time the time between frames

     */

    private void processLean(float time) {

        //查查我们是否需要倾斜

        if(lean != 0) {

            if(lean == -1 && leanAngle < 0) {

                leanAngle += -lean * 4 * time;

            } else if(lean == 1 && leanAngle > 0) {

                leanAngle += -lean * 4 * time;

            } else {

                leanAngle += -lean * 2 * time;

            }

            //最大倾斜为-11

            if(leanAngle > 1) {

                leanAngle = 1;

            } else if(leanAngle < -1) {

                leanAngle = -1;

            }

        } else { //我们不需要倾斜,让它直立

            if(leanAngle < LEAN_BUFFER && leanAngle > -LEAN_BUFFER) {

                leanAngle = 0;

            }

            else if(leanAngle < -FastMath.FLT_EPSILON) {

                leanAngle += time * 4;

            } else if(leanAngle > FastMath.FLT_EPSILON) {

                leanAngle -= time * 4;

            } else {

                leanAngle = 0;

            }

        }

      

        q.fromAngleAxis(leanAngle, leanAxis);

        model.setLocalRotation(q);

       

        lean = 0;

    }

 

    现在,我们需要在每帧调用这个方法去在需要的时候更新bike。所以,update方法被修改为包含一个processLean的调用。

 

       processLean(time);

 

    就像那样,现在我们的bike当转弯的时候倾斜!真正解决了bike的视觉!

 

 

8.4、旋转轮胎

         我们想增加的另一个移动的可视提示是轮胎的旋转。这个是微妙的增加,因为车轮没有很多变化去表示它们正在旋转,但是它对于那些细心观察的人是可以被注意到的。

         首先,我们需要获取模型部件的参考才能知道哪里是车轮。这个简单要求你对模型结构有一点了解。在bike.jme这个例子中,结构像下面:

 

 

         所以,我们需要获取Geometry的后轮(backwheel)和前轮(frontwheel)。我偶然知道这些是索引为15的孩子。实际上我通过名字获取。

 

       //获取前轮和后轮的引用

        backwheel = ((Node)model).getChild("backwheel");

        frontwheel = ((Node)model).getChild("frontwheel");

 

         下一步,在Vehicle中,我们将增加一个方法处理这些轮子的旋转。首先,我们通过调用一个vehicleIsMoving决定Vehicle是否正在移动。如果是,我们通过1/2速度修改轮子的旋转角度(由时间缩放)。

 

private void rotateWheels(float time){

      //vehicle移动的时候旋转轮子

      if(vehicleIsMoving()){

          //前进

          if(velocity > FastMath.FLT_EPSILON){

             angle = angle - ((time) * velocity * 0.5f);

                if (angle < -360) {

                    angle += 360;

                }

          }else{

             angle = angle + ((time) * velocity * .5f);

             if(angle > 360){

                 angle -= 360;

             }

          }

          rotQuat.fromAngleAxis(angle, wheelAxis);

          frontwheel.getLocalRotation().multLocal(rotQuat);

          backwheel.setLocalRotation(frontwheel.getLocalRotation());

}

}

 

         正如上面提到的,vehicleIsMoving只是决定了velocity是否足够接近0到应该停止。

 

   /**

    * 用于判断vehicle是否移动的便利方法。

    * 当速度velocity接近0时为真。

    * @return true vehicle正在移动,否则为false

    */

    private boolean vehicleIsMoving() {

       return velocity > FastMath.FLT_EPSILON ||

           velocity < -FastMath.FLT_EPSILON;

    }

 

         我们然后简单在在update中调用vehicle的新方法rotateWheels(time);

         这时候你运行程序,会发现vehicle行驶的时候“颠簸”得很厉害,这可能是作者的疏忽。后面经过本人对比,发现作者对程序Lesson8.java做了以下修改。

1、  增加了类变量private float agl;

2、  private void buildPlayer() 中增加:

agl = ((BoundingBox)(player.getWorldBound())).yExtent;

    3、将

        protected void update(float interpolation)中的:

   

       float characterMinHeight =

           tb.getHeight(player.getLocalTranslation()) +

           ((BoundingBox)player.getWorldBound()).yExtent;

       改为:

 

       float characterMinHeight =

           tb.getHeight(player.getLocalTranslation()) + agl;

 

OK了,现在平滑地运行吧。在享受高质量游戏的同时想想为什么?

8.5、增加一个Flag对象

         现在,我们将增加一面旗。如果你记起这个游戏的目的是快速收集旗帜。很好,这讲的将是那旗帜。flag的工作是处理它自己的timer和在terrain上随机的位置。

         所以,我们将创建一个新的Flag类从Node继承。

 

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.shape.Cylinder;

import com.jmex.terrain.TerrainBlock;

 

/**

 * Flag保存了游戏中的“goal”。驾驶员将为了分数尝试获取Flag

 * 这个类的主要工作是创建flag geometry

 * 还有在一段时间后在平面内随机移动自己的位置

 * @author Mark Powell

 *

 */

public class Flag extends Node {

    //生命10

    private static final int LIFT_TIME = 10;

    //从满生命开始

    private float countdown = LIFT_TIME;

    //指向用于放置的地形

    private TerrainBlock tb;

   

    public Flag(TerrainBlock tb){

       super("flag");

       this.tb = tb;

      

       //创建一个旗杆

       Cylinder c = new Cylinder(

              "pole", 10, 10,

              0.5f, 50

       );

       this.attachChild(c);

       Quaternion q = new Quaternion();

       //选择旗杆为垂直

       q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0));

       c.setLocalRotation(q);

       c.setLocalTranslation(new Vector3f(-12.5f,-12.5f,0));

       this.setRenderQueueMode(Renderer.QUEUE_OPAQUE);

       this.setLocalScale(0.25f);

    }

   

    /**

     * update期间,我们减少time

     * 当它到了0,我们重设flag

     * @param time 间隔2帧的时间

     */

    public void update(float time){

       countdown-=time;

       if(countdown<=0){

           reset();

       }

    }

 

    /**

     * 设置生命时间为10

     * 然后随机在terrain上放置flag

     */

    private void reset() {

       countdown = LIFT_TIME;

        placeFlag();

    }

   

    /**

     * terrain上选择一个随机点并在那放置flag

     * 设置值在(45175)之间,这在force平面里面

     */

    public void placeFlag() {

       float x = 45 + FastMath.nextRandomFloat()*130;

       float z = 45 + FastMath.nextRandomFloat()*130;

       float y = tb.getHeight(x, z)+7.5f;

       localTranslation.x = x;

       localTranslation.y = y;

       localTranslation.z = z;

    }

}

         这相当直观。它获取了我们TerrainBlock类的引用,以便旗能放在平面上。它包含一个update方法去处理减少时间,计算它什么时候需要重设自己的位置。而那是通过placeFlag方法来完成的。你将注意平面上的flag的随机值将是0130+4545-175。这个数字是我决定将flag放在fence里面并使用大部分的区域。

         我们使用一个简单的cylinder作为旗杆。

         现在,为了真正使用这个Flag,我们修改Lesson8去创建Flagupdate它。首先增加一个新的方法buildFlag

 

    /**

     * 我们创建一个新的Flag类,

     * 因此我们将使用这个把Flag加到世界中。

     * 这个flag是我们相要获取的那个。

     */

    private void buildFlag() {

       flag = new Flag(tb);

       scene.attachChild(flag);

       flag.placeFlag();

    }

 

这将创建新的Flag,将它attachscene并随机设置位置。

    接下来我们将在initGame中加入对buildFlag的调用,在buildTerrain下调用。我们在buildTerrain后调用是因为我们使用terrain去放置Flag

 

    我们也需要update flag去更新countdown。因此在Lesson8update方法中增加flagupdate方法:

 

       flag.update(interpolation);

 

    通过那样,我们现在将有一个旗杆随机放置在terrain上,然后每10秒移动一次。然而,看着旗杆实在没趣,所以让我们增加旗帜。

 

你可能感兴趣的:(3D,jMonkeyEngine)