機能モジュールのパイプラインでキャラを制御する

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にしとけという気もする。

ゲーム内のキャラ制御を機能別にモジュール化できればコードの再利用性は高まると思うけど、モジュールの作り方、それらの組み合わせ方、妥当な書き方はまだよく分からんというところでした。

2DゲームエンジンPhaserの描画をthree.jsで3Dにする

小さな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ライブラリをガッチャンコしてゲームエンジンっぽいことをさせるのは可能なのか

ゲームを作る時にモノリシックなゲームエンジンを使う方法の他に、ブラウザゲームのプロトタイピングに役立つJavaScriptライブラリで述べたようなライブラリ群を機能ごとに組み合わせて作る、っていうアプローチも考えられる。そうした方が、グラフィックスはこれ、物理エンジンはこれ、という具合に自分が好きなAPIを使ってゲームを作れるし、一度に覚えなければならないことも少なくてすむ。

そういったライブラリを探すには、例えばhughsk/game-modulesというページがあって、ここにはゲーム向けの小さなライブラリがいっぱい挙げられている。これらから好きなモノをチョイスをして組み合わせれば、自分好みにカスタマイズしたゲームエンジンの出来上がりだ。

といけば簡単なのだが、実際はそんなに簡単ではない。ゲーム関連のライブラリは、requestAnimationFrame周りのフレームごとのアップデート処理やCanvas周りの描画ハンドリングはだいたい自前で抱え込んでいるから、それらの間でケンカしないように処理を融合させるのがまず大変。2次元Vectorとかの基本的なデータ型もそれぞれが別物を持っている可能性が高い。そういった面倒を見ながらうまいこと組み合わせなければならないね、となると腰が引ける。

別のアプローチとしてはプラグインの利用を前提としたごく小さなコアを持つ、モジュールフレンドリーなゲームエンジンを作るという方法がある。このアプローチのものとして例えばQuintusがある。Quintusはコア以外はモジュールとして提供されており、自分で独自のモジュールを作ることもできる。ただQuintusのコアは今見るとlodashとかAltJSの機能でカバーできるものも多数入っていてあまりコンパクトな感じがしないのと、独自モジュールも他のライブラリをラップして使う、ということは特に考慮されていなさそう。

既存のライブラリ機能は極力含まないような本当に小さなコアと、グラフィックス機能はp5.jsでもthree.jsでもSnap.svgでも好きなモノが選べます、というような柔軟性を様々な機能向けに持ったプラグインシステム、それらを備えたエンジンが理想型だと思うんだけど、そんなものを作ることは可能なのかしらん。

ブラウザゲームのプロトタイピングに役立つJavaScriptライブラリ

ブラウザで動くゲームのプロトタイプを作るとき、もちろん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のサポートを受けた方が便利なことが多い。個人的にはTypeScriptVisual Studio Codeの組み合わせが手堅いと思っている。ES6をBabelを使って書くのも今風か。

オブジェクト管理というかタスクシステムというかアクターというか

ゲーム内のキャラクタ群を管理して1フレに1回アップデートしてくれるアレ。これもこの機能単独のライブラリは見つけられなかった。まああまりに地味すぎてライブラリにするモチベーションが無いからかもしれん。最低限のものは以下の作りでもいける。update()がfalseを返したら削除する実装。

var actors = [];
function updateActors() {
  actors = _.filter(actors, (a) => a.update() !== false);
}

ゲーム向けMath

ゲームでよく使う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の衝突みたいにライブラリを組み合わせる際に出てくる問題があるのがネック。車輪の再発明を避けつつ、自前ゲームエンジンを作って遊んでみる時にはいいアプローチかも。

手続き脳人間がWeb向け関数型言語elmを使ってゲームを書こうとしてみた

がまだ私には難しすぎる気がするよ……

