[phina.js] superMethodの使い方

## はじめに
初めまして、[Negiwine](https://qiita.com/Negiwine_jp)といいます。趣味でphina.jsを使っていて、有志の方々の記事によくお世話になっているので今回自分でも発信してみることにしました。よろしくお願いします。

この記事ではphina.jsの機能である**superMethod**について紹介します。superMethodは親クラスのメソッドを呼び出すときに使うものです。記事にしたものは見つからなかったので書くことにしました。ここでは名前の似ている**superInit**(別物です)についても少し書きます。

**環境**

- JavaScript: ES5
- phina.js: v0.2.1

## superMethodとは

### superInitについて
phina.jsを使っている皆さんなら**superInit**というのを既に知っているかと思います。**superInit**は以下のように使いました。

```js
phina.define('MainScene', {
    superClass: 'DisplayScene',

    init: function(){
        this.superInit();

        //...
    },
});
```

**superInit**は親クラスのコンストラクタを呼び出すときに使います。 例えば上の例では以下のように**DisplayScene**のコンストラクタにオプションを渡すことができます。

```js
//displayscene.js
phina.define('phina.display.DisplayScene', {
    //paramsにはwidth, heightを入れる
    init: function(params){
        //...
    },
    //...
});

phina.define('MainScene', {
    superClass: 'DisplayScene',

    init: function(){
        //DisplaySceneのコンストラクタ引数を渡せる
        this.superInit({
            width: 1280,
            height: 1920,
        });

        //...
    },
});
```

phina.jsではクラスの引数の多くはデフォルト値が設定されているので引数を渡さないこともありがちですが、親クラスに引数を渡せることは覚えておいて損はないはずです。

### superMethodについて
**superMethod**の説明です。はじめにも書きましたが**superMethod**は親クラスのメソッドを呼び出すものです。**superInit**とは親クラスの機能を使う、という点で共通していますが使いどころは違います。

**superMethod**はphina.defineでsuperClassを指定すると自動的にクラスに追加されます。具体的には以下のように使います。

```js
phina.define('ClassA', {
    init: function(){},

    methodA: function(){
        console.log('methodAが呼ばれました');
    },
});

phina.define('ClassB', {
    superClass: 'ClassA',
    init: function(){ this.superInit(); },

    methodB: function(){
        this.superMethod('methodA'); //メソッド名を指定
    },
});

var classB = ClassB();
classB.methodB(); //methodAが呼ばれました
```
[[runstantで確認]](http://runstant.com/Negiwine/projects/8408c3a6)

引数がある場合は以下のようにします。

```js
phina.define('ClassA', {
    init: function(){},

    methodA: function(arg1, arg2){
        console.log(arg1, arg2);
    },
});

phina.define('ClassB', {
    superClass: 'ClassA',
    init: function(){ this.superInit(); },

    methodB: function(arg1, arg2){
        //メソッド名のあとに順番に指定する
        this.superMethod('methodA', arg1, arg2);
    },
});

var classB = ClassB();
classB.methodB(10, 20); //10 20
```
[[runstantで確認]](http://runstant.com/Negiwine/projects/bbd810d8)

どちらも子クラスであるClassBから親クラスのmethodAを呼び出せています。

しかしこの場合ClassAとClassBは継承関係にあるので直接methodAを呼び出してもいいのでは？と思った方もいると思います。まったくその通りで上の例は無意味です。**superMethod**が役に立つのは子クラスで同名メソッドを作った時(オーバーライドした時)です。

## superMethodを使用したオーバーライド
コードを見たほうが早いと思うので早速書きます。

ClassA、子クラスであるClassBともにmethodAが定義されています。通常であれば同名メソッドは上書きになってしまうためClassBからはClassAのmethodAが呼び出せませんが、superMethodを使うことで親クラスの同名メソッドを呼び出すことができます。

```js
phina.define('ClassA', {
    init: function(){},

    methodA: function(arg1, arg2){
        console.log(arg1, arg2);
    },
});

phina.define('ClassB', {
    superClass: 'ClassA',
    init: function(){ this.superInit(); },

    methodA: function(arg1, arg2){
        arg1 = arg1 * 2; //引数の値を2倍にする
        arg2 = arg2 * 2; //

        //2倍した値がClassAのmethodAに渡される
        this.superMethod('methodA', arg1, arg2);
    },
});

var classB = ClassB();
classB.methodA(10, 20); //20 40
```
[[runstantで確認]](http://runstant.com/Negiwine/projects/05068231)

こうすることでClassBのmethodAでは引数が常に2倍されることになりました。

このようにメソッドの処理を拡張するときに**superMethod**が役立ちます。

## 実用例を考える
上の例ではまだ活用法がピンとこないかもしれないので少しだけ実用例を書きます。多くの場合役に立つのはphina.jsから呼ばれるupdateやdrawなどのメソッドをオーバーライドするときかと思います。

### updateに処理を足す
以下の例は基本クラスを元にしてプレイヤークラスを作る例です。基本クラスUnitでは、自身が画面外に出ないようにする処理だけをすることにし、プレイヤークラスでは移動の処理のみを行います。

```js
var SCREEN_WIDTH = 640;  //画面の幅
var SCREEN_HEIGHT = 960; //画面の高さ
var PLAYER_SPEED = 5;    //スピード

phina.define('Unit', {
    superClass: 'Sprite',

    init: function(image, width, height){
        this.superInit(image, width, height);
    },

    update: function(){
        var pos = this.position;

        //(0 <= x <= SCREEN_WIDTH)に収める
        pos.x = pos.x.clamp(0, SCREEN_WIDTH);

        //(0 <= y <= SCREEN_HEIGHT)に収める
        pos.y = pos.y.clamp(0, SCREEN_HEIGHT);
    },
});

phina.define('Player', {
    superClass: 'Unit',

    init: function(){
        this.superInit('player');
    },

    update: function(app){
        //キーボード取得
        var key = app.keyboard;

        //キー操作で上下左右に動かす
        if(key.getKey('left')){ this.x -= PLAYER_SPEED; }
        if(key.getKey('right')){ this.x += PLAYER_SPEED }
        if(key.getKey('up')){ this.y -= PLAYER_SPEED }
        if(key.getKey('down')){ this.y += PLAYER_SPEED }

        this.superMethod('update');
    },
});
```
[[runstantで確認]](http://runstant.com/Negiwine/projects/c0dad757)

通常はupdateメソッドが上書きされUnitのupdateは呼ばれませんが、superMethodを使いUnitのupdateを呼び出しています。こうすることでPlayerのupdateが呼ばれた時、Unitのupdateも実行することができます。Unitを継承したplayerは画面外に出ないことが確認できます。

キー操作については以下の記事を参考にさせていただきました。

[[phina.js-Tips-029] Spriteをキーボードで操作する](https://qiita.com/alkn203/items/c2496fc6c0f772f54308) by [alkn203](https://qiita.com/alkn203) さん

### drawメソッドに処理を足す
以下の例はdrawメソッドに細工をしてドット絵を綺麗に表示させる例です。phina.jsではSpriteなどのオブジェクトを表示する際にdrawメソッドが呼ばれることになっています。spriteのdrawが呼ばれる前にcanvasにオプションをセットすることで拡大時のアンチエイリアスを無効にします。

```js
phina.define('PixelSprite', {
  superClass: 'Sprite',
  
  init: function(image, width, height){
    this.superInit(image, width, height);
  },
  draw: function(canvas){
    canvas.save();                        //canvasの状態をスタックに保存
    canvas.imageSmoothingEnabled = false; //拡大時の補完を無効にする

    this.superMethod('draw', canvas);     //Spriteのdrawメソッド呼び出し
    
    canvas.restore();                     //他に影響が出ないように状態を戻す
  },
});
```
[[runstantで確認]](http://runstant.com/Negiwine/projects/d0d82f25)

このクラスを使った方はドット絵の戦闘機がぼやけずに綺麗に表示されていることがわかると思います。

## おわりに
いかがでしょうか、あまり使いどころが多くないかもしれませんが知っておくと便利だと思います。superMethodの使い方をちょっとでもわかってもらえれば嬉しいです。コードのサンプルを載せましたが、ここがおかしい、間違っている、というのがあったら指摘してもらえると大変助かります。

最後になりますがrunstantでコメントを頂いた[axion014](https://qiita.com/axion014)さん、canvas操作とても参考になりました。ありがとうございます。

今回は最後まで読んでいただきありがとうございました。

### おまけ
オーバーライドをするときsuperMethodを使わない場合、親クラスのメソッドを呼び出すには以下の方法があります。

```js
  draw: function(canvas){
    canvas.save();
    canvas.imageSmoothingEnabled = false;

    //直接クラスを指定してメソッドをcallする
    Sprite.prototype.draw.call(this, canvas);
    
    canvas.restore();
  },
```
[[runstantで確認]](http://runstant.com/Negiwine/projects/dc1d4781)

phina.jsを使わない素のJSではよくやる方法です。しかし、せっかくsuperMethodが用意されているのでそちらを使うのが良いと思います。