Help us understand the problem. What is going on with this article?

クラス継承をしてみよう【応用】

More than 1 year has passed since last update.

はじめに

この記事は、phina.js Advent Calendar 2016 の23日目の記事です。

クラス継承

phina.jsを使い慣れてくると、既存のクラスに機能を追加したいと思うことがあります。もちろんイチから作るのもありですが、ちょっとした機能追加であれば既存のクラスを継承したクラスを作った方が効率的です。今回はButtonクラスを例にその方法を説明します。

AnimateButtonクラスを作る

現在のButtonクラスの仕様はシンプルで、最低限の機能しかありません。今回はこのButtonクラスを継承してAnimateButtonというクラスを新しく作りたいと思います。追加する仕様は単純で、「ボタンを押している間は縮小して押しているように見せる」です。(下のボタンは画像なので押せません)

手順1 Buttonクラスのソースをコピーする

本来であれば、ローカル環境にソースをダウンロード→エディタで編集→ビルド→テストというのが手順かもしれませんが、今回は簡単な継承例ということでソースを丸ごとコピーして、runstant上で作業します。コピー場所はコードの冒頭にします。(phina.globalize()よりも前)

以下がButtonクラスのソースです。

/**
 * @class phina.ui.Button
 * Button
 */
phina.define('phina.ui.Button', {
  superClass: 'phina.display.Shape',
  /**
   * @constructor
   */
  init: function(options) {
    options = (options || {}).$safe(phina.ui.Button.defaults);
    this.superInit(options);

    this.cornerRadius = options.cornerRadius;
    this.text         = options.text;
    this.fontColor    = options.fontColor;
    this.fontSize     = options.fontSize;
    this.fontWeight     = options.fontWeight;
    this.fontFamily   = options.fontFamily;

    this.setInteractive(true);
    this.on('pointend', function() {
      this.flare('push');
    });
  },
  prerender: function(canvas) {
    canvas.roundRect(-this.width/2, -this.height/2, this.width, this.height, this.cornerRadius);
  },

  postrender: function(canvas) {
    var context = canvas.context;
    // text
    var font = "{fontWeight} {fontSize}px {fontFamily}".format(this);
    context.font = font;
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillStyle = this.fontColor;
    context.fillText(this.text, 0, 0);
  },

  _static: {
    defaults: {
      width: 200,
      height: 80,
      backgroundColor: 'transparent',
      fill: 'hsl(200, 80%, 60%)',
      stroke: null,

      cornerRadius: 8,
      text: 'Hello',
      fontColor: 'white',
      fontSize: 32,
      fontWeight: '',
      fontFamily: "'HiraKakuProN-W3'", // Hiragino or Helvetica,
    },
  },

  _defined: function() {
    phina.display.Shape.watchRenderProperty.call(this, 'cornerRadius');
    phina.display.Shape.watchRenderProperty.call(this, 'text');
    phina.display.Shape.watchRenderProperty.call(this, 'fontColor');
    phina.display.Shape.watchRenderProperty.call(this, 'fontSize');
    phina.display.Shape.watchRenderProperty.call(this, 'fontFamily');
  },

});

手順2 クラス名を変更し、不要な部分を削除する

クラス名を変更し、更にphina.ui.Buttonを継承するように変更します。

/**
   * @class phina.ui.AnimateButton
   * AnimateButton
   */
  phina.define('phina.ui.AnimateButton', {
    superClass: 'phina.ui.Button',

見た目や基本動作はButtonクラスのものを使うので基底クラスに関する部分は削除し、以下のようにスッキリさせます。

/**
   * @class phina.ui.AnimateButton
   * AnimateButton
   */
  phina.define('phina.ui.AnimateButton', {
    superClass: 'phina.ui.Button',
    /**
     * @constructor
     */
    init: function(options) {
      options = (options || {}).$safe(phina.ui.Button.defaults);
      this.superInit(options);
    },

    _static: {
      defaults: {
      },
    },
  });

手順3 押している間の見た目を変更する

押している間は少し縮小させたいので、pointstayイベント内に処理を書きます。
scaleMinは後のプロパティ設定で説明します。

// プッシュ時の処理を追加
this.on('pointstay', function() {
  // 少し縮小
  this.setScale(scaleMin, scaleMin);
});

押し終わった後は元のサイズに戻さないといけないので、pointendイベント内に処理を書きます。
1.0が元のサイズです。

this.on('pointend', function() {
  // 元に戻す
  this.setScale(1.0, 1.0);
});

オプション クラス特有プロパティを設定できるようにする

折角ですので、AnimateButtonクラス特有のプロパティをコンストラクタで指定できるようにします。
今回は、縮小サイズを指定できるようにしたいと思います。

init: function(options) {
  options = (options || {}).$safe(phina.ui.AnimateButton.defaults);
  this.superInit(options);
  // 独自プロパティ
  var scaleMin = options.scaleMin;

$safeはプロパティ(連想配列)をマージする関数で、optionsで与えられた値とデフォルトで定義されている値を結合します。
プロパティ名がバッティングした場合、optionsで与えられた値をデフォルトの値で上書きすることはありません。

独自プロパティの値は、optionsからもらいます。

_static: {
  defaults: {
    scaleMin: 0.95,
  },
},

独自プロパティのデフォルト値は、上のように書きます。これで、コンストラクタで値を渡して指定できるようになります。

完成コード

/**
 * @class phina.ui.AnimateButton
 * AnimateButton
 */
phina.define('phina.ui.AnimateButton', {
  superClass: 'phina.ui.Button',
  /**
   * @constructor
   */
  init: function(options) {
    options = (options || {}).$safe(phina.ui.AnimateButton.defaults);
    this.superInit(options);
    // 独自プロパティ
    var scaleMin = options.scaleMin;
    // プッシュ時の処理を追加
    this.on('pointstay', function() {
      // 少し縮小
      this.setScale(scaleMin, scaleMin);
    });
    this.on('pointend', function() {
      // 元に戻す
      this.setScale(1.0, 1.0);
    });
  },

  _static: {
    defaults: {
      scaleMin: 0.95,
    },
  },
});
// グローバルに展開
phina.globalize();
/*
* メインシーン
*/
phina.define("MainScene", {
// 継承
superClass: 'DisplayScene',
// 初期化
init: function() {
  // 親クラス初期化
  this.superInit();

  var txt = 'Push me';
  // デフォルトのボタン
  Button({text: txt}).addChildTo(this)
          .setPosition(this.gridX.center(), this.gridY.center(-2))
          .onpush = function() {
            console.log('Default');  
          };
  // 拡張ボタン
  AnimateButton({text: txt}).addChildTo(this)
                 .setPosition(this.gridX.center(), this.gridY.center(2))
                 .onpush = function() {
                   console.log('Animate');
                 };
},
});
/*
* メイン処理
*/
phina.main(function() {
// アプリケーションを生成
var app = GameApp({
  // MainScene から開始
  startLabel: 'main',
});
// 実行
app.run();
});

[runstantで確認]

おわりに

説明をだいぶ端折ってしまいましたが、phina.jsでのクラス継承についての説明は以上です。このように拡張したクラスは自分専用に使っても良いですし、他のユーザーにも使ってもらいたいということであれば、Githubでプルリクエストをしても良いでしょう。phina.jsへの理解も深まりますので、お勧めです。

alkn203
javascript製のゲームライブラリphina.jsを愛用している趣味プログラマ。ファミコン世代。 Hobbyist programmer javascript/html5/tmlib.js/phina.js/retro games
http://qiita.com/alkn203/items/bca3222f6b409382fe20
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした