首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

MIDP 1.0 動畫實作全攻略:Timer + AnimatedImage 打造流暢幀動畫

     編輯:J2ME

動畫核心原理

動畫 = 連續幀高速切換。人眼視覺暫留效應使 ≥ 15fps 即感知為流暢運動。MIDP 1.0 無內建動畫 API,但 java.util.Timer + TimerTask 提供精確週期性觸發,配合 Canvas.repaint() 即可實現。

核心類別:AnimatedImage (可排程的動畫精靈)


public class AnimatedImage extends TimerTask {
    private Canvas canvas;           // 關聯畫布,用於觸發重繪
    private Image[] frames;          // 等尺寸幀陣列
    private int[][] clipRects;       // 可選:每幀透明區域裁剪矩陣 [x,y,w,h]...
    private int currentFrame = 0;
    private int x, y;                // 繪製左上角座標
    private int frameW, frameH;      // 單幀尺寸

    // 三種建構子重載
    public AnimatedImage(Image[] frames) { this(null, frames, null); }
    public AnimatedImage(Canvas canvas, Image[] frames) { this(canvas, frames, null); }
    public AnimatedImage(Canvas canvas, Image[] frames, int[][] clipRects) {
        this.canvas = canvas;
        this.frames = frames;
        this.clipRects = clipRects;
        if (frames != null && frames.length > 0) {
            frameW = frames[0].getWidth();
            frameH = frames[0].getHeight();
        }
        // 驗證 clipRects 長度
        if (clipRects != null && clipRects.length < frames.length) {
            throw new IllegalArgumentException("clipRects length < frames.length");
        }
    }

    // 移動精靈位置
    public void move(int x, int y) { this.x = x; this.y = y; }

    // 手動推進幀 (供 TimerTask 執行緒呼叫)
    public void advance(boolean repaint) {
        if (++currentFrame >= frames.length) currentFrame = 0; // 循環
        if (repaint && canvas != null && canvas.isShown()) {
            canvas.repaint(x, y, frameW, frameH); // 部分重繪優化
            canvas.serviceRepaints(); // 立即執行 paint (關鍵!)
        }
    }

    // 實際繪製邏輯:支援透明色模擬 (clipRects)
    public void draw(Graphics g) {
        if (frameW == 0 || frameH == 0) return;
        Image frame = frames[currentFrame];
        if (clipRects == null || clipRects[currentFrame] == null) {
            g.drawImage(frame, x, y, Graphics.TOP | Graphics.LEFT);
        } else {
            // 多次繪製模擬透明:每次設定不同裁剪區
            int[] clip = clipRects[currentFrame];
            int oldX = g.getClipX(), oldY = g.getClipY();
            int oldW = g.getClipWidth(), oldH = g.getClipHeight();
            for (int i = 0; i + 3 < clip.length; i += 4) {
                g.setClip(x + clip[i], y + clip[i+1], clip[i+2], clip[i+3]);
                g.drawImage(frame, x, y, Graphics.TOP | Graphics.LEFT);
            }
            g.setClip(oldX, oldY, oldW, oldH);
        }
    }

    // TimerTask 介面:週期性推進
    public void run() {
        if (frameW == 0 || frameH == 0) return;
        advance(true);
    }
}

幀資源載入工具


// 從 JAR 載入連續編號 PNG: /images/bird0.png, bird1.png ...
private Image[] loadFrames(String prefix, int count) throws IOException {
    Image[] imgs = new Image[count];
    for (int i = 0; i < count; i++) {
        imgs[i] = Image.createImage(prefix + i + ".png");
    }
    return imgs;
}

動畫畫布:AnimatedCanvas (管理多精靈、雙緩衝)


public class AnimatedCanvas extends Canvas {
    private Display display;
    private Image offscreen;              // 離屏緩衝
    private Vector sprites = new Vector(); // AnimatedImage 集合

    public AnimatedCanvas(Display display) {
        this.display = display;
        if (!isDoubleBuffered()) {
            offscreen = Image.createImage(getWidth(), getHeight());
        }
    }

