Admittedly something small.
2016年12月1日

モナドのまほう 第8話『たまにはデモします』

JavaScript WebGL Blender Babylon.js purescript


babylonjsとかPureScriptとかを使って好きなように楽しくゲームを作る日記です。思いついたことを取り留めもなく書いています。

デモ

キャラクターが歩き回れるようになったので、簡単なデモを用意しました。こちらのページを開くと、ちょっと不安になるくらいのロード時間のあとでプレイが始まります。

WASDで歩き回れます。左上の"add"ボタンを押すとマウスでブロックを積めるようになり、"Remove"ボタンを押すとブロックを削除できるようになります。Chromeで開発しているのでChromeが最適だと思いますが、私の環境ではFirefoxやEdgeでも問題なく動いています。手元のマシンでしかテストしていませんし、WebGLを使っていて環境依存は多少あると思いますが、たぶん動くと思います。ウェブをプラットフォームにすると簡単にデモを見せられるので便利ですね。怪しいネイティブなアプリケーションなんて誰も使いたくないと思いますし。

ちなみに、Electronでももちろん動きます。気持ちの問題ですが、Electronで動かすとなんか大作ゲームな予感がしてちょっとテンションがあがります。ブラウザで遊ぶと、同じゲームなのになんか所詮はお手軽なミニゲーム、っていう思い込みが働いてしまいます。刷り込みってこわいです。

これでボクセル世界の構築とその中を歩きまわることができるようになったので、サンドボックスゲームの枠組みとしては概ね形になってきたと思います。十分にリファクタリングをしてから、元のプロジェクトと統合しようと思います。というかグラフィック部分は完全に書きなおされているので、引き継ぐのはfirebaseの部分くらい……。ほとんど書き直しじゃないですか……。ただ、コリジョンの実装の方法については未だに決めかねています。上のデモでは当たり判定がガバガバなので段差の上にぴょんと乗り上げてしまうのですが、いずれはこれも直さなくてはなりません。自前で衝突判定を実装するか、それともcannon.jsのような本格的な物理エンジンを導入するか、悩みどころです。cannonを使ったほうがいろいろ自由が増すのですが、一方で本格的な物理エンジンを使うといわゆる「havok神の怒り」が発動しやすくて挙動をコントロールするのが難しくなります。パフォーマンスの問題に突き当たる可能性もあります。一方で、自前の実装は大変ですが、パフォーマンスチューニングをしやすいですし、予想外の挙動を抑えられるのでその意味では実装は楽です。どうしよう……。

あと、世界が完全に停止しているのが不自然ですね。多少は画面に動きを与えないと臨場感が出ません。Minecraftの影Modみたいに、草木を風で揺らしたり、水面を動かしたいです。あと動物を歩かせたいです。なんか記事に書くのはめんどうくさくてコードを出してませんので、必要ならgithubも覗いてみてください。純粋関数型プログラミングの学習の参考になればと思って、ソースコードはぜんぶ公開しています(でもオープンソースではないです)。純粋関数型プログラミング言語でもゲームは作れるんですよ!

一人称視点vs三人称視点

一人称視点を基本にすると没入感は増すのですが、ジャンプなどのアクションがやりにくいですし、プレイヤーのキャラクターを見せることが難しくなります。マインクラフトのように一切ストーリーのないゲームなら完全に一人称でもいいのですが、そういうゲームは新規のプレイヤーにとって遊びにくくなりますし、多少はプレイヤーにストーリーや課題を与えたほうが遊びやすい気がします。そのため、基本的には三人称視点で、必要に応じて一人称視点に切り替えられるようにしようと思ってます。ただし、三人称視点の扱いもかなり難しいところが多いです。

三人称で地下に潜ると、カメラが壁を突き抜けるため断面図状態になります。これはこれでテラリアみたいでプレイはしやすそうなのですが、ちょっと開放感が溢れすぎてしまって地下の閉塞感が台無しです。一人称視点ならこういうことはないのですが、このあたりどうするかも考えなくてはいけません。よくある手としては壁を突き抜けないようにカメラを近づける方法がありますが、これをやるとカメラが大きくブレて見づらいですし、狭い空間ではカメラの距離が近くなりすぎて余計に狭苦しいです。課題は多いです。

シェーダ

水面はまだゼリー状の質感です。babylonで提供されている水シェーダがうまく動かないのですが、まあコピーして改造すれば動くとは思うのであとで実装しようと思います。babylonの水シェーダがちゃんと動けばこんな感じになるそうです。そういえば草もランダムに生やしました。雑すぎて、弁当に入っている「バラン」っぽいです。

なんかもっと絵に馴染む感じにしたいです。どうしよう……。

また遠足テスト