elmはHaskellに似た構文を持つ関数型言語のAltJS。コンパイルするとJavaScriptが生成されるのでブラウザ上で動くゲームも作れる。なのでごく簡単なミニゲームをelmで作ってみた。

ゲームライブラリ相当の部分を除くと250行強というところなので、コードの分量的にはCoffeeScriptで書くのと似たようなものかちょい長めというところかなあ。でもコードを書く際には関数型言語ならではのかなり違う発想が求められるので、なかなか苦労する点も多い。elmについて、このミニゲームを書いた時に気づいた点をメモしておく。

elmいいところ

サンプルにPongがある

なんで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と組み合わせることで、妙な挙動をしたところとその前後を簡単に調べることができる。これは便利。

elmイマイチなところ

なんでも型エラー

ささいな間違いがなんでも型エラー (Type mismatch)になる。しかもエラーメッセージが不親切。

例えば関数の引数の数を間違えたというささいな間違いに対して長大な型エラーが出力されたりする。関数に対して一部の引数を与えることができるカリー化でそういった関数も評価できてしまうせいもあるとは思うのだが、そういった間違いも無理やり評価してその結果いろんなところに型エラーを発見したことになってしまっている感じ。よく見ると本質的なエラーが書いてあるところもあるのだが、それ以外のエラー指摘に埋もれてしまっていて発見が非常に困難。

特にelmのコンパイラは型エラーはこのへんの記述に関係あります、というメッセージの下に関数まるごとが表示されたりすることが頻発するので、発見がより困難になる。

関数型への考え方の転換コスト

elmは関数型言語であるからにして代入を許さない。参照透過性を保つためだ。

これはゲームの書き方にいろんな影響を与える。Update処理はゲーム内オブジェクトを書き換えるのでは無く、1フレーム前の状態を見て今のフレームの状態を生成する処理になる。また逐次的な処理を行う際の書き方も、前状態から次状態の値を生成する関数を作り、その値を入力としてさらに次の値を生成する関数、という書き方になりがちで、その受け渡しをするための一時変数名が大量に必要になったりする。

この辺の記述の無駄さ加減がコードを書いている際に気になる。まあそのような逐次的な処理をしないような作りにしましょう、というのが正しいプラクティスなのかもしれんが、手続き脳人間にはなかなかつらい。

パースエラー

elmのパーサーこなれてないよ。特にレコード操作。elmはレコードにすでにあるフィールドをアップデートする時の構文(<-)と、フィールドを追加する時の構文(=)が違うのだが、これを間違った時にそこを指摘せずにその後ろのカンマを指摘したりする。あとアップデートはカンマで複数書けるのに追加は複数書けないとかいう構文自体の謎仕様もある(外部ライブラリのFocusを使えばマシにはなるが)。しかもその仕様を踏んだ時のエラーメッセージがまた謎。

あと型の宣言でのエラーや変数名の重複などについて、その行番号を教えてくれないのは何なんですかね。

結局Signalとは何者だったのか

Signal、結局よく分かってない。今困っているのは乱数のシードに与えるための、ゲーム開始時の時刻を取る方法が分からないこと。Time.timestampでSignal (Time, a)は取り出せるっぽいのだが、このTimeをRandom.initialSeedに流し込むのはどうすればいいんだ。

elm、書き方の発想が手続き型言語と違って書いている分にはいろいろ楽しいけど苦しみも多い。これからどうしようかなあ。もうちょっと継続して使って見るか、やめて手続き脳の山に帰るか、悩ましいところだ。

今年面白かったゲーム

やけくそランク VS やけくそアイテムのインフレゲーRisk of Rain、タイムパイロット in 21世紀のLUFTRAUSERS、ミニマムA列車のMini Metroあたりは特にオススメ。そして2014年になってもまだスペースハリアーを遊んでいる未来。そして今でも面白い。

今年50のゲームを作って分かった面白いゲームを作る方法

なんてのは無いということが。

