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

J2ME 移動設備動畫實現:AnimatedImage 幀動畫引擎完整解析

     編輯:Java 程式設計入門

MIDP 1.0 無原生動畫 API,但透過 Canvas、Timer、Image 組合可輕鬆實現幀動畫。本文深度解析一個通用的 AnimatedImage 動畫引擎類,支援多幀循環、透明色模擬、位置控制、定時器驅動,為 J2ME 遊戲/UI 動畫提供生產級基礎組件。

動畫核心原理

  1. 幀序列:同尺寸 PNG 圖片陣列,每幀代表一個動畫狀態
  2. 定時驅動:TimerTask 定期推進幀索引
  3. 繪製回調:Canvas.paint() 中繪製當前幀
  4. 透明模擬:MIDP 1.0 不支援 Alpha 通道,通過裁剪矩形陣列繪製多次模擬透明區域

AnimatedImage 完整實現

import java.util.*;
import javax.microedition.lcdui.*;

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 frameWidth, frameHeight; // 幀尺寸

    // === 構造器重載 ===
    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 && clipRects != null && clipRects.length < frames.length)
            throw new IllegalArgumentException("clipRects length < frames length");
        if (frames != null && frames.length > 0) {
            frameWidth = frames[0].getWidth();
            frameHeight = frames[0].getHeight();
        }
    }

    // === 核心動畫控制 ===
    public void advance(boolean repaint) {
        if (++currentFrame >= frames.length) currentFrame = 0; // 循環
        if (repaint && canvas != null && canvas.isShown()) {
            canvas.repaint(x, y, frameWidth, frameHeight);
            canvas.serviceRepaints(); // 立即刷新
        }
    }

    // TimerTask 介面:定時器自動呼叫
    public void run() {
        if (frameWidth == 0 || frameHeight == 0) return;
        advance(true);
    }

    // === 繪製邏輯:支援透明色模擬 ===
    public void draw(Graphics g) {
        if (frameWidth == 0 || frameHeight == 0) return;
        int idx = currentFrame;
        if (clipRects == null || clipRects[idx] == null) {
            // 簡單模式:直接繪製整幀
            g.drawImage(frames[idx], x, y, Graphics.TOP | Graphics.LEFT);
        } else {
            // 透明模擬:保存原始裁剪區,分矩形繪製
            int oldX = g.getClipX(), oldY = g.getClipY();
            int oldW = g.getClipWidth(), oldH = g.getClipHeight();
            int[] rects = clipRects[idx];
            for (int i = 0; i + 3 < rects.length; i += 4) {
                g.setClip(x + rects[i], y + rects[i+1], rects[i+2], rects[i+3]);
                g.drawImage(frames[idx], x, y, Graphics.TOP | Graphics.LEFT);
            }
            g.setClip(oldX, oldY, oldW, oldH); // 還原
        }
    }

    // === 輔助方法 ===
    public void move(int x, int y) { this.x = x; this.y = y; }
    public int getWidth() { return frameWidth; }
    public int getHeight() { return frameHeight; }
}

資源載入與使用範例

從 JAR 載入幀序列

private Image[] loadFrames(String prefix, int count) throws IOException {
    Image[] imgs = new Image[count];
    for (int i = 0; i < count; i++) {
        // 檔案命名:walk0.png, walk1.png... 放在 JAR 根目錄或 /res/
        imgs[i] = Image.createImage("/" + prefix + i + ".png");
    }
    return imgs;
}

在 Canvas 中整合

public class GameCanvas extends Canvas {
    private AnimatedImage playerAnim;
    private Timer timer;

    public GameCanvas() {
        try {
            Image[] frames = loadFrames("walk", 8); // 8 幀走路動畫
            // 定義透明區域:每幀 4 個矩形 (頭、身、左腳、右腳)
            int[][] clips = new int[8][16]; // 8幀 x 4矩形 x 4座標
            // ... 初始化 clips 數據 ...
            playerAnim = new AnimatedImage(this, frames, clips);
            playerAnim.move(50, 50);
        } catch (IOException e) { /* 處理資源缺失 */ }

        timer = new Timer();
        timer.schedule(playerAnim, 0, 100); // 100ms/幀 = 10 FPS
    }

    protected void paint(Graphics g) {
        g.setColor(0xFFFFFF); g.fillRect(0, 0, getWidth(), getHeight()); // 清屏
        if (playerAnim != null) playerAnim.draw(g);
    }
}

關鍵設計決策解析

決策點方案理由
繼承 TimerTask直接實作 run() 推進幀無需額外線程類,Timer 統一調度,資源佔用極小
clipRects 透明模擬矩形陣列多次繪製MIDP 1.0 無 Alpha 支援,此法相容性最好,無需外部庫
canvas.serviceRepaints()立即刷新避免繪製請求排隊延遲,保證幀率穩定
isShown() 檢查僅可見時重繪後台/最小化時節省 CPU/電量
不可變幀尺寸構造時檢查所有幀同尺寸簡化繪製邏輯,避免運行時計算

效能優化建議

  • 幀率控制:10-15 FPS 足夠流暢,timer.schedule(task, 0, 80-100)
  • 避免重複創建 Image:loadFrames() 僅在初始化調用一次
  • 釋放資源:遊戲結束 timer.cancel()、frames = null、System.gc()
  • 雙緩衝:Canvas 預設雙緩衝,無需手動管理
  • 減少 clip 切換:透明區域矩形盡量合併,減少 setClip 呼叫

MIDP 2.0+ 現代替代方案

MIDP 2.0 引入 GameCanvas、Sprite、LayerManager 遊戲 API:

// MIDP 2.0 方式
Sprite sprite = new Sprite(Image.createImage("/walk.png"), frameWidth, frameHeight);
sprite.defineReferencePixel(frameWidth/2, frameHeight/2);
sprite.setPosition(50, 50);
// 遊戲迴圈中
sprite.nextFrame(); // 自動循環
sprite.paint(g);

遷移建議:新專案優先用 MIDP 2.0 Game API;存量 MIDP 1.0 代碼可封裝 AnimatedImage 平滑過渡。

總結

AnimatedImage 展示了在極度受限環境下,如何用 TimerTask + Canvas + 裁剪繪製構建通用動畫引擎。其設計精髓——職責單一、資源復用、平台適配、效能覺知——在現代 Canvas 2D/WebGL 輕量動畫庫、WebAssembly 遊戲引擎、嵌入式 GUI 框架 中依然閃耀著工程智慧的光芒。

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