    public void add(AnimatedImage sprite) {
        sprites.addElement(sprite);
    }

    protected void paint(Graphics g) {
        Graphics target = g;
        if (offscreen != null) target = offscreen.getGraphics();

        // 清背景
        target.setColor(0xFFFFFF);
        target.fillRect(0, 0, getWidth(), getHeight());

        // 繪製所有精靈
        for (int i = 0; i < sprites.size(); i++) {
            ((AnimatedImage) sprites.elementAt(i)).draw(target);
        }

        // 雙緩衝合成
        if (target != g) {
            g.drawImage(offscreen, 0, 0, Graphics.TOP | Graphics.LEFT);
        }
    }
}

主 MIDlet:AnimationTest


public class AnimationTest extends MIDlet implements CommandListener {
    private static final int BIRD_FRAMES = 7;
    private static final int NUM_BIRDS = 5;

    private Display display;
    private Timer timer = new Timer();
    private AnimatedImage[] birds;
    private Random rand = new Random();
    private Command exitCmd = new Command("Exit", Command.EXIT, 1);

    protected void startApp() {
        if (display == null) {
            display = Display.getDisplay(this);
            initAnimation();
        }
    }

    private void initAnimation() {
        try {
            AnimatedCanvas canvas = new AnimatedCanvas(display);
            Image[] frames = loadFrames("/images/bird", BIRD_FRAMES);

            int w = canvas.getWidth(), h = canvas.getHeight();
            birds = new AnimatedImage[NUM_BIRDS];
            for (int i = 0; i < NUM_BIRDS; i++) {
                birds[i] = new AnimatedImage(canvas, frames);
                birds[i].move(rand.nextInt(w), rand.nextInt(h));
                canvas.add(birds[i]);
                // 錯開啟動時間與幀率,產生自然群飛效果
                timer.schedule(birds[i], rand.nextInt(1000), rand.nextInt(300) + 100);
            }

            canvas.addCommand(exitCmd);
            canvas.setCommandListener(this);
            display.setCurrent(canvas);
        } catch (IOException e) {
            System.out.println("載入動畫資源失敗");
            notifyDestroyed();
        }
    }

    public void commandAction(Command c, Displayable d) {
        if (c == exitCmd) {
            timer.cancel();
            notifyDestroyed();
        }
    }
    // pauseApp, destroyApp 略...
}

關鍵效能優化要點

優化點說明
部分重繪canvas.repaint(x,y,w,h) 僅刷新精靈區域,減少像素處理量。
serviceRepaints()強制立即在 Timer 執行緒執行 paint,避免事件佇列延遲造成幀率抖動。
雙緩衝AnimatedCanvas 自動檢測 isDoubleBuffered(),軟體實作消除閃爍。
資源預載所有幀在 initAnimation 一次載入,運行期零 I/O。
幀率控制每個精靈獨立 Timer.schedule(task, delay, period),避免單一 Timer 任務堆積。
記憶體管理精靈數量建議 < 10;單幀 < 5KB;總動畫資源 < 100KB。

進階擴充方向

  • 精靈碰撞偵測:在 advance() 中檢查邊界與其他精靈矩形重疊。
  • 動畫狀態機:為每個精靈加入 state (idle/walk/jump),依狀態切換不同幀陣列。
  • 插值動畫:不靠幀圖,改用數學公式計算位置/旋轉/縮放 (如 x = startX + (endX-startX)*t),極度省記憶體。
  • 音效同步:配合 Manager.playTone 或 MMAPI 播放音效。

小結

MIDP 1.0 雖無動畫框架,但 Timer/TimerTask + Canvas 組合極具彈性。AnimatedImage 封裝了幀管理、定時推進、透明繪製、位置控制,配合 AnimatedCanvas 統一雙緩衝與多精靈合成,構成一套輕量、可擴充的動畫引擎核心。掌握此模式,即可在 64KB JAR 限制下打造媲美原生遊戲的視覺體驗。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved