執行緒是 J2ME 遊戲開發核心。本文以完整的俄羅斯方塊為例,從專案建立、Canvas 繼承 Runnable、遊戲迴路、按鍵處理到繪圖優化,完整展示 J2ME 多執行緒實戰模式。
java.lang.Runnable 與 Thread,但無執行緒群組、無 daemon 執行緒static final 常數定義座標、顏色、尺寸,利於移植與維護
TetrisMidlet (MIDlet) → 啟動顯示 TetrisCanvas
TetrisCanvas (Canvas + Runnable) → 遊戲主邏輯、繪圖、輸入處理
public class TetrisMidlet extends MIDlet {
public void startApp() {
Display.getDisplay(this).setCurrent(new TetrisCanvas());
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
}
public class TetrisCanvas extends Canvas implements Runnable {
private Thread gameThread = null;
public TetrisCanvas() {
initGame();
gameThread = new Thread(this);
gameThread.start(); // 啟動遊戲迴路
}
public void run() {
while (gameThread != null) {
// 遊戲邏輯更新
// repaint() 觸發重繪
// Thread.sleep() 控制幀率
}
gameThread = null; // 結束標記
}
// ...
}
為何不 extends Thread? Java 單一繼承限制,Canvas 已繼承自 Displayable,故採用 implements Runnable + 組合 Thread。
// 方塊定義:[x1,y1,x2,y2,x3,y3,x4,y4, color, 旋轉變體數]
private static final int[][] BLOCK_INFO = {
{1,0, 1,1, 1,2, 1,3, 0xFF0000, 2}, // I 型
{0,1, 1,1, 2,1, 3,1, 0xFF0000, 4},
{0,0, 0,1, 1,1, 1,2, 0x0000FF, 2}, // Z 型
// ... 共 19 種變體
};
// 遊戲區網格:12寬 x 22高,儲存顏色值(0=空)
private int[][] grid = new int[22][12];
public void run() {
while (gameThread != null) {
if (!isBlockLanded) {
currentY++; // 方塊下落
if (!isPositionValid()) {
isBlockLanded = true; currentY--;
clearLines(); // 檢查消行
try { Thread.sleep(400); } catch (Exception e) {}
} else {
repaint();
try { Thread.sleep(950 - 100 * speed); } catch (Exception e) {}
}
} else {
// 方塊落地處理:得分、產生新方塊、檢查遊戲結束
score += 50;
if ((score % 2000) < 50) speed++;
isBlockLanded = false;
repaint();
currentBlock = nextBlock;
nextBlock = random.nextInt(19);
currentX = 4; currentY = 0;
if (!isPositionValid()) initGame(); // 遊戲結束重置
}
}
}
protected void keyPressed(int keyCode) {
switch (getGameAction(keyCode)) {
case Canvas.LEFT: currentX--; if (!valid()) currentX++; break;
case Canvas.RIGHT: currentX++; if (!valid()) currentX--; break;
case Canvas.DOWN: currentY++; if (!valid()) currentY--; speed=9; break; // 快速下落
case Canvas.FIRE: rotateBlock(); break; // 旋轉邏輯
case Canvas.UP: /* 保留 */ break;
}
repaint();
}
private void draw3DBlock(Graphics g, int color, int x, int y, int w, int h) {
int oldColor = g.getColor();
g.setColor(0xFFFFFF); g.drawRect(x, y, w, h); // 高光邊
g.setColor(color); g.fillRect(x+1, y+1, w-2, h-2); // 主體
g.setColor(0x000000);
g.drawLine(x+w-1, y, x+w-1, y+h-1); // 右陰影
g.drawLine(x, y+h-1, x+w-1, y+h-1); // 底陰影
g.setColor(oldColor);
}
protected void paint(Graphics g) {
// 1. 繪製背景圖(若有)
try { g.drawImage(Image.createImage("/back.png"), 0, 0, Graphics.TOP|Graphics.LEFT); }
catch (Exception e) { g.setColor(0x89A5D1); g.fillRect(0,0,getWidth(),getHeight()); }
// 2. 繪製下一個方塊預覽、分數、速度、行數 UI
// 3. 繪製遊戲區已落地方塊
for (int r=0; r<21; r++)
for (int c=1; c<11; c++)
if (grid[r][c] != 0)
draw3DBlock(g, grid[r][c], offsetX+c*7, offsetY+r*7, 7, 7);
// 4. 繪製正在下落的方塊
if (!isBlockLanded) { /* 依 BLOCK_INFO 繪製 4 個小方塊 */ }
}
這個俄羅斯方塊雖簡單,卻涵蓋了 J2ME 遊戲開發 80% 的核心技術點,是極佳的入門實戰專案。