2019の15日目の記事です。前置きElmでアクション性のあるゲームを作ってみたい、と思った。フレームごとの処理を一から実装するのは大変に面倒そうだ。いったゲームを一度も作ったことがない自分がそういう基本的な部分から作り出しても、ゲームとして成立するところまで到達できずに挫折するのは目に見えている。そこで、Elm作者であるevancz氏作のゲームライブラリelm-playgroundを試してみることにした。以下ページの「Playground」にあるサンプルを見るに、比較的簡単なコードでそれっぽいものを作れそうだ。Gameもどきを作ってみる。elm-playgroundの簡単な解説も兼ねて。1.0.2インストールパッケージをインストールするだけ。evancz/elm-playground写経ドキュメントは以下にある。https://package.elm-lang.org/packages/evancz/elm-playground/latest/Playground眺めているだけではよく分からないので、とりあえずgameの一番下にあるサンプルコードを写経してみる。reactorで実行する。キーボードの十字キー入力で赤い正方形を移動させられる。これだけのコードで!実際どのコードが何の役割を果たしているのか見ていく。一見して分かる通り、elm-playground内では通常のElm開発で使うModelやCmd、Subを使うことはない。代わりにライブラリが提供する関数やデータを使っていくことになる。基本になるのがmemoryで、ここにはゲームの状態が保存される。Architectureで言うところのModelとほぼ同じ働きをすると思っていいだろう。y)というのがmemoryで、状態としてX座標とY座標を持つということになる。0)、つまりX座標もY座標も0というのが最初の状態となる。一点注意が必要で、elm-playgroundでは基本的に描画領域中央が原点になる。中央より左側は負のx値、下側は負のy値を持つことになる。これには慣れるまで少し戸惑った。一つ特徴的なのがcomputerで、ここにはマウスやキーボードの操作状態や描画領域のサイズ、実行時間など実行時の情報が保存されている。どんな操作をするにもまずはこれを参照することになるだろう。ここではcomputer.keyboardで押されているキーを検出している。toXは右キーが押されていれば1、左キーが押されていれば-1を返す。同様にtoYは上キーが押されていれば1、下キーが押されていれば-1を返す。これによりupdateでX座標とY座標を更新したmemoryを返している。viewではsquareで正方形を描画し、moveを使ってmemoryに保存された座標まで移動している。これでなんとなく流れが把握できた。応用すればキーボードで操作するゲームを作れそうだ。Gameもどきを作っていく。自機と地面を描画する。rectangleで地面、circleで自機を描画してそれぞれの初期位置に配置している。図形もやはり指定した座標を中心点として描画されるので、位置決めが面倒な場面がある。この時点ではupdateの内容がないので操作はできない。https://ellie-app.com/7sNNMrkMhy3a1この時点でのコードと動作サンプルをEllieに置いた。以下でもコードの全体像と動作サンプルはEllieを参照のこと。ジャンプ次に自機がジャンプできるようにする。ジャンプするために必要なのは自機の現在位置と速度なので、memoryの型をそのように定義する。}高さを変化させるロジックはupdateに書く。高さが0より上、つまり空中にいる場合は速度を常に低下させ続ける。地上で上キーが押されている場合はジャンプして速度を正の値にセットする。最後に現在の高さに速度を足したものを新しい高さとして、更新したmemoryを返す。高さが負の値になると地面にめり込んでしまうため最小値が0となるようにしておく。}この更新されたmemoryをviewで参照するようにすれば完了。右側から自機に迫ってくる敵を配置する。現時点では接触してもゲームオーバーになったりはしない。https://ellie-app.com/7sNPCt7WrMva1まずはmemoryにenemyを追加する。}updateで敵の位置も更新するようにする。}最後に敵の位置を参照するSVGを追加する。なにかと使いそうな各種座標やサイズ取得も関数化しておく。地球環境のことを考えて画面外に出たらリサイクルするようにする。当初は初期位置に戻っていく敵が画面に映らないように一旦地下に移動してから戻すようにしよう、などと考えていたが、moveはアニメーションでもなんでもなく、瞬時に設定された座標に移動するのだとわかったので単に初期位置に戻すだけにした。Enemyにして複数出した。今の状態だとゲーム性がなさすぎるので、敵に接触したらゲームオーバーになるようにする。https://ellie-app.com/7sNQwyLj7PMa1例によってまずはmemoryにcollidingを追加する。思い切ってゲームオーバーになったらupdateでmemoryをそのまま返し続けるようにした。これで状態の更新がなくなるので画面の更新もなくなる。...敵への接触判定を定義する。自機が円形なので見た目通りの判定を実装するのは面倒そうだ。いうわけでシューティングゲームっぽく自機の中心が敵に重なったら、ということにした。OVERと出すようにした。]スコア最後に生きている限り常時スコアが加算されていくようにする。これで最低限ゲームとしての体裁が完成するはずだ。updateのたびにscoreを加算すればそれで足りるということに気付いたのでそう実装した。}とはいえスコアの上昇があまりに激しかったので表示上は10で割っておくことにした。Architectureともまた違う方式で開発する必要があるため最初は少し戸惑ったが、慣れてしまえば簡単に進められた。型さえ合わせておけば大体正しい実装になっていく、というElm特有の感触はここでも変わらない。図形はともかく、文字も座標が中心点になるのはちょっと困った。文字の大きさは自明でないので、左寄せや上寄せにするにも表示しながらの調整が必要になる。乱数を使えないというのは辛い。Elmでは乱数を扱うときにCmdを経由する必要があるが、elm-playgroundではCmdが隠蔽されているためである。スコア表示まで完成して記事をおおよそ書き上げた後、もっとまともなゲームにしようと敵の配置をランダムにしようとしてここでつまずいた。この問題はissueにも挙がっていて、Playground.TimeやPlayground.Mouseを使ってランダムではないが予測困難な値を生成する方法が提案されていたりした。本当の乱数が欲しいときはこれでは困るが、代用できるケースもあるだろうと。time)として値を生成することでそれっぽく対応できたが、そうはいかないケースもあるだろうし、何とかなってくれたら嬉しいなあと思う次第。遊べるゲームを目指して今後もちまちま開発を続けていきたい。WolfdogGitHub:wolf-dogTwitter:@Wolfdog666E-mail:hivedark@gmail.com。