A3サイズの升目帖(旧)

ゲーム制作やプログラミングに関する雑記

ひたすらマップや素材作りをしている

ここ数日は、イベントで使うマップやゲームオブジェクトの実装を進めている。

作るものが多くてなかなか大変だ。

まだまだ粗削りだが、進捗としてスクショを2枚ほど貼って寝る。

序盤のイベントで使う貨物駅にて。

ゲーム画面

ゲーム画面

一応、テーマカラーがバイオレットなので紫系の色がワンポイントとしてあちこちにあしらわれている(はず)


今後

近いうちに長めのデモンストレーション動画を公開したいもんだ。

なんでもいいのでとにかく反応をもらえるというのはモチベーション維持に不可欠だからなあ。

いままでの動画は、いかにもテスト然とした無味乾燥な内容だったので、ちゃんとプレイしてるものを見せたい。

しかしながら準備が追っつかんのだよ。



シングルトンの誘惑に抗う(でもインスタンスのたらい回しはツラすぎる…)

うわぁ… またコンストラクタに引数追加しなきゃ…

ゲームには様々なインフラオブジェクトが必要になる。

レンダラー、ミキサー、ゲームパッド、フォント、セーブデータ、ワールドクロック、etc…

ゲームの根幹を成すものだけでもこれだけあるのに、ここにさらに、進行フラグ、ゲームオブジェクトマネージャ、ブロックフィールド、衝突空間、カメラ、シーンマネージャ、アイテムマネージャ等々のドメイン固有のオブジェクトがガサーッと加わるもんだから、その総数は両手じゃ数えきれない。

こいつらを今、すべてコンストラクタを介してたらい回ししている。正直ツラい。

自分はこんな感じの一節をいったい何回書いたんだ。

this(
    Renderer renderer,
    Mixer mixer,
    Gamepad gemepad,
    WorldClock worldClock,
    GameObjectManager gameObjectManager,
    CollisionZone collisionZone,
    // ...まだいっぱい続く。
    // クラス固有の引数がこの後さらに続く。
)
// nullが入ると困るんで、ちゃんとin契約も書きましょうね~
in (renderer !is null)
in (mixer !is null)
// ...まだいっぱい続く。
{
    // おっと、終わりじゃないぜ? メンバ変数に突っ込む作業が残ってる。
    _renderer = renderer;
    _mixer = mixer;
    // ...まだいっぱい続く。
}

// 当然、メンバ変数も山のように…
private Renderer _renderer;
private Mixer _mixer;
// ...

もちろんすべてのクラスが10も20もインフラオブジェクトを必要とするわけではない。体感では平均して5,6個くらいだろうか。

それでもコンストラクタが汚れてソースを読むときの雑音になっている事実は否めない。

何より、なんか新しい機能を追加しようとした際に、その実現に必要なインフラをコンストラクタにいちいち追加しなきゃいけないのが堪らなく面倒である。

当然、コンストラクタを呼び出す側がそのインフラを持っていない場合は、インフラを受け渡すというただそれだけのために、上位階層まで再帰的にコンストラクタを拡張しなきゃいけなくなる。時々なにと戦っているのかわからなくなることがある。


You、シングルトンにしちゃいなよ!

このささやきを何度耳にしただろう?

どうせインスタンスが一つしか存在し得ないものばかりなんだから確かにシングルトンでもいいような気はするのだが、3点ほど気になることがあって踏みとどまっている。

  1. たらい回しが面倒とか、コンストラクタが汚くなるとか、そういう理由でシングルトンを持ち出すのはマズいでしょ?
  2. ほぼテスト書けなくなるよ?
  3. 「シングルトン == グローバル変数 == 言語道断の悪」みたいな観念が出来上がっている。

どうしたもんかなあ。でも静的変数を共有するのはやっぱ良くないよなあ。

インフラを束ねたツールボックス的なクラスを作ればまあ 1. と 2. は解決するんだが、それって結局グローバル変数と何が違うの?と感じるところもあり、どうにも踏ん切りがつかん。落としどころとしてはベターなんだろうけど。

一般的にこういうときってどうするんだろうね?(虚空への問いかけ)



