[Siv3D] ゲーム開発未経験者が生成AIを使って人生初のゲーム作成に挑戦してみた

# はじめに

人生で初めてゲームを作りました。今回はその話です
([githubリポジトリ](https://github.com/ryota37/Siv3DFirstGame))

## 経緯

7月から課が異動となり、新たな案件に配属されました。
そして、キャッチアップ期間にメンターの方をあてがっていただき、週1で1on1のMTGを実施して頂くことになりました。
その案件では、私は主にC++を使うようなタスクを割り当てられました。しかし、

- 筆者は入社前にソフトウェアの開発をしたことがあまりない
- C++もあまり書き慣れていない

といった状態で不安が大きかったので、1on1のMTGにて上記の内容をメンターの方に伝えました。
そうしたところ、「じゃあ何でもいいから、何かを開発するという経験を積もう！」という話になり、とりあえず8月までの1ヶ月で何かを開発することになりました。開発の題材ですが、上記のMTGにて趣味としてSiv3Dのチュートリアルを触っているという話をしたため、「Siv3Dを使ってゲームを1本作ってみよう！」という方針になりました。

こういった経緯で、突然人生で初めてゲームを作ることになりました。
「どんなにクオリティが低くても規模が小さくても良い」と言われていたものの、素人がゼロからゲームを1本作るのは中々大変でした。
ですが、7月末には一応ゲームを1本作ることができたので、目標は達成したと言えると思います。

この記事では、素人がゼロからゲームを1本作るに至るまでの流れをご紹介します。

## Siv3Dとは

- [公式サイト](https://siv3d.github.io/ja-jp/)
- C++でゲームをお手軽に開発できる、オープンソースのライブラリ
- [筆者の過去記事](https://qiita.com/camino37/items/485b4608473e728d894b)もご参考ください

# 企画

課題の第一歩は、まず「どんなゲームを作るか？」を企画するところからでした。「なんでも良い」と言われているのが逆に大変で、「短い期間で開発できる、ゲームとして成立するギリギリのライン」がどこかを見定める必要がありました。
そこで目をつけたのが、Siv3Dのチュートリアルです。1番最初のサンプルプログラムの一部に、「画面下部にいる恐竜の絵文字が左右に移動する」というものがありました。

(下記コードは、[公式サイトのサンプルコード](https://siv3d.github.io/ja-jp/tutorial/hello/#12-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%92%E6%94%B9%E9%80%A0%E3%81%99%E3%82%8B)から関連する処理だけ抜き出したものです)

```cpp
# include <Siv3D.hpp>

void Main()
{
	// 絵文字からテクスチャを作成する | Create a texture from an emoji
	const Texture emoji{ U"🦖"_emoji }; 

	// プレイヤーの移動スピード | Player's movement speed
	double speed = 200.0;

	// プレイヤーの X 座標 | Player's X position
	double playerPosX = 400;

	// プレイヤーが右を向いているか | Whether player is facing right
	bool isPlayerFacingRight = true;

	while (System::Update())
	{
		// 左キーが押されていたら | If left key is pressed
		if (KeyLeft.pressed()) 
		{
			// プレイヤーが左に移動する | Player moves left
			playerPosX = Max((playerPosX - speed * Scene::DeltaTime()), 60.0);
			isPlayerFacingRight = false;
		}

		// 右キーが押されていたら | If right key is pressed
		if (KeyRight.pressed()) 
		{
			// プレイヤーが右に移動する | Player moves right
			playerPosX = Min((playerPosX + speed * Scene::DeltaTime()), 740.0);
			isPlayerFacingRight = true;
		}

		// プレイヤーを描く | Draw the player
		emoji.scaled(0.75).mirrored(isPlayerFacingRight).drawAt(playerPosX, 540); 
	}
}

```

ポイントは、

- ユーザーの左右キー入力に応じて恐竜の絵文字が画面下部で左右に移動する
- 移動する向きに応じて恐竜が向いている方向を変える

の2点です。
こんなシンプルな動作でも、元から実装されているものがあれば制作コストは削減することができると考えました。
というわけで、この「画面下部左右に動く恐竜」からゲーム制作をスタートさせました。
適当に恐竜を左右に動かして遊んでいる内に、(なんだか恐竜が餌を求めて彷徨いているみたいだなぁ)と感じました。
その時、ふと『星のカービィ64』というゲームに収録されている「とるとるバトル」というミニゲームでのことを思い出しました。
このミニゲームは、上から落ちてくる果物をキャッチしてその数を競うというもので、シンプルなルールながら友達と遊ぶと結構盛り上がる名作ゲームです。そして、このゲームでは、画面下部でプレイヤー(カービィやワドルディなど)が左右に動くだけという、まさに今回の恐竜の絵文字と同じ動作をしていました。
このような経緯で「とるとるバトル」から着想を得て、今回開発するゲームのコンセプトを「恐竜を操作して空から降ってくる肉をキャッチする」というものにしました。

# 要件定義

一度ゲームの軸が決まってしまえばこちらのもの。ゲームを面白くするアイデアは次々出てきます。

- 制限時間を設けて、時間内にどれだけ肉をキャッチできるか競う要素を入れる
- 肉以外にももっと得点の高いものや、逆に触れたら得点がマイナスになってしまうものも落下させる

などなど。
しかし、このゲーム制作はあくまで課題。限られた期間でゲームを1本作らなければなりません。
よってまずは「最低限ゲームと呼べるラインはどこか？」というのを考えました。
「恐竜が空から降ってくる肉をキャッチする」からスタートすると、最低限ゲームと呼べるのは以下の要件を満たしているものだと考えました。

- 恐竜が左右に移動する
- 恐竜が肉をキャッチした判定をする
- 恐竜が肉をキャッチしたら「成功」、肉をキャッチできずに地面に落としてしまったら「失敗」と表示する

随分シンプルなゲームになってしまいますが、最低限

- ユーザーが操作する
- 「クリア」がある

という要素を満たしているので、ギリギリゲームと呼べるのではないかと考えました。


# 実装

満たすべき要件をクリアにしたところで、いよいよ実装です。
以下のような順番で実装をしました。

1. 公式チュートリアルのコードを流用して、恐竜の絵文字が左右に移動する部分の実装
1. 肉が落下し、恐竜と肉が接触したら肉が消えるようにする実装
1. リファクタリング: 恐竜や肉の処理を、`Player`や`Falling_object`といった個別のクラスに分割する
1. ゲームの開始と再挑戦をできるボタンを実装
    - 既存実装では、ゲームを起動した瞬間に肉が降り始めていた
    - この実装により、ゲームを開始しても開始ボタンを押さない限り肉は降ってこないようになった
    - 肉を1回降らせた後も、ゲームを再起動しなくても再度肉を降らせられる再挑戦ボタンも実装した
1. リファクタリング: 初期値をコンストラクタを用いて設定するようにした
1. 恐竜が肉をキャッチした場合とキャッチし損ねて地面に落ちた場合に、成功・失敗を判定して画面に表示する機能を実装

### 生成AIの活用

記事タイトルにもなっている生成AIですが、上記実装過程のあらゆる場面で使っていました。幸いChatGPTはSiv3D関連の知識を持っているらしく、「Siv3Dで〜〜したいんだけど」みたいに聞くとサンプルコードもセットで教えてくれました。なので、C++の文法やSiv3Dのライブラリの知識、実装方法・方針などで困ったらすぐにChatGPTに聞いていました。また、エラーの解決にもChatGPTは役立ちました。思考停止でコードとエラーメッセージを貼り付けただけで解決の糸口が掴めたこともありました。
GithubCopilotも非常に優秀で、(よく言われることですが)コメントを書いただけで自分がまさに書こうとしていたコードが補完に出てくるという状況もしばしばありました。
このように、AIの活用方法について特に新規性はないですが、普通に調べごとをしたり普通にコードを書いたりといった場面でも欠かせないツールになっているということを改めて実感しました。

## 添削

課題は終わってから一気に全てを公開したわけではなく、制作途中のものを毎週1on1MTGにてお見せしていました。その中でいろいろな指摘をいただき、それを元にリファクタリングをしていました。上記の実装手順にて新規機能実装とリファクタリングが入り乱れているのはそのためです。

### 指摘された事項

- 処理を全てmain関数内に書くのではなく、クラスとして外部に切り出すと良い
- メンバ変数の初期値はコンストラクタを用いるべき
- (未対応) `Player`クラスと`Falling_object`クラスで共通する部分は統合できると良い
- (未対応)既存のクラスは「位置や速度の情報」と「ゲームのロジック」の複数の責務を持ってしまっている。

また、上記指摘と合わせて以下のような各種設計原則についても教えていただきました。(ググれば出てきそうな内容ですので、詳細な説明は省きます)

- 凝集度
- 結合度
- SOLID原則
- DRY原則
- KISSの原則

# 今後の展望

一応1ヶ月でゲームを完成させるという目標は達成しましたが、まだまだ変えたい/変えるべき箇所はあります。既存実装の中でいじるべき部分、追加で実装したい機能、機能面で影響はないが設計原則の観点からリファクタリングしたい箇所、などなど。。。

まだまだ改善の余地のあるゲームですが、一旦ここで公開させていただきます。
今後気が向けばバージョンアップしていく可能性もあります。

# 感想・学びなど

「処理をクラスに切り出す」「コンストラクタで初期値を設定する」といったオブジェクト指向のごくごく基本的なところですが、自分でコードを書くとなると思ったよりこの辺が頭から抜け落ちてしまい、他の人から指摘されないと気付けませんでした。自分でコードを書くこと、他の人にコードを見せることの大事さを改めて知りました。
また、そもそも「自分で書いたコードを自力でクラスに分割する」という行為がほぼ初めてだったので新鮮でした。自分で一からクラスに分割しようとすると、どのような基準でどのような分量を切り分ければ良いか判断に迷うところがありました。ですが、絶対的な正解が無い中で自分であれこれ考えて少しでもコードを良いものにしようとする作業は、実際に自分でコードを書くことでしかえられない貴重な経験でした。
加えて、各種設計原則についてもほとんど知らなかったため、学びになりました。何も知らない状態でこの設計原則の話をされても頭に入ってこなかったような気はします。しかし、一旦自分でコードを書いていい感じに(?)散らかってきたタイミングで設計原則について学ぶことにより、設計原則の内容がスムーズに頭に入ってきたように思います。つまり、「設計原則を意識せずに書くとコードがとっ散らかる」という実体験と結びつくことにより、学習効果が上がったように感じました。

課題自体は、一応締め切りはあるものの失敗しても誰かに迷惑がかかるでもなく、余計なプレッシャーを感じることなくのびのびと取り組むことができました。
また、このゲームの成果を1on1ではなく課の全体に披露したところ、良い感じの評価を頂けて嬉しかったです。やはり褒められるというのはモチベに繋がると思いました。

# 最後に

というわけで、以上が素人がゼロからゲームを作ってみた記録でした。
最後に、現時点でのゲームのソースコードとバイナリを共有します。

- ソースコード: https://github.com/ryota37/Siv3DFirstGame
- バイナリ(ゲーム本体): https://github.com/ryota37/Siv3DFirstGame/releases/tag/v0.1.0
    - `Siv3DFirstGame.exe`をダウンロードして実行すればゲームが起動するはずです。
    - 現状ではWindows版しかないです。

記事にある通り非常にシンプルなゲームですが、遊んでみてもらえると嬉しいです。(ゲーム制作初心者の習作ですので、荒い作りな上に動作も不安定かもしれません。もし不具合などありましたら教えて頂けますと幸いです。)

## 加筆

スクショがあった方がどんなゲームか分かりやすいのでは？というご指摘をいただきました。(コメントありがとうございました！)
折角ですので、ゲームの動いている様子を動画で収録してみました。
↓の動画にてどんなゲームか確認していただけますと幸いです。

![sivf.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726845/3a678ba1-37b9-7f82-6077-b4c9a25e0866.gif)