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