マップ境界のカメラ制御とか細かいのを色々実装した

グラフィック、サウンド、テキスト…

ここのところ素材作りばっかりで、なかなか絵になる進捗ができない。

それでもまあ、細かいことだが成果を公開しておくか。


ゲーム画面

うん、やはり地味。

えー、これが何かと言うと、今までプレイヤーの動くがままに追随していたカメラをマップの境界でストップさせるようにした、というもの。やっとカメラの動作に締まりが出た。

また、これまた地味だが、今までキャラクターの前面にしか描画できなかったブロックをキャラクターの背面にも描画できるようにした。画像内の背景の木箱なんかがまさにそれだ。
ロックフィールドを複数レイヤー構成にしていたおかげで楽に実装することができた。(なおマップエディタ側の実装)

背面ブロックは、このように小回りの利く背景やオーナメントとして使えるので極めて便利なのだ。


8月も中旬ですか

とにかく今は必要な素材をせっせと作っていくしかない。

素材さえある程度出揃えば、あとは爆発的に進めることができるはず。

分かっちゃいたが、ここが辛抱どころだあ。



【D言語】DMD 2.097.2 がリリースされた(アナウンスされた)

D言語のロゴマーク


※本当にただの個人的な雑記です。


DMD 2.097.2 のリリースがアナウンスされていた。

forum.dlang.org

リリース自体は8月9日ごろにされていたが、公式からのアナウンスがなかったので、あれ?どうしたかな?と思って様子を見ていたが、何時間か前に広報されたっぽい。

今回はリビジョン(DMDでは末尾の連番を何と呼んでいるか分からないが…)のバージョンアップで、バグフィックスがメインのようだ。

今夜にでも作業がひと段落したらインストールしなきゃ(使命感)

ちなみに、これまでのリリースで色々な試験的機能が追加されており、筆者は大抵 -preview=dip1000 をオンにして、コンパイラの監視にガチガチに縛られた快適なプログラミングライフを送っている。-preview=in も併用すると素晴らしい感じになる。

あと、-preview=shortenedMethods もいいよね。1行メソッドが C# みたいに書けて便利。

(そういえば、@nodiscard ってもう使えるんだっけ…?)




さて、時期は不明だが、次はマイナーバージョンアップの 2.098.0 がリリースされる予定だ。楽しみにしております。感謝!



プレイヤーキャラの基本動作を実装した

走る、ジャンプするといった、プレイヤーキャラのごく基本的なアクションを実装した。(うわ、これ画質上げなきゃなあ)

動画内でとんだり跳ねたりしているのは、本作の主人公のミツバくんだ。

しかしまあ、素材の準備に相当骨が折れるな…。グラフィックとかほんとに大変だわ。

ブログの更新も夜中になってしまった。盆休みじゃなかったら明日終わってたな。


ペースはどうですか?

8月中旬でプレイヤー実装か。微妙だなあ。

そろそろテスト用じゃない本番のマップを構成していかないといかんな。

動画として記録したことで色々課題も見えてきたので、そこらへんも軌道修正していこう。

とにかく、粗削りでもいいので最後まで通しで作り上げることはかなり大切。

ゲーム画面



オブジェクト同士の当たり判定を実装した

四分木衝突空間の実装が完了した。

これでオブジェクト対オブジェクトの衝突も可能になったぞ。

(なお、今回から動画はニコ動のリンクを貼り付けることにする。gifに変換するとサイズが馬鹿でかくなってはてなブログの10MB規制に引っかかってしまうためだ。gifは画質も落ちるし…)

四分木衝突空間は、だいぶ昔に書いたソースコードがあったので、そいつをHDDから引っ張り出してD言語に書き直した。

なんか昔の実装は結構無駄なことやってたな… かつての自分は何を考えていたんだ…? まあ過去の資産が無駄にならずに済んでよかった。

それにしてもモートン序列ってすげえよな。数学全然わからん勢の筆者でも数学のミラクルを感じる。


こんなとき役立った rdmd

分割レベルごとの先頭の空間にアクセスするオフセット配列が必要でこんなコードを書いたのだが、

