JavaFX中如何让Canvas上绘制的移动角色始终保持在窗口中心?
实现思路
核心逻辑是固定玩家在视口中心,移动地图偏移量而非移动玩家在Canvas上的绘制坐标,不需要做大尺寸Canvas,让Canvas尺寸始终匹配窗口可视区域即可,具体修改点如下:
- 新增
offsetX、offsetY全局变量存储地图绘制偏移值 - 绑定Canvas宽高和BorderPane中心区域的可视宽高,窗口大小变化时自动调整Canvas尺寸
- 每次移动玩家后重新计算偏移量,保证玩家绘制位置在Canvas中心
- 绘制地图时叠加偏移量,仅渲染可视区域内的瓦片降低性能消耗
- 增加边界判断,避免地图边缘移动时出现空白区域
完整修改代码
import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; import javafx.scene.layout.BorderPane; import javafx.scene.paint.Color; import javafx.stage.Stage; public class Main extends Application { String[][] map = new String[40][20]; int[] playerCoords = {3,10}; // 瓦片尺寸常量 private static final int TILE_SIZE = 32; // 地图绘制偏移量 private double offsetX = 0; private double offsetY = 0; // 自行替换为你的瓦片资源路径 Image tileset = new Image("/colored.png", 815 * 2, 373 * 2, true, false); Canvas canvas = new Canvas(); GraphicsContext context = canvas.getGraphicsContext2D(); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage){ BorderPane borderPane = new BorderPane(); borderPane.setCenter(canvas); // 绑定Canvas宽高和BorderPane的中心区域尺寸,窗口缩放自动适配 canvas.widthProperty().bind(borderPane.widthProperty()); canvas.heightProperty().bind(borderPane.heightProperty()); // 窗口大小变化时重绘 borderPane.widthProperty().addListener((obs, oldVal, newVal) -> refresh()); borderPane.heightProperty().addListener((obs, oldVal, newVal) -> refresh()); map[3][10] = "p"; refresh(); Scene scene = new Scene(borderPane, 800, 600); scene.setOnKeyPressed(keyEvent -> { switch (keyEvent.getCode()) { case UP: this.movePlayer(true); refresh(); break; case DOWN: this.movePlayer(false); refresh(); break; case LEFT: this.movePlayerHorizontal(-1); refresh(); break; case RIGHT: this.movePlayerHorizontal(1); refresh(); break; } }); stage.setScene(scene); stage.setResizable(true); stage.show(); } void movePlayer(boolean up){ int direction = up ? -1 : 1; int newPosRow = playerCoords[0] + direction; if ( newPosRow >= 0 && newPosRow < map.length) { map[playerCoords[0]][playerCoords[1]] = null; playerCoords[0] = newPosRow; map[playerCoords[0]][playerCoords[1]] = "p"; } } // 新增左右移动逻辑,可选 void movePlayerHorizontal(int direction){ int newPosCol = playerCoords[1] + direction; if ( newPosCol >= 0 && newPosCol < map[0].length) { map[playerCoords[0]][playerCoords[1]] = null; playerCoords[1] = newPosCol; map[playerCoords[0]][playerCoords[1]] = "p"; } } void refresh(){ double canvasWidth = canvas.getWidth(); double canvasHeight = canvas.getHeight(); context.setFill(Color.BLACK); context.fillRect(0, 0, canvasWidth, canvasHeight); // 计算偏移量,让玩家处于Canvas中心 offsetX = (canvasWidth / 2) - (playerCoords[1] * TILE_SIZE + TILE_SIZE / 2.0); offsetY = (canvasHeight / 2) - (playerCoords[0] * TILE_SIZE + TILE_SIZE / 2.0); // 边界修正,避免地图边缘露出空白 offsetX = Math.min(offsetX, 0); offsetX = Math.max(offsetX, canvasWidth - map[0].length * TILE_SIZE); offsetY = Math.min(offsetY, 0); offsetY = Math.max(offsetY, canvasHeight - map.length * TILE_SIZE); // 只绘制可视区域内的瓦片,优化性能 int startCol = Math.max(0, (int) (-offsetX / TILE_SIZE) - 1); int endCol = Math.min(map[0].length, (int) ((-offsetX + canvasWidth) / TILE_SIZE) + 1); int startRow = Math.max(0, (int) (-offsetY / TILE_SIZE) - 1); int endRow = Math.min(map.length, (int) ((-offsetY + canvasHeight) / TILE_SIZE) + 1); for (int i = startRow; i < endRow; i++ ){ for (int j = startCol; j < endCol; j++){ double drawX = j * TILE_SIZE + offsetX; double drawY = i * TILE_SIZE + offsetY; if (map[i][j] != null && map[i][j].equals("p")){ //player Tile 28, 0 context.drawImage(tileset, 952,0, 32,32, drawX, drawY, 32,32); } else{ context.drawImage(tileset, 0,0, 32,32, drawX, drawY, 32,32); } } } } }
关键修改说明
- 移除了固定Canvas尺寸的逻辑,改为绑定父容器宽高,始终和窗口可视区域一致,不会出现Canvas大于窗口的情况
- 偏移量计算逻辑保证玩家坐标对应的绘制点永远落在Canvas中心,玩家移动时实际是地图在偏移,玩家视觉上固定在中心
- 边界修正逻辑避免了玩家走到地图边缘时出现空白区域,到边界后会停止地图偏移,玩家正常向边界移动
- 只渲染可视区域瓦片,大幅降低大地图下的绘制性能消耗
内容的提问来源于stack exchange,提问作者Verano137
相关产品推荐
相关产品推荐

