ここまでノベルパートにてノベル用システムを構築してきました。
探索パートにもほぼ流用できますが、一部分の分離・統合を行う必要があります。
- セリフシステム:Cut進行専用になっているため、探索にも対応できるように
- セーブロード:Cut進行専用になっているため、探索用保存ステータスなども追加し復元可能に
以前の開発バージョンUnity2023版とは比べ物にならないくらいに、ノベルパートでのシステムを実装しているので、
まずは探索パートに対応させ基礎システムを再構築していきます。
プロジェクトの目的(初期設計意図)
- ノベル(Cut)と探索(Location)の共存
- Messenger(セリフシステム)を共通UIとして利用
- Save/Loadで両モードを復元可能にする
- 進行構造は完全分離する
全体アーキテクチャ
基本構造
Cut(ノベル:1カット)
├ Sys_CutManager
├ Sys_CutController
└ Sys_Messenger(共通利用)
Location(探索:場所)
├ Sys_LocationController
├ Sys_LocationUIManager(拡張用)
└ Sys_Messenger(共通利用)
探索パートもCut進行と構造を同じようし、Location(場所)進行としています。
Cut内に状況を作る代わりに「Location内に状況を作る」となっています。
・探索パート(その1) 独自構築
・探索パート(その2) 独自構築
Cutバージョン・Locationバージョンに分け、それぞれに対応するシステムに再構築していきます。
設計思想
- Cut = 直線進行(演出単位)
- Location = 空間状態(状態単位)
- Messenger = 共通会話UI
- Save = モード依存分岐
フェーズ進行まとめ
フェーズ1:Cut依存分離
■ 目的:MessengerをCut依存から解放
■ 成果:
・Explore(探索)でもMessenger動作可能
・CutとExplore(探索)のUI共通化成立
フェーズ2:Explore(探索)基礎構築
■ 目的:Location単位で探索を成立
■ 成果:
・LocationController導入
・Active/Inactiveで切替
・Location遷移構造完成

● Sys_LocationController
・移動時に表示されるメッセージを登録。
・現在のLocationから移動する・移動できるLocationを事前に登録。
※現状はボタンの方で機能しているため、Sys_LocationUIManagerともに不要。
・タイミングはディレイで設定可能に。
・セーブスロットに表示させていたセルフ部分はロケーション名に変更。

● Sys_LocationButton
・Location移動には専用ボタンを使用。
・現在Locationと移動先Locationを登録。
フェーズ3:Save/Loadシステム再構築
Save/Load分岐
■ 目的:ノベルと探索の保存分離
■ 成果:
・PlayMode(Novel / Explore)導入
・Scene復帰対応
・Location単位復元
探索状態保存拡張
■ 目的:探索内部状態の保存
■ 成果:
・メッセージ進行保存
・フラグ管理基盤
・ギミック状態設計追加
・イベントフラグ構造追加
探索完全復元コア
■ 目的:探索状態の再現性確保
■ 成果:
・カメラ状態保存・復元
・キャラクター状態保存・復元
・Location単位完全復元
・Save→Load 一貫性確立

