5.4 、让我们增加玩家
对于这个向导,我们将只是使用一个占位符代替交通工具。我们将在之后载入模型,但那只是没价值的工作,我们想要先让游戏的核心能运作。一个 Box 是一个好的占位符,因为它是我们交通工具的基础模型。
所以,让我们先增加一个 buildPlayer 的方法并在 initGame 中调用它。我们将接着创建一个 box 做为玩家的几何体并把这个 Box attach 到 node 。这个玩家 Node 将会是一个类变量,以便我们能在 update 期间访问它。我将创建一个中心为 (0,0,0) 和大小为 (0.35,0.25,0.5) ,让它看起来长和宽。 Node 接着被移到坐标 (100,0,100) 。我还没设置它的高度,我将在之后才那么做。
private void buildPlayer() {
//box 代替
Box b = new Box( "box" , new Vector3f(), 0.35f,0.25f,0.5f);
b.setModelBound( new BoundingBox());
b.updateModelBound();
player = new Node( "Player Node" );
player .setLocalTranslation( new Vector3f(100,0, 100));
scene .attachChild( player );
player .attachChild(b);
player .updateWorldBound();
}
如果你现在运行这个,实际上不会看到 player ,因为它深陷在 terrain 下面。我们在 update 里面设置 height ,这是因为我们将很快让交通工具在平面上移动,并需要让它保持在 terrain 上。所以,为了保持 box 在地面的顶部行驶,增加:
// 确保当玩家离开平面时我们不会坠落。
// 当我们增加冲突时, fence 将做它自己的工作并保持玩家在里面。
float characterMinHeight =
tb .getHeight( player .getLocalTranslation()) +
((BoundingBox) player .getWorldBound()). yExtent ;
if (
!Float. isInfinite (characterMinHeight) &&
!Float. isNaN (characterMinHeight)
)
player .getLocalTranslation(). y = characterMinHeight;
首先,我们获取玩家当前位置对应的 terrain 的高度。接着加上 BoundingBix 的偏移,我们这么做是因为 Box 位置的点是 Box 的中心。如果我们没加上偏移, box 将有一半沉入地下。我们使用包围对象的 BoundingBox 去获取对象的高度( yExtent )(但实际上如果你对模型了解得很好,你可以使用值代替)。最后,我们检查获取的高度去确认没有得到一些糟糕的值(非数字、无穷大等)。我们这么做是因为目前我们没做任何事去阻止玩家驾驶出 terrain 。
我们现在已经在 terrain 上拥有了玩家!
( 现在你可能还看不到这个画面,别急,后面会看到的 )
5.5 、跟随摄像机( ChaseCamera )
好了,现在我们有了玩家,我们应该有能力移动它。这将是一个第三人称游戏,意味我们在玩游戏的时候能看到自己的玩家(而不是以 player 的眼睛去看)。所以我们想要摄像头一直指向玩家并跟随他。为了这么做,我们将使用 ChaseCamera 。 ChaseCamera 将通过定义它跟随距离的参数一直追踪一个给出的对象。 ChaseCamera 也定义一些值让它平滑跟随。那就是它不能突然转向玩家。这种突然性的效果当然也是可以定义的。
所以,当我们使用 ChaseCamera ,视图将一直对着玩家。鼠标将允许 camera 在玩家四周旋转并一直面向它。鼠标滚轮将允许 camera 缩放(尽管在这个例子中缩放值很小)。
因此,创建一个 buildChaseCamera 方法并从 initGame 中调用它。我们在这里设置 ChaseCamera 的参数并创建它。 ChaseCamera 对象将成为一个类变量以致我们能 update 它(所以把它加到类的顶部)。
我们相对 ChaseCamera 设置的参数有一些。首先,我们将设置 Camera 的目标偏移玩家。我们通常想让 camera 看起来在玩家上一点。所以我们设置偏移( offset )值为( 0 ,玩家的 Y*1.5 , 0 ) . 这将让 camera 指向指向一个在玩家原始高度上面多一半的一个点。下一步,我们将设置滚动( rollout )值。这些值决定了我们能拉近或推远摄像机多少。我这里不想给太多自由,因此这个级别实际上很小。所以我们设置最大为 6 个单元,而最小为 3 个单元。下一步我们将设置 camera 能向上转动多高,在这个例子中为 45 度,注意是弧度。最后,我们将为 camera 设置开始起点的球形坐标, roll out 为 5 并升高 30 度。因为 camera 在一个“弹簧”系统中,如果交通工具行驶太快时,它能延迟落后一定距离。因此,我们将增加 camera 能落后的最小和最大值。 8 和 2 应该是可以的。
我们在一个 hash map 中设置这些参数。
private void buildChaseCamera() {
Vector3f targetOffset = new Vector3f();
targetOffset. y =
((BoundingBox) player .getWorldBound()). yExtent *1.5f;
HashMap<String, Object> props = new HashMap<String, Object>();
props.put(ThirdPersonMouseLook. PROP_MAXROLLOUT , "6" );
props.put(ThirdPersonMouseLook. PROP_MINROLLOUT , "3" );
props.put(
ThirdPersonMouseLook. PROP_MAXASCENT ,
"" +45*FastMath. DEG_TO_RAD
);
props.put(
ChaseCamera. PROP_INITIALSPHERECOORDS ,
new Vector3f(5,0,30*FastMath. DEG_TO_RAD )
);
props.put(ChaseCamera. PROP_TARGETOFFSET , targetOffset);
chaser = new ChaseCamera( cam , player , props);
chaser .setMaxDistance(8);
chaser .setMinDistance(2);
}
我们现在已经设置好了自己的 ChaseCamera ,但它在调用 update 方法之前不会产生任何作用。因此在 update 中加入:
chaser . update (interpolation);
现在,当应用程序运行时,你能看到 camera 在它初始化的位置并光滑地把镜头拉近直到最大的 6 个单元。你能接着移动鼠标去围绕 box 旋转 camera ,也能滚动鼠标滑轮去将 camera 拉近或推远一点。那就是所激动的,但没有什么东西可以追踪,因为 box 只是停在那里。让我们纠正那个。
这里补充一点是作者漏掉的,我们还需要在 game 的 update 方面里面加入下面代码以保证 camera 的位置一直在 terrain 上面:
// 我们不想 chase camera 走到世界下面,因此让它一直在水平面上 2 个单元。
if ( cam .getLocation(). y < ( tb .getHeight( cam .getLocation())+2)) {
cam .getLocation(). y = tb .getHeight( cam .getLocation()) + 2;
cam .update();
}
5.6 、我们自定义的输入处理
我们将创建自己的输入处理器( InputHandler )从而允许我们驾驶交通工具。这个 handler 的目标是允许我们行驶向前、向后和转向。我想这些控制的键被设置为: WASD 。幸运的是,为了做到这个,我们将能使用在 jME 中构建的 action 。名字是, KeyNodeForwardAction , KeyNodeBackwardAction , KeyNodeRotateRightAction 和 KeyNodeRotateLeftAction 。这些 action 处理一个 node 的旋转和移动,这些都基于速度和传入的时间。
InputAction 很直观。你简单将触发器( trigger )赋给 action ,并把键( key )赋给这些触发器。然后在每次 update 期间它将检查是否有任何键被按下,如果它们是 trigger 赋予的按键,那么则让 trigger 去调用相应的 action 。
创建一个新的叫做 FlagRushInputHandler 的类,它继承自 InputHandler 。这个类将只有 2 个方法, setKeyBindings 和 setActions 。 setKeyBindings 将创建 KeyBindingManager 并赋予 W,A,S,D 到相应的 trigger 名字,而 setActions 将为每个 trigger 创建 InputAction 。
FlagRushInputHandler.java
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.action.KeyNodeBackwardAction;
import com.jme.input.action.KeyNodeForwardAction;
import com.jme.input.action.KeyNodeRotateLeftAction;
import com.jme.scene.Spatial;
/**
* 游戏的 InputHnadler 。这控制了一个给出的 Spatial
* 允许我们去把它往前移、往后移和左右旋转。
* @author John
*
*/
public class FlagRushInputHandler extends InputHandler {
/**
* 提供用于控制的 node 。 api 将处理 input 的创建
* @param node 我们想移动的那个 node
* @param api library 将处理 input 的创建
*/
public FlagRushInputHandler(Spatial node, String api){
setKeyBindings(api);
setActions(node);
}
/**
* 将 action 类赋给 trigger 。这些 action 处理结点前移、后移和旋转
* @param node 用于控制的结点
*/
private void setActions(Spatial node) {
KeyNodeForwardAction forward =
new KeyNodeForwardAction(node,30f);
addAction(forward, "forward" , true );
KeyNodeBackwardAction backward =
new KeyNodeBackwardAction(node,15f);
addAction(backward, "backward" , true );
KeyNodeRotateLeftAction rotateLeft =
new KeyNodeRotateLeftAction(node,5f);
rotateLeft.setLockAxis(
node.getLocalRotation().getRotationColumn(1)
);
addAction(rotateLeft, "turnLeft" , true );
KeyNodeRotateRightAction rotateRight =
new KeyNodeRotateRightAction(node,5f);
rotateRight.setLockAxis(
node.getLocalRotation().getRotationColumn(1)
);
addAction(rotateRight, "turnRight" , true );
}
/**
* 创建 keyboard 对象,当键被按下时允许我们获取键盘的值。
* 它接着设置 action 作为触发器的基础,如果确认了键被按下( WASD )
* @param api
*/
private void setKeyBindings(String api) {
KeyBindingManager keyboard =
KeyBindingManager. getKeyBindingManager ();
keyboard.set( "forward" , KeyInput. KEY_W );
keyboard.set( "backward" , KeyInput. KEY_S );
keyboard.set( "turnLeft" , KeyInput. KEY_A );
keyboard.set( "turnRight" , KeyInput. KEY_D );
}
}
当这个类真的写完后,我们在自己的游戏中使用。创建一个 buildInput 方法,由 initGame 方法调用。这个方法将只有一行:
input = new FlagRushInputHandler(
player ,
settings . getRenderer ()
);
正如你所猜的,这里 input 也是类变量。为什么要在类里面呢?因为你将在游戏的 update 期间调用它的 update 。
就是那样!不管相信与否,我们现在具有做游戏的条件。 Box 能被驾驶。现在试试看。注意 ChaseCamera 将会落后于 box 一点然后尝试赶上,带来更平滑和真实的感觉。
接下来,我们将改进 box 的移动以便它能加速和减速。我们也将让它和环境交互得更好。继续收看!