问题描述
我正在JavaFX中创建一个自上而下的游戏,但是我在使用随玩家移动的相机的实现方面遇到了麻烦。
I am creating a top-down game in JavaFX, but I'm having trouble with the implementation of a Camera that moves with the player.
我的尝试创造这样的东西不是移动玩家,而是以与玩家想要相反的方向移动场景。这造成了玩家移动的错觉,但它需要不断移动所有场景中的对象,这显然会产生大量的性能问题。所以在此之后我创建了一个剪辑,并将所有地形节点放在一个剪切的矩形内。
My attempt at creating something like this was to instead of moving the player, move the scene in the opposite direction the player wanted to go. This created the illusion that the player was moving, but it required constant movement of all the objects in the scene which obviously created a ton of performance issues. So after this I made a clip, and put all the terrain nodes inside a clipped rectangle.
下面是我的 TerrainRenderer
创建剪切矩形及其内部内容的类。它的作用是拍摄一张图像,然后生成一堆矩形节点,以制作看起来像图像的地图。
Below is my TerrainRenderer
class that creates the clipped rectangle and the contents inside of it. What it does is take an image and then generate a bunch of rectangle nodes in order to make a map that looks like the image.
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;
}
下面你看到的是我使用精灵表的玩家的更新方法。截至目前,此代码仅翻译剪辑节点,但不翻译内容。
What you see below is my update method for the player that uses a sprite sheet. As of now this code only translates the clip node, but not the contents inside.
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);
}
}
我想要的结果看起来像(跳到6:10),但我如何在JavaFX中制作这样的东西呢?有什么建议吗?
The result I want would look something like this (skip to 6:10), but how would I make something like this in JavaFX instead? Any suggestions?
推荐答案
你还没有发布一个最小,完整,可验证的例子来说明实际问题是什么,所以你的问题很难完全回答。
You haven't posted a minimal, complete, and verifiable example showing what the actual problem is, so your question is difficult to answer completely.
我会通过绘制背景(例如在画布上)并将其放在带有移动部件的窗格中来处理类似的事情(玩家,通过你的描述的声音)。然后通过剪切和翻译窗格来显示背景的一部分。
I would approach something like this by drawing the background (e.g. on a canvas), and putting it in a pane with the moving parts (player, by the sounds of your description). Then show just a portion of the background by clipping and translating the pane.
这是一个非常快速的例子;它只是在一个大画布上放置一些随机的小矩形,然后按下光标(箭头)键在场景周围移动一个蓝色矩形(播放器)。主窗格的剪辑和平移绑定到玩家的位置,因此玩家总是出现在中心,除非您靠近窗格的边缘。
Here's a very quick example; it just puts some random small rectangles on a large canvas and then moves a blue rectangle (player) around the scene on pressing the cursor (arrow) keys. The clip and translation of the main pane are bound to the player's position so the player always appears in the center, except when you get close to the edges of the pane.
这需要一点时间才能启动,出于某种原因,我有时会看到一个空白的屏幕,直到我将玩家移动了几个位置;我没有花太多时间在细节上,所以可能会有一些小错误。
This takes a little time to start up, and for some reason I sometimes see a blank screen until I have moved the player a couple of places; I didn't spend too much time on niceties so there may be some little bugs in there.
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);
}
}
更复杂的方法,内存密集程度更低,将是一种平铺机制,其中主视图由许多移动的瓦片组成,并根据需要按需创建。这更复杂,但允许基本上任意大小的场景。
A more complex approach, which would be less memory intensive, would be a "tiling" mechanism where the main view consists of a number of tiles which are moved, and created as needed on demand. This is more complex but allows for essentially arbitrary-sized scenes.
这篇关于JavaFX中的2D相机?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!