phina.jsとProtonでパーティクルしてみる。その1「Protonの使い方」

# はじめに
phina.jsでゲームを作ろうとするとエフェクトが欲しくなりますよね。
「図形を組み合わせて作ることもできるけど、パーティクルを使いたい！」
そこで見つけたのが「[Proton](http://a-jie.github.io/Proton/)」というパーティクルエンジンです。

# Protonとは？
> Proton is a lightweight and powerful javascript particle engine.
With it you can easily create countless cool effects.

Protonは軽くて強いJavaScript製パーティクルエンジン！
簡単にクールなエフェクトが沢山作れるよ！

> - Seven kinds of renderers
  - canvas - CanvasRenderer
  - dom - DomRenderer
  - webgl - WebGLRenderer
  - pixel - PixelRenderer
  - easeljs - EaselRenderer
  - pixi.js - PixiRenderer
  - custom - CustomRenderer

7種類のレンダラーがあるよ！

> - Integratable into any game engine.

どのゲームエンジンでも使えるよ！

proton.min.jsのファイルサイズを確認してみると**64.6KB**！確かに軽いです。
なんだかphina.jsとも仲良くしてくれそうなので、とりあえず試してみます。

# サンプルを動かす
[このページ](http://a-jie.github.io/Proton/)に書いてあるコードを写してphina.jsで動くようにしてみます。

```javascript:main.js
var SCREEN_W = 640;
var SCREEN_H = 640;

phina.globalize();

phina.define('MainScene', {
  superClass: 'DisplayScene',
  init: function(options) {
    this.superInit(options);
    this.backgroundColor = 'black';

    var plainElement = PlainElement({
      width: SCREEN_W,
      height: SCREEN_H
    }).setPosition(SCREEN_W/2, SCREEN_H/2).addChildTo(this);

    // plainElementからcanvasを取得する
    var canvas = plainElement.canvas.domElement;

    // Proton本体を作る
    var proton = new Proton();
    // エミッター(パーティクルの発生源)を作る
    var emitter = new Proton.Emitter();
    // rateを設定する
    emitter.rate = new Proton.Rate(Proton.getSpan(10, 20), 0.1);
    // Initializeを追加する
    emitter.addInitialize(new Proton.Radius(1, 22));
    emitter.addInitialize(new Proton.Life(2, 4));
    emitter.addInitialize(new Proton.Velocity(2, Proton.getSpan(0, 360), 'polar'));
    // Behaviourを追加する
    emitter.addBehaviour(new Proton.Color('ff0000', 'random'));
    emitter.addBehaviour(new Proton.Alpha(1, 0));
    // エミッターの座標を設定する
    emitter.p.x = canvas.width / 2;
    emitter.p.y = canvas.height / 2;
    emitter.emit();
    // Proton本体にエミッターを追加する
    proton.addEmitter(emitter);
    // canvas rendererを追加する
    var renderer = new Proton.CanvasRenderer(canvas);
    proton.addRenderer(renderer);

    this.on('enterframe', function(e) {
      // 毎フレーム proton.update()を呼んであげる
      proton.update();
    });
  }
});

phina.main(function() {
  var app = GameApp({
    startLabel: 'main',
    width: SCREEN_W,
    height: SCREEN_H,
    fps: 60
  });
  app.run();
});
```

![get-started](https://qiita-image-store.s3.amazonaws.com/0/157380/d396a456-b530-185d-6dd3-401d9591f409.gif)
こんな感じに動きました。

どうやらエミッターにInitializeとBehaviourを追加してパーティクルに動きを加えていくようです。
チュートリアルが用意されていなかったので、試しながら使い方をまとめていきます。

# Protonの使い方

## 初期設定する「Initialize」たち。

### `new Proton.Body(image, w, h)`
パーティクル本体の画像を指定します。
引数の`image`は画像のURLかImageオブジェクト。`w`と`h`は省略可能です。
画像の指定が無かった場合、普通の円形のパーティクルになります。

`new Proton.B()`と省略できます。

```javascript:ex.
var image = AssetManager.get('image', 'particle').domElement;

var bodyEx1 = new Proton.Body(image, 100, 100);
var bodyEx2 = new Proton.B('./image/particle.png');
```

### `new Proton.Life(a, b, c)`
パーティクルの生存時間を指定します。
`a`と`b`に数を指定した場合はa～bのからランダムな値(小数含む)になります。
`c`は省略可能ですが`true`を指定すると、ランダムの範囲がa-b～a+bに変わります。
`a`のみに数を指定すると`a`の値固定になり、配列を指定するとその中からランダムにピックアップされます。

`new Proton.L()`と省略できます。

```javascript:ex.
var lifeEx1 = new Proton.Life(1, 4);       // 1～4 からランダム
var lifeEx2 = new Proton.Life(3, 1, true); // 2～4 からランダム
var lifeEx3 = new Proton.Life(2);          // 2 固定
var lifeEx4 = new Proton.L([1, 3, 5]);     // 1 か 3 か 5
```

### `new Proton.Mass(a, b, c)`
パーティクルの質量を指定します。
指定方法は上記の`Proton.Life`と同じです。

`new Proton.M()`と省略できます。

### `new Proton.Position(zone)`
パーティクルを発生させる範囲をZoneで指定します。
Zoneについては「[発生・行動範囲を決める「Zone」たち。](#発生行動範囲を決めるzoneたち)」で説明します。

`new Proton.P()`と省略できます。

### `new Proton.Radius(a, b, c)`
パーティクルの半径を指定します。
指定方法は上記の`Proton.Life`と同じです。

`new Proton.R()`と省略できます。

### `new Proton.Rate(numpan, timepan)`
パーティクルを1度に発生させる個数と、発生の間隔を指定します。
`numpan`と`timepan`はそれぞれ`Proton.Life`のように指定します。ただ少しだけ方法が違い、`Proton.getSpan(a, b, c)`というものを使って指定します。具体的にはこんな感じです。

```javascript:ex.
// 発生個数: 10～20, 発生間隔: 0.1～0.25
var rateEx1 = new Proton.Rate(Proton.getSpan(10, 20), Proton.getSpan(.1, .25));

// 発生個数: 4 or 8 or 16 or 32, 発生間隔: 0.5
var rateEx2 = new Proton.Rate([4, 8, 16, 32], .5);

// 発生個数: 5～15, 発生間隔: 0.2
var rateEx3 = new Proton.Rate(new Proton.Span(10, 5, true), .2);

emitter.rate = rateEx1;
```
この子だけ特殊で、エミッターの`addInitialize()`ではなく`rate`プロパティに渡してあげます。

rateEx2のように数や配列で指定したり、rateEx3のように`Proton.getSpan()`ではなく`new Proton.Span()`を使っても同じ表現ができます。

また、`Proton.Life`などもこのSpanを利用できます。

```javascript:ex.
var lifeEx5 = new Proton.Life(Proton.getSpan(2, 4));
var lifeEx6 = new Proton.Life(new Proton.Span(1, 3));
```

### `new Proton.Velocity(rpan, thapan, type)`
パーティクルの発生方向や強さを指定します。
指定方法は2種類あります。
1つ目は`new Proton.Velocity(x速度, y速度)`。
x速度とy速度をそれぞれ数か配列かSpanで指定します。

2つ目は`new Proton.Velocity(速度, 角度, 'polar')`。
速度と角度をそれぞれ数か配列かSpanで指定します。
`'polar'`ではなく`'p'`や`'P'`と省略できます。

![velocityイメージ](https://qiita-image-store.s3.amazonaws.com/0/212027/671ff164-629f-9383-98cb-218e66f28a7b.png)
1つ目はxとyの速度を直接指定するのに対して、2つ目は放射状にパーティクルを発生させます。※画像上のコードは略しています。
個人的には後者の方が馴染みがあるので好きです。

`new Proton.V()`と省略できます。

```javascript:ex.
var velocityEx1 = new Proton.Velocity(2, 2);
var velocityEx2 = new Proton.Velocity(2, Proton.getSpan(0, 360), 'polar');
var velocityEx3 = new Proton.V(Proton.getSpan(1, 3), Proton.getSpan(-45, 45), 'p');
```

## 動きを加える「Behaviour」たち。

### `new Proton.Alpha(a, b, life, easing)`
![alpha](https://qiita-image-store.s3.amazonaws.com/0/212027/66f35952-f828-9502-e5d0-b703d11d5aa3.gif)
透明度を`a`から`b`に変化させます。
`life`はこの動きを加え続ける時間で省略可能。デフォルトでは`Infinity`が設定されます。
`easing`も省略可能で`Proton.easeLinear`がデフォルトで設定されます。
phina.jsのtweenerで使えるイージングの名前がそのまま使えます。

`new Proton.A()`と省略できます。

```javascript:ex.
var alphaEx1 = new Proton.Alpha(1, 0);
var alphaEx2 = new Proton.A(.8, 0, Infinity, Proton.easeInSine);
```
`life`と`easing`はbehaviourの引数の後ろ2つに必ずありますが、全て同じなので以降は説明を省きます。

### `new Proton.Attraction(targetPosition, force, radius, life, easing)`
![attraction](https://qiita-image-store.s3.amazonaws.com/0/157380/4571d07a-5434-d14c-0f56-7983a8ba2aea.gif)
パーティクルを引きつける力を加える場所を作ります。
`targetPosition`には引きつける中心の座標を`new Proton.Vector2D(x座標, y座標)`で指定します。`new Proton.Vector(x座標, y座標)`でもOKです。
`force`と`radius`はそのまま力と半径の意味で省略可能です。デフォルトでは100と1000が設定されます。

```javascript:ex.
var attractionEx1 = new Proton.Attraction(new Proton.Vector2D(100, 100));
var attractionEx2 = new Proton.Attraction(new Proton.Vector(10, 10), 15, 300);
```

### `new Proton.Collision(emitter, mass, callback, life, easing)`
![collision](https://qiita-image-store.s3.amazonaws.com/0/157380/9f3035a2-1774-318d-673c-99c2ba67f0bb.gif)
パーティクルに衝突判定を追加します。
`emitter`にエミッターを指定します。`mass`と`callback`は省略可能です。
`mass`には`true`or`false`で指定します。デフォルトでは`true`でパーティクルの質量が影響しますが、`false`にすると質量に影響しなくなります。
`callback`には衝突が起きた時にコールバックする関数を指定します。

```javascript:ex.
var collisionEx1 = new Proton.Collision(emitter);

var callback = function(particle) {
  particle.radius += 0.1;
};
var collisionEx2 = new Proton.Collision(emitter, false, callback);
```

### `new Proton.Color(color1, color2, life, easing)`
![color](https://qiita-image-store.s3.amazonaws.com/0/157380/3748e11a-7650-2ddb-8c12-693ef2fc2121.gif)
`color1`から`color2`に色を変化させます。
色は16進数カラーコード(#は省略可能)か、ランダムな色になる`'random'`が使えます。
複数の色を指定したい場合は配列を使い、`color1`のみ指定した場合は単色になります。

```javascript:ex.
var colorEx1 = new Proton.Color('#ffff00', '#ff0000');
var colorEx2 = new Proton.Color(['ffffff', 'ffff00'], 'random');
var colorEx3 = new Proton.Color('00ffff');
```

### `new Proton.CrossZone(zone, crossType, life, easing)`
![crosszone](https://qiita-image-store.s3.amazonaws.com/0/157380/7645bd00-24d8-5f8a-88b4-c56698277d71.gif)
パーティクルの行動可能な範囲を指定します。
`zone`にはZoneで範囲を指定します。
`crossType`には`'dead'`か`'bound'`か`'cross'`を指定します。デフォルトは`dead`です。
`dead`はZoneの外に出たらパーティクルが消えます。
`bound`はZoneの端でパーティクルが跳ね返るようになります。
`cross`はZoneの端から出ると反対の端から出てきます。(RectZoneのみ対応)

```javascript:ex.
var zone = new Proton.RectZone(0, 0, canvas.width, canvas.height);
var crossZoneEx1 = new Proton.CrossZone(zone, 'bound');
```

### `new Proton.Force(fx, fy, life, easing)`
![force](https://qiita-image-store.s3.amazonaws.com/0/157380/56308a15-f980-26a1-a182-9bf17e14571c.gif)
パーティクルに力を加えます。
`fx`と`fy`にそれぞれの方向の力を数で指定します。

`new Proton.F()`と省略できます。

```javascript:ex.
var forceEx1 = new Proton.Force(2, 0);
var forceEx2 = new Proton.F(-3, 2, Infinity, Proton.easeInSine);
```

### `new Proton.Gravity(g, life, easing)`
![gravity](https://qiita-image-store.s3.amazonaws.com/0/157380/cc215eef-3eeb-cc7d-7f3a-5147be248326.gif)
パーティクルに重力を加えます。
`g`に重力の強さを数で指定します。

`new Proton.G()`と省略できます。

```javascript:ex.
var gravityEx1 = new Proton.Gravity(1);
var gravityEx2 = new Proton.G(5, Infinity, Proton.easeInSine);
```

### `new Proton.GravityWell(centerPointopt, force, life, easing)`
![gravity-well](https://qiita-image-store.s3.amazonaws.com/0/157380/890138ec-1a1e-b01b-695b-0ad22f6af724.gif)
パーティクルに拡散させるような力を加える場所を作ります。
GravityWellは「重力井戸」という意味のようですが、よく分かりませんでしたw
`centerPoint`には`Proton.Vector2D`で位置を指定します。
`force`は省略可能で、デフォルトで100が設定されます。

```javascript:ex.
var gravityWellEx1 = new Proton.GravityWell(new Proton.Vector2D(10, 10));
var gravityWellEx2 = new Proton.GravityWell(new Proton.Vector(10, 10), 20);
```

### `new Proton.RandomDrift(driftX, driftY, delay, life, easing)`
![random-drift](https://qiita-image-store.s3.amazonaws.com/0/157380/a1059dee-5ff0-ed93-0244-068b9d6d492c.gif)
パーティクルを上下左右に揺らします。
`driftX`と`driftY`にそれぞれの方向の大きさを数で指定します。
`delay`にも数を指定しますが、値が小さい方が頻繁に揺れるようになります。

```javascript:ex.
var randomDriftEx1 = new Proton.RandomDrift(10, 0, .035);
var randomDriftEx2 = new Proton.RandomDrift(0, 10, .1);
```

### `new Proton.Repulsion(targetPosition, force, radius, life, easing)`
![repulsion](https://qiita-image-store.s3.amazonaws.com/0/157380/6a2e0e4a-194d-5178-959d-11666bac45a5.gif)
パーティクルを反発させる力を加える場所を作ります。
`targetPosition`には`Proton.Vector2D`で位置を指定します。
`force`と`radius`は省略可能で100と1000がデフォルトで設定されます。

```javascript:ex.
var repulsionEx1 = new Proton.Repulsion(new Proton.Vector2D(10, 10));
var repulsionEx2 = new Proton.Repulsion(new Proton.Vector(10, 10), 10, 200);
```

### `new Proton.Rotate(a, b, style, life, easing)`
![rotate](https://qiita-image-store.s3.amazonaws.com/0/157380/d3517498-f304-639d-9246-2115add5e1b5.gif)
パーティクルを回転させます。~~トマピコかわいい。~~
指定方法は3種類あります。
1つ目は`new Proton.Rotate(始まりの回転速度, 終わりの回転速度, 'to')`。
回転速度を決めて回転させます。回転速度は数か配列かSpanで指定します。
`style`には`'to'`か`'To'`か`'_'`を、省略した場合もこの`'to'`になります。

2つ目は`new Proton.Rotate(始まりの角度, 回転速度, 'add')`。
始まりの角度と回転速度を決めて回転させます。1つ目は回転速度が変化するのに対して、回転速度が固定になります。

3つ目は`new Proton.Rotate('Velocity')`。
パーティクルの移動する方向に合わせて自動で向きを変えます。
`'Velocity'`ではなく`'V'`や`'v'`でも、全部省略して`new Proton.Rotate()`ともできます。

```javascript:ex.
var rotateEx1 = new Proton.Rotate(0, [-15, 15]);
var rotateEx2 = new Proton.Rotate(Proton.getSpan(0, 360), [-3, 3], 'add');
var rotateEx3 = new Proton.Rotate('V');
```

### `new Proton.Scale(a, b, life, easing)`
![scale](https://qiita-image-store.s3.amazonaws.com/0/157380/857f5e4e-fdd2-0ee7-7555-775c32835d55.gif)
パーティクルの大きさを`a`から`b`に変化させます。
`a`と`b`にはそれぞれ数か配列かSpanで指定します。

```javascript:ex.
var scaleEx1 = new Proton.Scale(1, Proton.getSpan(0, 1));
var scaleEx2 = new Proton.Scale(1, 0, Infinity, Proton.easeInCubic);
```

## 発生・行動範囲を決める「Zone」たち。

### `new Proton.CircleZone(x, y, radius)`
円形のZoneを作ります。
`x`と`y`に円の中心の座標、`radius`に円の半径を指定します。

### `new Proton.ImageZone(imageData, x, y, d)`
画像からZoneを作ります。画像の透明ではない場所がZoneの範囲になります。
`x`と`y`は左上のx座標とy座標を指定します。中心ではないので注意が必要です。
`d`には1以上の数を指定します。大きいほど範囲が荒くなります。省略可能でデフォルトでは2が設定されます。

imageDataの取得方法がイマイチですが、上手く動いた例を書いておきます。

```javascript:ex.
var context = canvas.getContext('2d');
var image = AssetManager.get('image', 'zone').domElement;

// new Proton.Rectangle(x, y, width, height)で getImageData に必要な rect を作る
var rect = new Proton.Rectangle(0, 0, image.width, image.height);

// Proton.Util.getImageData(context, image, rect)は imageData を返してくれる関数
var imageData = Proton.Util.getImageData(context, image, rect);

var imageZone = new Proton.ImageZone(imageData, 0, 0);
```

### `new Proton.LineZone(x1, y1, x2, y2, direction)`
線形のZoneを作ります。
`x1`と`y1`に始点の座標、`x2`と`y2`に終点の座標を指定します。
`direction`は省略可能です。CrossZoneで利用時にcrossTypeがdeadのときのみ影響します。
線に対してパーティクルが右か下方向に来る場合に`'>'`か`'R'`か`'right'`か`'down'`を使い、左か上方向に来る場合はそれ以外(`'<'`か`'L'`か`'left'`か`'up'`が良いかも)を使います。
デフォルトでは`'>'`が設定されます。

### `new Proton.PointZone(x, y)`
点のZoneを作ります。Positionでしか利用できません。
`x`と`y`に座標を指定します。

### `new Proton.RectZone(x, y, width, height)`
矩形のZoneを作ります。
`x`と`y`に左上の座標を、`width`と`height`に横幅と高さを指定します。
xとyが中心の座標ではなく、左上の座標なことに注意してください。

# さいごに
Protonの使い方をまとめただけで長くなってしまったので、今回はここまでにします。
「phina.jsでエフェクトとして使う」という目標を達成できていないので、今度別の記事を書こうと思います。

Runstantに[簡単なサンプル](http://runstant.com/matsu7089/projects/13885cff)を置いておきました。
皆さんもphina.jsとProtonで遊んでみてくださいね！