※画面は開発中のものです。実際の製品とはぜんぜん違います。
各話一覧
- 第1話『画像が表示できました』
- 第2話『ゲームループとキー入力ができました』
- 第3話『オンラインゲームになりました』
- 第4話『WebGLを使い始めたらどう見てもマインクラフトです』
- 第5話『Babylonjsでゲートオブバビロン』
- 第6話『Blenderで涼風青葉ちゃんごっこの巻』
- 第7話『オープンワールドという泥沼』
- 第8話『たまにはデモします』
- 第9話『サウンドエフェクトの作業をしてコーディングで荒んだ心を癒やします』
- 第10話『ゲッダン☆と謎の儀式《バッド・ノウハウ》』
- 第11話『タイトル画面があるとゲームっぽい』
- 第12話『RPGアツマールに私も集まーる』
- 第13話『ネオアームストロングCannon.js砲じゃねえか完成度高けーなオイ』
- 第14話『冒涜的Firebase活用法』
- 第15話『babylon.jsの水面マテリアルの流れは絶えずして』
- 第16話『Gamepad APIでブラウザでもゲームパッドを使う/Sentryでクラッシュレポート』
- 第17話『アカネチャンカワイイヤッター!』
- (以下続刊)
デモとリポジトリ
-
https://aratama.github.io/cubbit/ ←現状のデモはこっちです。BGMが流れるのでボリュームを最大にして爆音でお聴きください。わりと読み込みに時間がかかるので、画面下のプログレスバーが伸びるまで気長にお待ちください。Chrome推奨です。FirefoxやEdgeでもたぶん動きますが、Firefoxだとホイールの挙動が違う問題があるのと、Edgeでも一人称視点モードでPointer Lockがちゃんと効いていないようです。WASDで移動、スペースでジャンプ、マウス右ドラッグやホイールで視点の変更です。画面下にあるホットバーのボタンを押すとブロックを置くモードになり、マウスクリックでブロックを置いたり取り除いたりできるようになります。申し訳ありませんがちょっと調整中です。というか、ちょっとアレがアレで開発が中断されています。 -
https://github.com/aratama/cubbit ←コードのリポジトリはこっちに移動です
というわけで、オンラインゲームを作ることにします!
現状のコンセプトはこんな感じです。開発当初の目論見からずいぶん変わりました。
- 開発途中でもワクワク感がありそうなので、プレイヤーが広大なフィールドを自由にうろうろできるオープンワールドでマインクラフトっぽいサンドボックスゲームな感じで! 周囲数十キロメートルを自由に散策できます。地下や天空も数キロの高度まで移動可能。まだ地形生成が単純なので、どこまで行っても同じような風景ですが。
- 簡単にアクセスできるように、ブラウザで動きます
- グラフィックスはWebGLを使ったガチ3Dだよ! 3Dのゲームは作るのが大変ですが、babylonjsがきっとなんとかしてくれる! キャラクターの3DポリゴンモデルもBlenderで筆者が自分で作ってるよ!
- Firebaseを使ってマルチプレイヤーなオンラインゲームにします!(無謀) オンラインゲームは大変ですが、Firebaseがきっとなんとかしてくれる!
- コードはPureScriptという純粋関数型プログラミング言語で! この筆者がJavaScriptなんかで書くわけないだろ! いいかげんにしろ!
このエントリはゲーム制作過程の日記的なものです。今回はとにかく自分が $ {\bf {\Huge \color{orange}楽\color{lime}し\color{aqua}く} }$ 開発するのが目標! ゲームを作りたくなったから作る! Firebase使ってみたいから使う! PureScriptが好きだから使う! WebGL使いたくないけど使う! そんな感じで、このエントリは勢いだけで書きます! ついてこれる奴だけついてこいッ!!
ちなみにプロジェクト名がzombieなのはゾンビが出てくる的なゲームにしようかと検討しているからです。どうせみんなゾンビ好きでしょ!(偏見)。某潜入アクションゲームだって遂にゾンビアクションゲームになっちゃったし。まあ行き当たりばったりに作っているので、ゲームの方向性はまたそのうち変わると思います。 開発当初に比べてもう完全にゲームの方向性が変わっていて、メルヘンな感じになってます。どこに向かっているのかは筆者にもよくわかりません。
プログラムのエントリポイント!
プログラムはmain
から始まりmain
に終わる! 基本中の基本!
main = do
pure unit
よし出来た! ちなみに、pure unit
というのは『何もしない』ってことです。ここまで書けたら、とりあえずビルドできることを確認! よしOK! 次っ!
画像の読み込み!
次はタイルチップ用の画像を読み込みます。今使っているライブラリではwithImage
という関数で画像を読み込めるので、これを使いましょう。最初に引数に画像のパス、ふたつめの引数に読み込んだ画像を受け取るためのコールバック関数を渡せばOK!
main = do
withImage "grass.png" \grass → do
pure unit
よし出来た!grass.png
という芝生っぽいタイルチップ画像を読み込んで、画像オブジェクトを変数grass
で受け取っています。ここでやっているのは、JavaScriptで言えばこんな感じのコードです。
widthImage("grass.png", function(grass){
return;
});
PureScriptなのでやけに見た目がスカスカしてますが、落ち着いて読めば別に難しくもなんともないですね。ちなみに読み込んだgrass.png
はこれ:
筆者がpixlrで10秒で描きました。超ハイクオリティ!
Canvasオブジェクトの取得!
次は画像を描画するためのキャンバスオブジェクトを取ってきましょう。このライブラリにはgetElementById
の簡単なラッパgetCanvasElementById
が付属しているのでこれを使います。canvas
というID(直球)を文字列で指定して呼び出すだけです。
canvasMaybe ← getCanvasElementById "canvas"
これでgetCanvasElementById
の結果が変数canvasMaybe
に代入されます。それだけです。え?変な矢印←
は何かって?PureScriptは変数に代入するときに<-
のほかに←
も使えるんですよ。なんか見た目が可愛いので筆者は最近←
を使うのがお気に入りなんです。Qiitaの構文ハイライトで思いっきり駄目出しされてますけど。
場合分け!
しかしここで筆者は大ピンチ!なんとgetCanvasElementById
の結果canvasMaybe
はキャンバスオブジェクトそのものではありません。getElementById
とは別モノじゃないか! 指定したIDのcanvas
要素があるかどうかはわからないので、取得が成功した場合と失敗した場合で場合分けをしなくてはならないのです。場合分けには**case
文**を使います。
case canvasMaybe of
Nothing → pure unit
Just canvas → pure unit
よし出来た!失敗した時はNothing
、成功した時はJust
のほうが実行され、Just
のすぐ後ろにある変数canvas
にキャンバスオブジェクトが入っています。まだ分岐後に何もしていない(pure unit
)ですけどね。JavaScriptだと別に場合分けの必要はないですし、PureScriptのほうがちょっとだけ面倒ですが、ここは三匹の子豚精神で乗り切ります。生き残るのは苦労してに備えた奴なのです!への備えを怠るは食われてしまえばいいのです。
標準出力!
さて、エラーを握りつぶしてしまってはいけないので、エラー時にはコンソールにエラーの内容を出力しておきましょう。コンソールに文字を書くには、log
関数を使います。要するにconsole.log
と同じものです。
case canvasMaybe of
Nothing → log "canvas not found."
Just canvas → pure unit
よしできた!それだけ!
グラフィックコンテキストの取得!
今使っているライブラリはHTML5 Canvas APIの薄いラッパなので、APIもほとんど同じです。キャンバスオブジェクトを取得できたら、次はグラフィックコンテキストを取得しましょう。JavaScriptではcanvas.getContext('2d')
というようにgetContext
メソッドを呼ぶところですが、よく考えたらPureScriptはオブジェクト指向プログラミング言語じゃない……! それじゃあcanvas
オブジェクトのgetContext
メソッドを呼べないじゃないですか! もうだめだぁ……おしまいだぁ……でも諦めるのはまだ早い! 実は普通にgetContext
メソッドのラッパであるgetContext2D
関数を呼ぶだけでOK!
context ← getContext2D canvas
なあんだ。え?これじゃあ語順的にcanvas
のgetContext
メソッドを読んでいるように見えない?だったら**#
演算子**を使えばいいんです!
context ← canvas # getContext2D
ほら!語順がひっくり返った! これで『canvas
にgetContext2D
のメッセージを送っている』とか言い張れるでしょ! #
演算子はほんとに関数と引数の位置をひっくり返すだけのための演算子です。PureScriptは演算子も自由に定義できるので、こんな工夫も簡単です。
まずは画像をひとつだけ描画してみる!
次はdrawImage
で画像を描画しましょう
drawImage context grass 0.0 0.0
よしできた!これでプログラムを実行すると、左上に画像が表示されるはずです!
よっしゃああああああああ! きたあああああああ! **『純粋関数型』**なんていう変なジャンルのプログラミング言語でもグラフィックスのプログラミングできてるああああああ! ちなみに画面サイズは1280 * 720
ピクセル固定にしてます。
繰り返し!
芝生一枚じゃあまりに寂しいので、繰り返し描画して、もっとたくさん縦横に並べましょう。Javascriptで言えば、forループを使ってこんな感じをやりたいわけです。
for(var y = 0; y <= 15; y++){
for(var y = 0; y <= 15; y++){
context.drawImage(grass, 36 * x, 36 * y);
}
}
でも……噂によれば関数型言語にはループがなくて、繰り返しには再帰を使うらしいじゃないですか……。ひいいいい! 再帰とか難しすぎる……ダメだぁ……お終いだぁ……。でも諦めるのはまだ早い! PureScriptには**for
関数という見た目も使い方もfor
文そのまんまの関数**があります。for
文というよりは、イメージ的にはJavaScriptのArray.prototype.forEach
メソッドに近いでしょうか。コードはこんな感じ。
for (0 .. 15) \y -> do
for (0 .. 15) \x -> do
drawImage context grass (36.0 * toNumber x) (36.0 * toNumber y)
よしできた!0 .. 15
は0
から15
までの16
個の整数が格納された配列を表します。これに対してfor
関数で繰り返しを行っているわけです。
PureScriptだとtoNumber
で型を明示的に変換しなくちゃいけないのがちょっと面倒ですが、ここも三匹の子豚精神で乗り切ります。実行するとこんな感じに表示されます。
いやっっほおおおう!ふっさふさ!かなり草がふっさふさだよ! 関数型プログラミングではあんまりループを使わない(?)と主張する人もいます。確かにfor
関数はあくまで関数であってループではないものの、コードの見た目や考え方としてはforループそのものです。少なくとも筆者はfor
関数はforループの代わりくらいにしか思ってません。別に関数型プログラミングだからってそこまで脳みそを大幅に切り替える必要はないのです。
つーか誰だよ!純粋関数型プログラミング言語は副作用を扱うのが難しいって大嘘をぶっこいた奴は!キャンバスに画像を描画するとか思いっっっっっきり副作用だけど、実際にコードを書いてみるとJavaScriptと大して変わらねーじゃねーか! さっきも平気でコンソールに文字書いてたし! これじゃあオブジェクト指向が絡んでくるJavaScriptのほうがよっぽど難しいだろ!
ここまでの全体像!
main = do
withImage "grass.png" \grass → do
canvasMaybe ← getCanvasElementById "canvas"
case canvasMaybe of
Nothing → log "canvas not found."
Just canvas → void do
context ← getContext2D canvas
for (0 .. 9) \y → do
for (0 .. 9) \x → do
drawImage context grass (36.0 * toNumber x) (36.0 * toNumber y)
モジュールのインポートの部分とかは省略しました。まだ10行です。
今後の方針ですが
開発方針は完全に勢いだけ&いきあたりばったりです! またまだ続きます! 最初はもっと簡単なミニゲームを作ろうかと思ったんですが、そういう簡単なゲームって正直遊んでも一瞬で飽きるんですよね。ゲームを作るからには、ある程度は継続的に楽しく遊べてほしいわけです。その点、こういうオープンなワールドなゲームなら、後付けで幾らでも機能を実装し続けられます。それに、ゲームとしての機能やバランスがガバガバでも、フィールドをうろうろしているだけで何となくワクワク感がありますからね。何より作っていて楽しいです!