作ったものは上のページにまとめた。全ゲームのスクリーンショットがアニメGIFになっていて、クリックすればそのゲームが遊べる。個人的な意見としては、左上の方が楽しめて、右下のほうが退屈できます。

すべてブラウザで遊べる昔ながらのミニゲーム。半分Flash、半分HTML5HaxeCoffeeScriptで書いた。ソースも置いてあります。

1年で50作れば年の終わり頃には余裕で面白いゲームを狙って作れるようになるかなあと思ったけど、脳内で面白そうと思ったゲームが実際に作るとひどくつまらないということは相変わらず多発するので、やはりイケてるゲームを作る簡単なセオリーなんてものは無い。あるいはまだ見つかって無い。ひたすら作って、遊んで、面白くなるまで直すしかないね。

まあでも昔ながらのミニゲームを作るのに役立ちそうないくつかの方策はあったような気がするので、忘れないようメモしておく。

  • 斬新さを少しずつ削る

せっかく自作ミニゲーム作るんだから、今までに無かった面白くて斬新なギミックやフィーチャーが入ったゲームが作りたい、という方針で作るとだいたいとても斬新でとても面白くないゲームができる。大事なことはそこであきらめないで、ゲームに盛ってしまった斬新さを少しずつ削って、そこそこ面白くてそこそこ斬新なゲームに仕立てていくこと。保守的な作りによせる、ということが後から出来るようになるとゲーム作りははかどる。

  • 無理やり発想を広げるワンキーゲーム

削る前の斬新さを探すのもそこそこ大変である。そういった場合はとりあえずワンキーゲーム作りをトライ。ワンキーしか使えないという制約が強制的に新しいゲームを作るのに役立つ。ワンキーにどういう動作、仕組みを割り当てるか、という発想の起点が分かりやすいのもいいところ。

  • 誘爆は友達

困ったら何か誘爆させとけ。連鎖で何かが爆発すればみんな笑顔。うまく調理しないとありがちなゲームになる危険性もあるけど。

  • 重力も友達

地面に落ちるとか惑星の引力に引かれるとかの、ゲームに重力を取り入れたり、重力を操作できるギミックを入れたりするのは安直だがいい方策。磁力とかもいい。

昔の名作は名作たる所以があるだけあって楽しいギミックがてんこ盛りである。そういったギミックをつまみ食いしてゲームに取り入れたり、レトロゲーの一部分だけを別ゲーとして仕立てるというのは、ミニゲームを作る際には役立つ。

これは面白いゲームを作るというよりはどうやって手早くゲームを作るかという話だが、プログラムにレベルデザインを自動生成させることに慣れるとミニゲームを短時間で作るにはとても役立つ。自動生成する仕組みそのものも重要だが、自動生成したレベルが理不尽なものにならないように、あらかじめ正解を作っておいてそれ以外の部分を後から埋めるとか、作ったレベルから正解のルートだけを安全に加工するとか、そういうロジックまで書ける方が望ましい。

  • 納得できる終わりをもたらす難度曲線

ミニゲームはゲームがどんどん難しくなってやがてプレイヤーの能力を超えたところでプレイヤーを負かして終わり、という作りが多くなる。その場合、いきなり難しくなって理不尽に負ける、では無くてあともうちょっと頑張れば勝てたのに負けた、くらいの感覚になるように難しくなり具合、いわゆる難度曲線を調整できているのが望ましい。

  • リスク駆動開発

かと言ってゲーム序盤がやたら簡単なのもヒマだ。そういった時は、プレイヤーになんらかのリスキーな行動をさせてそれにボーナスを与えるべし。リスクへのボーナスを適切に与えることで、ゲーム開始1秒から納得性の高いゲームオーバーが提供できる。どういったリスクをプレイヤーに与えようか、という点から新たなギミックを考えるリスク駆動開発もオススメ。

今思いつくのはこんなところ。今年作ったゲームを遊んでくれたり感想をくれたりした方々に感謝いたします。