MIDP 2.0 雖提供 Sprite、Graphics,但實戰中仍需資源載入保護、實體狀態封裝、高效文字渲染三大基建。本文構建三個核心工具類。
public class ImageTools {
private ImageTools() {} // 工具類禁用實例化
public static Image getImage(String path) {
try {
return Image.createImage(path);
} catch (Exception ex) {
System.out.println("Load image failed: " + path + " - " + ex);
return null; // 呼叫端需檢查 null,實現優雅降級
}
}
}
用法:Image img = ImageTools.getImage("/res/bg.png"); 失敗返回 null,不拋異常、不中斷流程。
為什麼不直接用 Sprite? 一張 Sprite 圖集常被多個實體共用(如多架同型敵機),每個實體需獨立:位置、幀序、存活、生命週期、動畫速度。GameObject 即為「實體實例」,Sprite 為「模板」。
public class GameObject {
public Sprite sprite; // 共用模板
public boolean alive; // 存活標記
private int lifeCount; // 生命週期計數器
public int lifetime; // 總壽命(幀數),0=永久
public int speed; // 動畫速度(幀間隔),0=每幀更新
private int animCount; // 動畫計數器
public GameObject(Image img, int frameW, int frameH) {
sprite = new Sprite(img, frameW, frameH);
reset();
}
public void move(int dx, int dy) { sprite.move(dx, dy); }
public void moveTo(int x, int y) { sprite.setPosition(x, y); }
public void update() {
if (!alive) return;
if (++animCount > speed) {
animCount = 0;
sprite.nextFrame();
if (lifetime != 0 && ++lifeCount > lifetime) alive = false;
}
}
public void paint(Graphics g) {
if (alive) sprite.paint(g);
}
public void reset() {
alive = true;
lifeCount = 0;
animCount = 0;
sprite.setFrame(0);
}
}
痛點:Graphics.drawString 字體光柵化極慢、無法自定義風格、不支援換行。解法:美術製字體圖集(等寬/等高),程式建立 char[] → frameIndex 映射,繪製時 O(1) 定幀 + drawRegion。
public class Font {
private Sprite sprite; // 字體圖集
private int charW, charH; // 單字尺寸
private int[] charMap; // ASCII 0-127 映射表,-1=無此字
private Graphics g; // 目標繪圖面
public Font(Graphics g, Image img, int charW, int charH, char[] chars) {
this.g = g;
this.charW = charW; this.charH = charH;
sprite = new Sprite(img, charW, charH);
charMap = new int[128];
for (int i = 0; i < 128; i++) charMap[i] = -1;
for (int i = 0; i < chars.length; i++) {
charMap[chars[i]] = i; // Character.hashCode() == ASCII for 0-127
}
}
public void drawChar(char ch, int x, int y) {
int idx = (ch < 128) ? charMap[ch] : -1;
if (idx >= 0) {
sprite.setPosition(x, y);
sprite.setFrame(idx);
sprite.paint(g);
}
}
public void drawString(String str, int x, int y) {
int len = str.length();
for (int i = 0; i < len; i++) {
drawChar(str.charAt(i), x + charW * i, y);
}
}
}
// 初始化(通常在遊戲啟動時)
Image fontImg = ImageTools.getImage("/font.png"); // 16x16 像素/字,含 0-9 A-Z 符號
char[] fontChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.:-+*/".toCharArray();
Font gameFont = new Font(g, fontImg, 16, 16, fontChars);
// 繪製分數
gameFont.drawString("SCORE: 12345", 10, 10);
// 繰製生命值
gameFont.drawString("LIFE: 3", getWidth() - 80, 10);
charMap 儲存 short[] {frameIndex, charWidth},drawString 累加寬度而非固定 charW。drawString(str, x, y, maxWidth) 遇界換行,支援 \n 硬換行。x。sprite.setColor (MIDP 3.0) 或預製多色圖集切換。| 方案 | 繪製 20 字耗時 | JAR 增量 | 靈活度 |
|---|---|---|---|
| drawString | ~45 ms | 0 | 低(系統字體唯一) |
| Font (本文) | ~8 ms | ~2 KB + 字體圖集 | 高(任意風格、顏色、特效) |
| 預渲染整句圖片 | ~1 ms | 大(每句一圖) | 極低(動態內容不可用) |