JavaScript
canvas
phina.js

phina.jsのclipとblendModeで遊ぶ

はじめに

まずは以下のゲームをやってみていただきたい。(音が出ます)

https://pentamania.github.io/find-shimauma-chan

注目してほしいのはシマウマちゃんがカワイイこと...ではなく、
画面遷移する際などの円形のセクシーなエフェクトと、ゲーム中にスポットライトが当たったように周りが暗くなる演出の部分です。

shima-trans-anim.gif

shima-spotlight.gif

これらはそれぞれclipblendModeというphina.js(v0.2.0)の機能を使って実現しています。

clip()

clip()はcanvasAPIのclipと一緒で、指定したパスの範囲内だけを描画するメソッドです。

phina.jsでclipを使うには以下のように描画系クラス(例としてSpriteクラス)にclipというメソッドを新たに定義し、引数で渡されるCanvasを使ってパスを設定します。

phina.define('ClippedImage', {
  superClass: 'phina.display.Sprite',

  init: function(src) {
    this.superInit(src);
    this.clipCenter = phina.geom.Vector2(0, 0);
    this.clipRadius = 160;
  },

  clip: function(c) {
    var cc = this.clipCenter;
    c.beginPath();
    c.arc(cc.x, cc.y, this.clipRadius, 0, Math.PI*2, false);
  }
});

するとスプライトは指定したパスの範囲内しか描画されなくなります。

phina-clip-sample.png

実行例

さらにクリップ範囲を動的に変えることで(例えば円の半径など)、前出のようなエフェクトが実現できます。
clipは自前のcanvasを持つクラス(DisplayElement派生クラス)なら何でも有効で、子要素にも適用されます。

blendMode

blendModeはDisplayElement派生クラスのプロパティの一種で、指定した値に応じて「自分自身」と「自身より前に描画されたオブジェクト群」とを相互に影響させることが出来ます。(説明が難しい…)
使い方はDisplayElement系クラスのblendmodeプロパティに値を指定するだけです。

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

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

    // サンプル用ロゴ
    var logo = Sprite("logo")
    .setPosition(this.width/2, this.height/2)
    .addChildTo(this);

    // blendModeを変えた描画オブジェクト
    this.spotLight = SpotLight()
    .setPosition(this.width/2, this.height/2)
    .addChildTo(this);
  },

  update: function(app) {
    var p = app.pointer;
    this.spotLight.setPosition(p.x, p.y)
  }
});

phina.define('SpotLight', {
  superClass: 'phina.display.CircleShape',

  init: function() {
    this.superInit({
      radius: 180,
    });
    this.blendMode = 'destination-in'; // ←ここ
  },
});

上記の例では"destination-in"を指定することで、自身より前に描画されたオブジェクト(logo)の、自身(円)の範囲と重なる部分だけが表示されるようになります。

実行例(アプリ上でマウスカーソルを動かしてみよう)

指定できる値はcanvasAPIのglobalCompositeOperationプロパティと一緒です。
また自身(円)より後に描画されたものについては何の影響も与えません。

扱いは簡単ですが、描画されなかった部分はapp本体のbackgroundColor(デフォルトでは白)一色になってしまい、別の画像等を指定できないという欠点があります。
(もしかしたら対策があるのかもですが、自分は「全然分からん!」って感じでした…)

clipとblendMode、上手く使ってなんかいい感じのエフェクトを作っていきましょう。

おまけ

ゲームのリポジトリ

ネズミのフレンズ :rat:

mickey-spot-sample.gif

  clip: function(c) {
    var cc = this.clipCenter;
    var edgeLength = 40;
    var sr = this.spotRadius;

    c.beginPath();
    c.arc(cc.x, cc.y+edgeLength, sr, 0, Math.PI*2, false); // 中心
    c.closePath();
    c.arc(cc.x+edgeLength+sr, cc.y-edgeLength-sr, sr, 0, Math.PI*2, false); // 右耳
    c.closePath();
    c.arc(cc.x-edgeLength-sr, cc.y-edgeLength-sr, sr, 0, Math.PI*2, false); // 左耳
  },