【個人ゲーム開発25日目】戦術SLG制作

今日は「ゲームとしての完成度」を上げる日

今日は戦術シーンを中心に、演出・AI改善・ヘルプ機能・エディタツールと、 幅広いタスクを一気に片付けました。
数をこなした日というより、それぞれが「ゲームとしての完成度」に直結する実装ばかりで、 やりごたえのある一日でした。


■ ミッション結果演出の実装

バナーがバーンと出てくる

これまでステージ終了時は、演出も何もなくそのまま結果シーンに飛んでいました。 いわゆる「シュール」な遷移です。
さすがにこれはまずいと思い、本日ようやく手を入れました。

実装したのは4種類のバナー演出です。

  • 敵全滅 → MISSION COMPLETE
  • 敵拠点占領 → BASE CAPTURED
  • 味方全滅 → MISSION FAILED
  • 味方拠点奪取 → BASE LOST

バナーは大きいスケールからEaseOutQuadで縮みながらフェードインします。 勝利時はファンファーレSE、敗北時は重厚なSEが鳴り、 2.5秒ほど余韻を残してから結果シーンへ遷移します。

BASE LOSTの髑髏バナーはなかなか気に入っています。 プレイヤーへのプレッシャーとして十分機能してくれそうです。

実装上の工夫として、GameOverReason enumを新設して終了理由を管理しています。 CheckGameOver()からDelayedEndGame()に理由を渡すことで、 どのバナーを出すかを正確に制御できます。

CanvasはPhaseCanvasと独立したMissionClearCanvasを新規作成しました。 PhaseDisplayManagerがPhaseCanvasのCanvasGroupをalpha=0にする処理があるため、 同じCanvasに乗せると一緒に消えてしまうことが判明したためです。 Canvasを独立させるという判断は、今後も使えるパターンだと思います。

出来上がりはこんな感じ。


■ エンディングシーン強化

スタッフロールが「ちゃんと動く」ようになった

昨日仮実装したエンディングシーンですが、 スクロールが途中で止まる、一瞬テキストが見える、といった問題がありました。
本日はこれを全部潰しました。

スクロール高さの取得にはendingText.preferredHeightを使用しています。 ContentSizeFitterの更新待ちによる高さ取得失敗を回避するための措置です。
初期の一瞬チラ見えはCanvasGroup.alpha = 0で対応しました。 SetActive(false)にするとレイアウト計算が止まってしまうため、 alphaだけ0にする方式が正解でした。

スクロール完了後はFinalViewがフェードインして表示されます。 THANK YOU背景画像にはHLSLシェーダーで周辺減光効果(ビネット)を実装しました。 上下は狭く、左右は広めに黒くフェードする設定です。

FinalViewのお知らせパネルにはデモ版である旨の告知と、 ブログ・X(旧Twitter)の宣伝を入れています。 Steamストアページは近日公開予定として記載しています。


■ ヘルプウィンドウの実装

ゲームのルールを説明する場所ができた

戦術シーンのヘルプウィンドウに、ついに中身が入りました。
デモ版でヘルプが空のままというのはさすがにまずいと思っていたので、 本日対応しました。

実装したセクションは6種類です。

  • 基本ルール(勝利・敗北条件・占領)
  • ユニットタイプ(歩兵・装甲車・戦車・駆逐戦車・自走砲・野砲)
  • ZOC(Zone of Control)
  • 戦闘効果(地形・支援・包囲)
  • 地形について
  • ステージ評価(C〜SSの5段階)

日本語・英語・中国語の3言語に対応しています。
テキストはHelpContentManagerスクリプトで言語判定して流し込む方式で、 GameSessionManager.SystemData.languageを参照しています。
設定画面で言語を切り替えると、次回ヘルプを開いたタイミングで自動的に反映されます。

ヘルプウィンドウ上でマウスホイールを操作するとマップのズームも動いてしまう問題があったので、 CameraControllerIsZoomLockedフラグを追加して対応しました。


