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

今日は「戦闘の深み」を追求する日

今日は実装量が多かった。
ユニットの詳細表示拡充から始まり、戦闘バランス調整、そして経験値システムの設計・実装まで。
気づいたら夜になっていました。

「敵ユニットをクリックしたら詳細が見たい」「育てたユニットが強くなる実感が欲しい」
プレイヤーとして当然感じる欲求を、ひとつひとつ形にしていく日でした。


■ ユニット詳細表示の拡充

敵ユニットも、自ユニットも、クリックで詳細が見えるようになった

これまで詳細ウィンドウはユニットリストウィンドウ経由でしか開けませんでした。
マップ上のユニットを直接クリックして確認する手段がなかったのです。

本日、以下の2パターンを追加しました。

  • 敵ユニットを左クリック → 詳細ウィンドウを表示
  • 味方ユニット選択中(移動範囲表示中)に自ユニットを左クリック → 詳細ウィンドウを表示

閉じ方は閉じるボタンか右クリックです。

実装上の課題として、右クリックによる閉じる処理の競合がありました。
UnitDetailWindowManagerCursorManagerの両方が右クリックを監視していたため、
「ウィンドウを閉じた直後に未行動ユニットへのフォーカスが発火する」という現象が起きました。

解決策は右クリック処理の一本化です。
CursorManager.Update()の冒頭で詳細ウィンドウの開放を確認し、
開いていれば閉じてそのままreturnする構造にしました。
右クリックの監視は一箇所に限る、という昨日の教訓をまた活かすことになりました。


■ 戦闘バランス調整

射程と機体サイズが命中率に影響するようになった

テストプレイ中に「攻撃側支援あり・防御側包囲済みの圧倒的優勢なのに攻撃側の方が被害が大きい」という
理不尽な結果が出ることがありました。乱数の振れ幅が大きすぎた問題もありましたが、
今回は以下の2つの補正を追加しました。

射程距離による命中下方補正

  • 距離1〜3:補正なし
  • 距離4:-5%
  • 距離5:-10%
  • 距離6:-15%
  • 距離7:-20%
  • 距離8以上:-25%(下限)

長距離砲が万能にならないよう、距離に応じた命中ペナルティを設けました。
最初は10%刻みで実装しましたが、長距離ユニットが全然撃破できなくなったので5%刻みに調整しています。

ユニットサイズによる回避補正

  • サイズ5(最大):補正なし
  • サイズ4:相手命中率-5%
  • サイズ3:相手命中率-10%
  • サイズ2:相手命中率-15%
  • サイズ1(最小):相手命中率-20%

小型ユニットは当てにくい。シンプルですが戦術の幅が広がります。
歩兵や偵察車両の生存性が上がりました。


■ 経験値システムの実装

戦い続けたユニットが強くなる

今回最も大きな実装です。
ネクタリスオマージュとして戦術的深度を高めるため、経験値システムを設計・実装しました。

基本仕様

  • 経験値範囲:0〜20(ステージ開始時は全員0)
  • 攻撃力・防御力が経験値1につき5%向上(最大で基礎値の2倍)

取得ルール

  • 攻撃側:ダメージを与えたら1、撃破したら2
  • 防御側:攻撃されたら1(距離・ダメージ問わず確定)、反撃で撃破したら2

防御側は「攻撃されるだけで経験になる」設計です。
遠距離から一方的に撃たれ続けるユニットが経験値ゼロのままというのは理不尽なので、
攻撃された時点で1獲得するルールにしました。

設計上のポイント

経験値の計算はCalculateCombat()内で戦闘結果と同時に確定させています。
HPと同じく「戦闘前の値」と「戦闘後の値」をCombatResultに格納し、
実際の付与はApplyExp()で行う設計です。

これにより戦闘画面が開いている時点で「この戦闘後の経験値がいくつになるか」がわかっており、
演出タイミングを自由に制御できます。

表示箇所

経験値は勲章画像で表現しています。味方は青、敵は赤のバナーデザインで、
0〜20の21段階、敵味方合わせて42枚の画像を用意しました。
表示箇所は戦闘画面・詳細ウィンドウ・下部パネルの3箇所です。

画像の管理はExpSpriteDataというScriptableObjectに一元化しています。
3箇所で使うたびに42枚をアサインするのは苦行なので、1つのアセットを共有参照する形にしました。

経験値の画像です。星の数と大きさで段階を表現しています。
ここまで育てるには相当な激戦をくぐり抜ける必要があります。実際のプレイでそうそう見られる段階ではないですが、
育てたプレイヤーへのご褒美として用意しました。

以下のように戦闘画面では表示されます。


■ 戦闘画面の数値精度向上

経験値による能力向上が正確に反映されるようになった

経験値システムの実装に伴い、攻撃力・防御力の内部型をintからfloatに変更しました。

経験値1、基礎ATK30のユニットの攻撃力は30 × 1.05 = 31.5です。
これをintで持つと32に丸められ、HP10のユニットの戦闘画面表示は320になります。
正しくは315のはずです。

計算過程は全てfloatで保持し、表示時のみMathf.RoundToInt()で整数化する設計に統一しました。
地味な修正ですが、数値表示の精度はゲームの信頼感に直結します。


■ その他・UI整理

ConfirmDialog(確認ダイアログ)をSystemWindowCanvas配下から独立したCanvasに移動しました。
Sort Orderを50に設定し、どの画面が開いていても最前面に表示されるよう整理しています。
あわせて全CanvasのSort Orderを整理し、描画順序の競合を解消しました。


■ シーン完成状況

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

執筆後記
今日は実装量が多かったです。特に経験値システムは設計から始めたので時間がかかりました。
「戦闘結果をCombatResultで一元管理する」という既存の設計思想を経験値にも適用できたのは良かったです。HPと同じパターンで実装できると、コードの一貫性が保てます。

苦労したのは戦闘演出のタイミング制御です。「経験値付与」「画面更新」「撃破演出」の順序が噛み合わず、戦闘画面が閉じた後に経験値が更新されたり、撃破演出と戦闘画面が重複したりと、しばらく格闘しました。
最終的にIsPlayingをfalseにするタイミングを調整し、Coroutineで適切に待機を挟むことで解決しています。

デモ版リリースまで残りタスクは着実に減っています。素材制作が本丸ですが、実装面はだいぶ形になってきました。