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

■ 20〜23日目のまとめ

今回は4日分をまとめて記録する。
※5/13、5/16、5/17は私用にて作業お休み。

20日目はScene_Mainの操作設計確定とUI骨格整備、21〜22日目は勢力情報パネルの本実装とデータ構造の大幅整備、23日目はコマンド設計の全体確定と外交情報パネルの本実装、各種バグ修正を実施した。

メインシーンの「見る」部分がほぼ出揃い、次はいよいよ「動かす」フェーズ管理の本実装に入る段階に来ている。


■ 20日目(5/12):操作設計確定・Panel_System新規作成

コマンド入力フェーズの操作設計確定

メインシーンの状態遷移と操作制御を確定した。

Idle状態では右ドラッグでマップスクロール、マウスホイールでズーム、拠点アイコンまたは勢力地図パーツの左クリックで地図情報パネル表示状態(ViewInfo)へ遷移する。それ以外への左クリック、または無操作10秒でUIパネル表示状態(CommandInput)へ自動遷移する。

右クリックは「短押し(0.2秒以内・離し時発火)」を遷移トリガーとし、右ドラッグと明示的に分離した。ViewInfo状態ではマップ操作を禁止し、情報閲覧に専念する設計とした。

Panel_System新規作成

セーブ・オプション・タイトルバック・ズームなどのシステムボタン群をManagerMainから独立させ、Panel_System.csとして切り出した。コールバック(Action)方式でManagerMainと連携する構成で、今後の状態制御が一元化しやすくなった。

ManagerMain改修

右クリック短押し判定を実装し、_rightDraggingフラグで押し時間を計測して離し時に発火する方式とした。MapContainer自体にButtonを設置するSetupMapClickHandler()を新設し、拠点・地図パーツ以外への左クリックをフォールバック処理する仕組みにした。これにより左クリックの吸い取り問題が解消した。

またBGM設計も確定した。プレイヤー保有拠点数に応じて5段階でBGMを切り替える方式で、使用素材はperitune.com(無料・商用利用可・ゲーム同梱可)を予定している。


■ 21〜22日目(5/14〜5/15):勢力情報パネル本実装・OfficerSaveData導入

Panel_FactionDetailInfo本実装

地図上の拠点または勢力地図パーツをクリックしたときに表示される勢力情報パネルを本実装した。表示項目は勢力名・勢力カラー・君主情報(顔小画像・名前)・各種ステータス(資金・兵数・国力・訓練度・民忠誠度・諜報力・名声・拠点数)・友好度・信頼度。

諜報レベルによる情報フィルタリングをGameUtility.GetIntelligenceLevel()として実装した。レベルに応じて閲覧できる情報が段階的に開放される。

  • レベル0:勢力名・拠点数のみ
  • レベル1:国力・君主情報(顔・名前)
  • レベル2:資金・兵数・民忠・名声
  • レベル3:全情報(訓練度・諜報力・外交情報)

プレイヤー自身の勢力はフィルタリングなし(レベル3扱い)。数値・記号専用のテキストはLocalizeTextを使用せずTextMeshProUGUI直接参照とする方針を今回から徹底している。

OfficerSaveData新設・全ファイル対応

武将データの管理構造を大幅に整理した。従来のSubordinateIds/CustomSubordinateIds(IDリスト2本)を廃止し、OfficerSaveDataクラスに統合した。MonarchId/CustomMonarchIdも廃止してOfficerSaveData Monarchに一本化した。

フィールド構成はOfficerId / CustomId / CurrentLoyalty / IsNormalの4つで、通常武将とカスタム武将を同一クラスで扱える。武将の解決はGameUtility.ResolveOfficer()経由で行う運用を徹底する。

対応ファイルはSaveDataGame.cs / GameUtility.cs / Panel_FactionDetailInfo.cs / ManagerStart.cs / DebugSaveDataCreator.csの5ファイル。構造変更に合わせてgame.jsonも再生成し、動作確認まで完了した。

Panel_FactionDetailInfoOfficer本実装

サブパネルとして武将一覧を表示するPanel_FactionDetailInfoOfficer.csを本実装した。表示枠は10枠固定(君主1名+配下最大9名)。

