第一人称视角相机位置不正确

First Person Camera is not on the correct position

我正在尝试基于绑定在 JavaFX 中创建第一人称相机。相机和实际位置都完美无缺。唯一的问题是它们不匹配!如图所示,实际位置(红框)是在圆圈中间,但是摄像头在外面。我该如何改变它?我做错了什么?

Player class 处理 PerspectiveCamera。

package game;

import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.PerspectiveCamera;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.robot.Robot;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;

public class Player extends Character {

    private static final Robot ROBOT = new Robot();
    private DoubleProperty relativeCenterX = new SimpleDoubleProperty();
    private DoubleProperty relativeCenterY = new SimpleDoubleProperty();

    protected PerspectiveCamera camera = new PerspectiveCamera();
    protected Rotate xAxis = new Rotate(0, 250, 0, 0, Rotate.Y_AXIS);
    protected Rotate yAxis = new Rotate(0, 0, 250, 0, Rotate.X_AXIS);
    protected Translate translate = new Translate();

    protected DoubleProperty centerX = new SimpleDoubleProperty();
    protected DoubleProperty centerY = new SimpleDoubleProperty();

    @SuppressWarnings("exports")
    public Player(Stage stage) {
        camera.getTransforms().addAll(xAxis, yAxis);

        centerX.bind(stage.widthProperty().divide(2));
        centerY.bind(stage.heightProperty().divide(2));

        relativeCenterX.bind(stage.xProperty().add(centerX));
        relativeCenterY.bind(stage.yProperty().add(centerY));

        camera.translateXProperty().bind(posX.subtract(centerX));
        camera.translateYProperty().bind(posZ);
        camera.translateZProperty().bind(posY.subtract(centerY));

        xAxis.angleProperty().bind(viewX.subtract(90));
        yAxis.angleProperty().bind(viewY);

        translate.xProperty().bind(posX);
        translate.zProperty().bind(posY);
        translate.yProperty().bind(posZ);
    }

    @SuppressWarnings("exports")
    public EventHandler<KeyEvent> getKeyHandle() {
        return e -> {
            switch (e.getCode()) {
            case A:
                view(-1, 0);
                break;
            case D:
                view(1, 0);
                break;
            case W:
                move(1, 1, 0);
                break;
            case S:
                move(-1, -1, 0);
                break;
            case SPACE:
                move(0, 0, 10);
                break;
            case F:
                move(0, 0, -10);
                break;
            default:
                break;
            }
        };
    }

    @SuppressWarnings("exports")
    public EventHandler<MouseEvent> getMouseHandle() {
        return e -> {
            view(e.getSceneX() - centerX.doubleValue(), centerY.doubleValue() - e.getSceneY());
            Platform.runLater(() -> {
                ROBOT.mouseMove(relativeCenterX.intValue(), relativeCenterY.intValue());
            });
        };
    }

    @SuppressWarnings("exports")
    public PerspectiveCamera getPespectiveCamera() {
        return camera;
    }
}

角色class计算位置和视角。

package game;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;

public abstract class Character {

    protected DoubleProperty posX = new SimpleDoubleProperty();
    protected DoubleProperty posY = new SimpleDoubleProperty();
    protected DoubleProperty posZ = new SimpleDoubleProperty();

    protected DoubleProperty viewX = new SimpleDoubleProperty();
    protected DoubleProperty viewY = new SimpleDoubleProperty();

    protected DoubleProperty speed = new SimpleDoubleProperty(10);

    public void move(double x, double y, double z) {

        double fX = Math.cos(Math.toRadians(viewX.get()));
        double fY = -Math.sin(Math.toRadians(viewX.get()));
        double fZ = 1;

        posX.set(posX.get() + fX * x * speed.get());
        posY.set(posY.get() + fY * y * speed.get());
        posZ.set(posZ.get() + fZ * z);
    }

    public void view(double x, double y) {
        viewX.set(viewX.get() + x);
        viewY.set(viewY.get() + y);
    }

    @SuppressWarnings("exports")
    public DoubleProperty posXPorperty() {
        return posX;
    }

    @SuppressWarnings("exports")
    public DoubleProperty posYPorperty() {
        return posY;
    }

    @SuppressWarnings("exports")
    public DoubleProperty posZPorperty() {
        return posZ;
    }

    @SuppressWarnings("exports")
    public DoubleProperty viewXPorperty() {
        return viewX;
    }

    @SuppressWarnings("exports")
    public DoubleProperty viewYPorperty() {
        return viewY;
    }
}

我的应用程序,显示全部图形内容。

package graphics;