世界は161616ブロックの『チャンク』に分割されて管理されるのですが、キャラクターの周囲のチャンクは自動的にロードされ、同時に遠くにあるチャンクは自動的にアンロードされていきます。このため巨大な世界を歩き回ってもメモリが足りなくなったりしないというわけです。それで、キーボードのwキーの上に乾電池を置いて数分放置するという自動化されたテストを実施したところ、水平方向のZ座標で1万メートルを超える位置に来ても落ちたりせずにちゃんとプレイできるのが確認できました。ただ、数千メートルを超える位置に来るとシャドウがおかしくなる問題が見つかりました。原因は調査中ですが、Babylonjsのバグを疑っています(babylonのバグを踏みすぎて疑心暗鬼)。

垂直方向についてもテストするためにひたすらブロックを縦に積み、これで上空1000メートルです。1000回クリックしました(手動)。

地上は遠すぎてアンロードされてしまうので見えませんが、見えたとしてもフォグがかかってほとんと判別できないと思います。それでもすぐにアンロードされて見えなくなってしまうのでは超高層建築の楽しみが減ってしまうので、この辺りもどうするか検討中です。

高さに制限がないと、ライティングでも問題が増えます。一部のチャンクを読み込んだだけでは、そこに日光が当たるのかどうか判別できないからです。どうしよう……。っていうかもうすぐ大きなリファクタリングをするつもりなので、そろそろテストも自動化しないと……ゲームの自動テストなんてどうすりゃいいんだ……。

もういちど規模の概算をする

元のプロジェクトとの統合の前に、Firebaseでデータを扱いきれるかどうか、もう一度検討してみます。ただのチャットアプリとかならともかく、ゲームが世界が無制限のオープンワールドなゲームだけに、下手をすると開発時のテストプレイだけで一瞬でfirebaseの無料枠を使いきってしまう可能性もあるので、ここはちゃんと最初からケチっておいたほうがいい気がします。

チャンクを読み込む際は配列で読み込むと効率がいいのでしょうが、firebaseは配列を直接扱えません。一つの方法としてはJSON.stringifyして文字列として転送してしまう方法が考えられますが、この表現にすると今度はブロックをひとつ置くたびにチャンク全体を送らなければならなくなります。1チャンクが161616=4096ブロックで、カンマ区切りの数値を文字列にしてケチって表現すると、UTF8だとして1チャンクあたり10キロバイトほど。一方で、オブジェクトとしてチャンクを表現するともっと効率が悪そうです。つまり、

[0, 0, 1, ..., 0, 1]

というような配列を

{
    "0": 0,
    "1": 0,
    "2": 1,
    ...
    "4094": 0,
    "4095": 1
}

こんな感じのオブジェクトとして送受信することになります。ゲーム開始時にはチャンクをまるごと受信しなければならないので、これはかなりの大きさになってしまいます。ただし、一旦ゲームが開始すると、ブロックを置いた時にはその部分だけを書き換えればいいため、チャンク全体をひとつの文字列に押し込むよりはずっと効率がよくなるはずです。将来を見越して、ここはオブジェクトとして表現することにします。

なお、地形データすべてを先にfirebaseに格納してしまえばクライアント側で生成する必要がなくなるのですが、ちょっとデータ量が多くなりすぎます。10チャンク距離までロードするとすると、21 * 21 * 21 = 9261チャンクを読み込むことになるので、10kbyte * 9261で92メガバイトほど。これで少し歩き回れば、簡単に数百メガバイトを消費するでしょう。ローカルならどうということはない数字ですが、起動しただけでこの量ではfirebaseの無料枠1GBを一瞬で使い切りそうです。しかもストアが厳しいのはもちろん、ダウンロード枠が10GBしかないので、下手をするとテストプレイを繰り返すだけでこっちも一瞬で使い切ると思われます。保存するのは一度でもブロックを置いたり削除したチャンクだけにする必要があると思います。

ライティング

そういえば、頂点色を利用してブロックに簡易的な陰影をつけています。これには「アンビエントオクルージョン」なんてたいそうな名前がついていますが、要するに物体の凹んだところは光が当たる量が少ないので暗くなる、というだけの話です。解説の記事もありました。

これがないとかなり見た目がのっぺりとして凹凸が見分けにくくなり、見た目だけでなくプレイアビリティにも影響してきます。物理的に厳密に計算するのは大変ですが、シミュレーションではないのでなんとなくそれっぽく見えさえすればいいのです。上の記事のやり方とは少し違いますが、筆者も同様のものを実装しています。

それと、NotchさんによるMinecraftのライティングシステムの簡単な解説も見つけたのでメモです。まあこれだけじゃ情報が少なすぎてあんまり参考にはならない気がしますが……。

次のお話


このエントリーをはてなブックマークに追加