1枠を担当するOfficerSimpleInfo.csを新規作成した。表示項目は顔小画像・兵科アイコン・武将名・忠誠度・特技・武力/知力/魅力。君主の忠誠度は常に「—」で固定。配下の忠誠度は諜報レベル3以上またはプレイヤー勢力の場合に数値表示、それ以外は「???」でマスクする。


■ 23日目(5/18):コマンド設計確定・外交情報パネル本実装・バグ修正

コマンド設計確定

CommandType enumをGameEnums.csに追加し、全コマンド体系を確定した。

  • 内政系:開発 / 施し / 徴発
  • 軍事系:出陣 / 徴兵 / 募兵 / 訓練 / 迎撃
  • 外交系:同盟 / 親睦 / 要請 / 破棄
  • 人事系:捜索
  • 謀略系:諜報 / 計略 / 引抜
  • 援軍系:援軍出陣 / 援軍迎撃

解雇・褒賞はコマンドクラス管理外で即時処理、迎撃・援軍はWarクラスで別途管理する設計とした。要請コマンドは同盟勢力全員へ自動送信(個別指定不要)。出陣は経路指定方式(OriginBastionId+TargetBastionId)で戦争の一意特定を行う。

ランタイム用のOfficerCommand.csを新規作成し、セーブ用のOfficerCommandSaveDataをSaveDataGame.csに追加した。GameSaveSlot.PlayerCommandsでターン中断セーブに対応、GameSaveSlot.UnlockedSubordinateSlotsで開放済み配下枠数を永続管理する。

Panel_FactionDetailInfoDiplomacy本実装

外交情報サブパネルを本実装した。表示枠は9枠固定で敗北済み勢力を除外して表示する。

1枠を担当するDiplomacySimpleInfo.csを新規作成した。表示項目は勢力名・同盟有無・友好度(数値+段階テキスト)・信頼度(数値+段階テキスト)。このパネルは外交ボタン自体が諜報レベル3でのみ開放される前提のため、表示はレベル3固定とした。

信頼度の段階テキストを今回確定した。友好度とは別定義で以下の通り。

数値日本語英語
0〜19敵視Hostile
20〜39不信Suspicious
40〜59中立Neutral
60〜79好意Trusting
80〜99信頼Devoted
100信任Absolute

多言語対応改善

マスクテキストを言語依存のGetMaskText()メソッドに変更した。日本語は「???」、英語以降は半角「???」を返す。数値テキスト向けのマスクはMaskTextNumeric定数(半角「???」固定)に統一した。

Panel_Commands・Panel_CommandInterface実装

旧Panel_OfficerCommandCard.csをOfficerCommandCard.csに置き換えた。CardStateはEmpty / Locked / Idle / Doneの4種で、空席・ロック状態は顔画像欄に専用画像を表示する方式とした。

Panel_CommandInterface.csをモック実装した。ShowInput()(コマンド入力モード)とShowReadOnly()(閲覧専用モード)を実装し、onCloseコールバックに対応した。

バグ修正

今回は4件のバグを修正した。

  • ロードゲーム→タイトル→新規ゲームでロードデータが残留する問題:ManagerMain.Start()でNewGameData.Clear()が呼ばれていなかったのが原因。ロード・新規どちらのルートでも呼ぶよう修正した。
  • Panel_CommandInterface初期表示バグ:InitializeUI()でHideImmediate()の呼び出しが重複・漏れていたのが原因。
  • ViewCommandInterface状態から右クリックで戻れないバグ:Update冒頭のisViewCommandInterface変数が未定義だったのが原因。
  • Panel_CommandInterface閉じるボタンで状態遷移しないバグ:onCloseコールバック未実装・_onCardClick誤呼び出しが原因。Panel_Commands側にonCommandInterfaceCloseコールバックを追加し、ManagerMain側でViewCommands状態への遷移を統一した。

■ 現時点の開発状況

カテゴリ状況
データクラス全般✅ 完了
GameUtility✅ 完了
Scene_Initialize✅ 完了
Scene_Title✅ 完成
Scene_Start✅ 完了
Scene_Main(地図表示・スクロール・ズーム)✅ 完了
Scene_Main(勢力情報パネル)✅ 完了
Scene_Main(武将コマンドパネル)✅ 完了
Scene_Main(コマンド設計)✅ 確定
Scene_Main(フェーズ管理・コマンド入力)⬜ 未実装
Scene_Main(戦闘計算・AI)⬜ 未実装
Scene_End⬜ 未着手

