論争パート対応オートセーブ

絶対調味ロジック

論争パートのセーブは、その時点での保存復元がいまのところ複雑でうまく再現できないため、
ラウンド開始時の「オートセーブ」のみに対応させました。

主にMessenger(セリフシステム)が複数必要になり、それに対応させた弊害みたいです。
何度か「どこでもセーブ」の構築を試したのですが、いまのところ惨敗です。

オートセーブ対応版

■ 概要

論争パート(探索パート基準)のセーブ対応を実装。
論争中はセーブ不可オートセーブのみ有効とし、ロード後は該当ラウンド(Location)の最初から復元する設計。

■ 変更ファイル一覧

ファイル名変更内容
Sys_Debate_Init.csロード時の0初期化スキップ追加
Sys_LocationController.csisDebateLocationフラグ追加、
オートセーブ追加、InitializeForNormalEntryをpublic化
Sys_SaveManager.cs論争Location用ロード復元分岐追加、
OnEnableForLoad呼び出し追加、フラグ復元後のyield return null追加
Sys_Messenger.cs論争中のオートセーブ停止
Sys_Flag_Controller.csOnEnableForLoadメソッド追加(flagsToSetは実行しない)
UI_MenuPanel.cs論争中セーブボタン無効化・SetDisabled()追加
UI_SaveLoadPanel.cs論争中セーブタブ非表示対応
Sys_ConfigTabController.csSetSaveTabVisibleメソッド追加

■ 設計の核心

保存データ:
SAV_in_debate(Bool)のみ追加。
SAV_debate_roundは不要。
Location=Roundの既存構造をそのまま利用。

オートセーブタイミング:
Sys_LocationController.OnEnable内のisDebateLocationブロックで発火。
SAV_in_debate = trueのセット後にAutoSaveを呼ぶ順番が重要。

ロード復元フロー:
RestoreSavVariables(SAV_変数復元)
→ yield return null(1フレーム待機)
→ inDebate判定
→ InitializeForNormalEntry()(該当RoundをLocationの最初から復元)
→ IsLoading = false
→ OnEnableForLoad()(Flag_Controller再発火)

論争以外の探索Locationは従来通りApplyStateで復元。

■ 運用上の注意点

isDebateLocationの設定:
論争パートの各Round(LocationController)のInspectorでisDebateLocationにチェックを入れること。
チェックがないとオートセーブ・セーブ無効化・ロード復元が機能しない。

SAV_変数の参照オブジェクト:
Sys_Flag_ControllerのflagsToCheckでSAV_変数を参照する場合、targetObjectを必ず指定すること。
targetObjectがnullの場合、ロード後にOnEnableForLoadで再発火しないケースがある。

セーブボタン:
論争中はメニュー画面のセーブボタンが自動的にDisabled状態になる。
ロード画面のセーブタブも非表示になる。ロードは論争中も可能。

debate_scoreの初期化:
Sys_Debate_Initはロード時に0初期化をスキップする。
論争シーン入場時のみ0初期化される。

OnEnableForLoad:
flagsToSetは実行しない設計。
ロードで復元したSAV_変数を上書きしないための意図的な仕様。
将来flagsToSetの再実行が必要なケースが出た場合はその時点で対処する。

ScoreおよびHPバーの保存・復元

■ 概要

各ラウンド開始時(オートセーブが走る瞬間)において、
Score」および「HPバーの位置(値)」が正しくセーブデータに保存されず、
ロード復帰時に直前の状態が復元されない、あるいは位置がズレてしまう問題を解決します。

本修正では、SaveData クラスに「Score」と「HP(またはHPバーの位置)」を格納するパラメータを追加し、
セーブ・ロード時にこれらの値が完全に同期されるようにシステムを統合しました。

■ 具体的な修正箇所と工程

① セーブデータ構造の拡張
Sys_SaveManager.cs 内の SaveData クラスに、ScoreとHPを保存するためのフィールドを追加しました。

② セーブ実行時のデータ保存(データ充填)
オートセーブおよび手動セーブが走る際、
UIマネージャーやステータス管理マネージャーから「現在のScore」と「HPバーの値」を取得し、
セーブデータに書き込みます。
・Sys_SaveManager.cs の FillNovelData またはセーブ共通処理への追加。
注意:
Save および AutoSave のルーチン内で、この FillStatusData(data) が必ず呼び出されるように配置しています。

③ ロード実行時のデータ復元
シーンのロード(ノベルシーンまたは探索シーンへの遷移)が完了した直後に、
セーブデータからスコアとHPバーの値を読み込み、画面上のUIへ即座に適用(セット)します。
・Sys_SaveManager.cs の LoadRoutine 内(共通後処理、またはフェードイン前)への追加。

オブジェクト復元機能

■ 発生していた問題

  • 現象:セーブデータをロードした際、フラグ状態(例:SAV_Win_Ob_R1 = true)は正しく復元されているにもかかわらず、対応するWinオブジェクトなどの表示(SetActive)が切り替わらず、非表示のままになっていた。
  • 副次的な不具合:以前のコードでは、非アクティブなオブジェクトからコルーチンを開始しようとしたため、実行時に Coroutine couldn’t be started because the game object is inactive! というエラー(警告)が発生していた。

