《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'));
}
}
}
复制代码