import game.Player;
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class GameStage extends Application implements Runnable {

    @Override
    public void run() {
        launch();
    }

    @SuppressWarnings("exports")
    @Override
    public void start(Stage stage) throws Exception {

        BorderPane pane = new BorderPane();
        Scene scene = new Scene(pane, 500, 500);

        Group content = new Group(), map = new Group();
        ContentScene subscene = new ContentScene(content, map, 500, 500);
        subscene.widthProperty().bind(scene.widthProperty());
        subscene.heightProperty().bind(scene.heightProperty());
        pane.getChildren().add(subscene);
        pane.setBottom(map);

        Player player = new Player(stage);
        Box box = new Box(50, 50, 50);
        box.translateXProperty().bind(player.posXPorperty());
        box.translateYProperty().bind(player.posZPorperty());
        box.translateZProperty().bind(player.posYPorperty());
        box.rotateProperty().bind(player.viewXPorperty());
        box.setMaterial(new PhongMaterial(Color.RED));
        content.getChildren().add(box);

        Rectangle rectangle = new Rectangle(5, 5);
        rectangle.translateXProperty().bind(player.posXPorperty().divide(10));
        rectangle.translateYProperty().bind(player.posYPorperty().divide(10));
        rectangle.setFill(Color.RED);
        map.getChildren().add(rectangle);

        subscene.setCamera(player.getPespectiveCamera());
        scene.addEventHandler(KeyEvent.KEY_PRESSED, player.getKeyHandle());
        scene.addEventHandler(MouseEvent.MOUSE_MOVED, player.getMouseHandle());
        scene.setFill(Color.BLACK);

        Cursor cursor = Cursor.CROSSHAIR;
        scene.setCursor(cursor);

        stage.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
            if (e.getCode() != KeyCode.F11) {
                return;
            }
            if (stage.isFullScreen()) {
                stage.setFullScreen(false);
            } else {
                stage.setFullScreen(true);
            }
        });

        stage.setAlwaysOnTop(true);
        stage.setScene(scene);
        stage.show();
    }

    private class ContentScene extends SubScene {

        public ContentScene(Group content, Group map, double width, double height) {
            super(content, width, height, true, SceneAntialiasing.BALANCED);

            PhongMaterial material = new PhongMaterial(Color.AQUA);

            for (int v = 0; v < 3_600; v += 180) {
                for (int y = 0; y < 500; y += 100) {
                    Box box = new Box(50, 50, 50);
                    box.setTranslateX(Math.sin(v / 10) * 1_000);
                    box.setTranslateY(y);
                    box.setTranslateZ(Math.cos(v / 10) * 1_000);
                    box.setMaterial(material);
                    content.getChildren().add(box);

                    Rectangle rectangle = new Rectangle(5, 5);
                    rectangle.translateXProperty().bind(box.translateXProperty().divide(10));
                    rectangle.translateYProperty().bind(box.translateZProperty().divide(10));
                    rectangle.setFill(Color.AQUA);
                    map.getChildren().add(rectangle);
                }
            }
        }
    }
}

多亏了 Thomas,我才得以解决问题。现在的代码是这样的:

package graphics;

import game.Player;
import javafx.application.Application;
import javafx.scene.AmbientLight;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class GameStage extends Application implements Runnable {

    @Override
    public void run() {
        launch();
    }

    @SuppressWarnings("exports")
    @Override
    public void start(Stage stage) throws Exception {

        // Parents
        BorderPane pane = new BorderPane();
        Group content = new Group(new AmbientLight()), map = new Group();
        Pane stackpane = new Pane(map);

        // Scenes
        Scene scene = new Scene(pane, 500, 500);
        SubScene contentSubscene = new SubScene(content, 500, 500, true, SceneAntialiasing.BALANCED);
        contentSubscene.widthProperty().bind(scene.widthProperty());
        contentSubscene.heightProperty().bind(scene.heightProperty());
        SubScene minimapSubscene = new SubScene(stackpane, 256, 256);
        minimapSubscene.setFill(Color.DARKGREY);

        pane.getChildren().add(contentSubscene);
        pane.setBottom(minimapSubscene);

        // Create Player
        Player player = new Player(stage);
        Rectangle currentPosition = new Rectangle(5, 5);
        currentPosition.layoutXProperty().bind(minimapSubscene.widthProperty().divide(2));
        currentPosition.layoutYProperty().bind(minimapSubscene.heightProperty().divide(2));
        currentPosition.setFill(Color.RED);
        stackpane.getChildren().add(currentPosition);

        map.layoutXProperty().bind(player.posXPorperty().divide(-10).add(minimapSubscene.widthProperty().divide(2)));
        map.layoutYProperty().bind(player.posYPorperty().divide(-10).add(minimapSubscene.heightProperty().divide(2)));

        // Create Box in
        PhongMaterial material = new PhongMaterial(Color.AQUA);
        for (int v = 0; v < 3_600; v += 180) {
            for (int y = 0; y < 500; y += 100) {
                Box box = new Box(50, 50, 50);
                box.setTranslateX(Math.sin(v / 10) * 1_000);
                box.setTranslateY(y);
                box.setTranslateZ(Math.cos(v / 10) * 1_000);
                box.setMaterial(material);
                content.getChildren().add(box);

                Rectangle boxPosition = new Rectangle(5, 5);
                boxPosition.translateXProperty().bind(box.translateXProperty().divide(10));
                boxPosition.translateYProperty().bind(box.translateZProperty().divide(10));
                boxPosition.setFill(Color.AQUA);
                map.getChildren().add(boxPosition);
            }
        }

        contentSubscene.setCamera(player.getPespectiveCamera());
        scene.addEventHandler(KeyEvent.KEY_PRESSED, player.getKeyHandle());
        scene.addEventHandler(MouseEvent.MOUSE_MOVED, player.getMouseHandle());
        scene.setCursor(Cursor.CROSSHAIR);
        scene.setFill(Color.WHITE);

        stage.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
            if (e.getCode() != KeyCode.F11) {
                return;
            }
            if (stage.isFullScreen()) {
                stage.setFullScreen(false);
            } else {
                stage.setFullScreen(true);
            }
        });
        stage.setAlwaysOnTop(true);
        stage.setScene(scene);
        stage.show();

    }
}
package game;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;

