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

MIDP 1.0 實現 TiledLayer 瓦片地圖捲動引擎

     編輯:J2ME

前言

MIDP 2.0 Game API 提供 TiledLayer / LayerManager 內建瓦片地圖捲動。MIDP 1.0 需自行實現:**瓦片資料結構、可視區域裁剪、座標轉換、高效繪製**。本文提供完整可運行的瓦片地圖引擎核心代碼 (含 30×40 地圖測試數據)。

核心架構

模組職責關鍵變數
MapData地圖邏輯數據byte[][] tiles 瓦片索引陣列、mapW/mapH 像素尺寸
TileSet圖塊圖集Image[] tileImages 從大圖切割得來
Camera可視窗口screenX/Y 左上角世界座標、screenW/H 螢幕尺寸
Renderer繪製邏輯計算可視瓦片範圍 → 逐塊 drawImage

關鍵算法:可視瓦片範圍計算


// 螢幕像素座標 → 瓦片索引範圍
int startCol = screenX / tileW;
int endCol   = startCol + screenCols + ((screenX % tileW) > 0 ? 1 : 0); // 含部分顯示邊緣
if (endCol > mapCols) endCol = mapCols;

int startRow = screenY / tileH;
int endRow   = startRow + screenRows + ((screenY % tileH) > 0 ? 1 : 0);
if (endRow > mapRows) endRow = mapRows;

screenCols = screenW / tileW + (screenW%tileW>0?1:0) 為螢幕可容納瓦片列數 (含不滿一塊)。

完整引擎代碼 (核心類 CanvasMain)

public class CanvasMain extends Canvas implements Runnable {
    // --- 鍵碼常數 (跨平台虛擬鍵) ---
    public static final byte VK_UP = -1, VK_DOWN = -2, VK_LEFT = -3, VK_RIGHT = -4;
    public static final byte VK_FIRE = -5, VK_SOFT1 = -6, VK_SOFT2 = -7;
    // Nokia/SE/Moto 具體 keyCode 映射於 keyPressed 中處理

    // --- 地圖參數 ---
    private static byte tileW = 16, tileH = 16;
    private static byte[][] map;          // 邏輯地圖 [row][col]
    private static Image[] tileImages;    // 圖塊圖集
    private static int mapRows, mapCols, mapW, mapH;

    // --- 攝影機參數 ---
    private static int screenX = 0, screenY = 0;
    private static int screenW = 154, screenH = 136; // 可視區像素
    private static int screenCols, screenRows;
    private static byte speed = 7; // 捲動步長

    // --- 遊戲迴圈 ---
    private static final int FRAME_MS = 80;
    private static boolean running = false;

    public CanvasMain() {
        setFullScreenMode(true);
        initMap();
        initScreen();
        System.gc();
    }

    private static void initMap() {
        // 內嵌 30x40 測試地圖數據 (0~77 為瓦片索引)
        map = new byte[][] {
            {68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,28,9,18,3,3,73,19,25,25},
            {68,68,68,68,38,38,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,70,68,28,18,3,3,13,14,3,3,19},
            // ... 省略中間 38 行 ...
            {25,25,25,25,25,29,29,29,29,77,68,68,68,68,68,69,69,69,72,29,36,29,29,25,29,15,72,69,68,68,68,68,68,69,69,72,29,29,29,35}
        };
        mapRows = map.length;
        mapCols = map[0].length;
        mapW = mapCols * tileW;
        mapH = mapRows * tileH;

        // 載入圖集:從 /village.png 切割 16x16 瓦片
        tileImages = splitImage("/village.png", tileW, tileH);
    }

    private static void initScreen() {
        screenCols = screenW / tileW + (screenW % tileW > 0 ? 1 : 0);
        screenRows = screenH / tileH + (screenH % tileH > 0 ? 1 : 0);
        if (screenCols > mapCols) screenCols = mapCols;
        if (screenRows > mapRows) screenRows = mapRows;
    }

    // --- 圖集切割工具 ---
    private static Image[] splitImage(String path, int cw, int ch) {
        try {
            Image src = Image.createImage(path);
            int cols = src.getWidth() / cw;
            int rows = src.getHeight() / ch;
            Image[] tiles = new Image[rows * cols];
            for (int i = 0; i < tiles.length; i++) {
                int sx = (i % cols) * cw;
                int sy = (i / cols) * ch;
                tiles[i] = Image.createImage(src, sx, sy, cw, ch, Sprite.TRANS_NONE);
            }
            return tiles;
        } catch (IOException e) { return null; }
    }

