Edited at
phina.jsDay 11

phina.jsの簡単さから基礎知識まで

More than 1 year has passed since last update.

ちょっと今年のAdvent Calenderで基本的な説明してる人がいないと思ったので書きました。

いや、本当、簡単ですよ。


どこが?

個人的には、ソースコードのその可読性が大きいですね。基本的なJavascript/Canvasの知識だけあれば、どこからでも十分読めます。おかげでデバッグがしやすいですし、拡張性も高いです。

また、分からないことがあっても質問などするとすぐ答えが返ってきますし、対応が早いです。Gitterのチャット欄などを見ると、次々とユーザーさんが質問、要望を出しては開発陣の皆様が対応していく様子が伺えますし、私自身も一度それでお世話になっています。


追記:

Gitterも引き続き使用できますが、主な開発者の皆さんはSlackへ移行しました!可能な方はこちらで質問をお願いします!


さらに、低レベルのAPIが充実している点もスムーズに開発するために重要なポイントです。

そしてこれらを本当に有意義なものにするのが、「単体で開発に必要な機能がカバーされている」という点です。特に2Dのゲーム、小規模なアプリに関してはこれ1つで用が足ります。

結果としては、ゲームを作る際に覚える事がとても少なく、適当にオブジェクトをポンポン置いていって、適当にxy座標をいじくってれば完成しちゃうようなイメージになります。

まあ、phina.jsに対する私のほめ言葉などは置いておいて、ここは具体的なその使い方を見てみましょう。


基本

以下が最小のテンプレートになります。


白い画面を表示する

phina.globalize();

phina.define('MainScene', {
superClass: 'DisplayScene',
init: function() {
this.superInit();
}
});

phina.main(function() {
var app = GameApp({
startLabel: 'main'
});
app.run();
});


phina.define関数はクラスを作成する機能を持っています。ES6のclass構文と違い、クラスを使用する部分より下に書いても良いところが強みです。superClassが継承元を表し、init関数はコンストラクタとして用いられます。他の名前のプロパティを追加すると、基本的にはメンバとして機能します。

追記: superInitについて

何らかのクラスを継承したクラスにおいては、スーパーコンストラクタ呼び出しとしてthis.superInit()を必要とします。

例えinit内に書く処理が他になかったとしても、superInitは必要です。

省くとエラーになります。

また、this.superInit(options)といった風に、引数を渡すこともできます。

MainSceneクラスは、基本的には画面にあたる部分と考えられます。通常は、このクラスのinit関数内に実際の処理を書いていきます。phina.mainの中はアプリケーション全体の設定を行う時以外はほとんど変更しません。

phina.jsではオブジェクトの生成にnew演算子を使いません。単にHoge(arg)と関数を呼び出して生成できます。そのままでは画面とオブジェクトが関連づけられていないので、画面に表示されません。addChildTo(this)を付け加えることで、表示されるようになります。

文字の表示を行うLabelを追加し、画面中央に表示してみましょう。

MainSceneのコードを以下のように変更するだけです。


「Hello, phina.js!」と表示

phina.define('MainScene', {

superClass: 'DisplayScene',
init: function() {
this.superInit();
var label = Label('Hello, phina.js!').addChildTo(this)
// これ以降でlabelに対して様々な操作をする
.setPosition(this.gridX.center(), this.gridY.center()); // 位置を指定
}
});

また、phina.jsはメソッドチェーン指向です。先ほど出てきたaddChildTosetPositionなどは全てthisを返します。


〇〇がしたい


図形を表示したい

Shape系統のクラスを使いましょう。


  • RectangleShape

  • CircleShape

  • TriangleShape

  • StarShape

  • PolygonShape

  • HeartShape

  • PathShape

以上の7つがあらかじめ用意されています。基本的には普通にオブジェクトを生成してaddChildToすればOKです。各々のプロパティや詳しい使い方は割愛します。


足りねえ、他にはないのか?

自分で作りましょう。phina.display.Shapeを継承したクラスを作り、その中にprerender関数を定義すればOKです。prerender関数の第一引数にCanvasが渡されてきます。そこに描画すればいいです。