■ 根本的な原因

Unityのライフサイクルと実行順における 「レースコンディション(処理順のねじれ)」 が原因でした。

  1. コルーチンの制限:
    Unityの仕様上、非アクティブ状態のゲームオブジェクトにアタッチされたスクリプトから StartCoroutine を呼び出すことはできません。ロード時に非アクティブだったオブジェクトの復元処理が、エラーによって途中で阻害されていました。
  2. 実行順(OnEnable)の限界:
    各オブジェクトが自身の OnEnable やコルーチンの1フレーム遅延(yield return null)を利用して「自分の状態を自分で判断する」設計になっていました。しかし、ロード中の親オブジェクト(Locationなど)のアクティブ化タイミング、フラグ復元のタイミング、各オブジェクトの初期化順がフレーム単位でズレるため、再発火が正常に処理されないケースが多発していました。
  3. 探索範囲の制限:
    従来の Sys_SaveManager は、ロード復帰時に「アクティブになった特定のLocation(Roundオブジェクト)の配下」のみから Sys_Flag_Controller を探して叩いていました。そのため、Locationの枠外に置かれたグローバルな監視オブジェクトや、その時点で非アクティブな別の階層にあるコントローラーは完全に無視されていました。

■ 施した改善・修正内容

「個々のオブジェクトが自分で起きるのを待つ」のをやめ、
「すべてが整った最後の瞬間に、セーブManagerが上から一斉に叩いて起こす(強制同期)」 というイベント駆動型・集中管理型の設計にシフトしました。

① コルーチンの完全廃止と直接評価メソッドの追加(Sys_Flag_Controller.cs)
・不安定な StartCoroutine や IEnumerator による1フレーム遅延処理をすべて削除しました。
・代わりに、外部からいつ呼ばれても安全にフラグチェックを強制実行できる RefreshFlagsOnLoad() メソッドを実装しました。これにより、自身や対象が非アクティブであってもエラーを出さずに、フラグに基づいた SetActive の状態をその場で確定させます。

② シーン全体の全コントローラーに対する一斉適用(Sys_SaveManager.cs)
・LoadRoutine(探索復帰処理)の最下部を修正しました。
・特定のLocation配下(target.GetComponentsInChildren)だけを叩くのをやめ、Resources.FindObjectsOfTypeAll<Sys_Flag_Controller>() を用いて、シーン内に存在するすべてのコントローラー(非アクティブ状態のものも含む)を漏らさず収集するようにしました。
・ロードが完全に完了し、すべてのオブジェクトの配置やアクティブ状態、フラグデータ(savVariables)の書き戻しが終わった「極限のタイミング」で、収集した全コントローラーの RefreshFlagsOnLoad() を一斉に実行させます。

■ 既存システムへの安全性について

本修正により、既存の機能やデータが損なわれるリスクは極めて低いです。

  • ノベルモードへの影響:なし
    セーブデータの構造(SaveDataクラス)や、ノベルの進行度復元ロジックには1文字も手を加えていません。
  • 探索モードのセーブへの影響:なし
    JSONに書き出すデータやセーブ処理自体は一切変更していません。
    ロード時の「再発火の合図」をより確実にしただけの修正です。
  • データの互換性:あり
    今回の修正前後でセーブデータの構造は同一なため、修正前に作成したセーブデータであっても、修正後のシステムで問題なくロード・復元できます。

■ 今後の開発における注意点・留意事項

  1. ロード直後の強制同期仕様
    シーン内の「すべての」Sys_Flag_Controller がロード直後に強制的にフラグ判定を実行します。そのため、「ロードされた直後はフラグが満たされていても非表示にしておき、特定のイベントを通過した後に表示させたい」 というような、時間差を持たせたいオブジェクトがある場合は注意が必要。
    そういったオブジェクトは、「ロード直後は条件を満たさないダミーフラグ」にしておくか、コントローラーの判定用フラグ自体をイベントの進行に合わせて切り替える設計にする。
  2. プレハブとシーン上実体の区別
    Resources.FindObjectsOfTypeAll は非常に強力で、非アクティブなオブジェクトも取得できますが、メモリ上にある「プレハブ(アセット)」まで取得してしまう性質があります。
    今回マージしたコードでは、fc.gameObject.scene.isLoaded を使って「シーン上に実際に配置されている実体のみ」に処理を通す安全装置(フィルタリング)を入れています。
    今後この周辺をいじる際も、この安全装置を削除しないようにすること。
  3. 「常にアクティブな監視オブジェクト」設計の推奨
    もし今後、Location(Round)の切り替えが激しくなり、オブジェクトの親子関係が複雑化して表示バグが再発しそうになった場合は、前述した 「Locationの階層外に『常にアクティブなフラグ評価専用の空オブジェクト』を置き、そこから各Location内のターゲットをリモート操作する」 設計へ移行すること。
    今回の修正により、どちらの構成であっても完璧に動作する土台は整ってる。

コメント