きっかけ
-
ゲームプログラム初心者が作ると勉強になるゲーム20選という動画をたまたまYouTubeで見かけました
- ゲームプログラム初心者なので、とりあえずこの動画で紹介されているゲーム20個を作ってみようと思いました
- 今年中にこの20個のゲームを全て実装しきる、ということを目標に個人でちまちまゲーム開発を続けています
- 現在20個中7個実装が完了している(あくまで最低限の実装ですが)
- 今回の記事は、その中の「一筆書きゲーム」を作ってみたという内容です
大まかな開発プロセス
- Siv3D公式のチュートリアルに、良い感じに流用できそうなマス目状のデモがあったため、このコードを改造して実装することにしました
- まずはマス目だけがある状態から実装をはじめ、大まかに以下のような手順を経てゲームを完成させました
- プレイヤーを示す丸が動くようにする
- 丸がマス目の外に行かないように「壁」を作る
- 一度踏んだマスを灰色にする
- 一度踏んだマスに行けないようにする
- 一旦右下をゴールに指定し、ゴールマスに到達したらメッセージが出るようにする
- ゴールマスをランダムなマスに配置する
- 一筆書きでゴールできるマスは、左上をスタートとすると市松模様状に分布していることに気づき、その中でランダムなマスがゴールマスとして指定されるようにした
- 過去に市松模様を排他的論理和で表現できることに気づいて記事を書いたことがあり、その経験が生きた
- プレイヤーがゴールマスに到達した時に、全てのマスが埋まっているかどうかを判定するロジックを実装
- 全てのマスが埋まっている時のみ成功、それ以外は失敗というメッセージを出すようにする
工夫点など
- 前回記事 『ゲームプログラマのためのコーディング技術』が良かった話 でも紹介した本に載っていたテクニックを早速実践してみた
- なるべく小さい単位で処理を関数として切り出す
- 例:
DrawCircle
関数 (プレイヤーを示す丸を描画するための関数) は、たった一行だけの処理をわざわざ関数として切り出している - シンプルな策ながら驚くほどコードの読みやすさ・書きやすさが向上したように思う
- 最近読んだ『UNIXという考え方』 にも出てきた Small is beautiful という考え方や、オブジェクト指向の設計原則である「単一責務の原理」にも合致しているように感じた
void DrawCircle(const Circle& circle) { circle.draw(Palette::Aqua); }
- 例:
- マジックナンバーに名前を付ける
- 例:
Coordinate
という名前空間に、特定の座標に関するマジックナンバーをまとめ、それらに一目で分かるような名前を付けた - ひと手間かかるので最初はちょっと面倒だったが、後からコードを読み直した時に意図がスッと掴めて良かった
- ちょっとした改変を加えるような作業をする時もグッとやりやすくなったように思う
namespace Coordinate { constexpr int TOP_GRID_X = 50; constexpr int TOP_GRID_Y = 50; constexpr int BOTTUM_GRID_X = 750; constexpr int BOTTUM_GRID_Y = 550; std::vector<int> pair = random_pair_generator(); int GOAL_X = 50 + 100 * pair[0]; int GOAL_Y = 50 + 100 * pair[1]; }
- 例:
- 早期リターン
-
例: ゲームの結果を表示する
PrintResult
関数- この関数は、 プレイヤーがゴールの座標に到達した時、全てのマスを通過していたら "Success!!!" そうでない場合は "Failure!!!" という文字列を表示するという役割を担っている
- プレイヤーがゴール座標に到達していない時はその時点でreturnし、その後の処理をスキップするような実装としている
-
ネストが1個減るくらい大したことなさそうだと思っていたが、実際にやってみると、そのひと手間をかけるだけでグッとコードの読みやすさ・弄りやすさが上がった
void PrintResult(const Circle& circle, const Grid<int32>& grid, const Font& font) { if (circle.x != Coordinate::GOAL_X) return; if (circle.y != Coordinate::GOAL_Y) return; if (isAllGridPassed(grid)) { font(U"Success!!!").drawAt(Vec2{ 400, 300 }, Palette::Black); } else { font(U"Failure!!!").drawAt(Vec2{ 400, 300 }, Palette::Black); } }
-
- なるべく小さい単位で処理を関数として切り出す
雑感
- 今回実践したのは『ゲームプログラマのためのコーディング技術』で紹介されていたテクニックの中でもごくごく初歩的なものばかりだが、それだけでもグッとコードが読みやすくかつ書きやすくなったのを感じた
- 少し前まではクラスやら継承やらスマートポインターやらデザインパターンを理解しようと躍起になっていたが、現時点の自分の理解度や実装しようとしているゲームの複雑さからすると、上記のような初歩的なテクニックを学ぶことが大事だったのだと改めて思い至った
- 仕事で触れるコードがやたらめったら複雑だったり高度な言語機能を使っていたりしたので、自分もそういうものを理解して使いこなせるようにならないといけないという焦りがあったのかもしれない
- このような基本的な所から徐々にステップアップしていって、いずれは高度な言語機能で高度なことをやってみたいなぁ、というほんのりした野望を記事の最後に置いておきます