You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
                }
            }
        }
    }
}
关键修改说明
  1. 移除了固定Canvas尺寸的逻辑,改为绑定父容器宽高,始终和窗口可视区域一致,不会出现Canvas大于窗口的情况
  2. 偏移量计算逻辑保证玩家坐标对应的绘制点永远落在Canvas中心,玩家移动时实际是地图在偏移,玩家视觉上固定在中心
  3. 边界修正逻辑避免了玩家走到地图边缘时出现空白区域,到边界后会停止地图偏移,玩家正常向边界移动
  4. 只渲染可视区域瓦片,大幅降低大地图下的绘制性能消耗

内容的提问来源于stack exchange,提问作者Verano137

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.12 21:21:01