■ 敵AI改善

渋滞が解消されて動きが自然になった

敵ユニットが通路で一列に並んで渋滞する問題を修正しました。

根本原因は経路探索の方式にありました。 従来は探索した経路のステップ数最小で経路を選んでいましたが、 地形コストを考慮した移動コスト最小の経路で選ぶGetPathMinCost()を新設しました。
直線距離ベースの選択は地形を無視して山に突っ込む副作用があったため、 A*で地形コストを正確に計算する方式としています。

また経路上の最遠マスに同陣営ユニットがいる場合のフォールバック処理も追加しました。 移動可能範囲内から目標への移動コストが最小のマスを探して移動します。 これにより渋滞を回避しつつ、適切な位置へ散開するようになりました。

あわせてStationaryパターンのバグも修正しました。 攻撃範囲にプレイヤーユニットが来ると移動してしまう挙動があったため、 移動実行前にStationaryチェックを追加しています。


■ 爆発エフェクトの3段階化

大きいユニットが派手に爆発するようになった

戦闘画面の爆発エフェクトとSEを、ユニットサイズに応じて3段階に分けました。

  • サイズ1 → Small(小爆発)
  • サイズ2〜3 → Medium(中爆発)
  • サイズ4〜5 → Large(大爆発)

UnitDataには以前からサイズ情報(unitSize、1〜5)を持たせていたので、 そこを参照して分岐するだけで済みました。
先行して設計しておいた恩恵が出た形です。

爆発画像はSmall・Medium・Largeの3種を新規作成しました。 Largeは大きなキノコ雲が立ち上がる迫力のある演出になっています。


■ マズルフラッシュ位置調整エディタツールの作成

ドラッグするだけで位置が決まる

マズルフラッシュの発生位置(muzzleOffset)の調整が、 これまでは数値を手入力して実行確認を繰り返す作業でした。
ユニット数が25種あることを考えると、かなりの手間です。

そこでUnityEditorの拡張ウィンドウとしてMuzzle Offset Editorを作成しました。

  • メニューの Tools → Muzzle Offset Editor で起動
  • ProjectウィンドウでUnitDataを選択すると自動で対象ユニットが切り替わる
  • プレビューエリア上でドラッグするだけでマズルフラッシュ位置を調整できる
  • 「保存」ボタンでUnitDataに書き込み(Undo対応)

スプライトのPixels Per Unit(100・200・300と混在)を自動補正しているため、 どのユニットでも正確な座標で設定できます。
実際に使ってみると、1ユニットあたり数秒で調整が完了します。 こういうツールを先に作っておくと後がずっと楽ですね。


■ その他の修正

  • 行動終了ユニットの色をDoneColorのみ適用に変更(チームカラーが出すぎていた)
  • タイトルシーンのアニメーション中にボタンが連打できてしまうバグを修正(CanvasGroup.blocksRaycasts制御)
  • HPテキストがユニットスプライトの裏に隠れる問題を修正(Order in Layer調整)
  • Pathfinding.cs・TurnManager.csのコードを整理(コメント日本語化・セクション分け)

■ シーン完成状況

シーン状況
タイトル✅ 完成
ブリーフィング✅ 完成
戦術(メインゲームプレイ)🔧 実装中
結果✅ 完成
エンディング✅ ほぼ完成

執筆後記
今日は本当に盛りだくさんな一日でした。
特に印象に残っているのはマズルフラッシュ位置調整ツールです。 「ちょっと作るのに時間かかるけど後が楽になる」という判断で作ったのですが、 実際に使ってみたら一発で「作って正解」と確信しました。
ツールに投資する判断ができるようになってきたのは、 個人開発の経験値が積み上がってきた証拠かもしれません。
敵AIの渋滞解消も地味ですが、マップを眺めていて気持ちよくなりました。 敵がちゃんと散開して攻めてくる。それだけでゲームの緊張感が全然違います。
デモ版完成まで残りのタスクを一つずつ潰していきます。