從《Java手機遊戲實例手冊》第 6 章範例改寫而來。書中源碼不全、圖片缺失,逼我自找素材、修 Bug、踩坑。雖成品簡單——趙雲騎馬從左跑到右、背景拼貼——但每個細節都讓我從「看懂」走到「會寫」。記錄完整代碼與關鍵踩坑點,給同路人參考。
MyCartoonMidlet.java // MIDlet 入口 MainCanvas.java // 遊戲畫布邏輯 res/ zhaoyun.png // 精靈圖 (6 幀橫向排列) bk.jpg // 背景素材 (60x40 單元)
package mycartoon;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Display;
public class MyCartoonMidlet extends MIDlet {
private MainCanvas canvas;
protected void startApp() {
if (canvas == null) {
canvas = new MainCanvas();
Display.getDisplay(this).setCurrent(canvas);
// 遊戲主迴路
while (true) {
try {
Thread.sleep(100); // 10 FPS
canvas.repaint();
} catch (Exception e) {}
}
}
}
protected void pauseApp() {}
protected void destroyApp(boolean unconditional) {}
}
package mycartoon;
import java.io.IOException;
import javax.microedition.lcdui.*;
public class MainCanvas extends Canvas {
private Sprite zhaoyun; // 精靈
private TiledLayer bgLayer; // 背景圖層
private LayerManager manager; // 圖層管理器
private Image bgImage;
public MainCanvas() {
try {
// 1. 精靈:6 幀,每幀 40x60
Image spriteImg = Image.createImage("/zhaoyun.png");
zhaoyun = new Sprite(spriteImg, 40, 60);
zhaoyun.setPosition(0, getHeight() / 2 - 30);
// 2. 背景圖層:4 列 x 8 行,每單元 60x40
bgImage = Image.createImage("/bk.jpg");
bgLayer = new TiledLayer(4, 8, bgImage, 60, 40);
bgLayer.setPosition(0, 0);
setBgCells(); // 設置單元格圖塊索引
// 3. 圖層管理器:繪製順序 = 添加順序 (先背景後精靈)
manager = new LayerManager();
manager.append(zhaoyun); // 顯示在上層
manager.append(bgLayer); // 顯示在下層
} catch (IOException e) {
e.printStackTrace();
}
}
private void setBgCells() {
// 第一行:索引 2,3,4,5 (特定圖塊)
bgLayer.setCell(0, 0, 2);
bgLayer.setCell(1, 0, 3);
bgLayer.setCell(2, 0, 4);
bgLayer.setCell(3, 0, 5);
// 第 2-8 行:全部填索引 6 (草地)
for (int y = 1; y < 8; y++) {
for (int x = 0; x < 4; x++) {
bgLayer.setCell(x, y, 6);
}
}
}
protected void paint(Graphics g) {
// 精靈動畫:循環播放 6 幀
int frame = zhaoyun.getFrame();
zhaoyun.setFrame((frame + 1) % 6);
// 精靈移動:每幀 +10px,超出右邊界從左側重入
int x = zhaoyun.getX() + 10;
if (x > getWidth()) x = -zhaoyun.getWidth();
zhaoyun.setPosition(x, zhaoyun.getY());
// 一鍵繪製所有圖層 (自動處理 Z 軸順序)
manager.paint(g, 0, 0);
}
}
初寫 public void MainCanvas() 加了 void,導致非構造函數,成員未初始化,startApp 中 repaint() 拋 NPE。構造函數絕不能有返回類型。
new TiledLayer(cols, rows, image, cellWidth, cellHeight) —— 先列後行,再寬高。我最初寫反,背景全亂。必須查閱 JavaDoc 確認參數順序。
append(zhaoyun) 後 append(bgLayer),精靈在上層。若直接用 sprite.paint(g) + layer.paint(g),繪製順序相反 (先背景後精靈)。統一用 LayerManager 管理,少犯錯。
書中 while(true) { sleep; repaint; } 會佔滿 CPU。正確做法:Thread.sleep 在渲染執行緒中,或用 Display.callSerially 配合 Timer。本例為教學簡化,實戰請用獨立渲染執行緒。
| 任務 | 工具 | 備註 |
|---|---|---|
| GIF 拆幀/合圖 | pngmate | 命令行、功能強、免費 |
| 批量透明化/格式轉換 | Total Commander (內建圖片轉換) | 鍵盤操作快、支援濾鏡 |
| 像素級編輯 | GraphicsGale / Aseprite | 專業像素動畫編輯器 |
第一個遊戲雖簡陋,但串聯了 MIDlet 生命週期、Canvas 繪製、Sprite 動畫、TiledLayer 拼貼、LayerManager 合成、資源載入與異常處理完整鏈路。動手改代碼、找素材、查文檔、修 Bug,才是唯一捷徑。