はじめに
はじめまして,またはお久しぶりです.コウスケです.
前回,Siv3D Advent Calendar 2023にてSiv3Dを使ったプログラムについての紹介記事を書かせていただきました.その記事の最後に書いていたシューティングゲームが完成しましたのでこの記事で紹介をしようと思います.
シューティングゲーム「TwoFaced」紹介
配布場所
配布場所Link
readmeを呼んでぜひ触ってみながら下の記事と一緒に見てみてください.
タイトル
ゲームを起動するとタイトルロゴがお出迎え.Oはゲームを起動するたびにランダムで表情が変わったりします.
ステージセレクト
ステージ名はどれも気が抜けたような名前です.下にはステージの内容について一口コメントがあります.
プレイ
TwoFacedは目の前に現れる敵を倒して数を減らし,避けきれない攻撃を防ぎ,クリアまで耐え抜くシューティングです.プレイヤーは弾を撃つ「攻」モードと敵の弾を防ぐ「守」モードの二つを切り替えてクリアを目指します.モード切替をすると右上の文字が状態に応じて変わるので今の状態はどちらかわからなくなった際には見てみましょう.また自機の顔も変わります(>_<).
画面の右にある「体」と書かれた文字の下の緑色のゲージが体力です.このゲージが下まで下がり0になるとゲームオーバーになります.体力がなくなる前に倒し,逃げ切り,クリアを目指しましょう.
「力」と書かれた文字の下の赤いゲージは弾と壁を使用した際に消費していくコストです.減ってしまった分は何もしないと回復していきます.一番下までいくとゲージが満タンになるまで弾と壁が出せなくなります.その代わりに何もしてない時と比べて早く一番上まで回復します.ただしピンチな状況には変わりないので注意しなければいけません.「守」モードは特に消費が大きいのでやりすぎはピンチに直結しています.
画面の右にある「待」と書かれた文字の下の紫色のゲージは「攻」モードと「守」モードの切り替えインターバルです.モードを切り替えた直後はすぐには切り替えできないのでこのゲージを確認してモードを切り替える必要があります.
多くのシューティングゲームは画面外に出られることはないとはほとんどありませんがこのゲームは出られちゃいます.ただし1ステージ合計で10秒外に出ちゃうとそのままゲームオーバーです.弾だらけで逃げきれなさそうなとき試してみてはいかがでしょうか.
ポーズメニューもあります.ポーズメニューを開けばステージのやり直しやステージから抜けることができます.もう一回最初からやってみたいときにどうぞ.
リザルト
ステージをクリアするとリザルトです.リザルトはクリアした際の残り体力によって「優」「良」「可」「不可」の4段階で判定されます.範囲で表すと
優>=90>良>=75>可>=50>不可
になってます.ちなみにこの画面でモード切替ボタンを押すと同じステージをリトライできます.いい評価を目指そうとした場合,敵を減らしてそのまま飛んで来る弾を減らし,「守」モードで敵の弾の厚い層を突破して弾の軌道を薄い場所を生み出すと当たりにくくなるので上手く立ち回ってみてください.先ほどの画面外にでるのも手だったりします.
プログラム解説
プログラムはGitHubで公開しています.解説での構造が気になったら見てみると解説の内仕組みや何かヒントを得られるかもしれないので良ければ一緒に眺めながら見てみてください.
GitHub
ここからはプログラムについてです.自身のプログラミングでの開発の歴史を辿ると自分がC++を使って初めて作成したのはシューティングゲームでした.その時のシューティングゲームは配列に敵と弾を詰め込みclassは特に継承するわけでなく単体でいくつか要素に合わせて独立して存在したりしていました.敵の出現時間もif,elseをコード上に直接書いて出現させたりしてました.なので今回は今までの作成した経験と知識はあるけど使ってはなかった方法で作ろうと思い作ってみました.
全体の流れの構成は図のように構築しました.タイトルからは一方通行でステージセレクト,ステージ,リザルトでぐるぐる回るようにシーンが遷移するようにプログラムを組みました.
ステージ構成
ポーズでの操作を抜いたゲームのコアである本編は大まかにこのような構成でデータのやり取りをしています.初めにCSVからステージのデータを受け取り,ゲームクリアの条件はGameRuleに,出現する敵の出現時間,敵の種類,出現座標,行動のデータを受け取りそれぞれ設定をします.
出現時間になるとEnemyManagerへ敵の種類と行動の情報を渡し,出現座標で出現して振る舞いを開始します.
EnemyManagerは敵を新たにスポーンさせてArrayで管理し,毎フレームで動作の更新と当たり判定のチェックを行っています.
PlayerManagerはプレイヤーのインプットを受け取り毎フレームの操作を更新して動作に反映をしています.
BulletManagerは弾がスポーンする情報を送られた際にArrayで格納をし,毎フレームで動作の更新と当たり判定のチェックを行っています.
この3つのManager達は構造がほぼほぼ一緒でそれぞれプレイヤー,敵,弾に関するクラスのポインタ変数を持っていてその派生クラスのポインタをnewでpushして様々な種類の自機,敵,弾を生み出し反映できるようになっています.今回は初めて派生クラスの作成,ポインタとnewを使ったバリエーション拡張できる構築を行いました.
自機敵は弾を生成する際と当たり判定をチェックする際にBulletManagerのポインタからアドレスを受け取って生成チェックを行っています.
当たり判定はSiv3DのRectFとCircleを使ってます.なので実は透過率が100%で見えていないので単純な図形のコリジョンだけで見てます.どこのゲームでも一緒かもしれませんね.
キャラクターの派生はこのように継承されています.キャラクターは座標,体力,当たり判定,発射クールタイム,発射間隔など全キャラクター共通の変数が入っています.プレイヤーはプレイヤー用の追加変数やモード切替の処理を加えたものになっています.さらにそこからEsquireは固有の能力とその処理と発射する弾とについての処理を行っています.名前は元々ロボットゲームを作ろうとしていたのでその名残だったりします.操作できるキャラクターを増やす場合にはPlayerを継承して新しい処理を追加して新キャラを作ります.Enemyには移動命令の保存と移動命令の実行が加えられています.Droneには発射する弾の処理を加えられています.敵の種類を増やす場合にはEnemyを継承して必要な処理を書き込み追加の敵を作成します.
敵の移動命令やゲームのルールが示されているStageDataはデータの構造を決めて作成をしました.
ゲームルール,クリア条件
敵のスポーン時間,敵の種類,初期スポーン座標,移動方法,移動方法,...,移動方法
敵のスポーン時間,敵の種類,初期スポーン座標,移動方法,移動方法,...,移動方法
:
敵のスポーン時間,敵の種類,初期スポーン座標,移動方法,移動方法,...,移動方法
というような情報のファイルをステージごとに作成されます.このファイルを増やせば増やすほどステージは増え続けるということです.
敵とその中の情報が増え続けるとさすがにCSVファイルを手作業では編集しきれないのでその編集するためのステージメイカーのアプリも同時に作成しました.ボタン操作を行い行動やその行き先の座標などを打ち込むことで表示を見ながら操作ができるようになっています.
ステージ選択ではステージリストはCSVに格納されておりそこにステージ名,コメント,ステージファイル名が書かれていてそれらを参照しています.
ステージ名1,ステージ名2,コメント,ステージファイル名
:
ステージ名1,ステージ名2,コメント,ステージファイル名
このような形式で保存されています.ここの行をステージファイルと合わせていけば増やした分だけステージ数を増やすことができます.
リザルトはシンプルにgetDataから受け取った体力を基に評価値と比較して結果を出力しています.
反省会
今回作成してみて反省した点を挙げていこうと思います.反省点は大きく作りすぎた,ポインタだけで情報の受け渡しをしたの2点が大きいと思っています.
大きく作りすぎたは文字通りなのですが久しぶりに作るゲームを大きくしてしまいました.1年間まるまるではありませんが長期間かけて最初に構想した作りたいシステムの形を作ろうとして作成してました.作りたかった機能は使える自機の種類を増やせる,敵の種類を増やせる,弾の種類を増やせる,ステージのロードができるです.解説で書いたポインタの機能ですね.この機能を使ったゲームを作るとしたら個人製作ではかなり大きなゲームだと思います.大きくし過ぎましたが作りたかった機能なのでこれはこれで為になりました.ステージのロードができるから派生してスポーンタイミング,敵キャラクターの行動をプリセットするためのファイルの構造や敵の行動原理をどうするかはかなり悩みました.そしてこれならランダムでもよかったかなとプチ反省点がここにあります.ステージの構成を手動ではかなり大変だったのでそれのためのアプリケーションを作りましたが手動でやるときよりもよくはなりましたが同じタイミングの敵の動きやタイムライン上でわからないのでこれも数がいると大変になりますね.地球防衛軍のように湧きポイントからわらわらでたり出現自体をランダム化するのでもよかったかなと思っています.ゼビウスやスターソルジャーはどのようにルーチンを組んでいたのでしょうか・・・
ポインタだけで情報の受け渡しをしたも言葉の通りですね.ポインタつまり*,$によって情報をやり取りしました.主にやり取りする情報は当たり判定つまり座標なのでそうすると柔軟に座標を取って来たり渡したりができなかったり,ソースがすごく見づらかったり,編集しづらかったりするのでかなり利便性が落ちました.その為追尾弾を作ろうにもごちゃごちゃになり種類を増やす目的を阻害したりしています.シングルトンパターンを知ってはいたのですがポインタでのやり取りをあまり理解していないのとどのポイントで使うべきなのかわからなかったので今回は使用しませんでした.ただ今回触ってみて使いどころがわかりました.触った中だと各Managerプログラムとinputで使うのが重要な点かなと思います.各接点になっているManagerはやはり必要で,inputはクラスで宣言しているのですが使用するポイントで宣言しているのでこれも一つで補いたいところです.またinputは今回射撃,モード切替,移動の入力をメニュー操作にも流用していたのでメニュー操作もシングルトンパターン化した入力で個別に関数群を宣言するのがよいかなと思いました.シングルトンパターン化すると自機や敵の関数内の弾の生成や当たり判定確認でおそらく引数を減らせて継承後に引数を増やさないといけない場面がなくせると見込んでます.ポインタでのやり取りの動きがわかったので次回作からはシングルトンパターンも活用して今以上にプログラムを使いやすくして可塑性を上げられたらなと思っています.
今回,Siv3Dの機能で実行ファイルに埋め込みができるのですがこれがなかなか便利だったので次回も使いまくろうと思ってます.
(おまけ)キャラクターデザイン
記事の中でも書きましたが元々は自分が好きなこともありロボットゲームを作ろうとしてました.組みたいプログラムの構想があったので初期案ではロボットゲームとだけ決まっていて特にストーリーや世界観があったわけではありませんでした.プログラムが出来上がりそうになった際に当たり判定を詰めないといけないのでキャラクターイメージを作成しようとしたのですがロボットを描く画力が足りませんでした・・・
キャラクターデザインやインターフェイスデザインなどは基本的に紙とペン,パワポで草案を生み出すのですが特に考えもなく生み出したのですがなかなかいいキャラクターが出来上がったのでプレイヤーキャラに採用しました.なかなか案がでなく進んでいなかったのでこれでかなり進みました.
モード切替した際の顔は目と口をその場で逆さまにしただけのシンプルなイメージです.イケイケとネガティブな感じが出ていて結構気に入っています.
メニューカーソルなどになっている顔は同じようにパーツごとにその場で横倒しに回転しただけです.ニュートラルなそこまで感情の波がない感じ.このイメージ1枚で左右反転をしただけのものをカーソルに採用しているので1枚分容量が軽いです.元々ファイルサイズは大きくないのであまり意味ないかも.
プレイヤーキャラがいるのなら敵キャラクターと弾のデザインが必要になります.敵キャラクターはプレイヤーキャラクターを生み出すよりもかなり早く流れで出来上がりました.プレイヤーキャラクターがモニターに見えたので敵キャラクターはリモコンをモチーフにしました.昔,照明用のリモコンを操作して消灯しようとした際に振り上げながらボタンを押すとPCモニターが突然消えました.どうやら真っすぐボタンを押すとモニターは消えないらしいのですが振り上げながらボタンを押すと赤外線の波長がモニターの電源の波長と一致するらしく消えるという現象が起きました.なので少し縦長で顔に見えるようにボタンを消してデザインしました.ちなみにNECの照明のリモコンです.
上記の理由から弾は赤外線つまり電波になりました.敵だけにしようとしたのですが描画テストの際に自機からも発射した際に違和感や見づらさを感じなかったのでそのままプレイヤーの弾になりました.
このような形で全体的にゆるいデザインのキャラクターが出来上がりタイトル,ステージセレクト,メニュー画面はシンプルでゆるい雰囲気になりました.
おわりに
このゲームは私が今まで作成した来たプログラムの中でソースファイルが多く含まれている物になりました.ここまで構造が大きいもの初めてだったので出来上がった今ではとても達成感があります.様々な要素は今まで作成してきた仕組みと経験から構成を組み立てていき,新しい仕組みを知ろうと挑戦をしました.反省点はあるものの良いものができたと感じています.このゲームの製作中にまた別のゲームの構想が思いついたので今度はそちらを作成しようと思っています.次は大きくせずにまた反省会でもあるようにシングルトンパターンを用いてなおかつ作ったことないなと思ったクリッカーゲームを作ろうかなと思っています.パーティクルシステムも作ろうかなと思ってます.大きくなりすぎないようにして年内には組み上げたいなと思っています.年1個以上を毎年の目標にモノ作りをしてるので2個はかなり挑戦的だなぁと個人艇には思っています.
今回はここで終わりにしたいと思います.また次回の製作記事でお話出来ればと思います.