[tmlib.js]tmlibで学ぶ、イベント駆動型のクラス設計

## tmlibで学ぶ、イベント駆動型のクラス設計

なんて難しそうなこといってますが…ゲーム開発をやっていて、後で即死するパターンとかそういうののメモです。

まずはモデルとなるソースコードを貼ります。
ゲームの動作を書く`GameScene`クラスと、今回はサンプルとしてエフェクトを書く`Effect`クラスを作ります。仕様を追加する際に、汎用性のある(というか、ある程度の仕様変更に耐えられる)`Effect`クラスを作っていきます。

```js

var SCREEN_WIDTH  = 640;
var SCREEN_HEIGHT = 960;
var SCREEN_CENTER_X = SCREEN_WIDTH/2;
var SCREEN_CENTER_Y = SCREEN_HEIGHT/2;

tm.main(function() {
    var app = tm.display.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT);
    app.fitWindow();
    app.replaceScene(GameScene());
    app.run();
});


tm.define("GameScene", {
    superClass: "tm.app.Scene",
    
    init: function() {
        var self = this;
        self.superInit();
        
        self.fromJSON({
            children: {
                "label": {
                    type: "tm.display.Label",
                    init: ["タッチするとエフェクトが出るよ"],
                    x: SCREEN_CENTER_X,
                    y: 800,
                }
            },
        });
        
        // エフェクト作成
        self.onpointingstart = function () {
            Effect().addChildTo(self);
        };
    },
    
    update: function () {
        var self = this;
        // 3秒だったらゲームオーバー
        if (self.app.frame > 30*2) {
            self.gameOver();
            self.update = function () {};
        }
    },
    // ゲームオーバーシーンへの遷移など
    gameOver: function () {
        var self = this;
        tm.display.Label("GAME OVER")
            .setPosition(SCREEN_CENTER_X, SCREEN_CENTER_Y)
            .setFontSize(40)
            .addChildTo(self);
    },
});

tm.define("Effect", {
    superClass: "tm.display.CanvasElement",
    
    init: function () {
        var self = this;
        self.superInit();
        self.startEffect();
    },
    
    startEffect: function () {
        var self = this;
        // 10個作る
        (10).times(function (i) {
            var effect = tm.display.RectangleShape(30,30)
                .addChildTo(self)
                .setPosition(SCREEN_CENTER_X, SCREEN_CENTER_Y);
            effect.tweener.clear()
                .to({
                    x: tm.util.Random.randint(-100, SCREEN_WIDTH+100),
                    y: tm.util.Random.randint(-100, SCREEN_HEIGHT+100),
                    alpha: 0,
                }, 600)
                .call(function () {
                    effect.remove();
                });
        });
    },
});
```

動作するサンプルは[こちら](http://phi-jp.github.io/runstant/release/alpha/#UEsDBAoAAAAAAJpyiEVblx+SzQwAAM0MAAAEAAAAZGF0YXsidmVyc2lvbiI6IjAuMC4xIiwiY3VycmVudCI6InNjcmlwdCIsInNldHRpbmciOnsidGl0bGUiOiJ0ZW1wbGF0ZSAtIHRtbGliLmpzIiwiZGV0YWlsIjoidG1saWIuanMg55So5YWs5byP44Ko44OH44Kj44K/LiDoibLjgIXjgajkvb/jgYjjgb7jgZnjgojimaoifSwiY29kZSI6eyJodG1sIjp7InR5cGUiOiJodG1sIiwidmFsdWUiOiI8IURPQ1RZUEUgaHRtbD5cbiBcbjxodG1sPlxuICAgIDxoZWFkPlxuICAgICAgICA8bWV0YSBjaGFyc2V0PVwiVVRGLThcIiAvPlxuICAgICAgICA8bWV0YSBuYW1lPVwidmlld3BvcnRcIiBjb250ZW50PVwid2lkdGg9ZGV2aWNlLXdpZHRoLCB1c2VyLXNjYWxhYmxlPW5vXCIgLz5cbiAgICAgICAgPG1ldGEgbmFtZT1cImFwcGxlLW1vYmlsZS13ZWItYXBwLWNhcGFibGVcIiBjb250ZW50PVwieWVzXCIgLz5cbiBcbiAgICAgICAgPHRpdGxlPiR7dGl0bGV9PC90aXRsZT5cbiAgICAgICAgPG1ldGEgbmFtZT1cImRlc2NyaXB0aW9uXCIgY29udGVudD1cIiR7ZGVzY3JpcHRpb259XCIgLz5cblxuICAgICAgICA8c3R5bGU+JHtzdHlsZX08L3N0eWxlPlxuXG4gICAgICAgIDxzY3JpcHQgc3JjPVwiaHR0cDovL3Jhd2dpdGh1Yi5jb20vcGhpLWpwL3RtbGliLmpzL2Q4MTk2Y2M1ZTYxOTk3OTE0NmE0MTIyNjA4YjNiMzIzYjNjZWJkMTMvYnVpbGQvdG1saWIuanNcIj48L3NjcmlwdD5cblx0XHQ8c2NyaXB0PiR7c2NyaXB0fTwvc2NyaXB0PlxuICAgIDwvaGVhZD5cbiAgICA8Ym9keT5cbiAgICAgICAgPGNhbnZhcyBpZD1cIndvcmxkXCI+PC9jYW52YXM+XG4gICAgPC9ib2R5PlxuPC9odG1sPiJ9LCJzdHlsZSI6eyJ0eXBlIjoiY3NzIiwidmFsdWUiOiJib2R5IHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZWVlO1xufSJ9LCJzY3JpcHQiOnsidHlwZSI6ImphdmFzY3JpcHQiLCJ2YWx1ZSI6IlxudmFyIFNDUkVFTl9XSURUSCAgPSA2NDA7XG52YXIgU0NSRUVOX0hFSUdIVCA9IDk2MDtcbnZhciBTQ1JFRU5fQ0VOVEVSX1ggPSBTQ1JFRU5fV0lEVEgvMjtcbnZhciBTQ1JFRU5fQ0VOVEVSX1kgPSBTQ1JFRU5fSEVJR0hULzI7XG5cbnRtLm1haW4oZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFwcCA9IHRtLmRpc3BsYXkuQ2FudmFzQXBwKFwiI3dvcmxkXCIpO1xuICAgIGFwcC5yZXNpemUoU0NSRUVOX1dJRFRILCBTQ1JFRU5fSEVJR0hUKTtcbiAgICBhcHAuZml0V2luZG93KCk7XG4gICAgYXBwLnJlcGxhY2VTY2VuZShHYW1lU2NlbmUoKSk7XG4gICAgYXBwLnJ1bigpO1xufSk7XG5cblxudG0uZGVmaW5lKFwiR2FtZVNjZW5lXCIsIHtcbiAgICBzdXBlckNsYXNzOiBcInRtLmFwcC5TY2VuZVwiLFxuICAgIFxuICAgIGluaXQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHNlbGYuc3VwZXJJbml0KCk7XG4gICAgICAgIFxuICAgICAgICBzZWxmLmZyb21KU09OKHtcbiAgICAgICAgICAgIGNoaWxkcmVuOiB7XG4gICAgICAgICAgICAgICAgXCJsYWJlbFwiOiB7XG4gICAgICAgICAgICAgICAgICAgIHR5cGU6IFwidG0uZGlzcGxheS5MYWJlbFwiLFxuICAgICAgICAgICAgICAgICAgICBpbml0OiBbXCLjgr/jg4Pjg4HjgZnjgovjgajjgqjjg5Xjgqfjgq/jg4jjgYzlh7rjgovjgohcIl0sXG4gICAgICAgICAgICAgICAgICAgIHg6IFNDUkVFTl9DRU5URVJfWCxcbiAgICAgICAgICAgICAgICAgICAgeTogODAwLFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgLy8g44Ko44OV44Kn44Kv44OI5L2c5oiQXG4gICAgICAgIHNlbGYub25wb2ludGluZ3N0YXJ0ID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgRWZmZWN0KCkuYWRkQ2hpbGRUbyhzZWxmKTtcbiAgICAgICAgfTtcbiAgICB9LFxuICAgIFxuICAgIHVwZGF0ZTogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIC8vIDPnp5LjgaDjgaPjgZ/jgonjgrLjg7zjg6Djgqrjg7zjg5Djg7xcbiAgICAgICAgaWYgKHNlbGYuYXBwLmZyYW1lID4gMzAqMikge1xuICAgICAgICAgICAgc2VsZi5nYW1lT3ZlcigpO1xuICAgICAgICAgICAgc2VsZi51cGRhdGUgPSBmdW5jdGlvbiAoKSB7fTtcbiAgICAgICAgfVxuICAgIH0sXG4gICAgLy8g44Ky44O844Og44Kq44O844OQ44O844K344O844Oz44G444Gu6YG356e744Gq44GpXG4gICAgZ2FtZU92ZXI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgICAgICB0bS5kaXNwbGF5LkxhYmVsKFwiR0FNRSBPVkVSXCIpXG4gICAgICAgICAgICAuc2V0UG9zaXRpb24oU0NSRUVOX0NFTlRFUl9YLCBTQ1JFRU5fQ0VOVEVSX1kpXG4gICAgICAgICAgICAuc2V0Rm9udFNpemUoNDApXG4gICAgICAgICAgICAuYWRkQ2hpbGRUbyhzZWxmKTtcbiAgICB9LFxufSk7XG5cbnRtLmRlZmluZShcIkVmZmVjdFwiLCB7XG4gICAgc3VwZXJDbGFzczogXCJ0bS5kaXNwbGF5LkNhbnZhc0VsZW1lbnRcIixcbiAgICBcbiAgICBpbml0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgc2VsZi5zdXBlckluaXQoKTtcbiAgICAgICAgc2VsZi5zdGFydEVmZmVjdCgpO1xuICAgIH0sXG4gICAgXG4gICAgc3RhcnRFZmZlY3Q6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgICAgICAvLyAxMOWAi+S9nOOCi1xuICAgICAgICAoMTApLnRpbWVzKGZ1bmN0aW9uIChpKSB7XG4gICAgICAgICAgICB2YXIgZWZmZWN0ID0gdG0uZGlzcGxheS5SZWN0YW5nbGVTaGFwZSgzMCwzMClcbiAgICAgICAgICAgICAgICAuYWRkQ2hpbGRUbyhzZWxmKVxuICAgICAgICAgICAgICAgIC5zZXRQb3NpdGlvbihTQ1JFRU5fQ0VOVEVSX1gsIFNDUkVFTl9DRU5URVJfWSk7XG4gICAgICAgICAgICBlZmZlY3QudHdlZW5lci5jbGVhcigpXG4gICAgICAgICAgICAgICAgLnRvKHtcbiAgICAgICAgICAgICAgICAgICAgeDogdG0udXRpbC5SYW5kb20ucmFuZGludCgtMTAwLCBTQ1JFRU5fV0lEVEgrMTAwKSxcbiAgICAgICAgICAgICAgICAgICAgeTogdG0udXRpbC5SYW5kb20ucmFuZGludCgtMTAwLCBTQ1JFRU5fSEVJR0hUKzEwMCksXG4gICAgICAgICAgICAgICAgICAgIGFscGhhOiAwLFxuICAgICAgICAgICAgICAgIH0sIDYwMClcbiAgICAgICAgICAgICAgICAuY2FsbChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgIGVmZmVjdC5yZW1vdmUoKTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG4gICAgfSxcbn0pOyJ9fX1QSwECFAAKAAAAAACacohFW5cfks0MAADNDAAABAAAAAAAAAAAAAAAAAAAAAAAZGF0YVBLBQYAAAAAAQABADIAAADvDAAAAAA=)


## 新しい仕様を追加する

ゲームオーバー前にエフェクトを表示して、ゲームオーバーを実行したい(させてほしい)という仕様を追加することになったとします。どう実装するでしょうか？

### 一番ダメなパターン

(動作するサンプルは[こちら](http://phi-jp.github.io/runstant/release/alpha/#UEsDBAoAAAAAAMCUiEXLP7CqAw4AAAMOAAAEAAAAZGF0YXsidmVyc2lvbiI6IjAuMC4xIiwiY3VycmVudCI6InNjcmlwdCIsInNldHRpbmciOnsidGl0bGUiOiJ0ZW1wbGF0ZSAtIHRtbGliLmpzIiwiZGV0YWlsIjoidG1saWIuanMg55So5YWs5byP44Ko44OH44Kj44K/LiDoibLjgIXjgajkvb/jgYjjgb7jgZnjgojimaoifSwiY29kZSI6eyJodG1sIjp7InR5cGUiOiJodG1sIiwidmFsdWUiOiI8IURPQ1RZUEUgaHRtbD5cbiBcbjxodG1sPlxuICAgIDxoZWFkPlxuICAgICAgICA8bWV0YSBjaGFyc2V0PVwiVVRGLThcIiAvPlxuICAgICAgICA8bWV0YSBuYW1lPVwidmlld3BvcnRcIiBjb250ZW50PVwid2lkdGg9ZGV2aWNlLXdpZHRoLCB1c2VyLXNjYWxhYmxlPW5vXCIgLz5cbiAgICAgICAgPG1ldGEgbmFtZT1cImFwcGxlLW1vYmlsZS13ZWItYXBwLWNhcGFibGVcIiBjb250ZW50PVwieWVzXCIgLz5cbiBcbiAgICAgICAgPHRpdGxlPiR7dGl0bGV9PC90aXRsZT5cbiAgICAgICAgPG1ldGEgbmFtZT1cImRlc2NyaXB0aW9uXCIgY29udGVudD1cIiR7ZGVzY3JpcHRpb259XCIgLz5cblxuICAgICAgICA8c3R5bGU+JHtzdHlsZX08L3N0eWxlPlxuXG4gICAgICAgIDxzY3JpcHQgc3JjPVwiaHR0cDovL3Jhd2dpdGh1Yi5jb20vcGhpLWpwL3RtbGliLmpzL2Q4MTk2Y2M1ZTYxOTk3OTE0NmE0MTIyNjA4YjNiMzIzYjNjZWJkMTMvYnVpbGQvdG1saWIuanNcIj48L3NjcmlwdD5cblx0XHQ8c2NyaXB0PiR7c2NyaXB0fTwvc2NyaXB0PlxuICAgIDwvaGVhZD5cbiAgICA8Ym9keT5cbiAgICAgICAgPGNhbnZhcyBpZD1cIndvcmxkXCI+PC9jYW52YXM+XG4gICAgPC9ib2R5PlxuPC9odG1sPiJ9LCJzdHlsZSI6eyJ0eXBlIjoiY3NzIiwidmFsdWUiOiJib2R5IHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZWVlO1xufSJ9LCJzY3JpcHQiOnsidHlwZSI6ImphdmFzY3JpcHQiLCJ2YWx1ZSI6IlxudmFyIFNDUkVFTl9XSURUSCAgPSA2NDA7XG52YXIgU0NSRUVOX0hFSUdIVCA9IDk2MDtcbnZhciBTQ1JFRU5fQ0VOVEVSX1ggPSBTQ1JFRU5fV0lEVEgvMjtcbnZhciBTQ1JFRU5fQ0VOVEVSX1kgPSBTQ1JFRU5fSEVJR0hULzI7XG5cbnRtLm1haW4oZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFwcCA9IHRtLmRpc3BsYXkuQ2FudmFzQXBwKFwiI3dvcmxkXCIpO1xuICAgIGFwcC5yZXNpemUoU0NSRUVOX1dJRFRILCBTQ1JFRU5fSEVJR0hUKTtcbiAgICBhcHAuZml0V2luZG93KCk7XG4gICAgYXBwLnJlcGxhY2VTY2VuZShHYW1lU2NlbmUoKSk7XG4gICAgYXBwLnJ1bigpO1xufSk7XG5cblxudG0uZGVmaW5lKFwiR2FtZVNjZW5lXCIsIHtcbiAgICBzdXBlckNsYXNzOiBcInRtLmFwcC5TY2VuZVwiLFxuICAgIFxuICAgIGluaXQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHNlbGYuc3VwZXJJbml0KCk7XG4gICAgICAgIFxuICAgICAgICBzZWxmLmZyb21KU09OKHtcbiAgICAgICAgICAgIGNoaWxkcmVuOiB7XG4gICAgICAgICAgICAgICAgXCJsYWJlbFwiOiB7XG4gICAgICAgICAgICAgICAgICAgIHR5cGU6IFwidG0uZGlzcGxheS5MYWJlbFwiLFxuICAgICAgICAgICAgICAgICAgICBpbml0OiBbXCLjgr/jg4Pjg4HjgZnjgovjgajjgqjjg5Xjgqfjgq/jg4jjgYzlh7rjgovjgohcIl0sXG4gICAgICAgICAgICAgICAgICAgIHg6IFNDUkVFTl9DRU5URVJfWCxcbiAgICAgICAgICAgICAgICAgICAgeTogODAwLFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgLy8g44Ko44OV44Kn44Kv44OI5L2c5oiQXG4gICAgICAgIHNlbGYub25wb2ludGluZ3N0YXJ0ID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgRWZmZWN0KCkuYWRkQ2hpbGRUbyhzZWxmKTtcbiAgICAgICAgfTtcbiAgICB9LFxuICAgIFxuICAgIHVwZGF0ZTogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIC8vIDPnp5LjgaDjgaPjgZ/jgonjgrLjg7zjg6Djgqrjg7zjg5Djg7xcbiAgICAgICAgaWYgKHNlbGYuYXBwLmZyYW1lID4gMzAqMikge1xuICAgICAgICAgICAgRWZmZWN0KHNlbGYpLmFkZENoaWxkVG8oc2VsZik7XG4gICAgICAgICAgICBzZWxmLnVwZGF0ZSA9IGZ1bmN0aW9uICgpIHt9O1xuICAgICAgICB9XG4gICAgfSxcbiAgICAvLyDjgrLjg7zjg6Djgqrjg7zjg5Djg7zjgrfjg7zjg7Pjgbjjga7pgbfnp7vjgarjgalcbiAgICBnYW1lT3ZlcjogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHRtLmRpc3BsYXkuTGFiZWwoXCJHQU1FIE9WRVJcIilcbiAgICAgICAgICAgIC5zZXRQb3NpdGlvbihTQ1JFRU5fQ0VOVEVSX1gsIFNDUkVFTl9DRU5URVJfWSlcbiAgICAgICAgICAgIC5zZXRGb250U2l6ZSg0MClcbiAgICAgICAgICAgIC5hZGRDaGlsZFRvKHNlbGYpO1xuICAgIH0sXG59KTtcblxudG0uZGVmaW5lKFwiRWZmZWN0XCIsIHtcbiAgICBzdXBlckNsYXNzOiBcInRtLmRpc3BsYXkuQ2FudmFzRWxlbWVudFwiLFxuICAgIFxuICAgIGluaXQ6IGZ1bmN0aW9uIChzY2VuZSkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHNlbGYuZ2FtZVNjZW5lID0gc2NlbmU7XG4gICAgICAgIHNlbGYuc3VwZXJJbml0KCk7XG4gICAgICAgIHNlbGYuc3RhcnRFZmZlY3QoKTtcbiAgICB9LFxuICAgIFxuICAgIHN0YXJ0RWZmZWN0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgdmFyIGNvdW50ZXIgPSAxMDtcbiAgICAgICAgLy8gMTDlgIvkvZzjgotcbiAgICAgICAgKGNvdW50ZXIpLnRpbWVzKGZ1bmN0aW9uIChpKSB7XG4gICAgICAgICAgICB2YXIgZWZmZWN0ID0gdG0uZGlzcGxheS5SZWN0YW5nbGVTaGFwZSgzMCwzMClcbiAgICAgICAgICAgICAgICAuYWRkQ2hpbGRUbyhzZWxmKVxuICAgICAgICAgICAgICAgIC5zZXRQb3NpdGlvbihTQ1JFRU5fQ0VOVEVSX1gsIFNDUkVFTl9DRU5URVJfWSk7XG4gICAgICAgICAgICBlZmZlY3QudHdlZW5lci5jbGVhcigpXG4gICAgICAgICAgICAgICAgLnRvKHtcbiAgICAgICAgICAgICAgICAgICAgeDogdG0udXRpbC5SYW5kb20ucmFuZGludCgtMTAwLCBTQ1JFRU5fV0lEVEgrMTAwKSxcbiAgICAgICAgICAgICAgICAgICAgeTogdG0udXRpbC5SYW5kb20ucmFuZGludCgtMTAwLCBTQ1JFRU5fSEVJR0hUKzEwMCksXG4gICAgICAgICAgICAgICAgICAgIGFscGhhOiAwLFxuICAgICAgICAgICAgICAgIH0sIDYwMClcbiAgICAgICAgICAgICAgICAuY2FsbChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgIGVmZmVjdC5yZW1vdmUoKTtcbiAgICAgICAgICAgICAgICAgICAgLS1jb3VudGVyO1xuICAgICAgICAgICAgICAgICAgICBpZiAoY291bnRlciA8PSAwKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoc2VsZi5nYW1lU2NlbmUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmdhbWVTY2VuZS5nYW1lT3ZlcigpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgIH0pO1xuICAgIH0sXG59KTsifX19UEsBAhQACgAAAAAAwJSIRcs/sKoDDgAAAw4AAAQAAAAAAAAAAAAAAAAAAAAAAGRhdGFQSwUGAAAAAAEAAQAyAAAAJQ4AAAAA))

```js
    // GameSceneクラス
    update: function () {
        var self = this;
        // 3秒だったらゲームオーバー
        if (self.app.frame > 30*2) {
            Effect(self).addChildTo(self);
            self.update = function () {};
        }
    },
```

```js
    // Effectクラス
    init: function (scene) {
        var self = this;
        self.gameScene = scene;
        self.superInit();
        self.startEffect();
    },

    startEffect: function () {
        var self = this;
        var counter = 10;
        // 10個作る
        (counter).times(function (i) {
            var effect = tm.display.RectangleShape(30,30)
                .addChildTo(self)
                .setPosition(SCREEN_CENTER_X, SCREEN_CENTER_Y);
            effect.tweener.clear()
                .to({
                    x: tm.util.Random.randint(-100, SCREEN_WIDTH+100),
                    y: tm.util.Random.randint(-100, SCREEN_HEIGHT+100),
                    alpha: 0,
                }, 600)
                .call(function () {
                    effect.remove();
                    --counter;
                    if (counter <= 0) {
                        self.gameScene.gameOver();
                    }
                });
        });
    },
```

`GameScene`クラスのインスタンスごと、`Effect`のコンストラクタに渡して保持しちゃってます。100％後悔します。もはやオブジェクト指向やり直したら？といわれるレベルです。他人がコードを見たとき追いかけることすら困難になります。相互参照です。でもたまに見ます。(ごめんなさい)

### これももちろんダメ

言わずもがーなー。

(動作するサンプルは[こちら](http://phi-jp.github.io/runstant/release/alpha/#UEsDBAoAAAAAAAyViEUrEmpiEA4AABAOAAAEAAAAZGF0YXsidmVyc2lvbiI6IjAuMC4xIiwiY3VycmVudCI6InNjcmlwdCIsInNldHRpbmciOnsidGl0bGUiOiJ0ZW1wbGF0ZSAtIHRtbGliLmpzIiwiZGV0YWlsIjoidG1saWIuanMg55So5YWs5byP44Ko44OH44Kj44K/LiDoibLjgIXjgajkvb/jgYjjgb7jgZnjgojimaoifSwiY29kZSI6eyJodG1sIjp7InR5cGUiOiJodG1sIiwidmFsdWUiOiI8IURPQ1RZUEUgaHRtbD5cbiBcbjxodG1sPlxuICAgIDxoZWFkPlxuICAgICAgICA8bWV0YSBjaGFyc2V0PVwiVVRGLThcIiAvPlxuICAgICAgICA8bWV0YSBuYW1lPVwidmlld3BvcnRcIiBjb250ZW50PVwid2lkdGg9ZGV2aWNlLXdpZHRoLCB1c2VyLXNjYWxhYmxlPW5vXCIgLz5cbiAgICAgICAgPG1ldGEgbmFtZT1cImFwcGxlLW1vYmlsZS13ZWItYXBwLWNhcGFibGVcIiBjb250ZW50PVwieWVzXCIgLz5cbiBcbiAgICAgICAgPHRpdGxlPiR7dGl0bGV9PC90aXRsZT5cbiAgICAgICAgPG1ldGEgbmFtZT1cImRlc2NyaXB0aW9uXCIgY29udGVudD1cIiR7ZGVzY3JpcHRpb259XCIgLz5cblxuICAgICAgICA8c3R5bGU+JHtzdHlsZX08L3N0eWxlPlxuXG4gICAgICAgIDxzY3JpcHQgc3JjPVwiaHR0cDovL3Jhd2dpdGh1Yi5jb20vcGhpLWpwL3RtbGliLmpzL2Q4MTk2Y2M1ZTYxOTk3OTE0NmE0MTIyNjA4YjNiMzIzYjNjZWJkMTMvYnVpbGQvdG1saWIuanNcIj48L3NjcmlwdD5cblx0XHQ8c2NyaXB0PiR7c2NyaXB0fTwvc2NyaXB0PlxuICAgIDwvaGVhZD5cbiAgICA8Ym9keT5cbiAgICAgICAgPGNhbnZhcyBpZD1cIndvcmxkXCI+PC9jYW52YXM+XG4gICAgPC9ib2R5PlxuPC9odG1sPiJ9LCJzdHlsZSI6eyJ0eXBlIjoiY3NzIiwidmFsdWUiOiJib2R5IHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZWVlO1xufSJ9LCJzY3JpcHQiOnsidHlwZSI6ImphdmFzY3JpcHQiLCJ2YWx1ZSI6IlxudmFyIFNDUkVFTl9XSURUSCAgPSA2NDA7XG52YXIgU0NSRUVOX0hFSUdIVCA9IDk2MDtcbnZhciBTQ1JFRU5fQ0VOVEVSX1ggPSBTQ1JFRU5fV0lEVEgvMjtcbnZhciBTQ1JFRU5fQ0VOVEVSX1kgPSBTQ1JFRU5fSEVJR0hULzI7XG5cbnRtLm1haW4oZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFwcCA9IHRtLmRpc3BsYXkuQ2FudmFzQXBwKFwiI3dvcmxkXCIpO1xuICAgIGFwcC5yZXNpemUoU0NSRUVOX1dJRFRILCBTQ1JFRU5fSEVJR0hUKTtcbiAgICBhcHAuZml0V2luZG93KCk7XG4gICAgYXBwLnJlcGxhY2VTY2VuZShHYW1lU2NlbmUoKSk7XG4gICAgYXBwLnJ1bigpO1xufSk7XG5cblxudG0uZGVmaW5lKFwiR2FtZVNjZW5lXCIsIHtcbiAgICBzdXBlckNsYXNzOiBcInRtLmFwcC5TY2VuZVwiLFxuICAgIFxuICAgIGluaXQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHNlbGYuc3VwZXJJbml0KCk7XG4gICAgICAgIFxuICAgICAgICBzZWxmLmZyb21KU09OKHtcbiAgICAgICAgICAgIGNoaWxkcmVuOiB7XG4gICAgICAgICAgICAgICAgXCJsYWJlbFwiOiB7XG4gICAgICAgICAgICAgICAgICAgIHR5cGU6IFwidG0uZGlzcGxheS5MYWJlbFwiLFxuICAgICAgICAgICAgICAgICAgICBpbml0OiBbXCLjgr/jg4Pjg4HjgZnjgovjgajjgqjjg5Xjgqfjgq/jg4jjgYzlh7rjgovjgohcIl0sXG4gICAgICAgICAgICAgICAgICAgIHg6IFNDUkVFTl9DRU5URVJfWCxcbiAgICAgICAgICAgICAgICAgICAgeTogODAwLFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgLy8g44Ko44OV44Kn44Kv44OI5L2c5oiQXG4gICAgICAgIHNlbGYub25wb2ludGluZ3N0YXJ0ID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgRWZmZWN0KCkuc3RhcnRFZmZlY3QoKS5hZGRDaGlsZFRvKHNlbGYpO1xuICAgICAgICB9O1xuICAgIH0sXG4gICAgXG4gICAgLy8gR2FtZVNjZW5l44Kv44Op44K5XG4gICAgdXBkYXRlOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgLy8gM+enkuOBoOOBo+OBn+OCieOCsuODvOODoOOCquODvOODkOODvFxuICAgICAgICBpZiAoc2VsZi5hcHAuZnJhbWUgPiAzMCoyKSB7XG4gICAgICAgICAgICBFZmZlY3QoKS5zdGFydEVmZmVjdChzZWxmKS5hZGRDaGlsZFRvKHNlbGYpO1xuICAgICAgICAgICAgc2VsZi51cGRhdGUgPSBmdW5jdGlvbiAoKSB7fTtcbiAgICAgICAgfVxuICAgIH0sXG4gICAgXG4gICAgLy8g44Ky44O844Og44Kq44O844OQ44O844K344O844Oz44G444Gu6YG356e744Gq44GpXG4gICAgZ2FtZU92ZXI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgICAgICB0bS5kaXNwbGF5LkxhYmVsKFwiR0FNRSBPVkVSXCIpXG4gICAgICAgICAgICAuc2V0UG9zaXRpb24oU0NSRUVOX0NFTlRFUl9YLCBTQ1JFRU5fQ0VOVEVSX1kpXG4gICAgICAgICAgICAuc2V0Rm9udFNpemUoNDApXG4gICAgICAgICAgICAuYWRkQ2hpbGRUbyhzZWxmKTtcbiAgICB9LFxufSk7XG5cbnRtLmRlZmluZShcIkVmZmVjdFwiLCB7XG4gICAgc3VwZXJDbGFzczogXCJ0bS5kaXNwbGF5LkNhbnZhc0VsZW1lbnRcIixcbiAgICBcbiAgICBpbml0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgc2VsZi5zdXBlckluaXQoKTtcbiAgICB9LFxuICAgIFxuICAgIHN0YXJ0RWZmZWN0OiBmdW5jdGlvbiAoc2NlbmUpIHtcbiAgICAgICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgICAgICB2YXIgY291bnRlciA9IDEwO1xuICAgICAgICAvLyAxMOWAi+S9nOOCi1xuICAgICAgICAoY291bnRlcikudGltZXMoZnVuY3Rpb24gKGkpIHtcbiAgICAgICAgICAgIHZhciBlZmZlY3QgPSB0bS5kaXNwbGF5LlJlY3RhbmdsZVNoYXBlKDMwLDMwKVxuICAgICAgICAgICAgICAgIC5hZGRDaGlsZFRvKHNlbGYpXG4gICAgICAgICAgICAgICAgLnNldFBvc2l0aW9uKFNDUkVFTl9DRU5URVJfWCwgU0NSRUVOX0NFTlRFUl9ZKTtcbiAgICAgICAgICAgIGVmZmVjdC50d2VlbmVyLmNsZWFyKClcbiAgICAgICAgICAgICAgICAudG8oe1xuICAgICAgICAgICAgICAgICAgICB4OiB0bS51dGlsLlJhbmRvbS5yYW5kaW50KC0xMDAsIFNDUkVFTl9XSURUSCsxMDApLFxuICAgICAgICAgICAgICAgICAgICB5OiB0bS51dGlsLlJhbmRvbS5yYW5kaW50KC0xMDAsIFNDUkVFTl9IRUlHSFQrMTAwKSxcbiAgICAgICAgICAgICAgICAgICAgYWxwaGE6IDAsXG4gICAgICAgICAgICAgICAgfSwgNjAwKVxuICAgICAgICAgICAgICAgIC5jYWxsKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICAgICAgZWZmZWN0LnJlbW92ZSgpO1xuICAgICAgICAgICAgICAgICAgICAtLWNvdW50ZXI7XG4gICAgICAgICAgICAgICAgICAgIGlmIChjb3VudGVyIDw9IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChzY2VuZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjZW5lLmdhbWVPdmVyKCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICByZXR1cm4gc2VsZjtcbiAgICB9LFxufSk7In19fVBLAQIUAAoAAAAAAAyViEUrEmpiEA4AABAOAAAEAAAAAAAAAAAAAAAAAAAAAABkYXRhUEsFBgAAAAABAAEAMgAAADIOAAAAAA==))

```js
    // GameSceneクラス
    update: function () {
        var self = this;
        // 3秒だったらゲームオーバー
        if (self.app.frame > 30*2) {
            Effect().startEffect(self).addChildTo(self);
            self.update = function () {};
        }
    },
```

```js
    // Effectクラス
    startEffect: function (scene) {
        var self = this;
        var counter = 10;
        // 10個作る
        (counter).times(function (i) {
            var effect = tm.display.RectangleShape(30,30)
                .addChildTo(self)
                .setPosition(SCREEN_CENTER_X, SCREEN_CENTER_Y);
            effect.tweener.clear()
                .to({
                    x: tm.util.Random.randint(-100, SCREEN_WIDTH+100),
                    y: tm.util.Random.randint(-100, SCREEN_HEIGHT+100),
                    alpha: 0,
                }, 600)
                .call(function () {
                    effect.remove();
                    --counter;
                    if (counter <= 0) {
                        scene.gameOver();
                    }
                });
        });
        
        return self;
    },
```

コンストラクタでエフェクトの実行を行うのではなく、直接Scene側から呼び出すようにしています。それ自体はいいのですが、これも`GameScene`を引数で渡して、`Effect`側で`GameScene`の関数を呼び出しています。さっきよりはまだマシですけど、これも汎用性ないっす。というか、`GameScene`クラスの`gameOver`関数を、汎用的に作るべき`Effect`クラスで呼ぶような構造はやめた方がいいです。他のプロジェクトで使いたいとか無理になるので。

### だいたい正解？

イベントを発火してキャッチする構造がなかったりしたら使います。callbackです。これで相互参照無くなりました。

(動作するサンプルは[こちら](http://phi-jp.github.io/runstant/release/alpha/#UEsDBAoAAAAAACB4iEWmfVDnDA4AAAwOAAAEAAAAZGF0YXsidmVyc2lvbiI6IjAuMC4xIiwiY3VycmVudCI6InNjcmlwdCIsInNldHRpbmciOnsidGl0bGUiOiJ0ZW1wbGF0ZSAtIHRtbGliLmpzIiwiZGV0YWlsIjoidG1saWIuanMg55So5YWs5byP44Ko44OH44Kj44K/LiDoibLjgIXjgajkvb/jgYjjgb7jgZnjgojimaoifSwiY29kZSI6eyJodG1sIjp7InR5cGUiOiJodG1sIiwidmFsdWUiOiI8IURPQ1RZUEUgaHRtbD5cbiBcbjxodG1sPlxuICAgIDxoZWFkPlxuICAgICAgICA8bWV0YSBjaGFyc2V0PVwiVVRGLThcIiAvPlxuICAgICAgICA8bWV0YSBuYW1lPVwidmlld3BvcnRcIiBjb250ZW50PVwid2lkdGg9ZGV2aWNlLXdpZHRoLCB1c2VyLXNjYWxhYmxlPW5vXCIgLz5cbiAgICAgICAgPG1ldGEgbmFtZT1cImFwcGxlLW1vYmlsZS13ZWItYXBwLWNhcGFibGVcIiBjb250ZW50PVwieWVzXCIgLz5cbiBcbiAgICAgICAgPHRpdGxlPiR7dGl0bGV9PC90aXRsZT5cbiAgICAgICAgPG1ldGEgbmFtZT1cImRlc2NyaXB0aW9uXCIgY29udGVudD1cIiR7ZGVzY3JpcHRpb259XCIgLz5cblxuICAgICAgICA8c3R5bGU+JHtzdHlsZX08L3N0eWxlPlxuXG4gICAgICAgIDxzY3JpcHQgc3JjPVwiaHR0cDovL3Jhd2dpdGh1Yi5jb20vcGhpLWpwL3RtbGliLmpzL2Q4MTk2Y2M1ZTYxOTk3OTE0NmE0MTIyNjA4YjNiMzIzYjNjZWJkMTMvYnVpbGQvdG1saWIuanNcIj48L3NjcmlwdD5cblx0XHQ8c2NyaXB0PiR7c2NyaXB0fTwvc2NyaXB0PlxuICAgIDwvaGVhZD5cbiAgICA8Ym9keT5cbiAgICAgICAgPGNhbnZhcyBpZD1cIndvcmxkXCI+PC9jYW52YXM+XG4gICAgPC9ib2R5PlxuPC9odG1sPiJ9LCJzdHlsZSI6eyJ0eXBlIjoiY3NzIiwidmFsdWUiOiJib2R5IHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZWVlO1xufSJ9LCJzY3JpcHQiOnsidHlwZSI6ImphdmFzY3JpcHQiLCJ2YWx1ZSI6IlxudmFyIFNDUkVFTl9XSURUSCAgPSA2NDA7XG52YXIgU0NSRUVOX0hFSUdIVCA9IDk2MDtcbnZhciBTQ1JFRU5fQ0VOVEVSX1ggPSBTQ1JFRU5fV0lEVEgvMjtcbnZhciBTQ1JFRU5fQ0VOVEVSX1kgPSBTQ1JFRU5fSEVJR0hULzI7XG5cbnRtLm1haW4oZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFwcCA9IHRtLmRpc3BsYXkuQ2FudmFzQXBwKFwiI3dvcmxkXCIpO1xuICAgIGFwcC5yZXNpemUoU0NSRUVOX1dJRFRILCBTQ1JFRU5fSEVJR0hUKTtcbiAgICBhcHAuZml0V2luZG93KCk7XG4gICAgYXBwLnJlcGxhY2VTY2VuZShHYW1lU2NlbmUoKSk7XG4gICAgYXBwLnJ1bigpO1xufSk7XG5cblxudG0uZGVmaW5lKFwiR2FtZVNjZW5lXCIsIHtcbiAgICBzdXBlckNsYXNzOiBcInRtLmFwcC5TY2VuZVwiLFxuICAgIFxuICAgIGluaXQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHNlbGYuc3VwZXJJbml0KCk7XG4gICAgICAgIFxuICAgICAgICBzZWxmLmZyb21KU09OKHtcbiAgICAgICAgICAgIGNoaWxkcmVuOiB7XG4gICAgICAgICAgICAgICAgXCJsYWJlbFwiOiB7XG4gICAgICAgICAgICAgICAgICAgIHR5cGU6IFwidG0uZGlzcGxheS5MYWJlbFwiLFxuICAgICAgICAgICAgICAgICAgICBpbml0OiBbXCLjgr/jg4Pjg4HjgZnjgovjgajjgqjjg5Xjgqfjgq/jg4jjgYzlh7rjgovjgohcIl0sXG4gICAgICAgICAgICAgICAgICAgIHg6IFNDUkVFTl9DRU5URVJfWCxcbiAgICAgICAgICAgICAgICAgICAgeTogODAwLFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgLy8g44Ko44OV44Kn44Kv44OI5L2c5oiQXG4gICAgICAgIHNlbGYub25wb2ludGluZ3N0YXJ0ID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgRWZmZWN0KCkuYWRkQ2hpbGRUbyhzZWxmKTtcbiAgICAgICAgfTtcbiAgICB9LFxuICAgIFxuICAgIC8vIEdhbWVTY2VuZeOCr+ODqeOCuVxuICAgIHVwZGF0ZTogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIC8vIDPnp5LjgaDjgaPjgZ/jgonjgrLjg7zjg6Djgqrjg7zjg5Djg7xcbiAgICAgICAgaWYgKHNlbGYuYXBwLmZyYW1lID4gMzAqMikge1xuICAgICAgICAgICAgRWZmZWN0KCkuYWRkQ2hpbGRUbyhzZWxmKS5zdGFydEVmZmVjdChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgc2VsZi5nYW1lT3ZlcigpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBzZWxmLnVwZGF0ZSA9IGZ1bmN0aW9uICgpIHt9O1xuICAgICAgICB9XG4gICAgfSxcbiAgICBcbiAgICAvLyDjgrLjg7zjg6Djgqrjg7zjg5Djg7zjgrfjg7zjg7Pjgbjjga7pgbfnp7vjgarjgalcbiAgICBnYW1lT3ZlcjogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHRtLmRpc3BsYXkuTGFiZWwoXCJHQU1FIE9WRVJcIilcbiAgICAgICAgICAgIC5zZXRQb3NpdGlvbihTQ1JFRU5fQ0VOVEVSX1gsIFNDUkVFTl9DRU5URVJfWSlcbiAgICAgICAgICAgIC5zZXRGb250U2l6ZSg0MClcbiAgICAgICAgICAgIC5hZGRDaGlsZFRvKHNlbGYpO1xuICAgIH0sXG59KTtcblxudG0uZGVmaW5lKFwiRWZmZWN0XCIsIHtcbiAgICBzdXBlckNsYXNzOiBcInRtLmRpc3BsYXkuQ2FudmFzRWxlbWVudFwiLFxuICAgIFxuICAgIGluaXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgICAgICBzZWxmLnN1cGVySW5pdCgpO1xuICAgIH0sXG4gICAgXG4gICAgLy8gRWZmZWN044Kv44Op44K5XG4gICAgc3RhcnRFZmZlY3Q6IGZ1bmN0aW9uIChjYWxsYmFjaykge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHZhciBjb3VudGVyID0gMTA7XG4gICAgICAgIC8vIDEw5YCL5L2c44KLXG4gICAgICAgIChjb3VudGVyKS50aW1lcyhmdW5jdGlvbiAoaSkge1xuICAgICAgICAgICAgdmFyIGVmZmVjdCA9IHRtLmRpc3BsYXkuUmVjdGFuZ2xlU2hhcGUoMzAsMzApXG4gICAgICAgICAgICAgICAgLmFkZENoaWxkVG8oc2VsZilcbiAgICAgICAgICAgICAgICAuc2V0UG9zaXRpb24oU0NSRUVOX0NFTlRFUl9YLCBTQ1JFRU5fQ0VOVEVSX1kpO1xuICAgICAgICAgICAgZWZmZWN0LnR3ZWVuZXIuY2xlYXIoKVxuICAgICAgICAgICAgICAgIC50byh7XG4gICAgICAgICAgICAgICAgICAgIHg6IHRtLnV0aWwuUmFuZG9tLnJhbmRpbnQoLTEwMCwgU0NSRUVOX1dJRFRIKzEwMCksXG4gICAgICAgICAgICAgICAgICAgIHk6IHRtLnV0aWwuUmFuZG9tLnJhbmRpbnQoLTEwMCwgU0NSRUVOX0hFSUdIVCsxMDApLFxuICAgICAgICAgICAgICAgICAgICBhbHBoYTogMCxcbiAgICAgICAgICAgICAgICB9LCA2MDApXG4gICAgICAgICAgICAgICAgLmNhbGwoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgICAgICBlZmZlY3QucmVtb3ZlKCk7XG4gICAgICAgICAgICAgICAgICAgIC0tY291bnRlcjtcbiAgICAgICAgICAgICAgICAgICAgaWYgKGNvdW50ZXIgPD0gMCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2soKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICAgICAgXG4gICAgICAgIHJldHVybiBzZWxmO1xuICAgIH0sXG59KTsifX19UEsBAhQACgAAAAAAIHiIRaZ9UOcMDgAADA4AAAQAAAAAAAAAAAAAAAAAAAAAAGRhdGFQSwUGAAAAAAEAAQAyAAAALg4AAAAA))

```js
    // GameSceneクラス
    update: function () {
        var self = this;
        // 3秒だったらゲームオーバー
        if (self.app.frame > 30*2) {
            Effect().addChildTo(self).startEffect(function () {
                self.gameOver();
            });
            self.update = function () {};
        }
    },
```

```js
    // Effectクラス
    startEffect: function (callback) {
        var self = this;
        var counter = 10;
        // 10個作る
        (counter).times(function (i) {
            var effect = tm.display.RectangleShape(30,30)
                .addChildTo(self)
                .setPosition(SCREEN_CENTER_X, SCREEN_CENTER_Y);
            effect.tweener.clear()
                .to({
                    x: tm.util.Random.randint(-100, SCREEN_WIDTH+100),
                    y: tm.util.Random.randint(-100, SCREEN_HEIGHT+100),
                    alpha: 0,
                }, 600)
                .call(function () {
                    effect.remove();
                    --counter;
                    if (counter <= 0) {
                        callback();
                    }
                });
        });
        
        return self;
    },
```


### この考え方をtmlibで覚えました

callbackと同じ考え方です。引数にすら縛られないので、さらに汎用性高し。tmlibだとイベントを管理しているのでこっちの方がいいです。

(動作するサンプルは[こちら](http://phi-jp.github.io/runstant/release/alpha/#UEsDBAoAAAAAALV4iEWMDH8CoA4AAKAOAAAEAAAAZGF0YXsidmVyc2lvbiI6IjAuMC4xIiwiY3VycmVudCI6InNjcmlwdCIsInNldHRpbmciOnsidGl0bGUiOiJ0ZW1wbGF0ZSAtIHRtbGliLmpzIiwiZGV0YWlsIjoidG1saWIuanMg55So5YWs5byP44Ko44OH44Kj44K/LiDoibLjgIXjgajkvb/jgYjjgb7jgZnjgojimaoifSwiY29kZSI6eyJodG1sIjp7InR5cGUiOiJodG1sIiwidmFsdWUiOiI8IURPQ1RZUEUgaHRtbD5cbiBcbjxodG1sPlxuICAgIDxoZWFkPlxuICAgICAgICA8bWV0YSBjaGFyc2V0PVwiVVRGLThcIiAvPlxuICAgICAgICA8bWV0YSBuYW1lPVwidmlld3BvcnRcIiBjb250ZW50PVwid2lkdGg9ZGV2aWNlLXdpZHRoLCB1c2VyLXNjYWxhYmxlPW5vXCIgLz5cbiAgICAgICAgPG1ldGEgbmFtZT1cImFwcGxlLW1vYmlsZS13ZWItYXBwLWNhcGFibGVcIiBjb250ZW50PVwieWVzXCIgLz5cbiBcbiAgICAgICAgPHRpdGxlPiR7dGl0bGV9PC90aXRsZT5cbiAgICAgICAgPG1ldGEgbmFtZT1cImRlc2NyaXB0aW9uXCIgY29udGVudD1cIiR7ZGVzY3JpcHRpb259XCIgLz5cblxuICAgICAgICA8c3R5bGU+JHtzdHlsZX08L3N0eWxlPlxuXG4gICAgICAgIDxzY3JpcHQgc3JjPVwiaHR0cDovL3Jhd2dpdGh1Yi5jb20vcGhpLWpwL3RtbGliLmpzL2Q4MTk2Y2M1ZTYxOTk3OTE0NmE0MTIyNjA4YjNiMzIzYjNjZWJkMTMvYnVpbGQvdG1saWIuanNcIj48L3NjcmlwdD5cblx0XHQ8c2NyaXB0PiR7c2NyaXB0fTwvc2NyaXB0PlxuICAgIDwvaGVhZD5cbiAgICA8Ym9keT5cbiAgICAgICAgPGNhbnZhcyBpZD1cIndvcmxkXCI+PC9jYW52YXM+XG4gICAgPC9ib2R5PlxuPC9odG1sPiJ9LCJzdHlsZSI6eyJ0eXBlIjoiY3NzIiwidmFsdWUiOiJib2R5IHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZWVlO1xufSJ9LCJzY3JpcHQiOnsidHlwZSI6ImphdmFzY3JpcHQiLCJ2YWx1ZSI6IlxudmFyIFNDUkVFTl9XSURUSCAgPSA2NDA7XG52YXIgU0NSRUVOX0hFSUdIVCA9IDk2MDtcbnZhciBTQ1JFRU5fQ0VOVEVSX1ggPSBTQ1JFRU5fV0lEVEgvMjtcbnZhciBTQ1JFRU5fQ0VOVEVSX1kgPSBTQ1JFRU5fSEVJR0hULzI7XG5cbnRtLm1haW4oZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFwcCA9IHRtLmRpc3BsYXkuQ2FudmFzQXBwKFwiI3dvcmxkXCIpO1xuICAgIGFwcC5yZXNpemUoU0NSRUVOX1dJRFRILCBTQ1JFRU5fSEVJR0hUKTtcbiAgICBhcHAuZml0V2luZG93KCk7XG4gICAgYXBwLnJlcGxhY2VTY2VuZShHYW1lU2NlbmUoKSk7XG4gICAgYXBwLnJ1bigpO1xufSk7XG5cblxudG0uZGVmaW5lKFwiR2FtZVNjZW5lXCIsIHtcbiAgICBzdXBlckNsYXNzOiBcInRtLmFwcC5TY2VuZVwiLFxuICAgIFxuICAgIGluaXQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHNlbGYuc3VwZXJJbml0KCk7XG4gICAgICAgIFxuICAgICAgICBzZWxmLmZyb21KU09OKHtcbiAgICAgICAgICAgIGNoaWxkcmVuOiB7XG4gICAgICAgICAgICAgICAgXCJsYWJlbFwiOiB7XG4gICAgICAgICAgICAgICAgICAgIHR5cGU6IFwidG0uZGlzcGxheS5MYWJlbFwiLFxuICAgICAgICAgICAgICAgICAgICBpbml0OiBbXCLjgr/jg4Pjg4HjgZnjgovjgajjgqjjg5Xjgqfjgq/jg4jjgYzlh7rjgovjgohcIl0sXG4gICAgICAgICAgICAgICAgICAgIHg6IFNDUkVFTl9DRU5URVJfWCxcbiAgICAgICAgICAgICAgICAgICAgeTogODAwLFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgLy8g44Ko44OV44Kn44Kv44OI5L2c5oiQXG4gICAgICAgIHNlbGYub25wb2ludGluZ3N0YXJ0ID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgRWZmZWN0KCkuYWRkQ2hpbGRUbyhzZWxmKTtcbiAgICAgICAgfTtcbiAgICB9LFxuICAgIFxuICAgIC8vIEdhbWVTY2VuZeOCr+ODqeOCuVxuICAgIHVwZGF0ZTogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIC8vIDPnp5LjgaDjgaPjgZ/jgonjgrLjg7zjg6Djgqrjg7zjg5Djg7xcbiAgICAgICAgaWYgKHNlbGYuYXBwLmZyYW1lID4gMzAqMikge1xuICAgICAgICAgICAgRWZmZWN0KCkuYWRkQ2hpbGRUbyhzZWxmKS5zdGFydEVmZmVjdCgpLm9uKFwiZWZmZWN0ZW5kXCIsIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBzZWxmLmdhbWVPdmVyKCk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIHNlbGYudXBkYXRlID0gZnVuY3Rpb24gKCkge307XG4gICAgICAgIH1cbiAgICB9LFxuICAgIFxuICAgIC8vIOOCsuODvOODoOOCquODvOODkOODvOOCt+ODvOODs+OBuOOBrumBt+enu+OBquOBqVxuICAgIGdhbWVPdmVyOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgdG0uZGlzcGxheS5MYWJlbChcIkdBTUUgT1ZFUlwiKVxuICAgICAgICAgICAgLnNldFBvc2l0aW9uKFNDUkVFTl9DRU5URVJfWCwgU0NSRUVOX0NFTlRFUl9ZKVxuICAgICAgICAgICAgLnNldEZvbnRTaXplKDQwKVxuICAgICAgICAgICAgLmFkZENoaWxkVG8oc2VsZik7XG4gICAgfSxcbn0pO1xuXG50bS5kZWZpbmUoXCJFZmZlY3RcIiwge1xuICAgIHN1cGVyQ2xhc3M6IFwidG0uZGlzcGxheS5DYW52YXNFbGVtZW50XCIsXG4gICAgXG4gICAgaW5pdDogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHNlbGYuc3VwZXJJbml0KCk7XG4gICAgfSxcbiAgICBcbiAgICAvLyBFZmZlY3Tjgq/jg6njgrlcbiAgICBzdGFydEVmZmVjdDogZnVuY3Rpb24gKGNhbGxiYWNrKSB7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgdmFyIGNvdW50ZXIgPSAxMDtcbiAgICAgICAgLy8gMTDlgIvkvZzjgotcbiAgICAgICAgKGNvdW50ZXIpLnRpbWVzKGZ1bmN0aW9uIChpKSB7XG4gICAgICAgICAgICB2YXIgZWZmZWN0ID0gdG0uZGlzcGxheS5SZWN0YW5nbGVTaGFwZSgzMCwzMClcbiAgICAgICAgICAgICAgICAuYWRkQ2hpbGRUbyhzZWxmKVxuICAgICAgICAgICAgICAgIC5zZXRQb3NpdGlvbihTQ1JFRU5fQ0VOVEVSX1gsIFNDUkVFTl9DRU5URVJfWSk7XG4gICAgICAgICAgICBlZmZlY3QudHdlZW5lci5jbGVhcigpXG4gICAgICAgICAgICAgICAgLnRvKHtcbiAgICAgICAgICAgICAgICAgICAgeDogdG0udXRpbC5SYW5kb20ucmFuZGludCgtMTAwLCBTQ1JFRU5fV0lEVEgrMTAwKSxcbiAgICAgICAgICAgICAgICAgICAgeTogdG0udXRpbC5SYW5kb20ucmFuZGludCgtMTAwLCBTQ1JFRU5fSEVJR0hUKzEwMCksXG4gICAgICAgICAgICAgICAgICAgIGFscGhhOiAwLFxuICAgICAgICAgICAgICAgIH0sIDYwMClcbiAgICAgICAgICAgICAgICAuY2FsbChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgIGVmZmVjdC5yZW1vdmUoKTtcbiAgICAgICAgICAgICAgICAgICAgLS1jb3VudGVyO1xuICAgICAgICAgICAgICAgICAgICBpZiAoY291bnRlciA8PSAwKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmZpcmUodG0uZXZlbnQuRXZlbnQoXCJlZmZlY3RlbmRcIikpO1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8g5Lul5LiL44Gn44KCT0tcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHNlbGYuZmxhcmUoXCJlZmZlY3RlbmRcIik7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICByZXR1cm4gc2VsZjtcbiAgICB9LFxufSk7In19fVBLAQIUAAoAAAAAALV4iEWMDH8CoA4AAKAOAAAEAAAAAAAAAAAAAAAAAAAAAABkYXRhUEsFBgAAAAABAAEAMgAAAMIOAAAAAA==))

```js
    // GameSceneクラス
    update: function () {
        var self = this;
        // 3秒だったらゲームオーバー
        if (self.app.frame > 30*2) {
            Effect().addChildTo(self).startEffect().on("effectend", function () {
                self.gameOver();
            });
            self.update = function () {};
        }
    },
```

```js
    // Effectクラス
    startEffect: function () {
        var self = this;
        var counter = 10;
        // 10個作る
        (counter).times(function (i) {
            var effect = tm.display.RectangleShape(30,30)
                .addChildTo(self)
                .setPosition(SCREEN_CENTER_X, SCREEN_CENTER_Y);
            effect.tweener.clear()
                .to({
                    x: tm.util.Random.randint(-100, SCREEN_WIDTH+100),
                    y: tm.util.Random.randint(-100, SCREEN_HEIGHT+100),
                    alpha: 0,
                }, 600)
                .call(function () {
                    effect.remove();
                    --counter;
                    if (counter <= 0) {
                        self.fire(tm.event.Event("effectend"));
                        // 以下でもOK
                        // self.flare("effectend");
                    }
                });
        });
        
        return self;
    },
```

かなり影響を受けた考え方だったので備忘録としてまとめてみました。
tmlibにマジ感謝。YO YO

### 最後に

イベントの発火は`counter`で数えて実行していますが、こういう風に書くとよりかっこいいです。

```js
    // Effectクラス
    startEffect: function () {
        var self = this;
        var counter = 10;
        // 10回 flow.passが呼ばれたら実行される
        var flow = tm.util.Flow(counter, function () {
            self.fire(tm.event.Event("effectend"));
            // 以下でもOK
            // self.flare("effectend");
        });
        
        // 10個作る
        (counter).times(function (i) {
            var effect = tm.display.RectangleShape(30,30)
                .addChildTo(self)
                .setPosition(SCREEN_CENTER_X, SCREEN_CENTER_Y);
            effect.tweener.clear()
                .to({
                    x: tm.util.Random.randint(-100, SCREEN_WIDTH+100),
                    y: tm.util.Random.randint(-100, SCREEN_HEIGHT+100),
                    alpha: 0,
                }, 600)
                .call(function () {
                    effect.remove();
                    flow.pass();
                });
        });
        
        return self;
    },
```