jMonkeyEngine译文 FlagRush5(2)跟随的摄像机(ChaseCamera
5.4 、让我们增加玩家 对于这个向导,我们将只是使用一个占位符代替交通工具。我们将在之后载入模型,但那只是没价值的工作,我们想要先让游戏的核心能运作。一个 Box 是一个好的占位符,因为它是我们交通工具的基础模型。 所以,让我们先增加一个 buildPlay
5.4、让我们增加玩家
对于这个向导,我们将只是使用一个占位符代替交通工具。我们将在之后载入模型,但那只是没价值的工作,我们想要先让游戏的核心能运作。一个Box是一个好的占位符,因为它是我们交通工具的基础模型。
所以,让我们先增加一个buildPlayer的方法并在initGame中调用它。我们将接着创建一个box做为玩家的几何体并把这个Box attach到node。这个玩家Node将会是一个类变量,以便我们能在update期间访问它。我将创建一个中心为(0,0,0)和大小为(0.35,0.25,0.5),让它看起来长和宽。Node接着被移到坐标(100,0,100)。我还没设置它的高度,我将在之后才那么做。
privatevoid 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中设置这些参数。
privatevoid buildChaseCamera() {
Vector3f targetOffset = new Vector3f();
targetOffset.y =
((BoundingBox)player.getWorldBound()).yExtent*1.5f;
HashMap
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
*
*/
publicclass 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 用于控制的结点
*/
privatevoid 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
*/
privatevoid 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的移动以便它能加速和减速。我们也将让它和环境交互得更好。继续收看!