■ 次回の作業予定

フェーズ管理の本実装に入る。1ターンを7フェーズで構成する設計はすでに確定しており、コマンド入力フェーズと結果表示フェーズの流れを繋ぐところから着手する予定。

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

■ 18〜19日目のまとめ

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

18日目はScene_StartからScene_Mainへのデータ受け渡し経路を確立し、メインシーンの骨格を実装した。19日目はマップの大幅強化と武将コマンドパネルの骨格実装を中心に進めた。
メインシーン制作へ本格的に着手し始めたが、設計書作成などあまり画像を出せないフェーズでの作業が多く、今は進捗立証が難しい段階です。


■ 18日目(5/10):データ受け渡し経路の確立とScene_Main骨格

データ構造の整備

GameEnums.csにGameResult.InProgress(値:-1)を追加し、従来のIsInProgress boolフラグを廃止して一本化した。あわせてカスタム武将の君主・在野登録対応としてFactionSaveDataにCustomMonarchId、WanderingOfficerSaveDataにCustomIdを追加した。

GameStartData新規作成

Scene_StartからScene_Mainへの初期データ受け渡し専用のstatic classを作成した。

ScenarioDataのマスタは一切書き換えず、全データを複製してGameSaveSlotとして渡す設計にした。NewGameDataとは独立した受け渡し口として機能する。Scene_MainのStart()で参照後にClear()を呼ぶことで使い捨てにする。

OnClickStart()本実装

以下の処理を実装した。

BuildInitialSlot()でScenarioDataを全複製し、プレイヤーの編集内容(君主・配下・勢力名)を上書き。BuildWanderingOfficers()で全武将を走査して在野登録。ResolveDisplacedFactions()でプレイヤーが他勢力から引き抜いた武将の代替を各勢力に割り当て。

引き抜きが発生した場合の処理も実装した。他勢力の君主を引き抜いた場合は配下の中から能力値合計最大の武将を格上げし、空いた枠には同レアリティの在野武将をランダム補充する。在野武将が同レアリティで見つからない場合は一段下で再抽選し、それでもなければ打ち切りとした。

Scene_Main骨格実装

GameSaveSlotからデータを受け取り地図表示を実装した。NewGame時はGameStartData.Slot、LoadGame時はManagerSaveData.GameData.Slots[index]を参照する。ScenarioDataはマスタ参照のみ(読み取り専用)。

地図は拠点図・勢力図の切替に対応した。ターン送りボタン、タイトルバック・ゲーム終了ボタンも実装した。フェーズ管理・コマンド・戦闘・AIは引き続き未実装。


■ 19日目(5/11):マップ強化・コマンドパネル・情報引き渡し対応

ボタン登録方式の統一

ManagerMain・ManagerStart・ManagerTitleの全ボタンについて、Inspector直設定からAddListener方式に統一した。

従来は一部ボタンがInspector上のOnClickリストに直接設定されており、状態遷移による一括制御ができない問題があった。RegisterButtons()を新設してStart()から呼ぶ方式に統一し、全ボタンで状態チェック(_state != Idle)が機能するようにした。コードを見れば全ボタンの挙動が把握できる状態にすることが目的で、InspectorとコードのどちらかだけでなくどちらもあわせてOnClickを確認しなければならない状態を解消した。

ワールドマップの2倍化

1920×1080のワールドマップをそのまま使用するとエリア戦で未使用エリアが広く残り見栄えが悪いため、3840×2160に拡大する方針に変更した。

単純な拡大ではピクセルが荒れるためAIアップスケーリングを採用した。Real-ESRGANのアニメ・イラスト向けモデル(RealESRGAN_x4plus_anime)をローカル環境で実行し、ディテールを保ったまま2倍化した。RTX3090 + CUDA環境で短時間で処理できた。Pythonとpipで環境を整えるだけで動作し、登録や料金は不要。

MapContainerの導入とスクロール・ズーム実装

