二维游戏使用参考

概述

本参考将会实现一个大量渲染元素,移动增删,执行帧动画的用例,案例素材来自kenney.nl

背景与基础UI布局

参考UI系统使用指引,实现背景地图以及基础的摇杆,人物之类的U功能。

用户开发脚本的引入

详情可参考 脚本开发 文档

import engine from "engine"

// 基本的ScriptComponent
@engine.decorators.serialize("Main")
export default class Main extends engine.Script {
	public onStart(): void {}
	public onEnable(): void {}
	public onDisable(): void {}
	public onUpdate(): void {}
}

将开发的Typescript文件放置于/assets目录或子目录下,然后在对应节点挂载需要的ScriptComponet,运行时会与普通组件一致,根据生命周期执行逻辑。

2DGame1.png

资源绑定与加载

详情可参考 资源系统 文档

将需要使用的素材加入到工程中。

2DGame2.png

在onStart阶段,加载需要的所有资源,可以利用loadTask上的currenttotal或根据回调统计,开发进度条,详情可以参考LoadTask的使用。

// 根据资源路径加载资源,缓存入对应的SpriteFrame组中。
const task1 = engine.loader.load("gameTexture/zombie.spriteframe");
task1.promise.then(spriteFrame => {
    this.zombieActionSpriteFrames[0] = spriteFrame as engine.SpriteFrame;
    this.loadedNumber++;
});

加载完毕后,update节点创建Zombie节点并挂到场上,场上active节点会马上进入当帧的渲染逻辑。

// Zombie类的内部实现
export class Zombie extends Charater {
    constructor() {
        super();
        // 创建二维节点
        this.entity = engine.game.createEntity2D('zombie' + zombieIndex);
        // 二维节点大小设置
        this.entity.transform2D.size = tmpVec2.setValue(this.width, this.height);
        // 二维节点添加UISripte组件用于图片绘制
        this.sprite = this.entity.addComponent(engine.UISprite) as engine.UISprite;
        zombieIndex++;
    }
    public setStartPosition(x: number, y: number) {
        this.entity.transform2D.position = tmpVec2.setValue(x, y);
    }
    public setStartSpriteFrame(spriteFrame: engine.SpriteFrame) {
        this.startSpriteFrame = spriteFrame;
        this.sprite.spriteFrame = spriteFrame;
    }
    public setSpriteFrame(spriteFrame: engine.SpriteFrame) {
        this.sprite.spriteFrame = spriteFrame;
    }
    public setAnimationSpriteFrame(spriteFrames: engine.SpriteFrame[]) {
        this.animationFrames = spriteFrames;
    }
}
const addZombieNumber = 20;
for (let i = 0; i < addZombieNumber; i++ ) {
    // Zombie起始位置,基于人物位置的一个区域内的随机位置
    const x = this.playerTransform2D.positionX + Math.ceil(Math.random() * this.areaX - this.areaX/2);
    const y = this.playerTransform2D.positionY + Math.ceil(Math.random() * this.areaY - this.areaY/2);
    // 创建Zombie类
    const zombie = new Zombie();
    zombie.setStartPosition(x, y);
    zombie.setStartSpriteFrame(this.zombieActionSpriteFrames[0]);
    zombie.setAnimationSpriteFrame(this.zombieActionSpriteFrames);
    // 将Zombie元素加到场上
    this.worldTransform2D.addChild(zombie.entity.transform2D);

    this.zombies.push(zombie);
}

渲染元素帧动画管理

运行阶段,或根据需要,在额外的计时器内统一执行需要帧动画切换的元素即可。

// SpriteFrame切换
public updateSpriteFrame() {
    if (this.useFrameAniamtion) {
        const nextFrameIndex = (this.frameIndex + 1) % this.animationFrames.length;
        this.setSpriteFrame(this.animationFrames[nextFrameIndex]);
        this.frameIndex = nextFrameIndex;
    } else {
        if (this.sprite.spriteFrame !== this.startSpriteFrame) {
            this.setSpriteFrame(this.startSpriteFrame);
        }
    }
}
// 动画计时器
this.animationTimer = setInterval(() => {
    for (let i = 0; i < this.zombies.length; i++) {
        const zombie = this.zombies[i];
        zombie.updateSpriteFrame();
    }
    this.player.updateSpriteFrame();
}, 160);

节点添加销毁与移动

public updatePosition(tx: number, ty: number) {
    const disX = tx - this.entity.transform2D.positionX;
    const disY = ty - this.entity.transform2D.positionY;
    const absDx = Math.abs(disX);
    const absDy = Math.abs(disY);

    const widthArea = this.width * 4 / 5;
    const heightArea = this.height * 4 / 5;

    if (absDx <= widthArea && absDy <= heightArea) {
        // 销毁节点
        this.entity.destroy();

        return true;
    } else {
        // ... 
    }
}

摇杆与触摸事件

编写对应的ScriptComponent,并绑定到摇杆节点,然后配合TouchInputComponent组件使用即可。

2DGame3.png

import engine from "engine";
declare type TouchInputEvent = import("engine/input/touch").TouchInputEvent;
declare type Entity2D = import("engine/scene/scene").Entity2D;

@engine.decorators.serialize("Control")
export default class Control extends engine.Script {
  constructor(public readonly entity: Entity2D) {
    super(entity);
    this.touchStartHandler = this.touchStartHandler.bind(this);
    this.touchEndHandler = this.touchEndHandler.bind(this);
    this.touchMoveHandler = this.touchMoveHandler.bind(this);
    this.touchCancelHandler = this.touchCancelHandler.bind(this);
  }

  public onEnable(): void {
    // ...
  }

  public onDisable(): void {
    // ...
  }

  public touchStartHandler(t: engine.TouchInputComponent, e: TouchInputEvent): void {
    // ...
  }

  public touchEndHandler(t: engine.TouchInputComponent, e: TouchInputEvent): void {
    // ...
  }

  public touchMoveHandler(t: engine.TouchInputComponent, e: TouchInputEvent): void {
    // ...
  }

  public touchCancelHandler(t: engine.TouchInputComponent, e: TouchInputEvent): void {
      // ...
  }

}

最终效果

2DGame-last.png