private static immutable int[] offsets = 0 ~ maxLevel.iota.cumulativeFold!((a, b) => a + 4 ^^ b)(0).array;

さて困った。ほんとに正しいオフセットがこのコードで生成されるのか自信がない。コーディングしているとたまにあるやつだ。

そんな時にサクッと結果を確認できるので rdmd は便利なのだ。

rdmd --eval で楽々検証

rdmd--eval 機能を使用する。

rdmd --eval="..." とコマンドを打ち込めば、ダブルコーテーション内のコードを main 関数で包み、コンパイルして即実行してくれる。いちいちソースファイルを準備する必要はない。

今回のオフセット配列の検証では、以下のようなコマンドを打ち込めばいい。VSCode でコーディングしていれば、おそらくターミナルが常に開いているはずなので、手間はかからないだろう。

(なお、結果を表示しないといけないので、writeln をくっつけている点だけ注意。また、最大分割レベル maxLevel7 と仮定している。)

rdmd --eval="(0 ~ 7.iota.cumulativeFold!((a, b) => a + 4 ^^ b)(0).array).writeln;"

で、実行すると、このように…

[0, 1, 5, 21, 85, 341, 1365, 5461]

…結果がものの数秒で表示されて「あ~よしよし、想定通りだな」とすぐにわかるわけだ。大変便利である。

rdmd で生成されるコードには、よく使いそうな module が一通り import されているので、ライブラリの参照をほとんど意識せず書けるというのもありがたい。

ちょっとした確認には rdmd --eval="..." 。勉強になった。


今後の作業

さて、グラフィックは出せる、サウンドも出せる、ユーザ入力も受け付ける、当たり判定もある…

ゲームとしての下地はおよそすべて完成しているわけだ。こうなるともはやできないことは何もないのよね。

そして、ここからが失踪者多発、最大の茨の道の入り口なのだ。

ここから先待ち受けるのは、大量のゲームオブジェクトの実装、大量のイベントの調整、大量の画像やサウンド等のリソースの準備…

とにかく 大量で地道な作業の連続 なのだ。

「グラフィック出せたやで!」とか「オブジェクト同士がぶつかったやで!」みたいな、飛躍的で瞭然な進歩はもはや存在しない

次の大きな喜びは、最後ゲームを完成させたその瞬間にしか訪れない のだ。

そこに至るまでにいかにモチベーションを維持するかというのは最大にして最も困難な問題である。

えーさん ますめ君の運命やいかに…!?



マップの当たり判定を実装した

D言語くんがマップ上で跳ね回っている画面

いままでグラフィックだけのハリボテ状態だったマップに当たり判定を付けた。

ようやくゲームオブジェクトとブロックが接触できるようになったぞ。


せっかくなので、これを祝して D言語くん に登場してもらった。

D言語くんとは、D言語公式マスコットキャラクターだ。

D言語本体よりも有名」などと揶揄されることもある彼だが、その絶妙なプロポーションのデザインや、どこか憎めない雰囲気ゆえに、一部の熱狂的なファンに今なお愛されてやまない。

ゲーム内でもご覧のように大地を踏みしめ軽やかに跳ぶ雄姿を見せつけてくれる。


さて、ブロックとの接触はできたが…

ブロック対ゲームオブジェクトの当たり判定は完成した。

次にやるべきは ゲームオブジェクト対ゲームオブジェクト の当たり判定だな。

ブロックは格子状に並んでいるので最適化は楽だったが、ゲームオブジェクトはそうもいかない。

先のD言語くんみたいに縦横無尽に動き回るのだ。


筆者が意味を理解して実装できる当たり判定アルゴリズムは以下の3つくらいだ。

  1. 四分木
  2. 軸ソート法
  3. 総 当 た り

まあ 3. は論外として 1. か 2. かに絞られるんだが、今回のゲームはマップが縦にも横にも長くなりうるので、軸ソート法は不向きなんだよなあ。

初代スーパーマリオみたいに横長のマップしかないなら、オブジェクトの左側座標でソートをかけることで衝突の試行回数をぐっと減らせる。実装もめちゃ楽。

でもやっぱりゲームの性質上、四分木しかないよなあ…。あれ実装むずいんだよなあ…。