雙緩衝將繪圖操作導向記憶體中的離屏圖片,完成整幀繪製後一次性複製到顯示表面,避免使用者看到中間繪製狀態。對於無獨立顯存的功能機,這是消除閃爍的唯一有效手段。
package org.wuhua.game;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
public abstract class GameCanvas extends Canvas {
private Image bufferImage; // 離屏緩衝
private int width, height; // 螢幕尺寸
private int clipX, clipY, clipWidth, clipHeight; // 區域更新
private boolean useClip = false;
protected GameCanvas() {
width = getWidth();
height = getHeight();
bufferImage = Image.createImage(width, height);
}
// 系統呼叫:將緩衝內容複製到螢幕
protected void paint(Graphics g) {
if (useClip) {
g.clipRect(clipX, clipY, clipWidth, clipHeight);
useClip = false;
}
g.drawImage(bufferImage, 0, 0, Graphics.TOP | Graphics.LEFT);
}
// 完整幀更新
public void flushGraphics() {
repaint();
serviceRepaints(); // 強制立即繪製 (MIDP 關鍵)
}
// 區域更新 (局部刷新)
public void flushGraphics(int x, int y, int w, int h) {
useClip = true;
clipX = x; clipY = y; clipWidth = w; clipHeight = h;
repaint();
serviceRepaints();
}
// 遊戲邏輯取得離屏 Graphics 進行繪製
protected Graphics getGraphics() {
return bufferImage.getGraphics();
}
// 全螢幕模式切換時重建緩衝 (Nokia 裝置必要)
protected void sizeChanged(int w, int h) {
if (h > height) { // 僅在高度增加時重建
bufferImage = Image.createImage(w, h);
width = w; height = h;
}
}
}
| 方法 | 作用 | 注意事項 |
|---|---|---|
| serviceRepaints() | 阻塞直到 paint() 完成 | 遊戲主迴必須呼叫,否則畫面不更新 |
| clipRect + flushGraphics(x,y,w,h) | 局部更新減少頻寬 | 僅重繪變化區域,提升幀率 |
| sizeChanged() | 處理螢幕尺寸變化 | Nokia 全螢幕切換會觸發,必須重建 bufferImage |
| getGraphics() | 取得離屏繪圖上下文 | 子類覆寫 paint() 時勿直接使用此 Graphics |
public class GameMain extends GameCanvas implements Runnable {
private Thread gameThread;
private boolean running = true;
public void start() {
gameThread = new Thread(this);
gameThread.start();
}
public void run() {
while (running) {
long frameStart = System.currentTimeMillis();
// 1. 邏輯更新
updateGameLogic();
// 2. 離屏繪製
Graphics g = getGraphics();
g.setColor(0x000000);
g.fillRect(0, 0, getWidth(), getHeight()); // 清背景
renderGame(g); // 繪製遊戲物件
// 3. 翻轉緩衝
flushGraphics();
// 4. 幀率控制
long elapsed = System.currentTimeMillis() - frameStart;
long sleep = Math.max(0, 40 - elapsed); // 目標 25 FPS
try { Thread.sleep(sleep); } catch (InterruptedException e) {}
}
}
}
| 現象 | 原因 | 解決 |
|---|---|---|
| 畫面仍有閃爍 | 未呼叫 serviceRepaints() 或 paint() 邏輯錯誤 | 確保 flushGraphics() → serviceRepaints() 順序 |
| 局部更新殘留舊圖 | clipRect 區域未覆蓋完整變化範圍 | 擴大 clip 區域或改用全螢幕 flushGraphics() |
| 全螢幕模式畫面變形 | sizeChanged 未重建 bufferImage | 覆寫 sizeChanged 同步更新緩衝尺寸 |
| 記憶體不足 OOM | bufferImage 過大或未釋放舊圖片 | 複用單一 bufferImage、離開遊戲時 null 化 |
雙緩衝是 J2ME 遊戲開發的基石技術。掌握其原理與正確實作,能在極受限的功能機硬體上實現流暢、無閃爍的遊戲體驗。這段代碼也見證了行動遊戲開發從「像素級優化」到現代引擎抽象的演進歷程。