    // --- 繪製核心 ---
    protected void paint(Graphics g) {
        g.setColor(0xFFFFFF);
        g.fillRect(0, 0, getWidth(), getHeight());

        // 計算可視瓦片範圍
        int startCol = screenX / tileW;
        int endCol = startCol + screenCols + ((screenX % tileW) > 0 ? 1 : 0);
        if (endCol > mapCols) endCol = mapCols;

        int startRow = screenY / tileH;
        int endRow = startRow + screenRows + ((screenY % tileH) > 0 ? 1 : 0);
        if (endRow > mapRows) endRow = mapRows;

        // 繪製可視瓦片
        for (int col = startCol; col < endCol; col++) {
            for (int row = startRow; row < endRow; row++) {
                int tileIdx = map[row][col] & 0xFF; // byte 轉無符號
                if (tileIdx != 0xFF && tileIdx < tileImages.length) { // 0xFF 視為空
                    int dx = 32 + col * tileW - screenX; // 32,64 為螢幕邊距
                    int dy = 64 + row * tileH - screenY;
                    g.drawImage(tileImages[tileIdx], dx, dy, Graphics.LEFT | Graphics.TOP);
                    // 調試:繪製瓦片邊框
                    g.setColor(0x00FF00);
                    g.drawRect(dx, dy, tileW, tileH);
                    g.setColor(0x000000);
                }
            }
        }
        // 繪製螢幕邊框與調試資訊
        g.drawRect(32, 64, screenW, screenH);
        g.drawString("View: " + screenW + "x" + screenH, 5, 5, 0);
        g.drawString("Speed: " + speed, 120, 5, 0);
    }

    // --- 輸入處理 ---
    private static int keyCode = 0;
    protected void keyPressed(int k) { keyCode = k; handleInput(); repaint(); }
    protected void keyRepeated(int k) { handleInput(); }
    protected void keyReleased(int k) { keyCode = 0; }

    private void handleInput() {
        int action = getGameAction(keyCode); // 標準游戲動作
        // 兼容數字鍵
        if (keyCode >= 48 && keyCode <= 57) {
            switch (keyCode) {
                case 50: action = Canvas.UP; break;    // 2
                case 56: action = Canvas.DOWN; break;  // 8
                case 52: action = Canvas.LEFT; break;  // 4
                case 54: action = Canvas.RIGHT; break; // 6
            }
        }
        switch (action) {
            case Canvas.UP:
                screenY = Math.max(0, screenY - speed); break;
            case Canvas.DOWN:
                screenY = Math.min(mapH - screenH, screenY + speed); break;
            case Canvas.LEFT:
                screenX = Math.max(0, screenX - speed); break;
            case Canvas.RIGHT:
                screenX = Math.min(mapW - screenW, screenX + speed); break;
        }
    }

    // --- 遊戲迴圈 ---
    public void run() {
        running = true;
        long last = System.currentTimeMillis();
        while (running) {
            long now = System.currentTimeMillis();
            long elapsed = now - last;
            if (elapsed < FRAME_MS) {
                try { Thread.sleep(FRAME_MS - elapsed); } catch (InterruptedException ignored) {}
            }
            last = System.currentTimeMillis();
            flushGraphics();
        }
    }
    private void flushGraphics() { repaint(); serviceRepaints(); }
    public static void exit() { running = false; }
}

MIDlet 啟動類

public class MapDemo extends MIDlet implements Runnable {
    private Display display;
    private CanvasMain canvas;
    private Thread thread;

    public MapDemo() {
        display = Display.getDisplay(this);
        canvas = new CanvasMain();
        display.setCurrent(canvas);
    }
    protected void startApp() {
        thread = new Thread(this);
        thread.start();
    }
    protected void pauseApp() {}
    protected void destroyApp(boolean u) { canvas.exit(); notifyDestroyed(); }
    public void run() { canvas.run(); }
}

優化與擴展方向

  • 分層渲染:背景層/前景層/碰撞層分離 byte[][]
  • 動態載入:大地圖分塊檔案,隨攝影機移動按需 Image.createImage / 釋放
  • 異步預載:背景執行緒預載入鄰近區塊瓦片圖
  • 碰撞檢測:map[row][col] 索引對應碰撞屬性表 (可通行/阻擋/傷害/觸發)
  • NPC/實體系統:世界座標實體,渲染時扣除 screenX/Y 得螢幕座標

總結

MIDP 1.0 瓦片地圖核心在於:**邏輯陣列 + 圖集切割 + 攝影機裁剪 + 邊界修正**。上述引擎核心 < 200 行,支援鍵盤/數字鍵雙模輸入、固定幀率迴路、調試視覺化。掌握此模式,即可構建 RPG 世界地圖、ACT 關卡場景、SLG 戰略大圖等核心玩法基礎。

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