JavaFX游戏机器人向右/向下移动时渲染在瓦片下方的问题
我正在开发一款基于JavaFX的网格瓦片游戏,机器人以GridPane中的Pane对象表示。目前遇到渲染问题:机器人向右或向下移动时会显示在瓦片下方,而向上或向左移动时则正常显示在瓦片上方。尽管尝试使用toFront()方法和setViewOrder(-100.0)调整Z层级,问题仍未解决。
updateRobot方法
public void updateRobot(RobotType robotType, Position newPosition, Orientation newOrientation) { Robot robot = this.robots.get(robotType); if (robot == null) { return; } Position oldPosition = robot.getPosition(); ImageView robotImageView = getRobotImageView(robotType); if (oldPosition != null && robotImageView != null) { // Calculate the movement delta double deltaX = (newPosition.x() - oldPosition.x()) * TILE_SIZE; double deltaY = (newPosition.y() - oldPosition.y()) * TILE_SIZE; // Bring the robot's pane to the front before starting the transition Pane robotPane = robotPanes.get(robotType); if (robotPane != null) { robotPane.toFront(); } // Create a movement transition TranslateTransition moveTransition = new TranslateTransition(Duration.seconds(2), robotImageView); moveTransition.setToX(robotImageView.getTranslateX() + deltaX); moveTransition.setToY(robotImageView.getTranslateY() + deltaY); // Determine the new rotation angle and calculate the angle difference double newAngle = getRotationAngle(newOrientation); double currentAngle = robotImageView.getRotate(); double angleDelta = newAngle - currentAngle; if (angleDelta > 180) { angleDelta -= 360; } else if (angleDelta < -180) { angleDelta += 360; } // Create a rotation transition RotateTransition rotateTransition = new RotateTransition(Duration.seconds(1), robotImageView); rotateTransition.setByAngle(angleDelta); // Play both transitions sequentially SequentialTransition sequentialTransition = new SequentialTransition(moveTransition, rotateTransition); sequentialTransition.setOnFinished(event -> { robot.setOrientation(newOrientation); robot.setPosition(newPosition); // Move the robot's pane to the new tile Pane oldTile = getTile(oldPosition.x(), oldPosition.y()); Pane newTile = getTile(newPosition.x(), newPosition.y()); if (robotPane != null) { oldTile.getChildren().remove(robotPane); newTile.getChildren().add(robotPane); robotPane.setViewOrder(-100.0); // Ensure the robot's pane is at the front after transition } // Reset translation and rotation to new values robotImageView.setTranslateX(0); robotImageView.setTranslateY(0); robotImageView.setRotate(newAngle); }); sequentialTransition.play(); } else { robot.setOrientation(newOrientation); robot.setPosition(newPosition); drawRobots(); } }
drawRobots方法
public void drawRobots() { Platform.runLater(() -> { for (Robot robot : this.robots.values()) { Position position = robot.getPosition(); if (position == null) { continue; } Pane robotsPane = robotPanes.computeIfAbsent(robot.getRobotType(), k -> { Pane newPane = new Pane(); newPane.setPrefSize(TILE_SIZE, TILE_SIZE); ImageView imageView = getImageView(robot.getRobotType().getGameFileName(), getRotationAngle(robot.getOrientation())); newPane.getChildren().add(imageView); newPane.setViewOrder(-100.0); // Lower Z-order for robots return newPane; }); Pane newTile = getTile(position.x(), position.y()); newTile.getChildren().add(robotsPane); } }); }
drawTile方法
public void drawTile(int x, int y) { List<Tile> tiles = this.map.getTilesAtPosition(x, y); if (tiles == null) { return; } Platform.runLater(() -> { Pane tilePane = createMapCell(tiles, x, y); tilePane.setViewOrder(100.0); // Higher Z-order for tiles this.grid.add(tilePane, x, y); }); }
- 使用
Pane.toFront()将机器人Pane置于顶层 - 为机器人Pane设置负的
viewOrder值(-100.0) - 确保瓦片先于机器人添加到
GridPane
问题核心在于机器人Pane被嵌套在TilePane内部,GridPane中后添加的TilePane(通常是右侧、下侧的瓦片)会在视觉层级上覆盖先添加的TilePane。当机器人移动到这些后添加的TilePane区域时,即使机器人Pane的viewOrder更小,也会被外层的TilePane覆盖。
修复步骤如下:
调整机器人的容器层级
不要将机器人Pane添加到TilePane的子节点中,而是直接作为GridPane的同级子节点添加,让机器人Pane和TilePane处于同一层级,直接通过viewOrder控制渲染优先级:public void drawRobots() { Platform.runLater(() -> { for (Robot robot : this.robots.values()) { Position position = robot.getPosition(); if (position == null) { continue; } Pane robotsPane = robotPanes.computeIfAbsent(robot.getRobotType(), k -> { Pane newPane = new Pane(); newPane.setPrefSize(TILE_SIZE, TILE_SIZE); ImageView imageView = getImageView(robot.getRobotType().getGameFileName(), getRotationAngle(robot.getOrientation())); newPane.getChildren().add(imageView); newPane.setViewOrder(-100.0); // 确保机器人在瓦片上方 return newPane; }); // 直接添加到GridPane,设置对应的行列 this.grid.add(robotsPane, position.x(), position.y()); // 确保机器人Pane始终在GridPane的顶层 robotsPane.toFront(); } }); }修改移动逻辑
不再将机器人Pane从旧Tile移到新Tile,而是更新它在GridPane中的行列索引,同时将过渡动画的目标对象改为机器人Pane(而非内部的ImageView),避免嵌套层级导致的覆盖:public void updateRobot(RobotType robotType, Position newPosition, Orientation newOrientation) { Robot robot = this.robots.get(robotType); if (robot == null) { return; } Position oldPosition = robot.getPosition(); Pane robotPane = robotPanes.get(robotType); ImageView robotImageView = getRobotImageView(robotType); if (oldPosition != null && robotPane != null && robotImageView != null) { // 计算移动偏移量 double deltaX = (newPosition.x() - oldPosition.x()) * TILE_SIZE; double deltaY = (newPosition.y() - oldPosition.y()) * TILE_SIZE; // 确保机器人Pane在过渡前处于顶层 robotPane.toFront(); // 创建移动过渡(目标为机器人Pane) TranslateTransition moveTransition = new TranslateTransition(Duration.seconds(2), robotPane); moveTransition.setToX(deltaX); moveTransition.setToY(deltaY); // 旋转逻辑保持不变 double newAngle = getRotationAngle(newOrientation); double currentAngle = robotImageView.getRotate(); double angleDelta = newAngle - currentAngle; if (angleDelta > 180) { angleDelta -= 360; } else if (angleDelta < -180) { angleDelta += 360; } RotateTransition rotateTransition = new RotateTransition(Duration.seconds(1), robotImageView); rotateTransition.setByAngle(angleDelta); SequentialTransition sequentialTransition = new SequentialTransition(moveTransition, rotateTransition); sequentialTransition.setOnFinished(event -> { robot.setOrientation(newOrientation); robot.setPosition(newPosition); // 更新机器人Pane在GridPane中的行列 GridPane.setColumnIndex(robotPane, newPosition.x()); GridPane.setRowIndex(robotPane, newPosition.y()); // 重置平移和旋转 robotPane.setTranslateX(0); robotPane.setTranslateY(0); robotImageView.setRotate(newAngle); }); sequentialTransition.play(); } else { robot.setOrientation(newOrientation); robot.setPosition(newPosition); drawRobots(); } }验证TilePane的viewOrder设置
保持drawTile方法中TilePane的viewOrder(100.0)不变,确保其渲染优先级低于机器人Pane的-100.0,这样机器人会始终被渲染在瓦片上方。
通过以上调整,机器人和瓦片处于同一层级,不再受TilePane添加顺序的影响,无论向哪个方向移动,都会稳定显示在瓦片上方。
内容的提问来源于stack exchange,提问作者Ryan Qchiqache

