FlagRush系列教程快完了~~今天正在看第9篇,现在先发第八篇吧。
注:本系列教程全部翻译完之后可能会以PDF的形式发布。
如果有什么错误可以到http://blog.csdn.net/kakashi8841留言或EMAIL:[email protected]给我。
jME版本 :jME_2.0.1_Stable
开发工具:MyEclipse8.5
操作系统:Window7/Vista
在这个向导中,我们将在平面上增加flag。这将给我们自己的“目标”,我们想捕获的对象。为了这么做,我们将讨论Cloth System(织物系统),它是jME中用于render看起来真实的flag。我们也将做一些优化和一些对bike动画的改进,让它看起来更好。
这节课我们将有几个优化要做。首先,加载一个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足够高就行,bike能drift超过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低一点,并让它更容易看到平面上的东西。
以上就是这次的优化。下面我们将改进游戏。
我对现在bike当我们做一个转弯时它的僵硬看起来很不爽。为了让bike看起来更流畅和更像一个真正的bike,我想增加倾斜以便bike能在转弯的时候倾斜。这里需要注意的一个概念是我将倾斜Vehicle包含的模型的node,而不是Vehicle它自己。这是因为Vehicle的责任是保存bike在terrain上的方向,这个倾斜只是纯粹的视觉效果。
我们要做的第一件事是为vehicle增加一个方式以便我们告诉它我们是否需要倾斜。在VehicleRotateAction中,我们在performAction方法下面增加以下这一行。
vehicle.setRotateOn(modifier);
这将告诉Vehicle我们将转左或右。而当然,我们需要增加一个方法给Vehicle。
public void setRotateOn(int modifier) {
lean = modifier;
}
这将为modifier设置一个新的类变量。Lean包含在一个给出的帧下bike倾斜的方向。
既然我们知道bike是怎样倾斜的,我们需要为bike的倾斜调整方向。如果lean是0,我们则不必倾斜。在这个例子中,我们看bike的leanAngle是否是其它的而不是0,如果是,我们将缓慢让bike从右到直。这给出一种效果,做一个转弯,然后把bike带到正确的位置上。如果lean不是0,那么我们根据lean的值调整leanAngle。我们将把它锁定为-1到1因此bike不会倒下(我们驾驶我们的驾驶员知道怎么骑一个bike)。我们然后根据leanAxis(Vector3f(0,0,1))设置bike的角度为一个新的leanAngle。将lean设置回0很重要,这样使我们能正确驾驶bike,如果玩家停止转弯。
/**
* processlean将基于一个lean因素调整bike模型的角度。
* 我们调整bike而不是Vehicle,
* 因为Vehicle关心的是bike在terrain上的位置
* @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;
}
//最大倾斜为-1到1
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的视觉!
我们想增加的另一个移动的可视提示是轮胎的旋转。这个是微妙的增加,因为车轮没有很多变化去表示它们正在旋转,但是它对于那些细心观察的人是可以被注意到的。
首先,我们需要获取模型部件的参考才能知道哪里是车轮。这个简单要求你对模型结构有一点了解。在bike.jme这个例子中,结构像下面:
所以,我们需要获取Geometry的后轮(backwheel)和前轮(frontwheel)。我偶然知道这些是索引为1和5的孩子。实际上我通过名字获取。
//获取前轮和后轮的引用
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了,现在平滑地运行吧。在享受高质量游戏的同时想想为什么?
现在,我们将增加一面旗。如果你记起这个游戏的目的是快速收集旗帜。很好,这讲的将是那旗帜。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。
* 设置值在(45和175)之间,这在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的随机值将是0到130+45或45-175。这个数字是我决定将flag放在fence里面并使用大部分的区域。
我们使用一个简单的cylinder作为旗杆。
现在,为了真正使用这个Flag,我们修改Lesson8去创建Flag并update它。首先增加一个新的方法buildFlag。
/**
* 我们创建一个新的Flag类,
* 因此我们将使用这个把Flag加到世界中。
* 这个flag是我们相要获取的那个。
*/
private void buildFlag() {
flag = new Flag(tb);
scene.attachChild(flag);
flag.placeFlag();
}
这将创建新的Flag,将它attach到scene并随机设置位置。
接下来我们将在initGame中加入对buildFlag的调用,在buildTerrain下调用。我们在buildTerrain后调用是因为我们使用terrain去放置Flag。
我们也需要update flag去更新countdown。因此在Lesson8的update方法中增加flag的update方法:
flag.update(interpolation);
通过那样,我们现在将有一个旗杆随机放置在terrain上,然后每10秒移动一次。然而,看着旗杆实在没趣,所以让我们增加旗帜。