セーブ・ロード設計の改善★
保存構造(Sys_LocationState):
・カメラ位置・回転
・アクティブなMessenger1つの相対パス・messageIndex・isFinished
フラグ変数:
SaveData.savVariables(SAV_接頭辞)に一元管理。
Sys_LocationState には持たせない。
復元フロー(LoadRoutine):
1. 全Locationを非アクティブ化
2. 対象Locationを SetActive(true)
3. ApplyState で全Messengerを停止・非アクティブ化
4. 対象Messengerの親チェーンをアクティブ化
5. ReserveMessageIndex でindex予約 → SetActive(true)
6. IsLoading = false
Messengerのロード復元:
OnEnable 時に reservedMessageIndex を参照して再開位置にジャンプ。
コルーチンのタイミング問題(IsLoadingがfalseになるタイミングとの競合)は reservedSkip フラグで解決。
・会話途中 → 指定indexから即時全表示で再開
・会話終了済み → FinishConversation() を直接呼び出し
ロード中の副作用対策:以下のスクリプトに IsLoading チェックを追加
・Sys_Flag_Controller.RegisterTriggers() / OnEnable()
・Sys_FlagTrigger.OnEnable()
・Cmd_Activate_Object.OnEnable()
これによりロード中のフラグ発火・オブジェクト制御をスキップし、ApplyState による復元と競合しない。
現在の完成状態
■ 探索システムは以下を満たしている
- Location単位で独立
- Messenger共有
- Save/Loadで完全復帰
- フラグ・イベント管理可能
- カメラ・キャラ復元可能
データ構造の中心
■ SaveData
- Scene名
- PlayMode(Novel / Explore)
- CutIndex / MessageIndex
- LocationName
- LocationState
■ LocationState
- メッセージ進行
- カメラ状態
- キャラ状態
- ギミック状態
- イベントフラグ
設計の重要原則
■ 探索はCutにしない → 状態が空間依存のため
■ Location単位で完結 → 全状態はLocationStateに集約
■ Find依存は許容(現段階) → 将来最適化対象
■ シンプル優先 → Manager層増やさない
分別統合システムの改善★
■ 修正・整理の全体方針
削除:
・Sys_GameModeManager — 完全削除、シーン分離で不要
・Sys_CutController の GameModeManager 参照
・Sys_CutManager.LoadLocationByName() — 残骸
・Sys_SaveManager の RestoreNovel() / RestoreExplore() / GetActiveLocationName() — 未使用デッドコード
・Sys_LocationState の activeCharacterName / localFlags / eventFlags — 未使用・重複
修正:
・モード判定を CutManager.Instance != null で統一
・static フラグの残留リセット
・SaveRoutine と AutoSaveRoutine の判定ロジック統一
・Sys_LocationState をシンプルに整理
・スキップ・既読の探索対応
Sys_Messenger の cachedCutIndex 初期値を 0 → -1 に変更。
探索パートでは CutManager が存在しないため -1 を使用し、ノベルパートの cutIndex=0 との衝突を回避。
■ 運用ルール
シーン構成:
・ノベルシーン:Sys_CutManager を置く。Sys_LocationController は置かない。
・探索シーン:Sys_LocationController を置く。Sys_CutManager は置かない。
フラグ変数の保存:
・セーブに含めたい変数は SAV_ 接頭辞をつければ自動的にセーブデータに入ります。
・LocationState にフラグを持たせる必要はありません。
| 項目 | ルール |
|---|---|
| ノベルシーン | Sys_CutManager を置く。Sys_LocationController は置かない |
| 探索シーン | Sys_LocationController を置く。Sys_CutManager は置かない |
| 探索Messenger | デフォルト非アクティブ。起動は各スクリプトで手動制御 |
| 会話終了後 | 探索パートのみ自動で非アクティブ化(CutManager.Instance == null で判定) |
| フラグ保存 | SAV_ 接頭辞をつければ自動的にセーブデータに含まれる |
| セーブデータ変更後 | 既存セーブは互換性がないため新規セーブし直してテスト |
■ PlayModeの判定箇所
判定箇所:Sys_SaveManager.DetectPlayMode()
static PlayMode DetectPlayMode(out Sys_LocationController activeLocation)
{
activeLocation = null;
if (Sys_CutManager.Instance != null)
return PlayMode.Novel;
foreach (var loc in FindObjectsByType<Sys_LocationController>(...))
{
if (loc.gameObject.activeInHierarchy)
{
activeLocation = loc;
return PlayMode.Explore;
}
}
return PlayMode.Explore; // フォールバック
}
判定ロジック:
・Sys_CutManager がシーンに存在する → ノベル
・アクティブな Sys_LocationController が存在する → 探索
判定タイミング:
・Save() 実行時
・AutoSave() 実行時
・ロード時は判定を行いません。セーブデータに記録された PlayMode をそのまま使います。
csharpif (data.playMode == PlayMode.Novel) { ... }
else { ... }
ノベルシーンには Sys_CutManager を置く、探索シーンには置かない。 それだけで全て自動的に判定されます。
改善★0523 ロード後のUI復元
探索パートでメッセージ終了後に表示されるUIが、セーブ→ロード後に復元されない問題。
■ 原因の構造
メッセージ終了
→ activateOnFinishedObjects(Msg_Front_End)をSetActive(true)
→ Cmd_Activate_Object.OnEnable()
→ IsLoading=true のためreturn(ブロック)
→ UIが表示されない
加えてMessenger自身も終了後にSetActive(false)されるため、CaptureStateで状態を捕捉できていなかった。
■ 解決方針
複雑な状態保存・復元ではなく、LocationControllerに復元対象を直接登録するシンプルな方式を採用。

■ 変更内容
Sys_LocationState.cs:変更なし(シンプルなまま維持)
Sys_LocationController.cs:
・loadActivateObjectsフィールドを追加
・ApplyState内でisFinished=trueの場合のみloadActivateObjectsをアクティブ化
■ 運用ルール
・メッセージ終了後に表示したいUIはloadActivateObjectsにInspectorから直接アサインする
・isFinishedフラグで条件制御されるため、メッセージ未終了時には表示されない
・DontDestroyOnLoad配下のオブジェクトも直接アサインなので参照問題が発生しない
改善★0525 Messengerの初期化ルール
修正を重ねている間に、
LocationControllerでの初期起動Messenger指定がなくなったことにより、
通常のロケーション移動の制御漏れが発生していた問題を解決しました。

