java.lang.Thread + Runnable因 Canvas 已繼承 Displayable,**不能再繼承 Thread**,故採用組合:
public class TetrisCanvas extends Canvas implements Runnable {
private Thread gameThread = null; // 組合線程對象
public TetrisCanvas() {
gameThread = new Thread(this); // this = Runnable 實例
gameThread.start(); // 啟動遊戲主循環
}
public void run() { // 遊戲主循環入口
while (gameThread != null) { // 以 thread 引用為生命標誌
// 1. 邏輯更新
updateGameState();
// 2. 觸發重繪
repaint();
// 3. 幀率控制
try { Thread.sleep(frameDelay); } catch (InterruptedException e) {}
}
}
// 停止遊戲(如 MIDlet.pauseApp 調用)
public void stopGame() {
gameThread = null; // run() 循環下一次判斷退出
}
}
private static final int BLOCK_SIZE = 7;
private static final int GRID_W = 12, GRID_H = 22;
private static final int OFF_X = 5, OFF_Y = 7;
// 19 種方塊定義:[x0,y0,x1,y1,x2,y2,x3,y3, color, 旋轉態數]
private static final int[][] BLOCKS = {
{1,0, 1,1, 1,2, 1,3, 0xFF0000, 2}, // I 型
{0,1, 1,1, 2,1, 3,1, 0xFF0000, 4}, // I 橫
{0,0, 0,1, 1,1, 1,2, 0x0000FF, 2}, // Z
// ... 其餘 16 種
};
private int[][] grid = new int[GRID_H][GRID_W]; // 只存顏色,0=空
private int curType, curX, curY; // 當前方塊
private int nextType; // 下一個
private int score, lines, level, speed;
private boolean feasible() {
for (int i = 0; i < 4; i++) {
int bx = curX + BLOCKS[curType][2*i+1];
int by = curY + BLOCKS[curType][2*i];
if (by < 0 || by >= GRID_H || bx < 0 || bx >= GRID_W) return false;
if (grid[by][bx] != 0) return false;
}
return true;
}
private void clearLines() {
// 先將當前方塊固化到 grid
for (int i = 0; i < 4; i++) {
int bx = curX + BLOCKS[curType][2*i+1];
int by = curY + BLOCKS[curType][2*i];
grid[by][bx] = BLOCKS[curType][8];
}
// 自底向上掃描
for (int y = GRID_H-2; y > 0; y--) {
boolean full = true;
for (int x = 1; x < GRID_W-1; x++) if (grid[y][x] == 0) { full = false; break; }
if (full) {
// 下落
for (int yy = y; yy > 0; yy--)
System.arraycopy(grid[yy-1], 0, grid[yy], 0, GRID_W);
y++; // 重新檢查當前行
lines++; score += 200;
if (score % 2000 < 200) speed++; // 每 2000 分加速
}
}
}
protected void keyPressed(int keyCode) {
int action = getGameAction(keyCode);
switch (action) {
case LEFT: curX--; if (!feasible()) curX++; break;
case RIGHT: curX++; if (!feasible()) curX--; break;
case DOWN: curY++; if (!feasible()) curY--; speed = 9; break; // 加速下落
case FIRE: // 旋轉
int old = curType;
curType = (curType + 1) % BLOCKS.length; // 簡化:實際需按旋轉態數循環
if (!feasible()) curType = old;
break;
}
repaint(); // 即時反饋
}
protected void paint(Graphics g) {
// 背景(預載入 Image,不在 paint 中 createImage!)
g.drawImage(bgImage, 0, 0, Graphics.TOP | Graphics.LEFT);
// 疊落方塊
for (int y = 0; y < GRID_H-1; y++)
for (int x = 1; x < GRID_W-1; x++)
if (grid[y][x] != 0)
draw3DBlock(g, grid[y][x], OFF_X + x*BLOCK_SIZE, OFF_Y + y*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
// 當前方塊(未固化時)
if (!landed) {
for (int i = 0; i < 4; i++) {
int bx = curX + BLOCKS[curType][2*i+1];
int by = curY + BLOCKS[curType][2*i];
draw3DBlock(g, BLOCKS[curType][8], OFF_X + bx*BLOCK_SIZE, OFF_Y + by*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
// 側邊信息:下一個、分數、等級…
drawSidePanel(g);
}
init() 中 Image.createImage(),**絕不在 paint() 中**int[]、StringBuffer,減少 GCrepaint(x,y,w,h) 或 setClip() 限制繪製區域| 錯誤 | 後果 | 修正 |
|---|---|---|
| 在 paint() 中 createImage | 嚴重卡頓、OOM | 移至 init() 預載入 |
| UI 線程調用 Thread.sleep | 界面凍結、來電無響應 | 所有 sleep 僅在遊戲線程 |
| 未停止線程就 MIDlet 銷毀 | VM 警告、資源洩漏 | pauseApp/destroyApp 中調用 stopGame() |
| keyPressed 邏輯過重 | 輸入延遲、丟幀 | 僅修改狀態變量,邏輯放線程 |
J2ME 線程編程核心:**Canvas 實現 Runnable + 組合 Thread + 遊戲線程獨享邏輯/睡眠 + UI 線程純渲染/輸入轉發**。俄羅斯方塊展示了完整的「狀態機 + 碰撞檢測 + 消行 + 即時輸入 + 雙緩衝」最小閉環,掌握此模板可快速構建各類即時遊戲。