JavaFX中的2D相机?

问题描述 投票:0回答:1

我正在JavaFX中创建一个自上而下的游戏,但是我在使用随播放器移动的Camera的实现方面遇到了麻烦。

我尝试创造这样的东西的目的不是移动玩家,而是按照玩家想要的相反方向移动场景。这造成了玩家移动的错觉,但它需要场景中所有物体的不断移动,这显然造成了大量的性能问题。所以在此之后我制作了一个剪辑,并将所有地形节点放在一个剪裁的矩形内。

下面是我的TerrainRenderer类,它创建了剪切的矩形及其内部的内容。它的作用是拍摄一张图像,然后生成一堆矩形节点,以制作看起来像图像的地图。

private static final Pane tileContainer = new Pane();
private static final Rectangle rectClip = new Rectangle();

private static void clipChildren(Region region) {
    region.setClip(rectClip);
    region.layoutBoundsProperty().addListener((ov, oldValue, newValue) -> {
        rectClip.setWidth(newValue.getWidth());
        rectClip.setHeight(newValue.getHeight());
    });
}

private static void drawTile(int x, int y, Color color) {
    final int TILE_SIZE = 15;
    Rectangle tile = new Rectangle(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
    tile.setFill(color);
    tileContainer.getChildren().add(tile);
}

public static Region generate() {
    final Image map = new Image("main/Images/IcJR6.png");
    for (int x = 0; x < (int) map.getWidth(); x++) {
        for (int y = 0; y < (int) map.getHeight(); y++) {
            drawTile(x, y, map.getPixelReader().getColor(x, y));
        }
    }
    tileContainer.setPrefSize(Main.getAppWidth(), Main.getAppHeight());
    clipChildren(tileContainer);
    return tileContainer;
}

public static Rectangle getRectClip() {
    return rectClip;
}

您在下面看到的是我使用精灵表的播放器的更新方法。截至目前,此代码仅转换剪辑节点,但不转换内部内容。

void update() {
    int speed;
    if (Main.isPressed(KeyCode.SHIFT)) speed = 6;
    else speed = 3;
    if (Main.isPressed(KeyCode.W)) {
        getAnimation().play();
        getAnimation().setOffsetY(96);
        moveY(speed);
    } else if (Main.isPressed(KeyCode.S)) {
        getAnimation().play();
        getAnimation().setOffsetY(0);
        moveY(-speed);
    } else if (Main.isPressed(KeyCode.D)) {
        getAnimation().play();
        getAnimation().setOffsetY(64);
        moveX(-speed);
    } else if (Main.isPressed(KeyCode.A)) {
        getAnimation().play();
        getAnimation().setOffsetY(32);
        moveX(speed);
    } else getAnimation().stop();
}

@Override
protected void moveX(int x) {
    boolean right = x > 0;
    for(int i = 0; i < Math.abs(x); i++) {
        if (right) TerrainRenderer.getRectClip().setTranslateX(TerrainRenderer.getRectClip().getTranslateX() + 1);
        else TerrainRenderer.getRectClip().setTranslateX(TerrainRenderer.getRectClip().getTranslateX() - 1);
    }
}

@Override
protected void moveY(int y) {
    boolean down = y > 0;
    for (int i = 0; i < Math.abs(y); i++) {
        if (down) TerrainRenderer.getRectClip().setTranslateY(TerrainRenderer.getRectClip().getTranslateY() + 1);
        else TerrainRenderer.getRectClip().setTranslateY(TerrainRenderer.getRectClip().getTranslateY() - 1);
    }
}

我想要的结果看起来像this(跳到6:10),但是我如何在JavaFX中做出类似的东西呢?有什么建议?

javafx camera 2d
1个回答
1
投票

您尚未发布一个最小,完整且可验证的示例,显示实际问题,因此您的问题很难完全回答。

我会通过绘制背景(例如在画布上)并将其放在带有移动部件(播放器,通过描述的声音)的窗格中来接近这样的事情。然后通过剪切和翻译窗格来显示背景的一部分。

这是一个非常简单的例子;它只是在一个大画布上放置一些随机的小矩形,然后按下光标(箭头)键在场景周围移动一个蓝色矩形(播放器)。主窗格的剪辑和平移绑定到玩家的位置,因此玩家总是出现在中心,除非您靠近窗格的边缘。

这需要一点时间来启动,出于某种原因,我有时会看到一个空白屏幕,直到我将玩家移动了几个地方;我没有花太多时间在细节上,所以可能会有一些小错误。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class ScrollAndClipBackground extends Application {

    private final int tileSize = 10 ;
    private final int numTilesHoriz = 500 ;
    private final int numTilesVert = 500 ;

    private final int speed = 400 ; // pixels / second
    private boolean up ;
    private boolean down ;
    private boolean left ;
    private boolean right ;

    private final int numFilledTiles = numTilesHoriz * numTilesVert / 8 ;   

    @Override
    public void start(Stage primaryStage) {
        Pane pane = createBackground();
        Rectangle player = new Rectangle(numTilesHoriz*tileSize/2, numTilesVert*tileSize/2, 10, 10);
        player.setFill(Color.BLUE);
        pane.getChildren().add(player);

        Scene scene = new Scene(new BorderPane(pane), 800, 800);
        Rectangle clip = new Rectangle();
        clip.widthProperty().bind(scene.widthProperty());
        clip.heightProperty().bind(scene.heightProperty());

        clip.xProperty().bind(Bindings.createDoubleBinding(
                () -> clampRange(player.getX() - scene.getWidth() / 2, 0, pane.getWidth() - scene.getWidth()), 
                player.xProperty(), scene.widthProperty()));
        clip.yProperty().bind(Bindings.createDoubleBinding(
                () -> clampRange(player.getY() - scene.getHeight() / 2, 0, pane.getHeight() - scene.getHeight()), 
                player.yProperty(), scene.heightProperty()));

        pane.setClip(clip);
        pane.translateXProperty().bind(clip.xProperty().multiply(-1));
        pane.translateYProperty().bind(clip.yProperty().multiply(-1));

        scene.setOnKeyPressed(e -> processKey(e.getCode(), true));
        scene.setOnKeyReleased(e -> processKey(e.getCode(), false));

        AnimationTimer timer = new AnimationTimer() {
            private long lastUpdate = -1 ;
            @Override
            public void handle(long now) {
                long elapsedNanos = now - lastUpdate ;
                if (lastUpdate < 0) {
                    lastUpdate = now ;
                    return ;
                }
                double elapsedSeconds = elapsedNanos / 1_000_000_000.0 ;
                double deltaX = 0 ;
                double deltaY = 0 ;
                if (right) deltaX += speed ;
                if (left) deltaX -= speed ;
                if (down) deltaY += speed ;
                if (up) deltaY -= speed ;
                player.setX(clampRange(player.getX() + deltaX * elapsedSeconds, 0, pane.getWidth() - player.getWidth()));
                player.setY(clampRange(player.getY() + deltaY * elapsedSeconds, 0, pane.getHeight() - player.getHeight()));
                lastUpdate = now ;
            }
        };

        primaryStage.setScene(scene);
        primaryStage.show();

        timer.start();
    }

    private double clampRange(double value, double min, double max) {
        if (value < min) return min ;
        if (value > max) return max ;
        return value ;
    }

    private void processKey(KeyCode code, boolean on) {
        switch (code) {
        case LEFT: 
            left = on ;
            break ;
        case RIGHT: 
            right = on ;
            break ;
        case UP:
            up = on ;
            break ;
        case DOWN:
            down = on ;
            break ;
        default:
            break ;
        }
    }

    private Pane createBackground() {

        List<Integer> filledTiles = sampleWithoutReplacement(numFilledTiles, numTilesHoriz * numTilesVert);

        Canvas canvas = new Canvas(numTilesHoriz * tileSize, numTilesVert * tileSize);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        gc.setFill(Color.GREEN);

        Pane pane = new Pane(canvas);

        pane.setMinSize(numTilesHoriz * tileSize, numTilesVert * tileSize);
        pane.setPrefSize(numTilesHoriz * tileSize, numTilesVert * tileSize);
        pane.setMaxSize(numTilesHoriz * tileSize, numTilesVert * tileSize);

        for (Integer tile : filledTiles) {
            int x = (tile % numTilesHoriz) * tileSize ;
            int y = (tile / numTilesHoriz) * tileSize ;
            gc.fillRect(x, y, tileSize, tileSize);
        }

        return pane ;
    }

    private List<Integer> sampleWithoutReplacement(int sampleSize, int populationSize) {
        Random rng = new Random();
        List<Integer> population = new ArrayList<>();
        for (int i = 0 ; i < populationSize; i++) 
            population.add(i);
        List<Integer> sample = new ArrayList<>();
        for (int i = 0 ; i < sampleSize ; i++) 
            sample.add(population.remove(rng.nextInt(population.size())));
        return sample;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

一种更复杂的方法,即内存密集度较低,将是一种“平铺”机制,其中主视图由多个移动的区块组成,并根据需要按需创建。这更复杂,但允许基本上任意大小的场景。

© www.soinside.com 2019 - 2024. All rights reserved.