一个图理解透视投影。
相机位置.posiiotn和.lookAt(相机拍摄目标位置)
在 com.jme3.app.Application 中默认了一个相机cam,其默认属性如下:
方法 | 描述 |
---|---|
cam.getLocation(), setLocation() | 摄像机的位置 |
cam.getRotation(), setRotation() | 摄像机旋转 |
cam.getLeft(), setLeft() | 照相机的左轴 |
cam.getUp(), setUp() | 机的上轴,通常为矢量3f (0,1,0) |
cam.getDirection() | 相机面对的矢量 |
cam.getAxes(), setAxes(left,up,dir) | 左、上、方向三个属性的一个访问器。 |
cam.getFrame(), setFrame(loc,left,up,dir) | location、left、up、direction 四个属性的一个访问器。 |
cam.resize(width, height, fixAspect) | 调整现有摄像机对象的大小,同时保留所有其他设置。将 fixAspect 设置为 true 以调整长宽比 |
cam.setFrustum( near, far, left, right, top, bottom ) | 视截头体由近/远平面、左/右平面、顶/底平面(所有距离都是浮点值)定义 |
cam.setFrustumPerspective( fovY, aspect ratio, near, far) | 视锥体由沿 y 轴的视角(以度为单位)、长宽比和近/远平面定义。 |
cam.lookAt(target,up) | 用来指定相机拍摄对象的目标坐标位置,target为目标的坐标,up为相机的上轴旋转方向。 |
cam.setParallelProjection(false) | 正常的视角 |
cam.setParallelProjection(true) | 平行投影透视 |
cam.getScreenCoordinates(worldPos) | 将给定位置从世界空间转换为屏幕空间。 |
更改视图端口、视图锥体或帧之后,调用 cam.update () ;
flyCam 扩展了 com.jme3.app.SimpleApplicatio 中的相机功能,使其很轻松地就能访问其AppState。
flyCam 类字段允许您访问一个 AppState,该 AppState 扩展了 com.jme3.app.SimpleApplication 中的默认相机。默认配置了其 InputManager 输入管理。使其能响应 WASD 键,用于向前和向后走,以及向两侧扫射; 移动鼠标旋转摄像机 ,滚动鼠标滚轮进行放大或缩小。QZ 键垂直升起或降低相机。
Q W up forw
A S D --> left back right
Z down
当玩家以第一人称视角操纵游戏角色时,这个实现方式是直接操纵flyCame摄像机(flyCam.setEnabled(true);) ,这时玩家永远看不到角色本身。然而,在第三人称视角的游戏中,玩家看到的是角色走路,而你(游戏开发者)想让摄像机跟着角色走路。
有两种方法可以让相机做到这一点:
Jme3支持可选的 Chase 相机,它可以跟踪移动目标 Spatial (com.jme3.input.ChaseCamera)。当你需要相机跟随游戏中的角色时,可以使用这个相机。
flyCam.setEnabled(false);
ChaseCamera chaseCam = new ChaseCamera(cam, target, inputManager);
方法 | 描述 |
---|---|
chaseCam.setSmoothMotion(true); | 当摄像机移动时,插入一个更平滑的加速/减速。 |
chaseCam.setChasingSensitivity(5f) | 追踪灵敏度越低,摄像机移动时跟踪目标的速度就越慢。 |
chaseCam.setTrailingSensitivity(0.5f) | 随后的灵敏度越低,当目标移动时,摄像机开始追踪目标的速度就越慢。默认值为0.5; |
chaseCam.setRotationSensitivity(5f) | 灵敏度越低,当拖动鼠标时,相机围绕目标旋转的速度就越慢。默认值为5。 |
chaseCam.setTrailingRotationInertia(0.1f) | 这样可以防止当目标在到达目标的尾随位置之前停止旋转时,摄像机突然停止。默认值为0.1 f。 |
chaseCam.setDefaultDistance(40); | 应用程序开始时到目标的默认距离。 |
chaseCam.setMaxDistance(40); | 最大缩放距离。默认值为40f。 |
chaseCam.setMinDistance(1); | 最小缩放距离。默认值为1 f。 |
chaseCam.setMinVerticalRotation(-FastMath.PI/2); | 相机围绕目标的最小垂直旋转角度。默认值为0。 |
chaseCam.setDefaultVerticalRotation(-FastMath.PI/2); | 在应用程序开始时,相机围绕目标的默认垂直旋转角度。 |
chaseCam.setDefaultHorizontalRotation(-FastMath.PI/2); | 在应用程序开始时,相机围绕目标的默认水平旋转角度。 |
要禁用鼠标旋转和缩放追逐相机,您可以使用以下方法。
//to disable rotation
inputManager.deleteMapping(CameraInput.CHASECAM_TOGGLEROTATE);
//to disable zoom out
inputManager.deleteMapping(CameraInput.CHASECAM_ZOOMOUT);
//to disable zoom in
inputManager.deleteMapping(CameraInput.CHASECAM_ZOOMIN);
代码示例:
import com.jme3.app.SimpleApplication;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.RectangleMesh;
public class TestChaseCamera extends SimpleApplication implements AnalogListener, ActionListener {
private Geometry teaGeom;
public static void main(String[] args) {
TestChaseCamera m = new TestChaseCamera();
m.start();
}
@Override
public void simpleInitApp() {
// 加载一个茶壶模型
teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
Material mat_tea = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
teaGeom.setMaterial(mat_tea);
rootNode.attachChild(teaGeom);
// 加载一个地板模型
Material mat_ground = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat_ground.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
Geometry ground = new Geometry("ground",
new RectangleMesh(new Vector3f(-25, -1, 25), new Vector3f(25, -1, 25), new Vector3f(-25, -1, -25)));
ground.setMaterial(mat_ground);
rootNode.attachChild(ground);
// 禁用默认的第一人称视觉相机
flyCam.setEnabled(false);
// 启用追踪相机
ChaseCamera chaseCam = new ChaseCamera(cam, teaGeom, inputManager);
// 反转相机的垂直旋转轴
// chaseCam.setInvertVerticalAxis(true);
// 反转相机的水平旋转轴
// chaseCam.setInvertHorizontalAxis(true);
// 开启/禁用平滑的摄像头运动
chaseCam.setSmoothMotion(true);
registerInput();
}
public void registerInput() {
inputManager.addMapping("moveForward", new KeyTrigger(KeyInput.KEY_UP), new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("moveBackward", new KeyTrigger(KeyInput.KEY_DOWN), new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping("moveRight", new KeyTrigger(KeyInput.KEY_RIGHT), new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("moveLeft", new KeyTrigger(KeyInput.KEY_LEFT), new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("displayPosition", new KeyTrigger(KeyInput.KEY_P));
inputManager.addListener(this, "moveForward", "moveBackward", "moveRight", "moveLeft");
inputManager.addListener(this, "displayPosition");
}
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("displayPosition") && isPressed) {
teaGeom.move(10, 10, 10);
}
}
@Override
public void onAnalog(String name, float value, float tpf) {
if (name.equals("moveForward")) {
teaGeom.move(0, 0, -5 * tpf);
}
if (name.equals("moveBackward")) {
teaGeom.move(0, 0, 5 * tpf);
}
if (name.equals("moveRight")) {
teaGeom.move(5 * tpf, 0, 0);
}
if (name.equals("moveLeft")) {
teaGeom.move(-5 * tpf, 0, 0);
}
}
}
是相机跟随角色的另外一种实现方式为使用 CameraNode,将相机绑定到角色上。可以将此 CameraNode 代码添加到 init 方法(例如 simpleInitApp ())。
//禁用flyCam
flyCam.setEnabled(false);
//创建相机节点
camNode = new CameraNode("Camera Node", cam);
//这种模式意味着摄像机会拷贝目标的动作:
camNode.setControlDir(ControlDirection.SpatialToCamera);
//将 camNode 附加到目标:
target.attachChild(camNode);
//调整 camNode 相对于目标的位置,使其可以看清目标
camNode.setLocalTranslation(new Vector3f(0, 5, -5));
//使其相机看向目标
camNode.lookAt(target.getLocalTranslation(), Vector3f.UNIT_Y);
如上代码显示, camNode.setLocalTranslation (new Vector3f (0,5,-5); ,必须为相机提供自己的起始位置。这取决于你的目标(玩家角色)的大小和它在特定场景中的位置。最理想的情况是,你把它设置在目标后面和上面一点的位置。
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.scene.shape.RectangleMesh;
import com.jme3.system.AppSettings;
/**
* 第三人称摄像机节点跟随目标(茶壶)。用 WASD 键跟随茶壶,通过拖动鼠标旋转。
*/
public class TestCameraNode extends SimpleApplication implements AnalogListener, ActionListener {
private Node teaNode;
private boolean rotate = false;
final private Vector3f direction = new Vector3f();
public static void main(String[] args) {
TestCameraNode app = new TestCameraNode();
AppSettings s = new AppSettings(true);
s.setFrameRate(100);
app.setSettings(s);
app.start();
}
@Override
public void simpleInitApp() {
// 加载一个茶壶模型
Geometry teaGeom
= (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
teaGeom.setMaterial(mat);
//创建一个节点来连接几何体和摄像头节点
teaNode = new Node("teaNode");
teaNode.attachChild(teaGeom);
rootNode.attachChild(teaNode);
// 创建地板
mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
Geometry ground = new Geometry("ground", new RectangleMesh(
new Vector3f(-25, -1, 25),
new Vector3f(25, -1, 25),
new Vector3f(-25, -1, -25)));
ground.setMaterial(mat);
rootNode.attachChild(ground);
// 创建相机节点
CameraNode camNode = new CameraNode("CamNode", cam);
// 设置方向为 SpatialToCamera,这意味着摄像机将复制节点的移动。
camNode.setControlDir(ControlDirection.SpatialToCamera);
// 将 camNode 附加到 teaNode
teaNode.attachChild(camNode);
// 设置位置,使其稍微远离茶壶节点
camNode.setLocalTranslation(new Vector3f(-10, 0, 0));
// 设置 camNode 看向 teaNode
camNode.lookAt(teaNode.getLocalTranslation(), Vector3f.UNIT_Y);
//禁用默认的第一人称相机
flyCam.setEnabled(false);
registerInput();
}
public void registerInput() {
inputManager.addMapping("moveForward", new KeyTrigger(KeyInput.KEY_UP), new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("moveBackward", new KeyTrigger(KeyInput.KEY_DOWN), new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping("moveRight", new KeyTrigger(KeyInput.KEY_RIGHT), new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("moveLeft", new KeyTrigger(KeyInput.KEY_LEFT), new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("toggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addMapping("rotateRight", new MouseAxisTrigger(MouseInput.AXIS_X, true));
inputManager.addMapping("rotateLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false));
inputManager.addListener(this, "moveForward", "moveBackward", "moveRight", "moveLeft");
inputManager.addListener(this, "rotateRight", "rotateLeft", "toggleRotate");
}
@Override
public void onAnalog(String name, float value, float tpf) {
direction.set(cam.getDirection()).normalizeLocal();
if (name.equals("moveForward")) {
direction.multLocal(5 * tpf);
teaNode.move(direction);
}
if (name.equals("moveBackward")) {
direction.multLocal(-5 * tpf);
teaNode.move(direction);
}
if (name.equals("moveRight")) {
direction.crossLocal(Vector3f.UNIT_Y).multLocal(5 * tpf);
teaNode.move(direction);
}
if (name.equals("moveLeft")) {
direction.crossLocal(Vector3f.UNIT_Y).multLocal(-5 * tpf);
teaNode.move(direction);
}
if (name.equals("rotateRight") && rotate) {
teaNode.rotate(0, 5 * tpf, 0);
}
if (name.equals("rotateLeft") && rotate) {
teaNode.rotate(0, -5 * tpf, 0);
}
}
@Override
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("toggleRotate") && keyPressed) {
rotate = true;
inputManager.setCursorVisible(false);
}
if (name.equals("toggleRotate") && !keyPressed) {
rotate = false;
inputManager.setCursorVisible(true);
}
}
}