Canvas_Main直下に3840×2160のMapContainerを新設し、Layer_Territory・Layer_Connection・Layer_Bastionを配下に移動した。

スクロール

右クリックまたは中ボタンドラッグで地図をスクロールできる。マップ外にはみ出さないようClampMapPosition()でスクロール範囲を制限している。

ゲーム開始時はプレイヤー勢力の所有拠点の重心座標を自動計算し、その位置が画面中央に来るよう初期表示位置を調整する。

ズーム

100%・75%・50%の3段階ズームに対応した。マウスホイールとズームボタン(3つ)の両方で操作できる。ズーム中心はマウスカーソル位置基準で計算している。拠点アイコンと接続ラインはRebuildPositions()でズーム倍率に追従して再配置する。ズーム時はSmoothStep補間による0.15秒のアニメーションを挟む。

GameUtility.cs新規作成

通常武将(OfficerDatabase)とカスタム武将(RecordData)からIOfficerを解決する共通ユーティリティとしてGameUtility.csを新規作成した。

Scene_Startでは用途が限定的だったため該当箇所に直書きしていたが、Scene_Mainではコマンド処理・AI処理・戦闘処理など至る所でIOfficer解決が必要になるため共通化した。今後はこのユーティリティを経由することを徹底する。

武将コマンドパネル骨格実装

メインシーン下部に武将カードを最大10枚並べるPanel_Commandsを実装した。左クリックで下からスライドイン、右クリックでIdle状態へ戻る。Panel_OfficerCommandCardが1枚のカードを担当し、顔画像(256×256・FaceSmall)と武将名を表示する。

これに合わせて256×256の切り出しスプライト(FaceSmall)を新規追加した。OfficerSpriteImporterで既存の_Faceスプライト(384×384)に加えて_FaceSmall(上部中央256×256)を一括登録できるよう拡張した。カスタム武将もImageImporter.CropFaceSmall()で生成・保存するよう対応した。

ManagerMainの状態遷移にCommandInput(コマンドパネル表示中)を追加した。

以下は、まだモックアップ実装の段階だが、マップの拡大/縮小とスクロールに対応したメイン画面。

勢力名・君主名のJp/En分離対応

従来、勢力名と君主名は1つの入力フィールドで日英共用していた。フォントアセットの言語対応の観点から、日本語・英語を独立した入力フィールドに分離した。未入力のままにすればデフォルト値がそのまま使われる。

Panel_FactionEditの入力フィールドをJp/En各2つに拡張し、Panel_FactionConfirm・ManagerStartも対応した。FactionSaveDataにMonarchNameJp・MonarchNameEnを追加してScene_Mainへの引き渡しに対応した。

また君主顔画像の差し替え情報(FaceSmallPathを含む)もGameSaveSlot経由でScene_Mainに引き渡すよう対応した。武将コマンドカードでは君主カードのみ、編集済み名前とFaceSmall画像をOverrideName/OverrideFace()で上書き表示する。

ゲーム設計書の整備(Googleドキュメント)

実装と並行してゲーム設計書をGoogleドキュメントで整備した。

今回確定・追記した内容は以下の通り。

ゲームフェーズ設計 1ターンを7フェーズで構成することを確定した。ユーザー入力が発生するのは④結果表示フェーズと⑥コマンド入力フェーズのみで、それ以外はクリックによるページ送り待ち程度に抑える設計としている。

コマンド仕様 内政・軍事・外交・人事・謀略の全コマンドについて計算式・適用特技・例外処理を確定した。武将1名につき1コマンドを入力する方式で、拠点数に応じて配下枠が最大9名まで拡張する仕様も確定している。

戦闘仕様 ベース戦力値の計算式、兵科補正テーブル、三つ巴以上の戦争処理、兵力の決算式、捕縛確率まで一通り確定した。

AI行動仕様 行動優先度を8段階で定義した。1年目は戦争しない・計略引抜は実行しないなどの制約ルール、同盟検討ロジック、難易度ボーナスの方針も記載した。

シーン内UI構成 状態遷移の定義を追記した。現時点では骨格のみで画面レイアウトは次フェーズで確定予定。

設計書は実装の進行に合わせて随時更新していく運用としている。


