JS1KデモBLCK4777のせめて音を作ってる部分だけでも分かりたい

今年のAssembly 2015 1kb intro勝者のJavaScriptデモBLCK4777、わずか1023 bytesでできているとは思えない美しいデモで話題になった。

1023 bytes版のファイルはPNG bootstrapping techniqueで圧縮されているそうで見た目はバイナリである。見ても分からない。

Full archiveに入っているsafe版は非圧縮のものなので、ちゃんと見えるJavaScriptが書いてある。さらにそれを見やすく整形して置いてくれた人がいるので、それを見ると、

はい分からない。

いや、もうちょっと頑張って読もう。safe版には、上のgistに置いてあるコードの他にブートストラップ用の以下のコードが含まれている。

c = b.getContext('2d');
T = String.fromCharCode;
p = 0;
document.querySelector('button').onclick = function() {
  this.textContent = 'Starting...';
  this.disabled = true;
  setTimeout(u,1);
};

ここで変数pは0に初期化されている。function uの最初の方にあるg = p ?から始まる三項演算子は0がfalse相当なので後ろの式が採用される。後ろの式はまるで関数の引数のようなものが書いてあるが、これはコンマ演算子だ。順に実行され、最後の結果が採用される。三項演算子コンマ演算子を使えば、if文相当を短く書ける。

ちなみにJavaScriptのショートコーディングについて知りたければSuperpacking JS Demosなどの記事を読むと良い。WebGL用の長いメソッド正規表現で無理やり短くする方法とか、PNGを使ったコードの圧縮方法などが書いてある。

コードに戻ると、pに値を設定するのは一番下のAudio.playの引数部分だ。Audio.playの引数は何の効用もないはずなので単にここで値を代入しているだけ。なので、ここでpの値が設定され、requestAnimationFrame(u)で再度uが呼び出されるまでにAudio、つまりこのデモの音が生成されている。

デモ全体を把握するのはキツイので、このAudioを作るまでをせめて見てみたい。pが0である条件で通るパスだけを切り出すと、以下のコードになる。

T = String.fromCharCode;
function u() {
  audio = "RIFFdataWAVEfmt " + atob("EAAAAAEAAQAAeAAAAHgAAAEACAA") + "data";
  g = 6177;
  h = f = C = 0;
  for (; g > f; h *= f % 1 ? 1 : 0.995) {
    s = Math.pow(Math.min(f / 5457, 1), 87) + 
        Math.pow(1 - Math.min(f / 5457, 1), 8);
    if (f == [1280, 1599, 2175, 2469, 2777, 3183,
              3369, 3995, 4199, 4470, 4777, 5120][C]) {
      C++;
      h = 640;
    }
    audio += T((1 + s * 8) * Math.random() + 
               (1 - s) * (h / 45 * (f * (2 + C / 3 % 1) & 1) + 
               (C > 3) * 8 * (f * (2 + (f / 8 & 3)) % 1)) | 1);
    f += 1 / 512;
  }
  new Audio("data:Audio/WAV;base64," + btoa(audio)).play();
}

はい分からない。

いや言いたいことは分かる。最初のaudioの初期化ではwavファイルのヘッダ相当のものを作っている。atobbase64をデコードしているのはフォーマットやチャネル数を指定するためのデータで、これはリニアPCM、モノラル、サンプリングレート30720Hz、bit/sampleは8bitを示している。

なのでaudioにTString.fromCharCodeを使って数値を足しているところで8bitのサンプルごとのデータを生成している。最後のnew Audioで作ったデータをbtoabase64エンコードし音を作っているのだ。

じゃあTの後ろの面妖な式は何だ。何だ。分からん。

ちくしょう!可視化だ!

データを適当に間引いた上でc3.js使ってグラフ化してみたけど……分からん。fと比較している配列内のフレーム数のところでなんらかの変化が起きているから、ここで曲の内容を変化させているのだとは思う。変化はCをインクリメントし、hを640にすることで起こしている。式にはC > 3という部分もあるので、ある程度フレームが進んだ後のみ使われる部分もあるのだろう。hは少しずつ減衰しているように見える。

これはいわゆるbytebeatと似たような手法なのだと思う。bytebeatは単純な式から様々な音を生成することができる手法で、実装方法もごく単純にフレーム数を引数とした式の出力をリニアPCMのサンプルごとの値にするだけというもの。本当に単純なのでショートコーディングにはとても向いた方法なのだが、問題はどんな式がどんな音をだすのかさっぱり分からないこと。

