ファイルへのリンクの後ろに#L21
とか#L21-L23
とかつければOk。
ただこれだとファイルのバージョンが変わった時に行がずれてしまうことがある。なのでGitHubのショートカットキーy
を押して、そのバージョンのファイルへのパーマネントリンクへURLを変更した方が良い。
ファイルへのリンクの後ろに#L21
とか#L21-L23
とかつければOk。
ただこれだとファイルのバージョンが変わった時に行がずれてしまうことがある。なのでGitHubのショートカットキーy
を押して、そのバージョンのファイルへのパーマネントリンクへURLを変更した方が良い。
Phaserをthree.jsで描画ネタのサンプルコードでもう一つ実験したのは、ゲーム内のキャラを機能のモジュールを組み合わせて制御できないかということ。例えば、上から降ってくるバルーンは、以下のコードのようにした。
interface Baloon extends Phaser.Sprite, U.HasMesh, U.HasName { } function setBaloon() { var x, y; var baloon = <Baloon> U.chain([ U.Name.set('Baloon'), U.Position.set( x = (Math.random() * .8 + .1) * 512, y = -.1 * 512), U.Body.set, U.GeomertyMaterialBody.addSquares (15, ['0110', '1111', '1111', '0110'], [0xeeeeaa]), U.Mesh.set, U.Sprite.removeWhenOutOfWorldBounds(60, 60, true), U.Body.setCollisionGroup(shotCollidingCg), U.Body.collides([shotCg, shotCollidingCg]), U.Body.setNotCollidingWorldBounds, U.Sprite.setUpdate((o: Baloon) => { o.body.thrust(66); U.PositionMesh.update(o); U.BodyMesh.update(o); }), ], U.Sprite.get()); }
elmで関数型にかぶれてた後に作ったので関数合成っぽくかけるようにした、んだけど実態は全然関数合成ではなくて、U.chainっていう関数の中身は配列内の関数にobjを順に渡しているだけ。
export function chain(funcs: Function[], obj = {}) { _.forEach(funcs, (f) => f(obj)); return obj; }
objはゲーム内のキャラを表すインスタンスで、PhaserではだいたいPhaser.Spriteを使う。objを扱う関数は、例えばその位置を設定するU.Position.setは以下のコード。
export interface HasPosition { position: Phaser.Point; } export module Position { export function set(x: number, y: number) { return (obj: HasPosition) => { obj.position.x = x; obj.position.y = y; } } }
positionを持つHasPositionなobjにxとyを設定する関数を返している。自力カリー化みたいなことをすることで、関数合成もどきみたいなことをできるようにしている。objを直接書き換えずに、位置を更新した新しいobjをreturnするようにすれば、lodashのflowで繋げられる行儀の良い関数になると思うけど、面倒だからこの方式で。
利点としてはパイプラインで書けるので流れるようなインタフェースっぽいのが実現できること。あと機能をモジュールで分離できるので、一つのクラスにすべての機能を詰め込むようなことをしなくて済む。物理エンジンのBodyと3DライブラリのMeshの両方を持つobjだけで使うユーティリティ関数とかも作れる。
export interface HasBodyMesh extends HasBody, HasMesh { } export module BodyMesh { export function update(obj: HasBodyMesh) { obj.mesh.rotation.z = -obj.body.angle * Math.PI / 180; } }
欠点としては各関数で手動のカリー化みたいなのをしなきゃいけないこと。Ramdaとかの自動カリー化をしてくれるライブラリが使えればいいんだけど、TypeScriptの型情報を保持したままカリー化するのは、インタフェースをうまいこと使ったりする必要があったりして難しそう。
あとデバッグが面倒。パイプラインでつないだ関数を実行する実態はすべてU.chainの中にいってしまうので、まともにブレークポイントが張れない。これが結構きつい。
TypeScriptにこだわらないのであれば、ES6とRamdaの組み合わせとかでもうちょっとマシに作れるかもしれないけど、型が無いのは個人的にはかなり魅了減なので難しいところだ。
この記事みたいに、さらにBacon.jsまで使えばより関数型でFRPっぽい書き方ができそうな気もするが、そこまでするんだったらもうelmにしとけという気もする。
ゲーム内のキャラ制御を機能別にモジュール化できればコードの再利用性は高まると思うけど、モジュールの作り方、それらの組み合わせ方、妥当な書き方はまだよく分からんというところでした。
小さなJavaScriptライブラリをガッチャンコしてゲームエンジンっぽいことをさせるのは可能なのかの微妙に続き。2DのHTML5ゲームライブラリPhaserを使うけど、描画は3Dライブラリのthree.jsを使いたい、みたいな組み合わせは、ちょっと工夫すればできることはできる。試しに作ってみた。
three.jsを初期化する時に、Phaserの画面サイズと同じサイズのthree.jsのrendererを作ってそのターゲットのcanvasからPIXIのtextureを作り、それをスプライトとして貼れば良い。
renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(game.width, game.height); var canvas: HTMLCanvasElement = renderer.domElement; baseTexture = new PIXI.BaseTexture(canvas, PIXI.scaleModes.DEFAULT); var texture = new PIXI.Texture(baseTexture); var textureFrame = new Phaser.Frame( 0, 0, 0, game.width, game.height, 'texture', game.rnd.uuid().toString()); var sprite = game.add.sprite(0, 0, texture, textureFrame);
キャラクタを動かす時はPhaserのSpriteと一緒にthree.jsのmeshも作り、フレーム毎にSpriteのpositionに合わせてmeshの位置をアップデートすれば良い。
obj.mesh.position.x = (obj.position.x - game.width / 2); obj.mesh.position.y = -(obj.position.y - game.height / 2);
Phaserに備え付けのP2物理エンジンと組み合わせても良い。bodyの回転に合わせてmeshのrotationをアップデートすれば回転方向も一致させることができる。
obj.mesh.rotation.z = -obj.body.angle * Math.PI / 180;
2DのSprite相当のものをレンダリングしているだけなので、座標的には2Dのままだが、three.jsでレンダリングできればその豊富なポストエフェクトやライティングを自由に使うことができ、絵作りの幅を広げることができる。工夫すれば背景に奥行きのある3Dの絵とかもできるだろう。
既存のJavaScriptゲームエンジンでも、描画周りを他のライブラリに差し替える、みたいなことは現状でも可能と言えば可能だ。ただ、他のライブラリのrendererで元のrendererを上書きする、みたいなところは裏ワザ的なことがどうしても必要で、先人の知恵に頼らないと厳しいことも多い。この辺がもうちょっと簡単になれば、いろんなライブラリを気軽に組み合わせてゲームを作ることができるんだけどね。
ゲームを作る時にモノリシックなゲームエンジンを使う方法の他に、ブラウザゲームのプロトタイピングに役立つJavaScriptライブラリで述べたようなライブラリ群を機能ごとに組み合わせて作る、っていうアプローチも考えられる。そうした方が、グラフィックスはこれ、物理エンジンはこれ、という具合に自分が好きなAPIを使ってゲームを作れるし、一度に覚えなければならないことも少なくてすむ。
そういったライブラリを探すには、例えばhughsk/game-modulesというページがあって、ここにはゲーム向けの小さなライブラリがいっぱい挙げられている。これらから好きなモノをチョイスをして組み合わせれば、自分好みにカスタマイズしたゲームエンジンの出来上がりだ。
といけば簡単なのだが、実際はそんなに簡単ではない。ゲーム関連のライブラリは、requestAnimationFrame周りのフレームごとのアップデート処理やCanvas周りの描画ハンドリングはだいたい自前で抱え込んでいるから、それらの間でケンカしないように処理を融合させるのがまず大変。2次元Vectorとかの基本的なデータ型もそれぞれが別物を持っている可能性が高い。そういった面倒を見ながらうまいこと組み合わせなければならないね、となると腰が引ける。
別のアプローチとしてはプラグインの利用を前提としたごく小さなコアを持つ、モジュールフレンドリーなゲームエンジンを作るという方法がある。このアプローチのものとして例えばQuintusがある。Quintusはコア以外はモジュールとして提供されており、自分で独自のモジュールを作ることもできる。ただQuintusのコアは今見るとlodashとかAltJSの機能でカバーできるものも多数入っていてあまりコンパクトな感じがしないのと、独自モジュールも他のライブラリをラップして使う、ということは特に考慮されていなさそう。
既存のライブラリ機能は極力含まないような本当に小さなコアと、グラフィックス機能はp5.jsでもthree.jsでもSnap.svgでも好きなモノが選べます、というような柔軟性を様々な機能向けに持ったプラグインシステム、それらを備えたエンジンが理想型だと思うんだけど、そんなものを作ることは可能なのかしらん。
ブラウザで動くゲームのプロトタイプを作るとき、もちろんPhaserとかの機能満載のゲームエンジンを使ってもいいのだが、こういったエンジンは多彩な機能に応じてAPIが豊富すぎて、使いこなせるようになるまでが若干面倒なことがある。そういった時、もっと軽量のJavaScriptライブラリを機能ごとに組み合わせた方がお手軽に作れるのではないか。そう思って、ゲームで使う機能ごとのライブラリを探してみた。
p5.jsが役立ちそう。p5.jsはビジュアルデザイン向けとして有名な言語ProcessingをJavaScript向けライブラリとして提供したもの。似たものとしてProcessing.jsもあるが、こちらは元のProcessing言語をブラウザ上で動かすことに主眼を置いており、ライブラリとして使う分にはp5.jsの方が使いやすい。
Get Started見れば分かるように、createCanvas
してellipse
するだけで楕円がすぐ書ける。円とか矩形とかでキャラを代用して表示する描画はすごく簡単にできる。draw
関数が一定時間ごとに呼び出されるので、ゲームループもこれで実現できる。
Sat.jsでいいのではないか。線、円、ポリゴンの当たり判定が簡単に判定できる。一つ問題があって、p5.jsと組み合わせるとき、p5.jsもSat.jsもそれぞれ独自の2次元Vectorを持っていて、それらの変換が必要なこと。ライブラリを組み合わせるとこういった問題がまま起こる。
物理エンジンが必要ならMatter.jsとかかしらん。Matter.jsは描画もしてくれるから、これ使うならp5.jsは不要かもしれん。
ゲーム向けキー入力をハンドリングするライブラリはあまり見つからない。世の中のキー入力ライブラリは複数キーのコンビネーションを扱うためのものがほとんどで、ゲーム向けにあるキーが今押されている、ちょうど今押された、今離された、みたいな情報を返すものはあまり無い。
かろうじて見つけたのはgame-keyboard。
ゲーム用ならWASD入力に応じて上下左右の移動量を返す専用関数なども欲しいところだ。
ゲームに限った話ではないが、コレクション操作などを簡単にするためにlodashを入れておくといろいろ捗る。
あとJavaScriptを素で書いてもいいが、最近はなんらかのAltJSのサポートを受けた方が便利なことが多い。個人的にはTypeScriptとVisual Studio Codeの組み合わせが手堅いと思っている。ES6をBabelを使って書くのも今風か。
ゲーム内のキャラクタ群を管理して1フレに1回アップデートしてくれるアレ。これもこの機能単独のライブラリは見つけられなかった。まああまりに地味すぎてライブラリにするモチベーションが無いからかもしれん。最低限のものは以下の作りでもいける。update()がfalseを返したら削除する実装。
var actors = []; function updateActors() { actors = _.filter(actors, (a) => a.update() !== false); }
ゲームでよく使うclampとかwrapとかを備えたMathライブラリ、ってのも見つからない。PhaserのMathとかUnityのMathfに含まれるユーティリティ関数群を備えたライブラリがあってもよさそうな気がするのだが。
まあ自分でNumberを後から拡張してもいいよね。
interface Number { clamp(min?: number, max?: number): number; wrap(min?: number, max?: number): number; } Number.prototype.clamp = function(min: number = 0, max: number = 1): number{ return this < min ? min : (this > max ? max : this); } Number.prototype.wrap = function(min: number = 0, max: number = 1): number{ var w = max - min; var v = this - min; return v >= 0 ? v % w + min : w + v % w + min; }
音は、プロトタイプにはいらない、よね?どうしても何か鳴らしたかったらjsfxとか。
ライブラリを組み合わせてカスタマイズしたオレオレゲームエンジンみたいのが簡単に作れるのは楽しい。が、いざライブラリを探してみると結構足りてないパーツがあるってのと、Vectorの衝突みたいにライブラリを組み合わせる際に出てくる問題があるのがネック。車輪の再発明を避けつつ、自前ゲームエンジンを作って遊んでみる時にはいいアプローチかも。
がまだ私には難しすぎる気がするよ……
elmはHaskellに似た構文を持つ関数型言語のAltJS。コンパイルするとJavaScriptが生成されるのでブラウザ上で動くゲームも作れる。なのでごく簡単なミニゲームをelmで作ってみた。
ゲームライブラリ相当の部分を除くと250行強というところなので、コードの分量的にはCoffeeScriptで書くのと似たようなものかちょい長めというところかなあ。でもコードを書く際には関数型言語ならではのかなり違う発想が求められるので、なかなか苦労する点も多い。elmについて、このミニゲームを書いた時に気づいた点をメモしておく。
なんでelmを使ってみようかと思ったかというとオフィシャルにPongを作るサンプルがあるからですよ(ただし上記のサンプルまだver.0.13の書き方なので注意。今の最新のelmは0.14)。elmは単なる関数型言語ではなくて、 Functional **Reactive** Programmingのための言語で、その特性を活かしてゲームを作るためのエッセンスが、このPongの作り方に書いてある。
elmではSignalと呼ばれる、時間で変化する値を関数型言語の枠組みで扱うための仕組みがある。時間で変化する値とは、例えばゲームのプレイヤーからのキー入力など。
まだ理解が追いついてないのだが、Signalでキー入力を扱うと、時系列のキー入力状態がリストとして管理される。キーが入力されるとその情報がこのリストの末尾に追加される。
Signalにはこのリストを過去から畳み込むための関数foldpがある。過去から畳み込む!もうこの時点でお前は何を言っているんだ感があるが、要はこれら入力に応じて今から次の未来への変換を行う関数を発火させて、別のSignalを作る仕組み。まあゲームでいうところの毎フレームのUpdate処理だ。
elmのmain関数は画面上のElementのSignalとして定義されている。なのでUpdate処理で生成されるSignalからElementのSignalを作れば、ゲームの画面出力が得られる。なので必要なのは入力や時間経過のSignalをfoldpでゲーム状態のSignalにした上でそれをElementのSignalにmapする関数、とか言い始めるとよく分からなくなるのでPongのサンプルを見て下さい。とにかくPongのサンプルを見ればelmでゲームを作る方法がすぐに分かって素晴らしい。
コアライブラリにあるGraphics.Collageでキャンバス上に書く丸や四角や線、Mouseでマウス入力、Keyboardでキー入力が扱えるので、ゲームに必要な基本的な入出力は外部ライブラリの助けを借りなくても実現できる。関数型言語のProcessingっぽい位置づけとも言える。
Keyboard.wasdっていう関数があるのが、elmでゲームを作りましょうという意図が感じられていいね。
関数型言語はコンパイルが通ればランタイムエラーがほとんど無いとよく言われるが、それは確かに感じた。ただそれが型のチェックのおかげなのか、関数型言語のおかげなのかはよく分からん。型チェックが欲しいだけならTypeScriptでもいいしなあ。
elmを使って楽しいのがこのタイムトラベルデバッガ。普通にゲーム書くだけで、それを自由にポーズしたり、時間を巻き戻したりすることができる。関数出力を監視するDebug.watchと組み合わせることで、妙な挙動をしたところとその前後を簡単に調べることができる。これは便利。
ささいな間違いがなんでも型エラー (Type mismatch)になる。しかもエラーメッセージが不親切。
例えば関数の引数の数を間違えたというささいな間違いに対して長大な型エラーが出力されたりする。関数に対して一部の引数を与えることができるカリー化でそういった関数も評価できてしまうせいもあるとは思うのだが、そういった間違いも無理やり評価してその結果いろんなところに型エラーを発見したことになってしまっている感じ。よく見ると本質的なエラーが書いてあるところもあるのだが、それ以外のエラー指摘に埋もれてしまっていて発見が非常に困難。
特にelmのコンパイラは型エラーはこのへんの記述に関係あります、というメッセージの下に関数まるごとが表示されたりすることが頻発するので、発見がより困難になる。
elmは関数型言語であるからにして代入を許さない。参照透過性を保つためだ。
これはゲームの書き方にいろんな影響を与える。Update処理はゲーム内オブジェクトを書き換えるのでは無く、1フレーム前の状態を見て今のフレームの状態を生成する処理になる。また逐次的な処理を行う際の書き方も、前状態から次状態の値を生成する関数を作り、その値を入力としてさらに次の値を生成する関数、という書き方になりがちで、その受け渡しをするための一時変数名が大量に必要になったりする。
この辺の記述の無駄さ加減がコードを書いている際に気になる。まあそのような逐次的な処理をしないような作りにしましょう、というのが正しいプラクティスなのかもしれんが、手続き脳人間にはなかなかつらい。
elmのパーサーこなれてないよ。特にレコード操作。elmはレコードにすでにあるフィールドをアップデートする時の構文(<-)と、フィールドを追加する時の構文(=)が違うのだが、これを間違った時にそこを指摘せずにその後ろのカンマを指摘したりする。あとアップデートはカンマで複数書けるのに追加は複数書けないとかいう構文自体の謎仕様もある(外部ライブラリのFocusを使えばマシにはなるが)。しかもその仕様を踏んだ時のエラーメッセージがまた謎。
あと型の宣言でのエラーや変数名の重複などについて、その行番号を教えてくれないのは何なんですかね。
Signal、結局よく分かってない。今困っているのは乱数のシードに与えるための、ゲーム開始時の時刻を取る方法が分からないこと。Time.timestampでSignal (Time, a)は取り出せるっぽいのだが、このTimeをRandom.initialSeedに流し込むのはどうすればいいんだ。
elm、書き方の発想が手続き型言語と違って書いている分にはいろいろ楽しいけど苦しみも多い。これからどうしようかなあ。もうちょっと継続して使って見るか、やめて手続き脳の山に帰るか、悩ましいところだ。
やけくそランク VS やけくそアイテムのインフレゲーRisk of Rain、タイムパイロット in 21世紀のLUFTRAUSERS、ミニマムA列車のMini Metroあたりは特にオススメ。そして2014年になってもまだスペースハリアーを遊んでいる未来。そして今でも面白い。