phina.jsでオリジナルのローディングシーンを作る

*phina.jsバージョン: v0.2.1*

## はじめに
この記事はphina.js Advent Calendar 2017 Advent Calendar 2017の2日目です。
アドカレの最初の方なので（？）ローディングシーンについての話です。

GameAppクラスでオプションとしてassetsを渡した状態でアプリを実行すると、最初のシーンに移行する前にアセット類をロードするため、ローディングシーンを介すようになります。  
上部に水色のバーが表示されるアレです。

```js
phina.globalize();
phina.main(function() {
  var app = GameApp({
    startLabel: 'main',
    // 以下を設定してるとロードを挟む
    assets: {
      image: {
        logo: 'http://cdn.rawgit.com/phi-jp/phina.js/develop/logo.png',
      }
    },
  });

  app.run();
});
```

![loading-example.png](https://qiita-image-store.s3.amazonaws.com/0/81823/1766fa86-de4c-1a3a-b09b-e785090ffff9.png)

改造するのはちょっと面倒くさい印象があったのですが、実は割りと簡単に自作することができると最近知りました。

**[こんなのとか](http://runstant.com/pentamania/projects/d89ee708)**

## 手順
元々定義されているLoadingSceneクラスをオーバーライドすることで、自作のものを使うよう仕向けることができます。
そして以下が最もシンプルな形となります。

```js
// ※globalizeしてる場合は単に"LoadingScene"
phina.define('phina.game.LoadingScene', {
  superClass: 'phina.display.DisplayScene',

  init: function(options) {
    this.superInit(options);
    var self = this;
    var loader = phina.asset.AssetLoader();

    // ローダーによるロード完了ハンドラ
    loader.onload = function() {
      // Appコアにロード完了を伝える（==次のSceneへ移行）
      self.flare('loaded');
    };

    // ロード開始
    loader.load(options.assets);
  },

});

```

問題なく動きますが何も表示されないので寂しく、ユーザビリティ的にもアレです。（ローディングを意識させたくないときは有効ですが）

とりあえずラベルを足してみます。

```js
// ~省略
init: function(options) {
  this.superInit(options);
  var self = this;
  this.backgroundColor = "#B8F7F6"; // 見辛いので背景色を変えます
  var loader = phina.asset.AssetLoader();

  // 明滅するラベル
  var label = phina.display.Label({
    text: "NOW LOADING...",
  })
  .addChildTo(this)
  .setPosition(this.width/2, this.height*0.2)
  label.tweener.clear()
  .setLoop(1)
  .to({alpha:0}, 500)
  .to({alpha:1}, 500)
  ;
  
  // ローダーによるロード完了ハンドラ
  loader.onload = function() {
    // Appにロード完了を伝える（=次のSceneへ移行）
    self.flare('loaded');
  };

  // ロード開始
  loader.load(options.assets);
},
// ~省略
```

少しそれっぽくなったかと思います。
（ロードするデータ量が少ないと一瞬すぎて分からないですが）

### ロード進捗に応じて何かする
デフォルトのローディングシーンではプログレスバーによって進捗状況が分かるようになっていますね。

これはloaderがロードの進行に応じてprogressイベントを発火するので、それをトリガーに処理を行うことで実現しています。
またprogressイベントは引数で進捗具合を0 ~ 1の小数で返してくれます。

進行状況に応じてラベルのテキスト内容を変えてみましょう。

```js
init: function(options) {
  this.superInit(options);
  var self = this;
  this.backgroundColor = "#B8F7F6";
  var loader = phina.asset.AssetLoader();

  // 明滅するラベル
  var label = phina.display.Label({
    text: "NOW LOADING...",
  })
  .addChildTo(this)
  .setPosition(this.width/2, this.height*0.2)
  label.tweener.clear()
  .setLoop(1)
  .to({alpha:0}, 500)
  .to({alpha:1}, 500)
  ;

  // ロードが進行したときの処理
  loader.onprogress = function(e) {
    // 進捗具合を％で表示する
    label.text = "ロード中… {0}%".format((e.progress * 100).toFixed(0));
  };
  
  // ローダーによるロード完了ハンドラ
  loader.onload = function() {
    // Appにロード完了を伝える（=次のSceneへ移行）
    self.flare('loaded');
  };

  // ロード開始
  loader.load(options.assets);
};
```

![myloading-example2.png](https://qiita-image-store.s3.amazonaws.com/0/81823/1bfe48ab-2db4-199f-ea6f-0e53c33fd127.png)

[（一瞬すぎて分かりづらい）サンプルコードです](https://goo.gl/27f4tE)

### アセットロード後のポスト処理
前述のように、loaderによるロードが完了してもscene自体のloadedイベントを発火しない限り、処理は進みません。
つまりアセットロード後に何か追加で処理を行うことも可能です。

例えば、ロード済みテクスチャにフィルターをかけたいときは以下のようになります。

```js
// ~省略
var self = this;
var AssetManager = phina.asset.AssetManager;

// loaderによるロード後の処理
var afterLoad = function() {
  // グレスケ化フィルターを掛ける
  var filtered = AssetManager.get('image', 'player').clone().filter(function(pixel, i, x, y, imageData) {
    if (pixel[3] === 0) return;

    var Y = 0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2];
    imageData.data[i] = Y; // r
    imageData.data[i+1] = Y; // g
    imageData.data[i+2] = Y; // b
  },)

  // フィルター後のテクスチャ登録
  AssetManager.set('image', 'player_gs', filtered);

  self.flare('loaded');
};

loader.onload = afterLoad;

// ~省略
```

複雑になりそうなら[Flowクラス](https://alkn203.github.io/blog/2016/12/12/phina-advent-12th/)（もしくはしPromise）を併用したり、ロード後の処理を別の関数に切り分けたりしましょう。

## まとめ
- まずLoadingSceneを上書き、assetLoaderにオプションを渡してロード開始
- ロード進捗はprogressで、終了はended
- 最後はthis.flare('loaded')

ローディングシーンは何気にタイトルシーンよりも先にプレイヤーが目にする部分だったりするため、侮れないところがあります。
余裕があればぜひ手を加えてみましょう。