public abstract class Character {

    protected DoubleProperty posX = new SimpleDoubleProperty();
    protected DoubleProperty posY = new SimpleDoubleProperty();
    protected DoubleProperty posZ = new SimpleDoubleProperty();

    protected DoubleProperty viewX = new SimpleDoubleProperty();
    protected DoubleProperty viewY = new SimpleDoubleProperty();

    protected DoubleProperty speed = new SimpleDoubleProperty(10);

    public void move(double x, double y, double z) {

        double fX = Math.cos(Math.toRadians(viewX.get()));
        double fY = -Math.sin(Math.toRadians(viewX.get()));
        double fZ = 1;

        posX.set(posX.get() + fX * x * speed.get());
        posY.set(posY.get() + fY * y * speed.get());
        posZ.set(posZ.get() + fZ * z);
    }

    public void view(double x, double y) {
        viewX.set(viewX.get() + x);
        viewY.set(viewY.get() + y);
    }

    @SuppressWarnings("exports")
    public DoubleProperty posXPorperty() {
        return posX;
    }

    @SuppressWarnings("exports")
    public DoubleProperty posYPorperty() {
        return posY;
    }

    @SuppressWarnings("exports")
    public DoubleProperty posZPorperty() {
        return posZ;
    }

    @SuppressWarnings("exports")
    public DoubleProperty viewXPorperty() {
        return viewX;
    }

    @SuppressWarnings("exports")
    public DoubleProperty viewYPorperty() {
        return viewY;
    }
}
package game;

import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.PerspectiveCamera;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.robot.Robot;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

public class Player extends Character {

    private static final Robot ROBOT = new Robot();
    private DoubleProperty relativeCenterX = new SimpleDoubleProperty();
    private DoubleProperty relativeCenterY = new SimpleDoubleProperty();

    protected PerspectiveCamera camera = new PerspectiveCamera(true);
    protected Rotate xAxis = new Rotate(0, 250, 0, 0, Rotate.Y_AXIS);
    protected Rotate yAxis = new Rotate(0, 0, 250, 0, Rotate.X_AXIS);

    protected DoubleProperty centerX = new SimpleDoubleProperty();
    protected DoubleProperty centerY = new SimpleDoubleProperty();

    @SuppressWarnings("exports")
    public Player(Stage stage) {
        camera.getTransforms().addAll(xAxis, yAxis);
        camera.setFieldOfView((40 + 62) / 2);
        camera.setNearClip(0.1);
        camera.setFarClip(100000);
        camera.setVerticalFieldOfView(true);

        centerX.bind(stage.widthProperty().divide(2));
        centerY.bind(stage.heightProperty().divide(2));

        relativeCenterX.bind(stage.xProperty().add(centerX));
        relativeCenterY.bind(stage.yProperty().add(centerY));

        xAxis.angleProperty().bind(viewX.subtract(90));
        yAxis.angleProperty().bind(viewY);

        camera.translateXProperty().bind(posX);
        camera.translateZProperty().bind(posY);
        camera.translateYProperty().bind(posZ);
    }

    @SuppressWarnings("exports")
    public EventHandler<KeyEvent> getKeyHandle() {
        return e -> {
            switch (e.getCode()) {
            case A:
                view(-1, 0);
                break;
            case D:
                view(1, 0);
                break;
            case W:
                move(1, 1, 0);
                break;
            case S:
                move(-1, -1, 0);
                break;
            case SPACE:
                move(0, 0, 10);
                break;
            case F:
                move(0, 0, -10);
                break;
            default:
                break;
            }
        };
    }

    @SuppressWarnings("exports")
    public EventHandler<MouseEvent> getMouseHandle() {
        return e -> {
            view(e.getSceneX() - centerX.doubleValue(), centerY.doubleValue() - e.getSceneY());
            Platform.runLater(() -> {
                ROBOT.mouseMove(relativeCenterX.intValue(), relativeCenterY.intValue());
            });
        };
    }

    @SuppressWarnings("exports")
    public PerspectiveCamera getPespectiveCamera() {
        return camera;
    }
}