phina.jsバージョン: v0.2.1
はじめに
この記事はphina.js Advent Calendar 2017 Advent Calendar 2017の2日目です。
アドカレの最初の方なので(?)ローディングシーンについての話です。
GameAppクラスでオプションとしてassetsを渡した状態でアプリを実行すると、最初のシーンに移行する前にアセット類をロードするため、ローディングシーンを介すようになります。
上部に水色のバーが表示されるアレです。
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();
});
改造するのはちょっと面倒くさい印象があったのですが、実は割りと簡単に自作することができると最近知りました。
手順
元々定義されているLoadingSceneクラスをオーバーライドすることで、自作のものを使うよう仕向けることができます。
そして以下が最もシンプルな形となります。
// ※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);
},
});
問題なく動きますが何も表示されないので寂しく、ユーザビリティ的にもアレです。(ローディングを意識させたくないときは有効ですが)
とりあえずラベルを足してみます。
// ~省略
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の小数で返してくれます。
進行状況に応じてラベルのテキスト内容を変えてみましょう。
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);
};
アセットロード後のポスト処理
前述のように、loaderによるロードが完了してもscene自体のloadedイベントを発火しない限り、処理は進みません。
つまりアセットロード後に何か追加で処理を行うことも可能です。
例えば、ロード済みテクスチャにフィルターをかけたいときは以下のようになります。
// ~省略
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クラス(もしくはしPromise)を併用したり、ロード後の処理を別の関数に切り分けたりしましょう。
まとめ
- まずLoadingSceneを上書き、assetLoaderにオプションを渡してロード開始
- ロード進捗はprogressで、終了はended
- 最後はthis.flare('loaded')
ローディングシーンは何気にタイトルシーンよりも先にプレイヤーが目にする部分だったりするため、侮れないところがあります。
余裕があればぜひ手を加えてみましょう。