それっぽい式を適当に組わせるUIがあれば、簡単に音が作れるんじゃないかと思って、前にbytebeatbank (これも音が出ます)ってのを作ったんだけど、やはり型にはまった式の組み合わせだけだとあまりバリエーションが出ないという問題があった。かといって自由な式を生成してしまうとまともな音が鳴ることはほぼ無いという別の問題が起こる。

bytebeatに限らず、こういった数式でそれっぽい音を簡単に作る方式があれば、短いコードでBGMなり効果音なりが作れて楽しいんだろうけど、そういう手法を解説しているページとか、欲しいところだ。bytebeatについては以下のページがたぶん役に立つ、んだろうけどこれを読み解くのも大変そうである。

ちなみに今回まるっとスキップした、絵を作る部分はこれにさらに輪をかけて謎の数式が乱舞しているので誰か解説して欲しい。BLCK4777の例では無いけど、短い数式や関数で驚くべきグラフィックスを作る方法については、以下のページなどが役に立つ、はず。

というわけでデモコーダーの超絶技法を知るのはとても大変そうであるという話でした。

New 3DSブラウザのゲーム開発向けJavaScript API対応具合チェック

Newニンテンドー3DS インターネットブラウザーの主な仕様を見ると

HTML4.01/HTML5/XHTML1.1/Fullscreen/Gamepad/SVG/WebSocket/Video Subtitle/
WOFF/Web Messaging/Server-Sent/Web Storageの一部/XMLHttpRequest/
canvas/Video/DOM1-3/ECMAScript/CSS1/CSS2.1/CSS3 の一部

のウェブ標準に対応していると書いてある。旧3DSの仕様と比べるとだいぶリッチに見える。特にFullscreenとGamepadに対応しているならゲーム開発向けに使えるんじゃないのかね、と思って少し調べた。

Fullscreen

これ本当に対応しているの?

  • document.documentElement.webkitRequestFullScreen():何も起きない
  • document.documentElement.webkitRequestFullscreen():何も起きない
  • document.documentElement.requestFullscreen():多分関数が無い
  • document.documentElement.mozRequestFullScreen():多分関数が無い

もちろんPCのChromeだとちゃんとフルスクリーンになるんだけど、New 3DS上では何も起きない。何か別の呼び出し方が必要なのか。そもそもNetFront Browserのベンダープレフィックスは何だ。

Gamepad

navigator.getGamepads()を使う標準的な方法で確かにパッドの状態をとれる。けど、デジタルパッドとAボタン以外はブラウザ自体の操作に割り当てられているので使えない。アナログパッドや他のボタン全滅。

いろいろと勘違いがあった、下記追記参照。

その他

CanvasはfillRectとputImageDataしか試してないけどちゃんと動く。JavaScript自体はあんまり速い印象はない。128 x 128ドットのピクセル操作とかをやらせるとてきめんに遅くなる。

当たり前だがデベロッパツールとかコンソールとかは無い。

これらをふまえると

フルスクリーンもゲームパッドもパフォーマンスもイマイチなので、これでアクションゲーム開発はちょっとキツイ。フルスクリーンが効いて、その時は全てのパッド入力が取れる、という仕様が理想だったのだが。タッチペン使うのならもうちょっといけるのかもしれん。

Gamepad追記

@tkoharaさんに教えてもらって、htmlに

<meta name="viewport" content="width=device-width, height=device-height, 
user-scalable=no, initial-scale=1, maximum-scale=1">

を追記してアナログパッドなどでの画面の拡縮、移動操作を禁止すれば、パッド操作も問題なくできることがわかった。

あとテストプログラムでキー入力で取れていたものとゲームパッドで取れていたものを混同していて、いろいろ間違っていた。

  • ゲームパッドを取得するのはwebkitベンダープレフィックス付きのnavigator.webkitGetGamepads()
  • gamepad.buttonsは押し込みの値が直接取れる古いインタフェース(pressedやvalueは無い)
  • 左のアナログパッドがaxesの0, 1、右が2, 3
  • デジタルパッドはボタンとして取れる
  • L, RボタンやBボタンは進む、戻るのナビゲーションに割り当てられているので、ゲームに使えるかどうかは画面遷移に依存。基本使えない

なのでアナログパッドとデジタルパッド、A, X, Yボタンは使えそうなので、頑張ればなんとかなる、か?

GitHubで行にリンクを張る

ファイルへのリンクの後ろに#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にしとけという気もする。

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

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