【Egret無料ご相談室】 EgretのDisplayObjectをちょいUnity Likeにする

# 話の発端

![image.png](https://qiita-image-store.s3.amazonaws.com/0/302078/78a3dfbd-9b4e-08be-172b-fcdb47020664.png)

元のご相談簡単なソースコードをいただきました

https://github.com/squmari/EgretEngine/tree/master/All_Stop_With_One_Click

↓
https://github.com/motoyasu-yamada/egret-unity-demo

# Before
下記のようにすべてのオブジェクトに `egret.startTick`を設定していました

```ts
class CreateImage extends  egret.DisplayObjectContainer {
:
        public constructor() {
        super();
        this.once(egret.Event.ADDED_TO_STAGE, this.generateEgretImage, this);
    }

   :
    public generateEgretImage(event:egret.Event): void {

        // 画像の生成
        this.egretImage = this.createBitmapByName("egret_icon_png");
        this.egretMoveFlag = true;

        //Enable touchEvent
        this.egretImage.touchEnabled =true;
        //this.egretImage.pixelHitTest = true;
        this.addChild(this.egretImage);

        this.egretImage.x = 100;
        this.egretImage.y = 100 + Math.floor( Math.random() * 300 );

        //画面にタッチした瞬間にtouchMethodを実行
        this.egretImage.addEventListener( egret.TouchEvent.TOUCH_BEGIN, this.egretTouch, this );

        egret.startTick(this.moveEgret, this);
    }
:
}
```

# After

`MonoBehaviour`という共通なクラスを作ってあげて

```ts
export class MonoBehaviour<T extends egret.DisplayObject> 
{ 
    private started : boolean = false;
    protected $egretDisplayObject : T;

    isStarted(): boolean {
        return this.started;
    }

    setStarted() : void {
        if(this.started !== false) {
            throw new Error("MonoBehaviour has been started already");
        }
        this.started = true;
    }

    egretDisplayObject() : T {
        return this.$egretDisplayObject;
    }

    async Start() : Promise<void> {};

    Update() : void {}
}
```

多数のオブジェクト(MonoBehaviourクラスを継承した)を管理する`Scene`クラスに引き渡して`Scene`クラスで一括管理します。

```ts:Main.ts
    private createAndStart() {
        const scene = new Scene(this);
        scene.add(new MyImageObject());
```

フレーム呼び出しは以下の一回だけ設定

```ts:Main.ts
    private createAndStart() {
:
        egret.startTick(() => { scene.doFrame(); return true; }, this);
    }
```

SceneクラスでMonobehaviourクラスの初期化(Start呼び出し)と、フレーム毎の更新処理(Update呼び出し）を行います。

```ts:Scene.ts
import { MonoBehaviour } from "./MonoBehaviour";

export class Scene {
    private egretDisplayObjectContainer : egret.DisplayObjectContainer;
    private monoBehaviours: MonoBehaviour<egret.DisplayObject>[] = [];

    constructor(egretDisplayObjectContainer: egret.DisplayObjectContainer) {
        this.egretDisplayObjectContainer = egretDisplayObjectContainer;
    }

    public remove(o : MonoBehaviour<egret.DisplayObject>) {
        this.egretDisplayObjectContainer.removeChild(o.egretDisplayObject());
        this.monoBehaviours = this.monoBehaviours.filter (e => e !== o);
    }

    public add(o: MonoBehaviour<egret.DisplayObject>) {
        this.egretDisplayObjectContainer.addChild(o.egretDisplayObject());
        this.monoBehaviours.push(o);
    }

    public doFrame() {
        const toBeStarted = this.monoBehaviours.filter(e => !e.isStarted());
        Promise.all(toBeStarted.map(e => e.Start()));
        toBeStarted.forEach(e => e.setStarted());

        this.monoBehaviours.forEach(e => e.Update());
    }
}
```

# おまけ

物理エンジン`p2`とゲームエンジンの表示エンジンをまとめて管理するための上記とは別な例はこちらです

https://github.com/motoyasu-yamada/BasketBallBoy/tree/master/src

`GameObject`と`GameWorld`と`PhysicsObject`クラスをご自由にお使いください。

こんな風に使います

```ts:GameMain.ts
const BASKET_SIZE_METER = 0.15;
const BALL_SIZE_METER = 0.6;
const FPS = 60;

class GameMain 
{
    public static destroy() 
    {
        GameWorld.destroy();
    }

    public static start(stage:egret.Stage, onGameOver:()=>void) 
    {
		const ballPixel = egret.MainContext.instance.stage.stageWidth / 10;
		GameWorld.init(ballPixel / BALL_SIZE_METER, stage);

		new Sky();

		new Ground();
		new TimeGauge(30);
        new ScoreUi();
        const ball = new Ball();

		stage.addEventListener(egret.TouchEvent.TOUCH_BEGIN, (e: egret.TouchEvent) => ball.up(), this);

		const basketManager = new BasketManager();

        GameWorld.on("gameover",() => {
            SoundManager.playGameOver();
            GameWorld.pause();
            onGameOver();
        });

        GameWorld.start();
    }

}
```