以前のようにデフォルトMessengerの明示的指定に戻しました。
■ 修正ポイント
① 既存機能(セーブ・ロード)は100%そのまま維持
Sys_SaveManager がロード処理を行う際、
Sys_SaveManager.IsLoading = true の状態でこのLocationを SetActive(true) にします。
今回の修正により、ロード時は OnEnable の先頭で即座に return されるため、
ロード時の挙動(ApplyState による正確な復元)には一切干渉しません。
② 通常遷移時の「ゴミ」を完全に排除
普通にマップ移動してきたときは、
まず配下の全Messengerに対して ForceStop() と SetActive(false) を一括実行します。
これにより、「Unityエディタ上でうっかりアクティブのまま保存してしまった別のMessenger」が誤作動する原因を根本からシャットアウトします。
③ インスペクターで一目瞭然に
新しく追加した defaultMessenger 枠に、そのロケーションに入った瞬間に動かしたいMessenger(GameObject)をドラッグ&ドロップするだけで設定が完了します。ヒエラルキーの上下順を変更しても、バグらなくなります。
■ 拡張の懸念点
「同じロケーションに、ゲームの進行度(フラグ)によって違うMessengerを初期起動させたいケース」
もし「このロケーションは、いつ来ても最初の会話(defaultMessenger)は1種類だけ」であれば、今回のシンプルな修正がベスト(最短ルート)です。
もしフラグによる分岐が必要な場合は、InitializeForNormalEntry の中でフラグマネージャーの値を参照する処理を追加する必要がありますが、現時点ではこれで意図通りの挙動になるはずです。
改善★0528 探索パートCinemachine対応・セーブロード修正
■ 発端の問題
Unity6 URP + Cinemachine導入後、
探索パートのLocation内でカメラが移動した状態でセーブしても、ロード後にデフォルトカメラ位置に戻ってしまう問題。
■ 原因の特定
CaptureState()・ApplyState()がCamera.mainのTransformを保存・復元していたが、
CinemachineはCinemachineBrainが毎フレームCamera.mainを上書きするため、保存も復元も機能していなかった。
■ 解決の方向性
「Camera.mainのTransformではなくどのVCamがアクティブかを保存・復元する」 方針に変更。
■ ヒエラルキーの再構成
変更前の問題:
・VCamがCmd_Activate_Objectで制御される状態セットの子に入っていた
・Cmd_Activate_ObjectがVCamごとON/OFFしてしまい復元と干渉
変更後の構造:
Location
├── VCams(VCamだけを独立して管理する親)
│ ├── CM_Cam_Default(常時Active)
│ └── CM_Cam_Chara(会話時用)
├── Msg_Sato(Cmd_Activate_Object)
│ ├── Sato_A(Messenger・Cmd_VCam_Switch・holdOnDisable=false)
│ └── Sato_B(Messenger・Cmd_VCam_Switch・holdOnDisable=true)
├── Msg_Sato_Choice(Cmd_Activate_Object・Cmd_LocationState_Marker)
└── Msg_Sato_Answer(Messenger・Cmd_VCam_Switch・Cmd_Activate_Object)
■ 新規作成スクリプト
Cmd_VCam_Switch.cs:MessengerのOnEnable/OnDisableに連動してVCamを切り替えるコンポーネント。

・役割:MessengerなどのGameObjectのアクティブ状態に連動して、VCamを切り替えるコンポーネント。
・設定項目:
| フィールド | 内容 |
|---|---|
| targetVCam | 会話時のVCam アクティブ時に表示したいVCam |
| defaultVCam | デフォルトVCam 非アクティブ時に戻すVCam |
| holdOnDisable | trueにすると非アクティブ時にデフォルトに戻さない (連続する状態間でカメラを維持したい場合に使用) |
・動作:
| タイミング | 動作 |
|---|---|
| OnEnable | targetVCamをON、defaultVCamをOFF |
| OnDisable | targetVCamをOFF、defaultVCamをON(holdOnDisable=falseの場合) |
| OnDisable | 何もしない(holdOnDisable=trueの場合) |
・使い方パターン:
| 状況 | 設定 |
|---|---|
| キャラに寄る | targetVCam=キャラカメラ、 defaultVCam=デフォルトカメラ |
| カメラ位置を動かさずデフォルトに戻したい | targetVCam=デフォルトカメラ、 defaultVCam=デフォルトカメラ |
| 連続する状態間でカメラを維持したい | holdOnDisable=true |
・運用ルール:
必ずVCams配下のVCamをアサインする
デフォルトカメラ(CM_Cam_Default相当)は常時Activeにしておく
カメラを切り替えたいGameObjectにアタッチする
ロード時はIsLoadingチェックにより発火しない(ApplyStateが復元を担当)
カメラ移動の発火タイミングが重要
・OnEnable(アクティブになるとき) → カメラを寄せる
・OnDisable(非アクティブになるとき) → カメラを戻す
Cmd_LocationState_Marker.cs:Messengerを持たない状態オブジェクト(選択肢UIなど)に付けるマーカー。

