首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

J2ME 遊戲屏幕滾動實現:LayerManager View Window 視口控制技術詳解

     編輯:J2ME

核心概念:View Window (視口窗口)

在 MIDP 2.0 LayerManager 架構中,**View Window** 定義了「世界座標系」中哪一矩形區域會被渲染到屏幕上。通過移動這個窗口,即可實現大場景在小屏幕上的平滑滾動,無需移動每個 Sprite/Layer 的絕對座標。

滾動原理圖解

世界座標系 (大場景,如 500x500 像素)
+---------------------------+
|                           |
|   [View Window 140x140]   |  ← 當前可見區域
|     (scnX, scnY)          |
|                           |
+---------------------------+

屏幕座標系 (手機屏幕,如 176x208)
+-----------+
|  View     |  ← LayerManager.paint(g, screenX, screenY)
|  Window   |     將視口內容繪製到屏幕 (screenX, screenY)
|  Content  |
+-----------+
  • 向右滾動:scnX++ → 視口在世界中右移 → 屏幕顯示內容左移
  • 向左滾動:scnX-- → 視口左移 → 內容右移
  • 垂直同理:調整 scnY

完整可運行示例:背景圖無限滾動

1. 遊戲畫布類


public class ScrollingGameCanvas extends GameCanvas implements Runnable {
    private boolean running = false;
    private int frameDelay = 20;
    private int scrW, scrH;
    private int viewW = 140, viewH = 140; // 視口大小
    private int scnX = 55, scnY = 20;     // 視口世界座標左上角
    
    private Image bgImage;
    private Sprite bgSprite;
    private LayerManager layerManager;
    
    public ScrollingGameCanvas() throws IOException {
        super(true);
        scrW = getWidth();
        scrH = getHeight();
        
        // 載入背景圖 (需大於視口尺寸才能滾動)
        bgImage = Image.createImage("/background.png"); // 如 300x200
        bgSprite = new Sprite(bgImage);
        
        layerManager = new LayerManager();
        layerManager.append(bgSprite); // 背景放最底層
    }
    
    public void start() {
        running = true;
        new Thread(this).start();
    }
    public void stop() { running = false; }
    
    public void run() {
        Graphics g = getGraphics();
        while (running) {
            handleInput();
            renderFrame(g);
            flushGraphics();
            try { Thread.sleep(frameDelay); } catch (InterruptedException ignored) {}
        }
    }
    
    private void handleInput() {
        int keys = getKeyStates();
        // 左移:視口 X 減小,邊界檢查
        if ((keys & LEFT_PRESSED) != 0) {
            if (scnX > 0) scnX--;
        }
        // 右移:視口 X 增加,不超過背景寬度 - 視口寬度
        if ((keys & RIGHT_PRESSED) != 0) {
            if (scnX + viewW < bgImage.getWidth()) scnX++;
        }
        // 上/下 同理
        if ((keys & UP_PRESSED) != 0) {
            if (scnY > 0) scnY--;
        }
        if ((keys & DOWN_PRESSED) != 0) {
            if (scnY + viewH < bgImage.getHeight()) scnY++;
        }
    }
    
    private void renderFrame(Graphics g) {
        // 清屏
        g.setColor(0xFFFFFF);
        g.fillRect(0, 0, scrW, scrH);
        
        // 關鍵:設置視口世界座標與尺寸
        layerManager.setViewWindow(scnX, scnY, viewW, viewH);
        // 繪製到屏幕 (20,20) 偏移處,留邊距給 UI
        layerManager.paint(g, 20, 20);
    }
}

2. MIDlet 啟動類


public class ScrollingMIDlet extends MIDlet {
    private Display display;
    
    public void startApp() {
        if (display == null) {
            display = Display.getDisplay(this);
            try {
                ScrollingGameCanvas canvas = new ScrollingGameCanvas();
                canvas.start();
                display.setCurrent(canvas);
            } catch (IOException e) {
                e.printStackTrace();
                // 顯示錯誤 Alert
            }
        }
    }
    public void pauseApp() {}
    public void destroyApp(boolean u) { notifyDestroyed(); }
}

關鍵 API 深度解析

LayerManager.setViewWindow(int x, int y, int width, int height)

  • x, y:視口在**世界座標系**中的左上角坐標 (即 scnX, scnY)
  • width, height:視口像素尺寸,通常等於或小於屏幕尺寸
  • 效果:後續 paint() 僅繪製各 Layer 與該矩形相交的部分

LayerManager.paint(Graphics g, int x, int y)

  • 將視口內容繪製到 g 的 (x, y) 處 (屏幕座標)
  • 自動處理 Layer Z-Order、Sprite 動畫幀、裁剪

進階滾動技巧

1. 平滑插值滾動 (跟隨主角)


// 目標視口中心 = 主角世界座標
int targetX = player.getX() - viewW / 2;
int targetY = player.getY() - viewH / 2;

// 邊界鉗制
targetX = Math.max(0, Math.min(targetX, worldWidth - viewW));
targetY = Math.max(0, Math.min(targetY, worldHeight - viewH));

// 線性插值 (lerp) 實現平滑跟隨
scnX += (targetX - scnX) * 0.2f; // 0.2 = 跟隨係數
scnY += (targetY - scnY) * 0.2f;

2. 視差滾動 (多背景層)


// 遠景層 (移動慢)
farLayer.setPosition(scnX * 0.2f, scnY * 0.2f);
// 中景層
midLayer.setPosition(scnX * 0.5f, scnY * 0.5f);
// 近景層 (跟隨視口)
nearLayer.setPosition(scnX, scnY);

3. 瓦片地圖優化

  • 大場景勿用單張大圖 (內存/載入慢),改用 TiledLayer + 瓦片集
  • 只繪製視口覆蓋的瓦片索引,動態更新可見區塊
  • 配合 TiledLayer.setAnimatedTile() 實現水面、火焰等動畫瓦片

邊界條件處理清單

  1. 視口不超世界邊界:scnX ∈ [0, worldW - viewW]
  2. 視口尺寸 ≤ 屏幕尺寸:否則 paint() 會裁剪或異常
  3. 背景 Sprite 尺寸 ≥ 世界尺寸:否則邊界會出現黑邊
  4. 坐標系一致性:所有 Layer/Sprite 位置均為世界座標,視口移動不改變它們

性能優化建議

  • 避免幀間 new Object:scnX, scnY 用基本類型,LayerManager 復用
  • 背景圖尺寸適中:過大圖片 (如 1000x1000) 解碼/繪製耗時,分塊或瓦片化
  • 靜態背景預渲染:不變遠景可預繪製到一張 Image,幀間直接 drawImage 而非 LayerManager
  • 剔除不見 Layer:layerManager.remove() 遠離視口的實體,動態增減

總結

LayerManager 的 View Window 機制將「世界座標」與「屏幕座標」解耦:遊戲邏輯在無限大的世界座標系中運算,渲染層只需移動一個矩形窗口即可實現任意方向、任意速度的平滑滾動。掌握 setViewWindow(scnX, scnY, w, h) + 邊界鉗制 + 插值跟隨,即可構建從簡單背景滾動到複雜視差多層、瓦片大世界的完整滾動系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved