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

求助:如何用FXGL将类Splix.io固定网格游戏改为无边界模式

实现FXGL版Splix.io的无边界移动效果

你的思路(放大窗口+移动棋盘)其实并不高效,Splix.io这类游戏的无边界逻辑核心是让玩家视觉上始终处于屏幕中心,反向移动棋盘并动态维护单元格,而非真的放大窗口。下面是具体实现步骤:

核心思路调整

放弃固定尺寸的棋盘数组,改用动态生成/销毁单元格的方式:

  • 玩家实体固定在屏幕中心,不做位置移动;
  • 记录玩家的「逻辑位置」(标记真实世界坐标,以整数格为单位);
  • 根据玩家输入,让所有棋盘单元格反向偏移,同时更新玩家的逻辑位置;
  • 当玩家逻辑位置靠近已生成单元格的边缘时,自动生成新单元格;
  • 销毁超出视野范围的单元格,避免内存溢出。

具体代码实现

1. 重构棋盘存储结构

把固定的Entity[40][40]换成哈希表,用自定义逻辑坐标类作为键:

private Map<LogicPos, Entity> cells = new HashMap<>();
private int playerLogicX = 0, playerLogicY = 0; // 玩家初始逻辑位置在世界原点
private static final int BLOCK_SIZE = 20;
private static final int BUFFER_RANGE = 10; // 屏幕外缓冲的单元格数量
private static final int CLEAN_RANGE = 20; // 超出该范围的单元格会被销毁

自定义LogicPos类(作为哈希表键,需实现equals和hashCode):

private static class LogicPos {
    int x, y;

