EgretEngineでRPGマップはどう実現するか？(3) 表示処理

前回の記事「[EgretEngineでRPGマップはどう実現するか？(2) 表示処理が呼び出されるまで](https://qiita.com/motoyasu-yamada/items/f68676073a16e7845f7a)」の続きです

# RpgGameView

前回の調査した通り下記の手順で呼び出されます

- loadResource
- initUI
- initData
- open

## loadResource

このメソッドは`RpgGameView`では実装されておらず親クラスの`BaseSpriteView`で実装されています

```ts:BaseSpriteView.ts
    public loadResource(loadComplete:Function, initComplete:Function):void {
        if (this._resources && this._resources.length > 0) {
            App.ResourceUtils.loadResource(this._resources, [], function ():void {
                loadComplete();
                initComplete();
            }, null, this);
        }
        else {
            loadComplete();
            initComplete();
        }
    }
```

`this._resources`は、`setResources`で設定されますが、実際には`setResources`は呼び出されていないので、`loadResources`処理は何もせずに`loadComplete`と`initComplete`を呼び出します。

```ts:BaseSpriteView.ts
    public setResources(resources:string[]):void {
        this._resources = resources;
    }
```

## initUIとinitData

`initUI`と`initData`をみると`RpgBackground`がくさいですが後にまわします。

```ts:RpgGameView.ts
    public initUI(): void {
        super.initUI();

        this.background = new RpgBackground();
        this.addChild(this.background);

        this.gameObjcetLayer = new egret.DisplayObjectContainer();
        this.addChild(this.gameObjcetLayer);

        this.gameEffectLayer = new egret.DisplayObjectContainer();
        this.addChild(this.gameEffectLayer);
    }

    public initData(): void {
        super.initData();

        this.monsters = [];
    }
```

## open

`gameModel`は、`RpgGameController`で`this.gameModel.mapId = mapId;`のように初期化しています。

`initBackground`と`initBlocks`に`mapId`が引き渡されているのでここをさらに追いかけます。

```ts:RpgGameView.ts

    public open(...param: any[]): void {
        super.open(param);

        var gameModel: RpgGameModel = param[0];

        this.initBackground(gameModel.mapId);
        this.initBlocks(gameModel.mapId);
        this.createPlayer(gameModel.playerData);
        this.createMonsters(gameModel.monsterNum);
    }
```

## initBackground と initBlocks

`RpgBackground.init`メソッドで背景を初期化しつつ、`initBlocks`でマップデータを読み込んでいます。

```ts:RpgGameView.ts
    private initBackground(mapId: number): void {
        this.background.init(mapId);
    }

    private initBlocks(mapId: number): void {
        var mapData: any = RES.getRes("map_" + mapId + "_data.json");
        this.blocksData = mapData.blocks;
    }
```

先ほどのリソース`"map_" + mapId + "_data.json"`は、前回おっかけた`RpgTest`クラスで動的に定義したものです。

```ts:RpgTest.ts
    private initMapResource(): void {
        var mapResPath: string = "resource/assets/rpgGame/map/" + this.mapId + "/";
:
        var mapRes: any[] = [
            {
                name: "data.json",
                type: "json"
            },
            {
                name: "mini.jpg",
                type: "image"
            }
        ];
```

## RpgBackground.init

`RpgBackground`の初期化処理は、マップデータ`map_<mapId>_data.json`と、ミニマップ画像`map_<mapId>_mini.png`を読み込んでいます。

そして`RpgTiles`クラスが実際にタイルを読み込み管理する処理のようです。

```ts:RpgBackground.ts
    public init(mapId: number) {
        this.mapId = mapId;

        var mapData: any = RES.getRes("map_" + mapId + "_data.json");
        this.mapWidth = mapData.width;
        this.mapHeight = mapData.height;

        this.miniBg = new egret.Bitmap();
        this.miniBg.texture = RES.getRes("map_" + mapId + "_mini.jpg");
        this.miniBg.width = this.mapWidth;
        this.miniBg.height = this.mapHeight;
        this.addChild(this.miniBg);

        this.tiles = new RpgTiles();
        this.tiles.init(mapId);
        this.addChild(this.tiles);
    }
```

## RpgTiles

`RpgTiles.init`ではマップ定義ファイル(data.json)から縦と横のタイルの数を計算しています。


```ts:RpgTiles.ts
    public init(mapId: number): void {
        this.mapId = mapId;
        var mapData: any = RES.getRes("map_" + mapId + "_data.json");

        this.cols = Math.floor(mapData.width / RpgGameData.GameTileWidth);
        this.rows = Math.floor(mapData.height / RpgGameData.GameTileHeight);
    }
```

## RpgTiles.updateCameraPos

そして`RpgTiles.updateCameraPos`が、お目当ての処理のようです。

`updateCameraPos($x: number, $y: number)`の`x`と`y`が移動後のカメラの位置ですね。

```ts:RpgTiles.ts
class RpgTiles extends egret.DisplayObjectContainer {
:
    public updateCameraPos($x: number, $y: number): void {
```

カメラ位置に該当するタイルの行と列を求めます。

```ts
        var currCol: number = Math.round($x / RpgGameData.GameTileWidth);
        var currRow: number = Math.round($y / RpgGameData.GameTileHeight);
```

画面に表示されるタイルの行数と列数をもとめます

```ts
        var screenCols: number = Math.ceil(App.StageUtils.getWidth() / RpgGameData.GameTileWidth) + 1;
        var screenRows: number = Math.ceil(App.StageUtils.getHeight() / RpgGameData.GameTileHeight) + 1;

        var halfScreenCols: number = Math.ceil(screenCols / 2);
        var halfScreenRows: number = Math.ceil(screenRows / 2);
```

次に、

画面の左端`minCol`と右端`maxCol`
画面の上端`minRow`と下端`maxRow`

を求めます

```ts
        var minCol: number = currCol - halfScreenCols;
        var maxCol: number = currCol + halfScreenCols;
:
        var minRow: number = currRow - halfScreenRows;
        var maxRow: number = currRow + halfScreenRows;
```

画面のタイル1つは`RpgTile`というクラスで実装されています。

画面に表示されるべきすべてのタイルの`RpgTile`が生成されていなければ新規に作成します

```ts
        var screenTiles = [];
        for (var i = minCol; i <= maxCol; i++) {
            for (var j = minRow; j <= maxRow; j++) {
                var tileKey: string = i + "_" + j;
                var tile: RpgTile = this.tiles[tileKey];
                if (!tile) {
                    tile = new RpgTile();
                    tile.init(this.mapId, i, j);
                    this.tiles[tileKey] = tile;
                }
                if (!tile.parent) {
                    this.addChild(tile);
                }
                screenTiles.push(tileKey);
            }
        }
```

カメラの移動前に表示していたタイルのうち表示領域外にでたタイルを削除します

```ts
        this.screenTiles.forEach(function (tileKey: string) {
            if (screenTiles.indexOf(tileKey) == -1) {
                var tile: RpgTile = this.tiles[tileKey];
                tile && App.DisplayUtils.removeFromParent(tile);
            }
        }.bind(this));
```

updateCameraPosの全体像

```ts:RpgTiles.ts
class RpgTiles extends egret.DisplayObjectContainer {
:
    public updateCameraPos($x: number, $y: number): void {
        var currCol: number = Math.round($x / RpgGameData.GameTileWidth);
        var currRow: number = Math.round($y / RpgGameData.GameTileHeight);

        var screenCols: number = Math.ceil(App.StageUtils.getWidth() / RpgGameData.GameTileWidth) + 1;
        var screenRows: number = Math.ceil(App.StageUtils.getHeight() / RpgGameData.GameTileHeight) + 1;

        var halfScreenCols: number = Math.ceil(screenCols / 2);
        var halfScreenRows: number = Math.ceil(screenRows / 2);

        var minCol: number = currCol - halfScreenCols;
        var maxCol: number = currCol + halfScreenCols;
        if (minCol < 0) {
            maxCol += -minCol;
            minCol = 0;
        }
        if (maxCol > this.cols) {
            minCol -= (maxCol - this.cols);
            maxCol = this.cols;
        }
        var minRow: number = currRow - halfScreenRows;
        var maxRow: number = currRow + halfScreenRows;
        if (minRow < 0) {
            maxRow += -minRow;
            minRow = 0;
        }
        if (maxRow > this.rows) {
            minRow -= (maxRow - this.rows);
            maxRow = this.rows;
        }

        var screenTiles = [];
        for (var i = minCol; i <= maxCol; i++) {
            for (var j = minRow; j <= maxRow; j++) {
                var tileKey: string = i + "_" + j;
                var tile: RpgTile = this.tiles[tileKey];
                if (!tile) {
                    tile = new RpgTile();
                    tile.init(this.mapId, i, j);
                    this.tiles[tileKey] = tile;
                }
                if (!tile.parent) {
                    this.addChild(tile);
                }
                screenTiles.push(tileKey);
            }
        }

        //移除不在屏幕内的格子
        this.screenTiles.forEach(function (tileKey: string) {
            if (screenTiles.indexOf(tileKey) == -1) {
                var tile: RpgTile = this.tiles[tileKey];
                tile && App.DisplayUtils.removeFromParent(tile);
            }
        }.bind(this));
        this.screenTiles = screenTiles;
    }
:
}
```

# まとめ

次回は `updateCameraPos`が、どう呼ばれるのかについて確認したいと思います。