The goals of the HTML5 Game Development series are: 1) To get an introduction to Egret small project development at minimal cost, as the official EGREt tutorials have always been for medium and heavy projects; Egret can be very lightweight; 3. Egret documents are more mature and friendly than Pixi. js and Spritejs; Learn to build an efficient development workflow from zero.

  • Create a Hello World in 3 minutes
  • HTML5 Game Development part II: Writing code in TypeScript
  • HTML5 Game Development (3) : Build TypeScript apps with Webpack
  • HTML5 Game Development part 4: Aircraft Wars display scenes and elements
  • HTML5 Game Development # 5: Airplane Wars Get everything moving

In this article we are going to make every element of the game work. This includes:

  • Let the background loop from top to bottom to achieve the effect of the plane flying up. This can be done by alternating two identical backgrounds up and down.
  • Let the enemy aircraft constantly appear from above and continue to move towards the friendly aircraft.

Game complete source: github.com/wildfirecod…

Online presentation: wildfirecode.com/egret-plane…

Animation implementation: refresh by frame

Each refresh of the game screen is one frame. If we can change the attributes of the scene element every frame, then we have dynamic effects.

To implement the frame loop, we register with egret.startTick and start a timer, onTick, which normally fires the callback method at a rate of 60FPS. FPS is how many frames per second. In addition, we will create an array ahead of time to store all objects that need to be refreshed by frame, as long as the onTick method of the IOnTick interface is implemented. Classes for these objects include Background, which handles Background loop movement, and EnemyAI, which controls enemy aircraft AI.

_IOnTicks: IOnTick[];// Store all objects that need to be refreshed by frame
async onAddToStage() {
    this._IOnTicks = [];// Create the array ahead of time. this.createGame(); . egret.startTick(this.onTick, this); . } createGame() { ... const background =new Background();// Use frame loop to move background loop
    this._IOnTicks.push(background);// Save to array
    this.addEnemy();// Add an enemy plane
}

addEnemy() {
    ...
    const enemy = new Enemy();// We instantiate EnemyAI in the Enemy class
    this._IOnTicks.push(enemy.AI);// Save to array
}

onTick() {
    this._IOnTicks.forEach(val= > val.onTick());// Execute the onTick method on all objects that need to be refreshed by frame
    return false;
}
Copy the code

EnemyAI and Background both implement IOnTick interfaces

class EnemyAI extends egret.EventDispatcher implements IOnTick {
    onTick() {
        // Here every frame is executed}}class Background implements IOnTick {
    onTick() {
        // Here every frame is executed}}Copy the code

Get the background moving

To achieve this effect, we need to create two bitmaps of the same background image. We created the utility method cloneImage to clone a bitmap. Pass an egret.Bitmap to this method, and you’ll get an egret.Bitmap that is exactly the same.

// cloneImage API
const cloneImage: (bitmap: egret.Bitmap) = > egret.Bitmap
Copy the code
 createGame() {
    const [bg, hero, enemy] = this._bitmaps;
    this.addChild(bg);
    const bg2 = cloneBitmap(bg);
    this.addChild(bg2);// Add the clone background image to the stage as well. const background =newBackground(bg, bg2); . }Copy the code

Finally, let’s look at how the Background class implements the Background loop.

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;// Every frame the background will be moved down 8px
        this._bg.y += SPEED;
        this._bg2.y += SPEED;
        const height = this._bg.height;// The background moves alternately
        if (this._bg.y > height) {
            this._bg.y = this._bg2.y - height;
        }
        if (this._bg2.y > height) {
            this._bg2.y = this._bg.y - height; }}}Copy the code

Get the enemy on the move

To generate one enemy per second, we must have a template bitmap of the enemy. In addition, when an enemy plane moves off-screen, we destroy it completely. We do the following design:

  • EnemyAIClass is responsible for the algorithm for enemy aircraft moving off-screen and broadcasting when moving off-screenonEnemyDisappearEvents.
  • EnemyClass is responsible for destroying enemy aircraft from the display layer.
  • Main.removeEnemyMethod responsible forEnemyAIObject Remove frame refresh list to avoid unnecessary calculation.
createGame() {
    const [bg, hero, enemy] = this._bitmaps; . this._enemyTemplate = enemy;// Save the enemy template
    setInterval((a)= > this.addEnemy(), 1000);// Generate an enemy aircraft every 1000ms
}

addEnemy() {
    const enemyImage = cloneImage(this._enemyTemplate);// Clone the picture of enemy aircraft
    this.addChild(enemyImage);
    this.centerAnchor(enemyImage);
    const enemy = new Enemy(enemyImage);
    enemy.AI.once('onEnemyDisappear'.this.onEnemyDisappear, this);// Listen for the broadcast event of enemy aircraft moving off screen
    this._IOnTicks.push(enemy.AI);
}

onEnemyDisappear(e: egret.Event) {
    const AI = e.currentTarget as EnemyAI;
    AI.enemy.destroy();// Destroy enemy aircraft from display layer
    this.removeEnemy(AI);// Remove EnemyAI objects from the frame refresh list to avoid unnecessary calculations.
}

removeEnemy(enemyAI: EnemyAI) {
    const index = this._IOnTicks.indexOf(enemyAI);
    this._IOnTicks.splice(index, 1);// Remove frame refresh object list
}
Copy the code

The Enemy class is responsible for destroying Enemy aircraft from the display layer

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);// Instantiate enemy AI
    }

    removeImage() {
        this.image.parent.removeChild(this.image);// Remove from the display layer
    }

    destroy() {
        this.removeImage(); }}Copy the code

EnemyAI class

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(a);this._image = enemy.image;
        this.enemy = enemy;

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

        this.setInitialPosition();// Set the initial position of enemy aircraft
    }

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

    onTick() {
        this._image.y += 5; // Every frame the enemy will move down 5px
        if (this._image.y > this._image.stage.stageHeight) { // Determine whether to move out of the screen
            // Broadcast an off-screen event
            this.dispatchEvent(new egret.Event('onEnemyDisappear')); }}}Copy the code

Running effect