また、ここに渡されてくるCanvasは、phina.graphics.Canvasという内部でラップされたCanvasです。素のCanvasには無い機能が含まれており、さらにメソッドチェーンで書けるようになっています。

以下に例を示しました。皆さんも必要に応じてこのようなクラスを作ってみてくださいね。


任意の底辺、高さで表示できる二等辺三角形のクラス

phina.define('DirectionShape', {

superClass: 'phina.display.Shape',

init: function(options) {
options = ({}).$safe(options, {
backgroundColor: 'transparent',
fill: '#ff5050',
stroke: '#aaa',
strokeWidth: 2,

width: 16,
height: 32
});
this.superInit(options);
},

prerender: function(canvas) {
canvas.beginPath()
.moveTo(0, this.height)
.lineTo(this.width, -this.height)
.lineTo(-this.width, -this.height)
.closePath();
}
});



画像を表示したい/音を鳴らしたい

外部のデータを簡単に読み込む機能として、phina.jsにはアセットと呼ばれる機能が存在します。使い方は簡単で、以下のようにGameAppオブジェクト初期化の引数に外部データのURLを含むオブジェクトを渡すだけです。


アセットの使用

phina.main(function() {

var app = GameApp({
startLabel: 'main', // ここは変更しない
assets: { // URLはassetsプロパティの中に記述する
image: { // 画像なのでimageに含める
hoge: 'hoge.png' // プロパティ名はプログラム中で使われる名前。値は実際のファイル名
}
/* 画像以外のファイル(音声等)も同様に
sound: {
a: 'a.mp3'
}
*/

}
});

app.run();
});


画像の表示にはSpriteクラスを使います。引数に前のプロパティー名で設定した名前を入れることで関連付けが行われます。


画像の表示

var anysprite = Sprite('hoge').addChildTo(this);

// anysprite.setImage('fuga');などとして、画像は変更可能

また、音声はAssetManagerクラスから読み込み、playメソッドで再生します。

AssetManager.get('sound', 'a').play(); // 音声の場合はこのように使用する

// ↖︎__ ここにプロパティー名で設定した名前を入れる。他は固定


それっぽく動かしたい

といった時のために、phina.jsにはTweenerというクラスがあります。

anyobject.tweener.to({x: 100, y: 150}, 1000).play();

とすると、anyobjectは1秒かけて(100, 150)の位置まで移動します。

toの第3引数にEASINGパラメータを渡すと動き方が変わりますし、toメソッドをbyに置き換えると、(100, 150)の位置に移動する代わりに、現在の位置から(100, 150)移動するようになります。

この辺の細かいところを説明しようとするととても長くなるので、[phina.js] Tweenerを使いこなそう! [Tweener 基本編]あたりを読むといいと思います。


このクラスどうやって使うの?/動作がおかしい

上のリンクもそうですが、今年の、または去年のAdvent Calenderを見れば大体の事は載っています。また、SlackGitter開発者のTwitterで遠慮せず聞いてみるといいです。それでほぼ間違いなく解決しますよ。

可能なら、その部分のソースコードを読むのもいいです(https://github.com/phi-jp/phina.js/tree/develop/src からクラスパスをたどって見れます)。動作をたどっていくうちに、原因がわかることもあります。


GameAppCanvasAppの設定

phina.jsのパフォーマンスは悪くないので、あまり極端なことをしなければ滑らかに動きます。ただし、デフォルトではFPSが30なので、60FPSにしたい場合は、引数にfps: 60を加えるといいです。

また、phina.jsの画面はデフォルトではページいっぱいに表示されます。大きさを固定する場合は引数にfit: falseを加える必要があります。

画面は起動時に動的に生成されていますが、配置も自分で決めたい場合は、さらにqueryパラメータに任意のCanvas要素を指定しましょう。

画面の縦幅、横幅はheight/widthパラメータで決めることができます。ただしCanvasAppの場合は、同時に各シーンにもサイズを指定しなければならない点に注意してください。


おわりに

この記事はゲームを作りたい人、Javascriptくらいは知ってるけど、phina.jsは初めてな人に向けて書きました。

如何せんQiitaへの投稿は初めてなもので、文章など大目に見てやってください。