■ 現時点の開発状況

カテゴリ状況
データクラス全般✅ 完了
GameUtility✅ 新規完了
Scene_Initialize✅ 完了
Scene_Title✅ 完成
Scene_Start✅ 完了
Scene_Main(地図表示)✅ 完了
Scene_Main(スクロール・ズーム)✅ 完了
Scene_Main(武将コマンドパネル骨格)✅ 完了
Scene_Main(フェーズ管理・コマンド)⬜ 未実装
Scene_Main(戦闘計算・AI)⬜ 未実装
Scene_End⬜ 未着手

■ 次回の作業予定

シーン内UI構成の設計を固めてから、コマンドメニューの実装に入る。フェーズ管理の骨格を先に作り、コマンド入力→結果表示の流れを繋ぐ予定。

【開発[#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()の本実装から始め、マップ表示・ターン制御・コマンド実装と進める予定。

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

■ 10〜11日目のまとめ

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

10日目はScene_Startの基本実装が中心。勢力選択から確定まで一通りのUIフローが動くようになった。11日目はそこに機能を追加しつつ、データ設計の抜け漏れを潰した。


■ 10日目:Scene_Start 基本実装

勢力選択・確定フロー

Scene_Startのメインとなる画面遷移を実装した。

世界地図上の拠点アイコンをクリックするとPanel_FactionInfoが表示され、勢力の基本情報を確認できる。そこから「選択」を押すとPanel_FactionConfirmに遷移し、君主・配下武将のカード表示、各種変更ボタンが並ぶ確定ウィンドウになる。

拠点表示モードと勢力図モードの切り替えも実装済み。勢力図モードはalphaHitTestMinimumThresholdで透過部分のクリック判定を除外している。

状態管理

6状態のEnumで一元管理している。

Idle / FactionInfo / FactionConfirm / OfficerSelect / FaceImageSelect / ErrorDialog

状態によって操作可否を切り替える設計で、後の機能追加時にも拡張しやすい構造にした。

OfficerCard Prefab

武将カードUIをPrefab化してCommonに配置した。勢力確定ウィンドウと武将選択ダイアログで共用する。顔絵・名前・兵科・特技・三能力値を表示する。


■ 11日目:機能追加とデータ設計

配下武将変更ボタン

Panel_FactionConfirmの配下武将カード下部にボタンを配置した。

ScrollViewの配下カード列と並列にPanel_SubChangeButtonsを置き、配下数に応じてボタンをInstantiateする方式にした。LayoutGroupの動的生成でいくつか試行錯誤があったが、ラッパー構造は使わず並列配置で解決した。後でデザインボタンに差し替える予定なので、ボタンはPrefab(OfficerChangeButton)から生成する設計にしている。

君主画像差し替え機能

顔変更ボタンから起動する画像取り込みウィンドウを実装した。

Panel_ImageImportはCommon配置のPrefabで、カスタム武将登録でも流用できる設計にしている。OSのファイル選択ダイアログにはStandaloneFileBrowserを使用した。取り込んだ画像は512×768にリサイズし、上部384×384を顔絵として自動切り出しする。

対応フォーマット:PNG / JPG / JPEG
最大ファイルサイズ:10MB
出力サイズ(立ち絵):512×768
出力サイズ(顔絵):384×384(上部中央クロップ)

画像処理はImageImporter.csとして共通化した。

勢力名・君主名の編集機能

Panel_FactionEditを新規作成した。勢力確定ウィンドウの「勢力編集」ボタンから開き、勢力名と君主名をその場で変更できる。

文字数制限は以下で確定した。

項目日本語英語
武将名15文字19文字
勢力名10文字25文字

この制限値は武将登録画面でも共通仕様となる。

SaveDataRecord の新規設計

アプリ通算の実績データクラスを新規に設計・実装した。

これまでセーブデータはSaveDataSystem(設定)とSaveDataGame(プレイ進行)の2種類だったが、コレクション解放状況やカスタム武将管理のためにSaveDataRecordを追加した。SaveDataCustomOfficerとして別管理を検討していたが、ファイルを増やしたくないという方針からSaveDataRecordに統合した。

フィールド内容
areaClearRecordsエリア戦クリア記録(シナリオIDごと)
nationalClearCount全国版クリア回数
unlockedOfficerIdsコレクション解放済み武将IDリスト
customOfficerSlotCountカスタム武将スロット上限
customOfficers登録済みカスタム武将リスト

バージョンマイグレーション機構も組み込んだ。アップデートでフィールドを追加した場合は、ロード時に旧バージョンのデータを自動補完する。

ManagerSaveData の拡張

SaveDataSystem・SaveDataRecordの両方を同じ初期化フローで管理するように整理した。初回起動判定はisInitializedフラグで行い、ファイルパスの参照はManagerSaveData内に集約している。

ユーティリティメソッドも追加した。

IsNationalScenarioUnlocked()  // 全国版解放判定
IsAreaCleared(scenarioId)     // 指定シナリオのクリア判定
CalcCustomOfficerSlotCount()  // スロット上限の算出

Scene_Title:状態管理の刷新

TitleSceneState EnumをManagerTitle.csに導入した。これまでboolを複数管理していたが、Enumに一元化して各ボタンイベントに状態チェックを入れた。状態の階層構造は以下の通り。

Opening → WaitingInput → MainMenu(起点)
                              ├ ScenarioSelect → DifficultySelect → StartScene遷移
                              ├ Collection
                              ├ ForgeOfficer
                              ├ LoadGame
                              └ Option

同一レベル間の平行移動はなし、戻るは常に1レベル上へ、という設計で統一している。

Panel_Scenario.cs 新規作成

シナリオ選択パネルの専用スクリプトを作成した。シナリオ名のテキスト表示(LocalizeText使用)と、全国版ボタンの有効/無効制御を実装している。全国版かどうかの判定はIsNationalScenarioフラグで行い、シナリオIDのマジックナンバーは使用していない。キャンセルボタンも追加した。

今回からパネルごとに専用スクリプト1本という方針を確立した。


■ 現時点の開発状況

カテゴリ状況
データクラス全般✅ 完了
SaveDataRecord✅ 新規完了
Scene_Initialize✅ 完了
Scene_Title(基本)✅ 完了
Scene_Title(Collection・ForgeOfficer・LoadGame)🔶 未実装
Scene_Start(基本UI)✅ 完了
Scene_Start(武将選択・遷移)🔶 未実装
Scene_Main⬜ 未着手

■ 次回の作業予定

タイトルシーンの残実装(Collection・ForgeOfficer・LoadGame)を優先して進める。Scene_Startのスタートボタン遷移はその後。

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

「凝った。」

当初、1週間くらいでそこそこ動くものを作れると思っていた。

作り始めたら、土台が気になった。
データ構造が気になった。
セーブの設計が気になった。
9日経って、まだタイトル画面しか動いていない。

でも後悔はしていない。


■ この8日間でやったこと

前回の記事(開発1日目)から約8日が経過した。
大きく3つのフェーズに分けて進めてきた。

  • フェーズ1(2〜4日目):素材調達とデータ設計
  • フェーズ2(5〜7日目):データ基盤の実装と自動化
  • フェーズ3(8〜9日目):仕様の言語化と画面実装の開始

順番に書いていく。


■ フェーズ1:素材とデータ設計

武将立ち絵102体

本作には102名の武将が登場する。全員分の立ち絵を生成AIを駆使して生成した。
プロンプトは3000文字くらい、英単語も400Wordsほど。調整に数日使った。塗り方にはかなり拘った。

スタイルは「semi-painted watercolor illustration style」に統一。自作の絵を参照画像として渡しスタイルを再現させる手法(塗りや顔などの作画)が安定することがわかった。生成した画像はUnity上で512×768にリサイズして使用する。顔絵(384×384)は立ち絵の上部中央をクリッピングして自動生成する仕組みにした。

世界地図の処理

地形有地図と白地図の2種類を用意し、Pythonで処理した。

白地図のボロノイ分割処理でB01〜B30の30枚のリージョンスプライトを自動生成したが、流石に形状がカクカクだったので結局手作業で地形より30個の白地図用ピースを作成した。地形有地図・白地図ともに海部分をflood fillで透過処理している。地形有地図の上に白地図30ピースを半透明で重ねる実装方針とした。


■ フェーズ2:データ基盤の実装と自動化

前のゲーム(鋼鉄の交響曲)で仕様変更や調整によるスパゲッティコード化に苦しんだ。
今回は最初から「土台に時間をかける」と決めていた。

ScriptableObjectとCSVインポーター

OfficerDataBastionDataをScriptableObjectとして設計し、CSVからの一括インポーターをEditorスクリプトで実装した。

KingdomChronicle/Import Officer Data
KingdomChronicle/Import Bastion Data
KingdomChronicle/Import Scenario Data

メニューから実行するだけで全データが自動登録される。武将102件・拠点30件・シナリオ4本のインポートが完了している。

Spriteの自動アサイン

武将立ち絵・顔絵・拠点スプライトの自動アサインもインポーター内に実装した。

武将立ち絵:CR_{ID:D3}       例:CR_001
武将顔絵 :CR_{ID:D3}_Face  例:CR_001_Face
拠点領域 :B{ID:D2}         例:B01

ISpriteEditorDataProviderを使ってSpriteのRectをプログラムから登録している。UnityはY軸が左下原点なのでRect計算時に注意が必要だった。一度ミスした。

セーブデータ設計

セーブデータは3層構造で設計した。

内容
SystemData設定系(音量・言語等)
SaveDataRecordプレイ記録(コレクション・クリア数)
SaveDataCustomOfficerカスタム武将データ
SaveDataGameゲーム進行データ(4スロット)

カスタム武将については、元データ削除後もゲームが破綻しないよう、スロット内にスナップショットを複製する設計にした。

ScenarioDatabaseを途中で削除した

当初シナリオデータを管理するScenarioDatabaseを作っていたが、途中で削除した。

シナリオは4本固定だ。102件の武将・30件の拠点と違い、IDで大量のデータを索引する必要がない。Resources.Load<ScenarioData>("Data/Scenarios/S01")で直接ロードする方が実装がシンプルになる。

「とりあえず作る」より「本当に必要かを問い直す」。
そういう判断を意識するようにしている。


■ フェーズ3:仕様の言語化と画面実装

実装より先に、仕様を言語化して確定させた。
曖昧なまま実装すると、後で大量の手戻りが発生する。

戦争発生パターン(4種)

パターン概要
1vs1侵攻戦・迎撃戦・遭遇戦
同時多発同一勢力が複数戦争に同時関与・戦力は完全分離
三つ巴補正値=他全勢力との1vs1補正値の平均・最大戦力値の勢力が勝利
援軍侵攻側は要請コマンド必須・迎撃側は自動通知

訓練度の参照タイミングは「コマンドを入力した時点の値」に確定した。出陣後に訓練しても戦力値には反映されない。

三すくみの補正倍率

有利兵科に対して×1.5の武力補正を適用する。

これにより武力60の弓兵が武力90の槍兵と同等の戦力値になる。単一兵科の高武力集中は弱点になり、混成編成が戦略的に有利という設計だ。騎馬が見た目上武力寄りのキャラ(騎士系)になりやすいので、槍と弓の高武力確保が必須戦略になる。

ターンサイクル

1. ターン開始フェーズ
   戦争結果反映・収入・イベント
   → ここまでの情報が次フェーズで見える

2. プレイヤーコマンド入力フェーズ
   入力時点の値で全パラメータ確定

3. AIコマンド入力フェーズ(非表示)
   次ターンの1で結果が開示される

AIは後出しになるため構造的に有利。この優位をどこまで活かすかを難易度設定と連動させる。以下の仕様は仮で最終的な難易度はテストプレーで調整する。

Easy  :AI資源収入 ×1.0(プレイヤーと同等)
Normal:AI資源収入 ×1.2
Hard  :AI資源収入 ×1.5

タイトルシーンのモック実装

仕様が固まったところで画面実装に着手した。

演出フローをManagerTitle.csに実装している。

黒フェードイン → 背景フェードイン → ロゴフェードイン
→ PUSH ANY KEY 点滅 → キー入力
→ パネル右スライドイン + ロゴ左移動 + ボタン上スライドイン

NewGameフローはNewGameDataという静的クラスでScenarioIdDifficultyを一時保持してシーン遷移する設計にした。ManagerSceneTransitionを汚さずに済む。


■ 現時点の開発状況

カテゴリ状況
武将データ(102名)✅ 完了
拠点データ(30件)✅ 完了
シナリオデータ(4本)✅ 完了
セーブデータ設計✅ 完了
タイトルシーン✅ モック完了
スタートシーン⬜ 次回着手
メインシーン⬜ 未着手
戦闘計算式⬜ 仕様詰め中

執筆後記

「1週間くらいで作れると思ってた」は、いつも嘘になる。

でも今回は悔しくない。
土台をちゃんと作ったから、これからが速くなるはずだ。

スタートシーンが動いたら、一気にゲームらしくなる。
それを楽しみに、次のセッションへ。

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

「増えた。」

39日目は、プロジェクトが2本になった日でした。

鋼鉄の交響曲の開発を続けながら、別のゲームを作り始めました。
正確には「作り始める準備が整った」という方が正しいかもしれません。

画面はまだ真っ暗で、ボタンもタイトルも仮のものだけ。
でも、ちゃんとシーンが遷移して、音が鳴って、セーブデータの場所が決まった。

それだけで、もう「動いている」と感じます。


■ ふりーむへのDEMO版登録

国内向けのプロモーション展開として、ふりーむにDEMO版を登録しました。

BOOTHとitch.io、フリーゲーム夢現に続く4つ目のプラットフォームです。

登録にあたっていくつか注意点がありました。

ゲーム紹介文には外部URLを記載できません。
Steam版の存在を「正式版は現在開発中です」という一文で示すことにしました。
直接誘導はできませんが、言葉で印象を残す形です。

ReadMeについても、素材クレジット以外の外部URLは削除が必要でした。
BOOTH・ブログへのリンクをすべて取り除き、Xのアカウント名表記のみ残しています。

セルフ審査の質問が細かく、「戦争テーマは該当するか」と少し迷いました。
ただ、ふりーむのガイドラインには「SRPG・隣国戦闘」が明確にOK例として挙げられていました。
残酷描写を目的とした作品ではないので、問題なしと判断しています。

現在は審査中です。結果はメールで届く予定です。


■ 展開状況まとめ

プラットフォーム状態
BOOTHv1.0.1 公開済み
itch.iov1.0.1 公開済み
フリーゲーム夢現公開済み
ふりーむ審査中
SteamKYC承認済み・30日待機中

■ 新プロジェクト「王国創世記」始動

知名度を作る手段として、無料ゲームをもう1本開発することにしました。

タイトルは「王国創世記 -Kingdom Chronicle: Fantasy Conquest-」。
ファンタジー世界を舞台にしたターン制戦略SLGです。

自分で作った将軍と配下武将で、20の国を持つ10勢力が覇を競います。
ルールはシンプルです。 隣接する国を攻め、外交で同盟を結び、謀略で配下を引き抜く。 難しい操作は不要で、戦略の判断だけに集中できる作りを目指しています。 鋼鉄の交響曲が「戦術」を楽しむゲームなら、こちらは「戦略」を楽しむゲームです。

鋼鉄の交響曲がニッチな戦術SLGなら、こちらは間口の広い戦略SLGで入口を作る。
そういう位置づけです。


■ 39日目でやったこと

鋼鉄の交響曲の開発をメインにしながら、王国創世記の基盤を1日で構築しました。

Unityプロジェクトを作り、シーン構成を決め、共通基盤のスクリプトを実装しました。
セーブ・ロード、多言語対応、シーン遷移、入力管理、音量設定、確認ダイアログ。
ゲームを作るたびに同じものを作っていたので、今回はテンプレートとして複製できる形に仕上げました。

次回以降の新規プロジェクトは、この状態からスタートできます。
1日分の作業が、今後何日かを短縮します。


執筆後記

プロジェクトが2本になった。

管理が増えるのは事実だけど、気持ち的には少し楽になった気がします。

鋼鉄の交響曲が行き詰まった時に、王国創世記を触ればいい。
王国創世記に詰まれば、鋼鉄に戻ればいい。

どちらかが止まっても、もう一方は動いている。
そういう状態を作っておくことが、長期開発の継続には大事なのかもしれません。