Demo3D 範例改寫swerve.m3g 場景並播放內建動畫first3d.First3DMIDletMobile 3D Graphics (JSR 184),Profile: MIDP 2.0,Configuration: CLDC 1.1將 WTK 安裝目錄下的場景檔複製至專案 res/ 目錄:
WTK2.2/apps/Demo3D/res/com/superscape/m3g/wtksamples/retainedmode/content/swerve.m3g
→ 專案/res/swerve.m3g
package first3d;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class First3DMIDlet extends MIDlet {
private First3DCanvas displayable = new First3DCanvas();
public void startApp() {
Display.getDisplay(this).setCurrent(displayable);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
}
package first3d;
import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;
import java.util.*;
public class First3DCanvas extends Canvas implements Runnable {
private World myWorld = null;
private Graphics3D g3d = Graphics3D.getInstance();
private Camera cam = null;
private int viewport_x, viewport_y, viewport_width, viewport_height;
private long worldStartTime = 0;
private int validity = 0;
public First3DCanvas() {
Thread thread = new Thread(this);
thread.start();
try {
// 1. 載入 .m3g 場景
myWorld = (World) Loader.load("/swerve.m3g")[0];
// 2. 視口初始化
viewport_x = 0;
viewport_y = 0;
viewport_width = getWidth();
viewport_height = getHeight();
// 3. 取得場景中定義的 Camera
cam = myWorld.getActiveCamera();
// 4. 依螢幕長寬比調整視口 (保持投影比例)
float[] params = new float[4];
int type = cam.getProjection(params);
if (type != Camera.GENERIC) {
float wAspect = (float) viewport_width / viewport_height;
if (wAspect < params[1]) { // params[1] = 投影高度比例
float height = viewport_width / params[1];
viewport_height = (int) height;
viewport_y = (getHeight() - viewport_height) / 2;
} else {
float width = viewport_height * params[1];
viewport_width = (int) width;
viewport_x = (getWidth() - viewport_width) / 2;
}
}
worldStartTime = System.currentTimeMillis();
} catch (Exception e) {
e.printStackTrace();
}
}
protected void paint(Graphics g) {
// 清除背景
g.setColor(0x000000);
g.fillRect(0, 0, getWidth(), getHeight());
// 綁定 3D 渲染目標
g3d.bindTarget(g);
g3d.setViewport(viewport_x, viewport_y, viewport_width, viewport_height);
// 更新動畫時間並取得有效性
long startTime = System.currentTimeMillis() - worldStartTime;
validity = myWorld.animate((int) startTime);
try {
g3d.render(myWorld);
} finally {
g3d.releaseTarget();
}
}
public void run() {
try {
while (true) {
// 重繪視口區域
repaint(viewport_x, viewport_y, viewport_width, viewport_height);
Thread.sleep(30); // 限制幀率 ~33 FPS
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Loader.load()Object[],第一個元素通常是 Worldres/ 目錄,打包進 JAR 根目錄程式碼中 cam.getProjection(params) 取得投影參數,params[1] 代表投影的高寬比。根據螢幕實際長寬比調整 viewport_width/height,避免畫面拉伸變形。這是保留模式場景在不同解析度裝置上正確顯示的關鍵。
World.animate(time)validity 指示場景圖是否有變更 (可用於優化)run() 持續呼叫 repaint()paint() 中完成 bindTarget → render → releaseTarget 標準流程repaint(x,y,w,h) 僅重繪視口區域,提升效能finally { g3d.releaseTarget(); } 確保渲染目標釋放,避免記憶體洩漏或繪圖鎖死。
| 現象 | 可能原因 | 解決方案 |
|---|---|---|
| 黑畫面、無畫面 | 視口設定錯誤、Camera 未啟用、模型在視野外 | 檢查 setViewport 參數、world.setActiveCamera、模型座標 |
| 畫面撕裂、閃爍 | 未雙緩衝、repaint 過於頻繁 | 加入 Thread.sleep、使用 flushGraphics() (若繼承 GameCanvas) |
| 載入失敗 NullPointerException | swerve.m3g 未打包進 JAR、路徑錯誤 | 確認 res/swerve.m3g 存在、build.xml 包含資源 |
| 動畫靜止 | 未呼叫 animate() 或時間參數錯誤 | 確認執行緒正常運行、worldStartTime 正確初始化 |
keyPressed 中調整 cam 位置/旋轉.m3g,選單切換時重新 Loader.loadrender 耗時,針對低端裝置簡化模型此範例展示了 M3G 保留模式的核心工作流程:載入場景 → 同步視口 → 以時間驅動動畫 → 持續渲染。掌握此流程即可開發豐富的 3D 行動應用。