2D游戏背景滚动教程(JavaSwing)

想象你正在制作一个2D游戏,角色可以在一个大世界里走动,但你的屏幕只能显示世界的一部分。你需要让背景随着角色移动,但当角色走到世界边缘时,背景不能再滚动,而且在屏幕中间区域走动时,背景也不该移动。

核心思路

  1. 世界比屏幕大:背景图片(世界)比游戏窗口大得多

  2. 视口概念:屏幕就像一扇窗户,只显示世界的一部分

  3. 角色带动视口:角色靠近屏幕边缘时,背景才开始移动

  4. 边界限制:背景不能滚出世界边界

图片示意

1.一开始

2.“死区”内移动时

3.背景中间区域,可显示屏幕的边缘

4.背景的边缘(实操中随着不同游戏有一定不同处理,如反向出现,死亡判定,掉落触发动画,根本不允许走到此位置等等)

代码片段 

1. 游戏基本设置与视口初始化
// 游戏窗口尺寸
private static final int SCREEN_WIDTH = 800;
private static final int SCREEN_HEIGHT = 600;

// 背景尺寸(比屏幕大)
private final int BG_WIDTH = 1600;  
private final int BG_HEIGHT = 1200; 

// 角色世界坐标(从世界中心开始)
private int playerX = BG_WIDTH / 2;  
private int playerY = BG_HEIGHT / 2; 

// 视口位置(决定显示世界的哪一部分)
private int viewportX;  
private int viewportY;  

// 初始化视口位置(使角色在屏幕中央开始)
private void centerViewportOnPlayer() {
    viewportX = playerX - SCREEN_WIDTH / 2; // 计算视口X位置
    viewportY = playerY - SCREEN_HEIGHT / 2; // 计算视口Y位置
    
    // 确保视口不超出背景边界(核心保护机制)
    viewportX = Math.max(0, Math.min(viewportX, BG_WIDTH - SCREEN_WIDTH));
    viewportY = Math.max(0, Math.min(viewportY, BG_HEIGHT - SCREEN_HEIGHT));
}
2. 滚动触发区域设置
// 定义滚动触发区域(屏幕边缘区域)
private final int SCROLL_ZONE_LEFT = 150;   // 左侧触发区宽度
private final int SCROLL_ZONE_RIGHT = 150;  // 右侧触发区宽度
private final int SCROLL_ZONE_TOP = 100;    // 顶部触发区高度
private final int SCROLL_ZONE_BOTTOM = 100; // 底部触发区高度

// 滚动速度系数(控制滚动平滑度)
private final float SCROLL_SPEED = 0.5f; 

// 在绘制方法中可视化滚动区域(调试用)
g2d.setColor(new Color(255, 68, 0, 50)); // 半透明橙色
g2d.fillRect(0, 0, SCROLL_ZONE_LEFT, SCREEN_HEIGHT); // 左侧区域
g2d.fillRect(SCREEN_WIDTH - SCROLL_ZONE_RIGHT, 0, SCROLL_ZONE_RIGHT, SCREEN_HEIGHT); // 右侧区域
// ... 其他区域类似
3. 键盘控制角色移动
// 键盘事件监听器
addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
        int step = 5; // 每次按键移动距离
        
        // WASD控制角色在世界中的位置
        switch (e.getKeyCode()) {
            case KeyEvent.VK_W -> playerY -= step; // 上移
            case KeyEvent.VK_S -> playerY += step; // 下移
            case KeyEvent.VK_A -> playerX -= step; // 左移
            case KeyEvent.VK_D -> playerX += step; // 右移
        }
        
        // 角色移动后更新视口
        updateViewport();
        repaint(); // 请求重绘画面
    }
});
4. 视口更新核心逻辑(最关键部分)
private void updateViewport() {
    // 计算角色在屏幕上的位置(世界坐标 → 屏幕坐标)
    int playerScreenX = playerX - viewportX;
    int playerScreenY = playerY - viewportY;
    
    int scrollDX = 0; // 水平滚动量
    int scrollDY = 0; // 垂直滚动量
    
    // 水平滚动检测
    if (playerScreenX < SCROLL_ZONE_LEFT) {
        // 角色进入左侧区域:背景向右滚动(视口左移)
        scrollDX = (int)((playerScreenX - SCROLL_ZONE_LEFT) * SCROLL_SPEED);
    } else if (playerScreenX > SCREEN_WIDTH - SCROLL_ZONE_RIGHT) {
        // 角色进入右侧区域:背景向左滚动(视口右移)
        scrollDX = (int)((playerScreenX - (SCREEN_WIDTH - SCROLL_ZONE_RIGHT)) * SCROLL_SPEED);
    }
    
    // 垂直滚动检测(原理同上)
    if (playerScreenY < SCROLL_ZONE_TOP) {
        scrollDY = (int)((playerScreenY - SCROLL_ZONE_TOP) * SCROLL_SPEED);
    } else if (playerScreenY > SCREEN_HEIGHT - SCROLL_ZONE_BOTTOM) {
        scrollDY = (int)((playerScreenY - (SCREEN_HEIGHT - SCROLL_ZONE_BOTTOM)) * SCROLL_SPEED);
    }
    
    // 应用滚动量
    viewportX += scrollDX;
    viewportY += scrollDY;
    
    // 边界约束(防止视口移出背景)
    viewportX = Math.max(0, Math.min(viewportX, BG_WIDTH - SCREEN_WIDTH));
    viewportY = Math.max(0, Math.min(viewportY, BG_HEIGHT - SCREEN_HEIGHT));
}
5. 背景绘制优化
// 只绘制视口可见部分(性能关键!)
g2d.drawImage(background,
        0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, // 在屏幕上的绘制区域
        viewportX, viewportY, // 背景源图像起点(世界坐标)
        viewportX + SCREEN_WIDTH, // 背景源图像终点X
        viewportY + SCREEN_HEIGHT, // 背景源图像终点Y
        null);
6. 角色绘制与坐标转换
// 将角色世界坐标转换为屏幕坐标
int playerScreenX = playerX - viewportX;
int playerScreenY = playerY - viewportY;

// 绘制角色(红色方块)
g2d.setColor(PLAYER_COLOR);
g2d.fillRect(
        playerScreenX - PLAYER_SIZE/2, // 屏幕X位置(居中)
        playerScreenY - PLAYER_SIZE/2, // 屏幕Y位置(居中)
        PLAYER_SIZE, PLAYER_SIZE);

// 调试信息(显示坐标)
g2d.drawString("角色世界坐标: (" + playerX + ", " + playerY + ")", 10, 20);
g2d.drawString("视口位置: (" + viewportX + ", " + viewportY + ")", 10, 40);
7. 游戏启动入口
public static void main(String[] args) {
    // 使用SwingUtilities确保线程安全
    SwingUtilities.invokeLater(() -> {
        JFrame frame = new JFrame("滚动背景游戏");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new ScrollBackgroundGame()); // 添加游戏面板
        frame.pack(); // 自动调整窗口大小
        frame.setLocationRelativeTo(null); // 屏幕居中
        frame.setVisible(true); // 显示窗口
    });
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值