《HTML5遊戲開發》系列文章的目的有:一、以最小的成本去入門egret小專案開發,官方的教程一直都是面向中重型;二、egret可以非常輕量;三、egret相比PIXI.js和spritejs文件更成熟、友好;四、學習從0打造高效的開發工作流。
- HTML5遊戲開發(一):3分鐘建立一個hello world
- HTML5遊戲開發(二):使用TypeScript編寫程式碼
- HTML5遊戲開發(三):使用webpack構建TypeScript應用
- HTML5遊戲開發(四):飛機大戰之顯示場景和元素
- HTML5遊戲開發(五):飛機大戰之讓所有元素動起來
本文我們將會讓遊戲的所有元素動起來。這包括:
- 讓背景從上到下迴圈移動,實現飛機向上飛行的效果。通過使兩張相同的背景圖上下交替的方式就可以實現。
- 讓敵機不斷地從上方出現,並持續地向友機移動。
遊戲完整原始碼: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;
}
複製程式碼
EnemyAI
和Background
都要實現介面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'));
}
}
}
複製程式碼