[PlayCanvas] Entityの配列管理

は、するなというお話。

# なぜか
ジャンルにもよりますが、ゲームでは大量に同一のオブジェクトを生成してそれを使い回す設計をする事があります。
UnityではListに参照をpushしていき、DisableになってるオブジェクトをEnableにしてパラメータを調整する事で再使用を実現します。
そして不要になったらDestroyし、RemoveAllなどでnullになった項目を削除すればよいでしょう。
PlayCanvasでも同じ設計で管理をしようとして私はやらかしました。(経験の浅さ故のミス)

# 例
仮に5つクローンして、2つだけ不要になった場合を想定します。

``` javascript
var Manager = pc.createScript('manager');

Manager.attributes.add('tmpl', {type: 'entity'});
Manager.attributes.add('entity_list', {type: 'entity', array: true});

Manager.prototype.initialize = function() {
    [...Array(5)].map(( element, index )=>{
        var clone = this.tmpl.clone();
        clone.name = "Clone_" + index.toString();
        this.entity_list.push( clone );
    });

    this.listup('--- クローン直後 ---'); // 1

    this.entity_list.map(( element, index )=>{
        if( index > 2 ){
            element.destroy();
        }
    });

    // nullまたはundefinedになる事を期待して、空でない要素だけの配列を作る
    this.entity_list = this.entity_list.filter(( element )=>{
        return element;
    });

    this.listup('--- デストロイ後 ---'); // 2
};

Manager.prototype.listup = function( message ) {
    console.log( message );
    this.entity_list.map(( element )=>{
        console.log( element.name );
    });
};
```

```
--- クローン直後 ---
Clone_0
Clone_1
Clone_2
Clone_3
Clone_4
--- デストロイ後 ---
Clone_0
Clone_1
Clone_2
Clone_3
Clone_4
```
クローンしたものをリストに登録し、あとから2つDestroyして中身を確認すると、Destroyしたはずの2つが残っているのがわかります。

# どうするか
現状詳しく調べていないので詳細は省きますが、先の方法は採用できません。
Destroyする時にどの要素が消えたのかを通知して、いちいち配列から外していくという方法もありますが、正直面倒です。
とはいえ使いまわしたりしたい場合は何かしらでEntityを観測する必要があるので、シンプルな方法で解決します。
そもそも配列で管理せず、なにかしらの子Entityにすることです。

``` javascript
var Manager = pc.createScript('manager');

Manager.attributes.add('tmpl', {type: 'entity'});

Manager.prototype.initialize = function() {
    [...Array(5)].map(( element, index )=>{
        var clone = this.tmpl.clone();
        clone.name = "Clone_" + index.toString();
        this.entity.addChild( clone );
    });

    this.listup('--- クローン直後 ---');

    this.entity.children.map(( element, index )=>{
        if( index > 2 ){
            element.destroy();
        }
    });

    this.listup('--- デストロイ後 ---');
};
```

```
--- クローン直後 ---
Clone_0
Clone_1
Clone_2
Clone_3
Clone_4
--- デストロイ後 ---
Clone_0
Clone_1
Clone_2
```

この方法であれば細かく処理を書く必要もありません。
ただし、親のEntityの設定に注意する必要はあります。

# どうしても実装したい
「いやEntityの構造的に無理」ってなった場合。(Entityの構造を見直したほうがいいと思うが)こればかりは実装する必要があるので、覚書程度に。

``` javascript
var Manager = pc.createScript('manager');

Manager.attributes.add('entity_list', {type: 'entity', array: true});

Manager.prototype.destroyUnuseEntites = function() {
    /* 現在進行系で有効なEntityを後ろに寄せる */
    this.entity_list.sort(( element ) => element.enabled ? 1 : -1 );

    /* 配列が空で無いこと、そして先頭がDisableな時にのみ処理する */
    while( this.entity_list.length > 0 && this.entity_list[0].enabled === false ){
        /* Shiftで除去しつつ、Entityを取り出す */
        var entity = this.entity_list.shift();
        /* 取り出したEntityをDestroyする */
        entity.destroy();
    }
};
```
使ってないものを全て削除するという実装。

「あまりにもユニークすぎるので再使用できず、Disableする意味がない」という場合もあると思う。その場合は生成時にEntityに一意のIDを発行するなりして、削除時にfindIndexで要素を検索→Destroyという手法を取ればいい。

``` javascript
var Manager = pc.createScript('manager');

Manager.attributes.add('entity_list', {type: 'entity', array: true});

Manager.prototype.destroyEntity = function(id) {
    /* IDと一致するものを探す(今回は仮にEntity名をIDとして使用するとする) */
    var index = this.entity_list.findIndex((element) => element.name === id );

    /* 要素が見つかればDestroyと除去を行う */
    if( index >= 0 ){
        this.entity_list[index].destroy();
        this.entity_list = this.entity_list.filter((element) => element.name != id );
    }
};
```

# おわりに
思った以上にUnityライクに書けるので、そのノリのまま書いたらやらかしたって話でした。