【開発[#2] 17日目】戦略SLG

■ 12〜17日目のまとめ

今回は6日分をまとめて記録する。

前回まででScene_Titleの基本実装は終わっていたが、各画面のデザインが仮素材のままだった。今回はそこを仕上げ、タイトルシーンを一旦完成とした。あわせてScene_Startの機能実装を本格的に進め、君主選択制御・武将変更パネルまで動くようになった。

結構、Claudeの提案に振り回される展開も多く、手戻りに近い無駄な工程もあったのでAIをディレクションする能力を高めるべきと反省する局面が多かった。前提条件にいつの間にか認識齟齬があったり、複雑化するほど丁寧な要件指定が必要と痛感した。
小さく閉じた機能設計精度は高いが規模が大きくなって複雑化してくると業務系アプリケーションではあまり問題ないのかもしれないが、ゲームのような独自性が高いアプリケーションだとAIの推論が的外れになる事も多いですね。


■ 12日目(5/3):バグ修正・多言語化対応・各種画面実装

LocalizeHelperの削除と多言語化設計の統一

大規模な対応となった。それまで使っていたLocalizeHelper.cs(日本語・英語の2言語決め打ちでテキストを返すユーティリティ)を全廃した。

理由は拡張性。bool isJapaneseの二択設計では将来的に中国語やドイツ語を追加できない。代わりにSystemLanguage型のswitch式を使う方針に統一した。この変更が影響したファイルは14本・34箇所にのぼった。

LocalizeText.SetDynamicText()への統一も同時に行い、TextMeshProUGUIには必ずLocalizeTextをアタッチするプロジェクト標準を確立した。

コレクション画面(Panel_Collection)実装

タイトルシーンの武将図鑑画面を実装した。

全132名をID順にNo.1〜No.132で表示する。24名/ページ(横8×縦3)・最大6ページのページング構成。CollectionCard.csをPrefab化してCommonフォルダに配置し、登録済み/未登録(?????表示)の2状態を持つ設計にした。

カードをクリックするとPanel_OfficerDetailが開き、立ち絵・能力値・兵科・特技・経歴などの詳細を確認できる。

カスタム武将登録画面(Panel_ForgeOfficer / Panel_CustomOfficerEdit)実装

「My Officers」から起動するカスタム武将登録機能を実装した。

画像を取り込むとSHA256ハッシュ値を生成し、そのハッシュをシードとしてパラメータを自動生成する。同じ画像からは必ず同じ結果が得られる設計になっている。

自動生成の内容:

項目方式
能力値(武力/知力/魅力)正規分布(Box-Muller法・平均60・標準偏差15)
初期忠誠均等分布(60〜90)
ボーナスポイント均等分布(10〜30)
兵科重み付き抽選(均等5種)
特技シード固定抽選(42種)

特技に兵科縛りがある6種(騎馬突撃・弓の名手・槍衾・剣聖・魔法集中・魔法陣)については、特技抽選後にUnitTypeを強制上書きする処理を入れている。

ボーナスポイントは各能力値に±ボタンで振り分けられる。元の生成値より下には下げられない制約あり。

確定後は変更不可・削除のみ可能という設計で確定した。

LoadGame画面実装

タイトルシーンのデータ読込画面を実装した。

オートセーブ1枠+手動3枠の計4スロット構成。各スロットにはシナリオ名・難易度・現在ターン(年・季節換算)・勢力名を表示する。エリア戦(青)・全土戦(赤)で背景色を色分けし、データなしスロットはグレーで読込ボタンを無効化する。

ターン数の変換は以下の通り。

エリア戦:最大40ターン(10年)
全土戦:最大80ターン(20年)
上限超過:「期限切れ」表示

■ 13日目(5/4):SlidePanelBase実装・パネル演出の共通化

SlidePanelBase

各パネルのスライドイン・スライドアウト演出を提供する共通基底クラスを作成した。

スライド方向(上下左右)・時間・距離はInspectorで設定可能。オプションでCanvasGroupによるフェードイン・アウトにも対応している。公開APIはShow() / Hide() / ShowImmediate() / HideImmediate() / SetInteractable()の5種。

この回から全パネルが継承対象となり、現時点では以下が対応済み。

Panel_Scenario / Panel_Difficulty / Panel_LoadGame / Panel_Collection / Panel_OfficerDetail / Panel_ForgeOfficer / Panel_CustomOfficerEdit / Panel_ImageImport / Panel_Option / Panel_Dialog / Panel_FactionInfo / Panel_FactionConfirm / Panel_FactionEdit / Panel_OfficerSelect

Panel_OptionとPanel_Dialogの配置方針変更

旧方針のDontDestroyOnLoad・Singletonパターンを廃止した。各シーンに直接Prefabを配置し、SerializeFieldで直接参照する方式に変更した。

ManagerOption・ManagerDialogという名称もPanel_Option・Panel_Dialogにリネームし、命名規則(Panel_*プレフィックス)に統一した。


■ 14日目(5/6):カスタム武将画面の修正・操作制御整備

ScrollView修正とCanvasGroup操作制御

カスタム武将一覧のScrollViewで、カードがContent配下ではなくScrollView直下に追加されて重なって表示される不具合を修正した。SerializeFieldのアサイン漏れが原因だった。

操作制御の方針をこの回で確定した。パネルが開いている間は他パネルの操作を一切不可とする。親子パネルが重なる場合はCanvasGroupのinteractable制御を使う。SetInteractable()をSlidePanelBaseの公開メソッドとして追加した。

最終更新日時機能

CustomOfficerDataにLastModifiedAtフィールドを追加した。ISO 8601形式で保持し、表示時は言語に応じたフォーマットで出力する。

日本語:yyyy/MM/dd HH:mm
英語:MMM dd, yyyy HH:mm

■ 15日目(5/7):CollectionCard・CustomOfficerEdit改修

CollectionCard改修

未登録カードの飾り枠が実行時に消える不具合を修正した。Sprite Frame Secretが未アサインのままspriteにnullが入っていたのが原因だった。

未登録用素材を分離した。変更前は顔・兵科共通の_spriteSecretを1枚使っていたが、_spriteSecretFace(顔用)と_spriteSecretUnitIcon(兵科用)に分離した。

ステータス表示(武力/知力/魅力)も追加した。登録済みは実数値、未登録は「??」表示。兵科アイコンカラーは#C8A84B(ゴールド)で統一した。

Panel_CustomOfficerEdit改修

兵科アイコン画像表示と特技説明文表示を追加した。特技説明文はGameEnumExtensions.GetDescription()で取得し、ClearParamDisplay / ApplyGeneratedData / LoadEditTargetの各タイミングで更新する。

GameEnumExtensionsにUnitType重み付き抽選メソッドGetWeightedUnitType(int seed)を追加した。スキル縛りなし時の兵科抽選に使用する。


■ 16〜17日目(5/8〜5/9):タイトルUI完成・スタートシーン本格実装

タイトルシーン3画面のデザイン確定

この段階でタイトルシーンを一旦完成とした。

ロード画面はセーブスロットの背景を色設定からSprite差し替え方式に変更した。エリア戦(青系)・全土戦(赤系)・データなし(グレー系)で別素材を使う。パネル上部に「Select Save Data」タイトルラベルを追加した。

シナリオ選択画面はエリア戦ボタンを青・全土戦ボタンを赤の別Spriteで色分けした。文字のシャドウはTMPのマテリアル制約をShadow用テキストオブジェクトの並置で回避した。

難易度選択画面はEasy(緑)・Normal(青)・Hard(赤)の横並びアイコンボタンに変更した。

ツールチップ実装

ホバーでテキストを表示するTooltipUI.cs・TooltipTrigger.csを新規作成した。

言語テキストはSystemLanguageエントリ方式で管理しており、言語追加時はInspectorにエントリを追加するだけで対応できる。CanvasGroupのinteractable=false時は表示しない制御を入れており、操作無効状態でのホバーにも対応している。

拠点データ拡充

エリア戦の拠点数が少なすぎると感じたため、各エリアに追加した。

エリア全国版拠点エリア専用追加合計
Area1(北部山地)104(B111〜B114)14
Area2(東海地方)84(B209〜B212)12
Area3(南部平野)125(B313〜B317)17

全拠点IDの管理を2桁(B)から3桁(B*)に統一した。BastionDataImporter.csのフォーマット指定をD2→D3に修正し、既存アセットもUnityエディタ上でリネームした。

接続設計はPythonスクリプトで地図上に可視化して確認した。一方通行チェックも全エリア・全土戦で実施済み。

勢力データ拡充

拠点増加に合わせて各シナリオの勢力数を増やした。

シナリオ追加勢力
S01アイゼンガルド辺境伯領 / ヴァルドレン王国
S02ティレニア海洋同盟 / コーラル諸島連邦
S03ヴェルディア王国 / サンフィールド伯爵領 / テラヴェルデ共和国

各勢力の識別カラーは既存15勢力と被らないよう選定した。

武将12名追加

コレクション画面が6ページきっちり埋まるよう武将を12名追加した。CSVインポートとシナリオ反映を確認済み。

スタートシーン:パネルのSlidePanelBase移行

Panel_FactionInfo・Panel_FactionConfirm・Panel_FactionEditをSlidePanelBase継承クラスに変換した。

SlidePanelBaseにSetSlideDirection()を追加し、パネルの左右スライド方向を動的に変更できるようにした。ただし今回のPanel_FactionInfoは固定方向(右)に統一し、コードからの動的制御をやめてInspector設定に一本化した。

ManagerStart.InitializeUI()でgameObject.SetActive(true)後にHideImmediate()を呼ぶ方式をManagerTitleから踏襲した。Hierarchyのアクティブ・非アクティブ状態に動作が左右されないよう徹底している。

Overlayによる操作ブロック方式を廃止し、CanvasGroupのSetInteractable()に完全移行した。StartSceneStateにFactionEdit状態を追加して全状態での制御を一元化した。

君主選択制御

スタートシーンの主要機能となる君主選択の可否制御を実装した。

条件選択可能な君主
全土戦unlockedOfficerIdsに登録されている君主
エリア戦・初回全国シナリオに登録されている君主
エリア戦・クリア後そのエリアシナリオの全君主

選択不可の勢力は城アイコンを暗色で表示し、地図上から視覚的に判別できるようにした。Panel_FactionConfirmでは選択不可時にStart等のボタンをinteractable=falseにし、理由を示すメッセージを表示する。全土戦とエリア戦でメッセージ内容を出し分けている。

勢力データはScenarioDataのマスタを直接書き換えず、ManagerStart起動時にFactionScenarioData.Clone()で複製したものを使う設計にした。

Panel_OfficerSelect 新規実装

全土戦の君主・配下変更用武将選択パネルを実装した。

CollectionCardを横8枚・縦スクロールで一覧表示する。コレクション武将→カスタム武将の順で続けて並べる設計のため切り替えボタンは不要になった。

フィルタ機能として兵科ボタン・能力値スライダー(武力/知力/魅力)・特技ドロップダウンを実装した。特技ドロップダウンはEnumのValueが飛び番(1〜6の後が11〜)になっているため、_skillEnumValuesリストでindex↔Value対応表を持つ方式にした。

カードをクリックすると即確定・パネルを閉じる。

IOfficerインターフェース導入

OfficerDataとCustomOfficerDataの共通化のためIOfficer.csを新規作成した。

GetName() / GetBiography()
Strength / Intelligence / Charisma / DefaultLoyalty
UnitType / SpecialSkill / Personality / Rarity
GetFaceSprite() / GetStandingSprite()
IsCustom

両クラスに明示的インターフェース実装で追加した。フィールドのプロパティ化はScriptableObjectのシリアライズを破壊するため禁止とし、IOfficer.Strength => Strengthの形で実装した。

これによりOfficerCard.Setup()・Panel_OfficerSelect・Panel_FactionConfirmの武将一時変更データをすべてIOfficer型で統一できた。メインシーンでの戦闘計算・AI処理でも型分岐が不要になる見込み。

Panel_FactionConfirmの武将一時変更設計

確定パネル上での武将変更は、開始ボタンを押すまで一時データとして扱う設計にした。

_tempMonarch(IOfficer):君主の一時変更
_tempSubordinates(IOfficer[]):配下の一時変更

Show()時にnullリセット、リセットボタンでnullに戻す。実データ(_factionListのClone)はキャンセルしても書き換わらない。開始ボタン押下時にGetTempMonarch() / GetTempSubordinates()で確定データを取得してGameStartDataを生成する設計になる。

カスタム武将の在野登録フラグ追加

CustomOfficerDataにIsWanderingInNational(bool)フィールドを追加した。

全土戦でカスタム武将を在野武将として登場させるかどうかを制御するフラグ。Panel_CustomOfficerEditにトグルを追加して編集可能にした。

武将選択画面ではこのフラグに関係なく全カスタム武将が選択対象になる。ゲーム開始後、選択されなかったカスタム武将のうちIsWanderingInNational=trueのものが在野武将として登場する。

スーパーゲ制デー(5/9)

毎月第2土曜日のスーパーゲ制デーに合わせてXに開発進捗動画を投稿した。

タグ(#スーパーゲ制デー)は長文投稿の折りたたまれた範囲に入ると検索インデックスに乗らないことを確認した。タグは投稿冒頭に配置するか、別リプライにタグのみ投稿する方式が確実。
Xに関してはフォロワーを増やす為だけの活動はアカウント品質低下を招くと判断し、内容のあるPOSTを心掛けて地道に展開したいと思う。実際、相互しますで増えたフォロワーはPOST内容に興味が無さそうでインプレッション率(POSTのインプレッション数/フォロワー数)の悪化しか招かない結果に(笑)
しかし、POSTに反応が増えてくるとやる気がアップしますね。


■ 現時点の開発状況

カテゴリ状況
データクラス全般✅ 完了
IOfficerインターフェース✅ 新規完了
GameStartData✅ 新規完了(実装は次回)
Scene_Initialize✅ 完了
Scene_Title✅ 一旦完成
Scene_Start(UI・操作制御)✅ 完了
Scene_Start(君主選択制御)✅ 完了
Scene_Start(武将変更パネル)🔶 デザイン調整中
Scene_Start(ゲーム開始処理)🔶 未実装
Scene_Main⬜ 未着手

■ 次回の作業予定

Panel_OfficerSelectのデザイン仕上げ後、Scene_Mainの実装に入る。

GameStartData.CreateFromScenario()の実装・OnClickStart()の本実装から始め、マップ表示・ターン制御・コマンド実装と進める予定。