- loadActivateObjects:ロード時にアクティブにしたいUIを登録
ロード後に表示したいUI(Marker状態)が重なっているような構築の場合、
状態の区切り目となるGameObjectにCmd_LocationState_Markerをアタッチし、
ロード後に表示したいものをloadActivateObjectsに登録します。
ただし、直前の状態オブジェクトは非アクティブにする必要があります。
■ 修正したスクリプト
Sys_LocationState.cs:
・cameraPosition・cameraRotationを削除
・activeVCamPaths(List)を追加:アクティブなVCamのパスを複数保存
・activeStatePathを追加:Messengerなし状態オブジェクトのパスを保存
Sys_LocationController.cs:
・CaptureState():VCams配下のアクティブVCamパスを保存、Markerのアクティブ状態を保存、アクティブMessengerを優先・終了済みMessengerをフォールバックで保存
・ApplyState():VCamを復元、Marker状態を復元しつつActivateLoadObjects()を呼ぶ、Messengerを復元
・IsApplyingState静的フラグを追加:ApplyState中はCmd_Activate_ObjectのOnEnableをスキップ
Cmd_Activate_Object.cs:
・OnEnableにSys_LocationController.IsApplyingStateチェックを追加
■ 運用ルール
| 状況 | 対応 |
|---|---|
| VCamの追加 | 必ずVCams配下に置く ※現状は名前は必ず「VCams」にすること |
| 会話時のカメラ切り替え | Cmd_VCam_Switchをアタッチ |
| 連続状態でカメラ維持 | holdOnDisable=trueに設定 |
| Messengerなしの状態オブジェクト | Cmd_LocationState_Markerをアタッチ |
| ロード後に表示したいUI(Messenger終了後) | LocationControllerのloadActivateObjectsに登録 |
| ロード後に表示したいUI(Marker状態) | Cmd_LocationState_MarkerのloadActivateObjectsに登録 |
| セーブデータ構造変更後 | 既存セーブデータを削除してから再テスト |
改善★0529 ロード後の再セーブ問題
■ 発生した不具合
あるLocationでセーブ→ロード後、そのまま再セーブ→再ロードするとloadActivateObjectsが発火しない。
Locationを移動してセーブした場合は問題なし。
■ 原因
ApplyState()で全MessengerがForceStop()・SetActive(false)される。
ロード後はアクティブなMessengerも終了済みMessengerも存在しない状態になるため、
再セーブ時にisFinished=false・activeMessengerPathが空で保存されてしまい、
次のロード時にloadActivateObjectsが発火しなかった。
同様の問題がCmd_LocationState_Markerのケースでも将来起きる可能性があった。
■ 修正内容
CaptureState()に2つの判定を追加。
1. loadActivateObjectsの再セーブ対応
Messengerが見つからない場合、loadActivateObjects内のいずれかがアクティブならisFinished=trueとみなす。
2. Cmd_LocationState_Markerの再セーブ対応
Markerがアクティブでない場合、Marker内のloadActivateObjectsがアクティブならactiveStatePathを保存する。
あわせてCmd_LocationState_MarkerにGetLoadActivateObjects()メソッドを追加。
■ 修正後のCaptureState()の判定優先順位
- VCams配下のアクティブVCamを保存
- アクティブなMarkerがあれば activeStatePath に保存
- Markerが非アクティブでも loadActivateObjects がアクティブなら activeStatePath に保存
- アクティブなMessengerがあれば会話途中として保存
- 終了済みMessengerがあれば isFinished=true として保存
- どちらもなく loadActivateObjects がアクティブなら isFinished=true とみなす
■ 変更ファイル
・Cmd_LocationState_Marker.cs:GetLoadActivateObjects()メソッド追加
・Sys_LocationController.cs:CaptureState()に判定ロジック2件追加

コメント