HTML5遊戲開發(五):飛機大戰之讓所有元素動起來

wildfirecode13發表於2018-11-03

《HTML5遊戲開發》系列文章的目的有:一、以最小的成本去入門egret小專案開發,官方的教程一直都是面向中重型;二、egret可以非常輕量;三、egret相比PIXI.js和spritejs文件更成熟、友好;四、學習從0打造高效的開發工作流。

本文我們將會讓遊戲的所有元素動起來。這包括:

  • 讓背景從上到下迴圈移動,實現飛機向上飛行的效果。通過使兩張相同的背景圖上下交替的方式就可以實現。
  • 讓敵機不斷地從上方出現,並持續地向友機移動。

遊戲完整原始碼:github.com/wildfirecod…

線上展示:wildfirecode.com/egret-plane…

動畫實現方式:按幀重新整理

遊戲畫面每重新整理一次,即為一幀。如果我們能在每一幀都對場景元素的屬性進行變更,這樣我們就獲得動態效果。

為了實現幀迴圈,我們向egret.startTick註冊並啟動一個計時器onTick,它通常會以60FPS的速率觸發回撥方法。這裡的FPS即多少幀每秒。另外,我們會提前建立一個陣列用來存放所有需要進行按幀重新整理的物件,那麼前提條件是必須實現IOnTick介面的方法onTick。這些物件的類包括處理背景迴圈移動的Background以及控制敵機AI的EnemyAI

_IOnTicks: IOnTick[];//用來存放所有需要進行按幀重新整理的物件
async onAddToStage() {
    this._IOnTicks = [];//提前建立這個陣列
    ...
    this.createGame();
    ...
    egret.startTick(this.onTick, this);
    ...
}

createGame() {
    ...
    const background = new Background();//利用幀迴圈實現背景迴圈移動
    this._IOnTicks.push(background);//儲存到陣列
    this.addEnemy();//新增一個敵機
}

addEnemy() {
    ...
    const enemy = new Enemy();//我們在Enemy類中例項化了EnemyAI
    this._IOnTicks.push(enemy.AI);//儲存到陣列
}

onTick() {
    this._IOnTicks.forEach(val => val.onTick());//執行所有需要按幀重新整理的物件的onTick方法
    return false;
}
複製程式碼

EnemyAIBackground都要實現介面IOnTick

class EnemyAI extends egret.EventDispatcher implements IOnTick {
    onTick() {
        //這裡每幀都會執行
    }
}
class Background implements IOnTick {
    onTick() {
        //這裡每幀都會執行
    }
}
複製程式碼

讓背景動起來

為了實現背景迴圈移動的效果,我們需要建立兩個同樣背景影象的點陣圖。 我們建立了工具方法cloneImage來克隆一個點陣圖。對這個方法傳入一個egret.Bitmap,你將會獲得一個一模一樣的egret.Bitmap

// cloneImage API
const cloneImage: (bitmap: egret.Bitmap) => egret.Bitmap
複製程式碼
 createGame() {
    const [bg, hero, enemy] = this._bitmaps;
    this.addChild(bg);
    const bg2 = cloneBitmap(bg);
    this.addChild(bg2);//將克隆的背景圖也新增到舞臺
    ...
    const background = new Background(bg, bg2);
    ...
}
複製程式碼

最後,我們來看看Background類是如何實現背景迴圈的功能。

import IOnTick from "./IOnTick";
export default class Background implements IOnTick {
    _bg: egret.Bitmap;
    _bg2: egret.Bitmap;
    constructor(bg: egret.Bitmap, bg2: egret.Bitmap) {
        this._bg = bg;
        this._bg2 = bg2;
        this._bg2.y = -bg2.height;
    }
    
    onTick() {
        const SPEED = 8;//每幀裡背景都會向下移動8px
        this._bg.y += SPEED;
        this._bg2.y += SPEED;
        const height = this._bg.height;//背景交替移動
        if (this._bg.y > height) {
            this._bg.y = this._bg2.y - height;
        }
        if (this._bg2.y > height) {
            this._bg2.y = this._bg.y - height;
        }
    }
}
複製程式碼

讓敵機動起來

為了每秒生成一架敵機,我們必須要有一個敵機的模板點陣圖。 另外,當敵機移動到螢幕之外時,我們要徹底的銷燬它。我們做以下設計:

  • EnemyAI類負責敵機移至螢幕之外的演算法以及在移至螢幕之外時廣播onEnemyDisappear事件。
  • Enemy類負責從顯示層銷燬敵機。
  • Main.removeEnemy方法負責將EnemyAI物件移除幀重新整理列表,避免不必要的計算。
createGame() {
    const [bg, hero, enemy] = this._bitmaps;
    ...
    this._enemyTemplate = enemy;//將敵機模板儲存起來
    setInterval(() => this.addEnemy(), 1000);//每1000ms生成一架敵機
}

addEnemy() {
    const enemyImage = cloneImage(this._enemyTemplate);//克隆敵機圖片
    this.addChild(enemyImage);
    this.centerAnchor(enemyImage);
    const enemy = new Enemy(enemyImage);
    enemy.AI.once('onEnemyDisappear', this.onEnemyDisappear, this);//監聽敵機移出螢幕的廣播事件
    this._IOnTicks.push(enemy.AI);
}

onEnemyDisappear(e: egret.Event) {
    const AI = e.currentTarget as EnemyAI;
    AI.enemy.destroy();//將敵機從顯示層銷燬
    this.removeEnemy(AI);//將EnemyAI物件移除幀重新整理列表,避免不必要的計算。
}

removeEnemy(enemyAI: EnemyAI) {
    const index = this._IOnTicks.indexOf(enemyAI);
    this._IOnTicks.splice(index, 1);//移除幀重新整理物件列表
}
複製程式碼

Enemy類負責從顯示層銷燬敵機

import EnemyAI from "./EnemyAI";
export default class Enemy {
    image: egret.Bitmap;
    AI: EnemyAI;
    constructor(image: egret.Bitmap) {
        this.image = image;
        this.AI = new EnemyAI(this);//例項化敵機AI
    }

    removeImage() {
        this.image.parent.removeChild(this.image);//從顯示層移除
    }

    destroy() {
        this.removeImage();
    }
}
複製程式碼

EnemyAI

import Enemy from "./Enemy";
import IOnTick from "../IOnTick";

class EnemyAI extends egret.EventDispatcher implements IOnTick {
    enemy: Enemy;
    _image: egret.Bitmap;
    initialX: number;
    initialY: number;
    constructor(enemy: Enemy) {
        super();
        this._image = enemy.image;
        this.enemy = enemy;

        this.initialX = this._image.stage.stageWidth / 2;
        this.initialY = -100;

        this.setInitialPosition();//設定敵機的初始位置
    }

    private setInitialPosition() {
        this._image.x = this.initialX;
        this._image.y = this.initialY;
    }

    onTick() {
        this._image.y += 5; //每幀裡敵機都會向下移動5px
        if (this._image.y > this._image.stage.stageHeight) { //判斷是否移至螢幕之外
            //廣播移至螢幕之外的事件
            this.dispatchEvent(new egret.Event('onEnemyDisappear'));
        }
    }
}
複製程式碼

執行效果

HTML5遊戲開發(五):飛機大戰之讓所有元素動起來

相關文章