手機網遊客戶端界面開發面臨屏幕適配、幀率優化、狀態管理等挑戰。本文以《瘋狂的炸彈》為例,講解狀態機模式管理多界面、分層渲染優化繪製、輸入事件分發機制。
public class GameCanvas extends Canvas implements Runnable {
// 界面狀態枚舉
private static final int STATE_LOADING = 0;
private static final int STATE_LOGIN = 1;
private static final int STATE_WAITING = 2; // 匹配等待
private static final int STATE_PLAYING = 3; // 遊戲主界面
private static final int STATE_GAMEOVER = 4;
private static final int STATE_SETTINGS = 5;
private int currentState = STATE_LOADING;
private int nextState = -1; // 待切換狀態
// 狀態切換(線程安全)
public synchronized void changeState(int newState) {
this.nextState = newState;
}
// 主循環中處理狀態切換
public void run() {
while (running) {
// 處理狀態轉換
if (nextState != -1) {
handleStateTransition(currentState, nextState);
currentState = nextState;
nextState = -1;
}
// 當前狀態邏輯更新
updateCurrentState();
// 觸發重繪
repaint();
// 幀率控制
sleepFrame(1000 / 20); // 20 FPS
}
}
private void handleStateTransition(int from, int to) {
// 退出舊狀態清理
switch (from) {
case STATE_PLAYING: stopGameLoop(); break;
case STATE_WAITING: stopWaitAnimation(); break;
}
// 進入新狀態初始化
switch (to) {
case STATE_PLAYING: initGameResources(); startGameLoop(); break;
case STATE_WAITING: startWaitAnimation(); break;
case STATE_GAMEOVER: showResult(); break;
}
}
}
protected void paint(Graphics g) {
// 1. 背景層 (靜態,可緩存)
drawBackground(g);
// 2. 地圖靜態層 (障礙物/裝飾)
drawMapStaticLayers(g);
// 3. 動態實體層 (玩家/炸彈/道具)
drawEntities(g);
// 4. 特效層 (爆炸/粒子)
drawEffects(g);
// 5. UI覆蓋層 (分數/血量/按鈕)
drawUIOverlay(g);
// 6. 狀態專屬UI (等待動畫/結算面板)
drawStateOverlay(g);
}
private void drawMapStaticLayers(Graphics g) {
// 只繪製可見區域
int startRow = cameraY / TILE_H;
int endRow = Math.min(MAP_ROWS, (cameraY + screenH) / TILE_H + 1);
for (int r = startRow; r < endRow; r++) {
for (int c = 0; c < MAP_COLS; c++) {
int x = c * TILE_W - cameraX;
int y = r * TILE_H - cameraY;
// 分層繪製,後繪製蓋前
drawTile(g, map.groundLayer[r][c], x, y);
drawTile(g, map.objectLayer[r][c], x, y);
drawTile(g, map.decorLayer[r][c], x, y);
}
}
}
private void drawEntities(Graphics g) {
// 玩家按Y排序實現偽3D遮擋
List sorted = new ArrayList<>(entities);
sorted.sort(Comparator.comparingInt(e -> e.getY()));
for (Entity e : sorted) {
if (e.isVisible(cameraX, cameraY, screenW, screenH)) {
e.draw(g, cameraX, cameraY);
}
}
}
public class ResourceManager {
private static final Map imageCache = new HashMap<>();
private static final Map spriteCache = new HashMap<>();
public static Image getImage(String path) {
return imageCache.computeIfAbsent(path, p -> {
try {
return Image.createImage(p);
} catch (IOException e) {
log.error("加載圖片失敗: " + p, e);
return Image.createImage(1, 1); // 佔位圖
}
});
}
public static Sprite getSprite(String imgPath, int frameW, int frameH) {
return spriteCache.computeIfAbsent(imgPath + "#" + frameW + "x" + frameH,
k -> new Sprite(getImage(imgPath), frameW, frameH));
}
// 遊戲結束時釋放
public static void dispose() {
imageCache.clear();
spriteCache.clear();
}
}
protected void keyPressed(int keyCode) {
// 通用鍵處理
if (keyCode == KEY_SOFT_LEFT) { // 左軟鍵
if (currentState == STATE_PLAYING) pauseGame();
else if (currentState == STATE_WAITING) cancelMatch();
else exitGame();
return;
}
// 狀態專屬處理
switch (currentState) {
case STATE_PLAYING:
handleGameInput(keyCode);
break;
case STATE_LOGIN:
handleLoginInput(keyCode);
break;
case STATE_SETTINGS:
handleSettingsInput(keyCode);
break;
}
}
private void handleGameInput(int keyCode) {
int action = getGameAction(keyCode);
switch (action) {
case Canvas.UP:
if (playerY > TOP_MARGIN)
sendMove(DIR_UP);
break;
case Canvas.DOWN:
if (playerY + SPRITE_H < screenH - BOTTOM_MARGIN)
sendMove(DIR_DOWN);
break;
case Canvas.LEFT:
if (playerX > 0) sendMove(DIR_LEFT);
break;
case Canvas.RIGHT:
if (playerX + SPRITE_W < screenW) sendMove(DIR_RIGHT);
break;
case Canvas.FIRE:
sendLayBomb();
break;
}
}
| 優化點 | 方案 | 收益 |
|---|---|---|
| 重複繪製 | 臟矩形/可見區域剪裁 | 減少50%+繪製調用 |
| 圖片創建 | 資源管理器單例緩存 | 避免GC抖動/OOM |
| 幀率不穩 | 固定時間步長+插值渲染 | 流暢度提升 |
| 內存佔用 | 圖片壓縮/PNG索引色/釋放不用資源 | 適配低端機 |
| 按鍵響應 | 客戶端預判+服務端校驗 | 降低延遲感知 |
// 屏幕適配:以基準分辨率(240x320)縮放
float scaleX = screenWidth / 240f;
float scaleY = screenHeight / 320f;
float scale = Math.min(scaleX, scaleY); // 保持比例
// 繪製時應用縮放
g.translate((screenWidth - 240*scale)/2, (screenHeight - 320*scale)/2);
g.scale(scale, scale);
// ... 繪製基準座標內容 ...
g.scale(1/scale, 1/scale);
g.translate(-(screenWidth - 240*scale)/2, -(screenHeight - 320*scale)/2);
狀態機模式讓複雜界面流轉變得清晰可控,分層渲染配合資源緩存是實現流暢遊戲體驗的基石。現代開發建議使用LibGDX/Unity等引擎替代手工Canvas開發。