    public LogicPos(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        LogicPos logicPos = (LogicPos) o;
        return x == logicPos.x && y == logicPos.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

2. 初始化游戏

不再生成40x40的固定棋盘,只生成玩家周围的单元格:

@Override
protected void initGame() {
    // 玩家固定在屏幕中心(400,400)
    player = spawn("player", 400, 400);
    
    // 生成初始缓冲范围内的单元格
    for (int y = -BUFFER_RANGE; y <= BUFFER_RANGE; y++) {
        for (int x = -BUFFER_RANGE; x <= BUFFER_RANGE; x++) {
            LogicPos pos = new LogicPos(x, y);
            // 单元格屏幕坐标 = (逻辑坐标 - 玩家逻辑坐标) * 块大小 + 屏幕中心
            double screenX = (x - playerLogicX) * BLOCK_SIZE + 400;
            double screenY = (y - playerLogicY) * BLOCK_SIZE + 400;
            Entity cell = spawn("cell", screenX, screenY);
            cells.put(pos, cell);
        }
    }
}

3. 处理玩家输入

修改输入逻辑,不再移动玩家,而是反向移动所有单元格并更新玩家逻辑位置:

@Override
protected void onKeyDown(KeyEvent event) {
    KeyCode key = event.getCode();
    if (key == KeyCode.RIGHT) {
        playerLogicX += 1;
        // 所有单元格左移,模拟玩家右移的视觉效果
        cells.values().forEach(cell -> cell.translateX(-BLOCK_SIZE));
        checkAndUpdateCells();
    } else if (key == KeyCode.LEFT) {
        playerLogicX -= 1;
        cells.values().forEach(cell -> cell.translateX(BLOCK_SIZE));
        checkAndUpdateCells();
    } else if (key == KeyCode.DOWN) {
        playerLogicY += 1;
        cells.values().forEach(cell -> cell.translateY(-BLOCK_SIZE));
        checkAndUpdateCells();
    } else if (key == KeyCode.UP) {
        playerLogicY -= 1;
        cells.values().forEach(cell -> cell.translateY(BLOCK_SIZE));
        checkAndUpdateCells();
    }
}

4. 动态生成/销毁单元格

实现checkAndUpdateCells方法,自动维护棋盘范围:

private void checkAndUpdateCells() {
    // 获取当前已生成单元格的逻辑坐标边界
    int minX = cells.keySet().stream().mapToInt(p -> p.x).min().orElse(playerLogicX);
    int maxX = cells.keySet().stream().mapToInt(p -> p.x).max().orElse(playerLogicX);
    int minY = cells.keySet().stream().mapToInt(p -> p.y).min().orElse(playerLogicY);
    int maxY = cells.keySet().stream().mapToInt(p -> p.y).max().orElse(playerLogicY);

    // 生成右侧新列
    if (playerLogicX >= maxX - BUFFER_RANGE) {
        int newX = maxX + 1;
        for (int y = minY - BUFFER_RANGE; y <= maxY + BUFFER_RANGE; y++) {
            LogicPos newPos = new LogicPos(newX, y);
            if (!cells.containsKey(newPos)) {
                double screenX = (newX - playerLogicX) * BLOCK_SIZE + 400;
                double screenY = (y - playerLogicY) * BLOCK_SIZE + 400;
                Entity cell = spawn("cell", screenX, screenY);
                cells.put(newPos, cell);
            }
        }
    }

    // 生成左侧新列
    if (playerLogicX <= minX + BUFFER_RANGE) {
        int newX = minX - 1;
        for (int y = minY - BUFFER_RANGE; y <= maxY + BUFFER_RANGE; y++) {
            LogicPos newPos = new LogicPos(newX, y);
            if (!cells.containsKey(newPos)) {
                double screenX = (newX - playerLogicX) * BLOCK_SIZE + 400;
                double screenY = (y - playerLogicY) * BLOCK_SIZE + 400;
                Entity cell = spawn("cell", screenX, screenY);
                cells.put(newPos, cell);
            }
        }
    }

    // 生成下侧新行
    if (playerLogicY >= maxY - BUFFER_RANGE) {
        int newY = maxY + 1;
        for (int x = minX - BUFFER_RANGE; x <= maxX + BUFFER_RANGE; x++) {
            LogicPos newPos = new LogicPos(x, newY);
            if (!cells.containsKey(newPos)) {
                double screenX = (x - playerLogicX) * BLOCK_SIZE + 400;
                double screenY = (newY - playerLogicY) * BLOCK_SIZE + 400;
                Entity cell = spawn("cell", screenX, screenY);
                cells.put(newPos, cell);
            }
        }
    }

    // 生成上侧新行
    if (playerLogicY <= minY + BUFFER_RANGE) {
        int newY = minY - 1;
        for (int x = minX - BUFFER_RANGE; x <= maxX + BUFFER_RANGE; x++) {
            LogicPos newPos = new LogicPos(x, newY);
            if (!cells.containsKey(newPos)) {
                double screenX = (x - playerLogicX) * BLOCK_SIZE + 400;
                double screenY = (newY - playerLogicY) * BLOCK_SIZE + 400;
                Entity cell = spawn("cell", screenX, screenY);
                cells.put(newPos, cell);
            }
        }
    }

    // 销毁超出清理范围的单元格
    Iterator<Map.Entry<LogicPos, Entity>> iterator = cells.entrySet().iterator();
    while (iterator.hasNext()) {
        Map.Entry<LogicPos, Entity> entry = iterator.next();
        LogicPos pos = entry.getKey();
        if (Math.abs(pos.x - playerLogicX) > CLEAN_RANGE || Math.abs(pos.y - playerLogicY) > CLEAN_RANGE) {
            entry.getValue().removeFromWorld();
            iterator.remove();
        }
    }
}

关键注意事项

  • 玩家的核心逻辑(比如占领单元格、碰撞检测)都要基于逻辑位置,而非屏幕坐标;
  • 可根据游戏体验调整BUFFER_RANGE和CLEAN_RANGE的值,缓冲范围越大,玩家移动时出现空白的概率越低;
  • 无需修改游戏窗口尺寸,始终保持800x800即可,视觉上的无限移动完全靠棋盘的动态调整实现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 18:59:57