動畫由離散幀組成,連續播放產生動態視覺。每幀在遊戲迴路中繪製一次。
速度控制:若不控制,快手機播放過快、慢手機過慢。解決方案:鎖定遊戲 FPS (教學二),使單幀時間固定,動畫在所有裝置速度一致。
int frameIndex = 0;
Image[] animFrames = new Image[12]; // 預載 12 幀圖片
public void playAnimation() {
Image current = animFrames[frameIndex];
drawImage(current, x, y); // 繪製到螢幕
frameIndex++;
if (frameIndex >= animFrames.length) {
frameIndex = 0; // 循環播放
// 或 frameIndex = animFrames.length - 1; // 停在最後一幀
}
}
若遊戲 FPS=20,動畫想每秒播 10 幀,需每 2 遊戲幀推進 1 動畫幀。
int animFrame = 0;
int animDelay = 2; // 每 2 遊戲幀換一次動畫幀
int animCounter = 0;
public void updateAnimation() {
if (++animCounter >= animDelay) {
animCounter = 0;
animFrame = (animFrame + 1) % animFrames.length;
}
drawImage(animFrames[animFrame], x, y);
}
class AnimModule {
Image[] frames; // 該模組的動畫幀陣列
int frameIndex; // 當前幀
int delay; // 幀延遲
int counter; // 計數器
int xOffset, yOffset; // 相對角色原點偏移
}
class Character {
AnimModule head, body, leftArm, rightArm, leftLeg, rightLeg;
int x, y; // 角色世界座標
void update() {
head.update(); body.update(); // ... 更新各模組幀索引
}
void draw(Graphics g) {
// 依繪製順序 (如腳→身→手→頭) 繪製各模組
leftLeg.draw(g, x, y);
rightLeg.draw(g, x, y);
body.draw(g, x, y);
leftArm.draw(g, x, y);
rightArm.draw(g, x, y);
head.draw(g, x, y);
}
}
所有模組幀圖打包成單一大圖 (Texture Atlas),載入後用 Image.createImage(src, x, y, w, h, transform) 切割,或直接由 Sprite/TiledLayer 管理。
while (state == STATE_LOGO) {
logoAnimation.update();
logoAnimation.draw(g);
if (logoAnimation.isFinished()) state = STATE_MENU;
}
// 遊戲迴路
player.setAction(ACTION_WALK);
player.update(); // 內部播放走路動畫模組
// 輸入控制位移
if (keyHeld(GAME_LEFT)) { player.x -= 2; player.setFlip(true); }
if (keyHeld(GAME_RIGHT)) { player.x += 2; player.setFlip(false); }
// 更新時同步動畫與位移
// 走路動畫模組的腿部位移量需與 player.x 速度匹配,防止滑步 (參考教學一步長算法)
class Enemy {
int state = STATE_PATROL;
void update() {
switch (state) {
case STATE_PATROL:
playAnimation(walkAnim);
movePatrol();
if (seePlayer()) state = STATE_CHASE;
break;
case STATE_CHASE:
playAnimation(runAnim);
moveTowardPlayer();
if (distance < ATTACK_RANGE) state = STATE_ATTACK;
break;
case STATE_ATTACK:
playAnimation(attackAnim);
if (animFinished()) state = STATE_CHASE;
break;
}
}
}
update()/draw(),職責清晰。