Cut進行用セーブロードの構築2【Unity6】

絶対調味ロジック

セーブロード構築の第二段階、専用UIを組み込んでいきます。

今回の作業目標

  • オートセーブをslot0固定にする設計
    slot0 = オートセーブ専用 という意図だと思いますが、
    問題: SaveSlot UIで「slot0」がオートセーブと手動セーブで衝突しないか?
    確認: slot0はオートセーブ専用として手動セーブから除外しますか?それとも手動でも使えますか?
  • 1ページ3スロットのページ方式
    3スロットしかないなら「ページ方式」は不要なUI複雑化
    9〜12スロットなら妥当
    30スロットなら別のスクロール方式の方がUXが良い場合も
    大胆な代替案
    ページ方式よりもスクロール方式+オートセーブ固定表示(最上部)の方がシンプルで操作しやすいケースが多い
  • ローディング画面のタイミング
    ロード時の「一瞬映り込み」をローディング画面で解決予定
    原因によって解決策が変わります。
    ローディング画面は「隠す」対処療法なので、根本原因を把握してから実装順序を決める方が良い。

ロード時の「一瞬映り込み」を解決

原因と対策

症状:「どのCutでセーブしても、ロード時にCut001が一瞬映り込む」状態。

原因:スタートCutだったため、常にアクティブにしている。

解決策エディタ上でCut_001〜全CutをInactiveにして保存するだけ。(コード不要)
StartCut() が呼ばれたとき正しいCutだけActiveになるので、映り込みはなくなります。

症状:しかし「初回ロード時のみ読み込みが一瞬あって、ちらつく」状態は起こる。

原因:テクスチャ・スプライトは初回描画時にGPUへ転送される。
これはUnityの遅延ロード(Lazy Loading)の仕様です。セーブシステムの問題ではありません。

解決策A. 暗幕(対処療法)映り込みを隠すだけ。根本は解決しない。
ロード開始 → 黒フェード → シーン復元完了 → フェードイン
解決策B. テクスチャプリロード(根本解決)
シーン開始時に全テクスチャを一度描画してキャッシュ → ロード時は既にキャッシュ済み → 遅延なし
具体的にはResources.Loadや画面外での事前レンダリング。

🔍 「暗幕の表示時間問題」の本質
あなたが抱えている問題は、映り込みが消えるまで待つ必要があるが
その時間が環境によって変わる → 固定時間では対応できない
これは時間で解決するのではなく、完了を検知して解除するべき問題です。

A. 固定フレーム待機
yield return new WaitForSeconds(0.5f); // 暗幕中に数フレーム待つ。
シンプル。でも低スペックでは不十分な可能性あり。

B. ダミーレンダリング後に解除
暗幕中に画面外でCutの素材を一度レンダリング → キャッシュ済みになってから暗幕OFF
根本解決に近いが実装複雑。

C. ローディングアニメーション+最低表示時間
最低0.5秒は表示(短すぎるチラつき防止)+ セットアップ完了を待つ → どちらか長い方が終わったら解除
現実的なバランス案。

正直な推奨Cが最もコスパが良いです。
ローディング画面はUX的にも「処理中である」ことをユーザーに伝えられるので、待ち時間への不満が減ります。
ただし本当にそれでいいですか?
今の映り込みは「初回だけ」「一瞬」という話でした。
そこにローディング画面という、そこそこ重い実装を入れるのは過剰対応になる可能性があります。

結論まずは暗幕隠しで様子見。表示時間やフェード時間を調整できるように。

ロード専用暗幕

Sceneで共有するために、暗幕のCanvasDontDestroyOnLoadにします。

設計案
Sys_FadeManager.cs
 ├ FadeOut()    // パッと暗幕ON
 ├ FadeIn(duration) // DOTweenでフェードアウト(暗幕消える)
 └ displayTime   // 暗幕の最低表示時間(Inspectorで調整可能)

ロード時の流れ:
SaveManager.LoadGame()
 → FadeManager.FadeOut()  // パッと暗幕ON
 → yield return new WaitForSeconds(displayTime)
 → シーンロード・復元処理
 → FadeManager.FadeIn()   // DOTweenでじわっと消える

スクリプト役割
Sys_FadeManager.cs(新規):暗幕ImageのON/OFF・フェード処理の実体
Sys_SaveManager.cs:ロード処理の司令塔。暗幕が必要な時にFadeManagerを呼ぶ

DontDestroyOnLoad暗幕の作り方

  • 新規空オブジェクトを作成
    名前:Black_Canvas
  • Black_CanvasにCanvasコンポーネントを追加
    RenderMode → Screen Space – Overlay
    SortingOrder → 999(他のUIより最前面)
  • Black_Canvasの子に黒いImageを作成
    名前:Black_Screen
    Color → 黒・Alpha255
    RectTransform → Stretch(全画面)
    初期状態 → アクティブのまま・Alpha0にしておく
  • Black_CanvasをHierarchyのルートに置く
    SaveManagerと同じ扱いであり、子にしてしまうとエラーが起きる。

ImageをStretch(全画面)にする方法

ゲーム画面サイズに自動で伸びます。
Left「0」Top「0」Right「0」Bottom「0」になっていれば成功です。

暗幕以外(任意のテキストや画像)もいっしょに表示させる設定

Black_Canvas ← ルートに置く
├ Black_Screen ← 暗幕Image
(将来追加するテキストやスプライトはここに)

コンポーネント一覧Black_Canvas
Canvas
│ ├ Render Mode → Screen Space – Overlay
│ └ Sort Order → 999
├ CanvasScaler(自動追加されているはず)
├ GraphicRaycaster(自動追加されているはず)
CanvasGroup ← 手動で追加
│ ├ Alpha → 0
│ ├ Interactable → チェックOFF
│ └ Blocks Raycasts → チェックOFF
Sys_FadeManager.cs ← アタッチ
Canvas Group → Black_Canvas自身のCanvasGroupをアサイン
├ Display Time → 0.5
├ Fade In Duration → 0.5
└ Fade In Ease → InOutQuad

Black_Canvas → アクティブのまま
Black_Screen → アクティブのまま

Black_Canvas
├ Black_Screen
LoadingText ← 追加するだけでOK
アクティブ状態で置いておく

暗幕まとめ

・ローディング画面より先に根本原因を探った
・暗幕をCanvasGroup一本に統一してシンプルにした
・将来の拡張を考えた構造にした

暗幕システム(Sys_FadeManager.cs)完成
  ├ CanvasGroup一括制御
  ├ FadeOut():パッとON
  ├ FadeIn():DOTweenでじわっとOFF
  ├ Ease・時間はInspectorで調整可能
  └ 子オブジェクトを追加するだけで拡張可能

ロード時チラつき問題解決
  ├ Cut_001をエディタ上でInactive化
  └ ロード時に暗幕で保護

セーブロード画面のUI設計

スロットの目標挙動と構造

【セーブ画面】
空スロット   → クリックでセーブ
既存スロット  → クリックで上書き確認ダイアログ → はいで上書き
Deleteボタン  → クリックで削除確認ダイアログ → はいで削除

【ロード画面】
空スロット   → クリックで反応なし
既存スロット  → クリックでロード開始
Deleteボタン  → クリックで削除確認ダイアログ → はいで削除

【ダイアログ】

A. 見た目は共通・メッセージだけ変える
 「上書きしますか?」
 「削除しますか?」
 → 1つのダイアログのテキストを差し替えて使い回す

B. ダイアログ自体を複数用意・見た目も別々
 → 上書き用・削除用・その他用を個別に作る
 → 将来デザインを個別に変えられる

A. SaveLoad_Canvasの子に置く
 → セーブロード画面を開いている時だけ使える
 → シンプル

B. 独立したCanvasとして別に置く
 → どこからでも呼び出せる
 → 将来他の場面でも使い回せる

全体UI構造

SaveLoad_Canvas ← DontDestroyOnLoad
├ Header
│ └ TitleText(「セーブ」or「ロード」で切り替え)
├ SlotArea
│ ├ Slot_01
│ │ ├ ScreenshotImage
│ │ ├ SaveTimeText
│ │ └ DeleteButton
│ ├ Slot_02
│ └ Slot_03
├ PageArea(将来用・今は非表示)
│ ├ PrevButton
│ └ NextButton
└ CloseButton

Dialog_Canvas ← DontDestroyOnLoad・独立
├ MessageText
├ YesButton
└ NoButton

作成スクリプト

Sys_DialogManager.cs:ダイアログ表示用
UI_SaveLoadPanel.cs:セーブロード画面表示用
UI_SaveSlot.cs:スロット用

ダイアログ作成

Dialog_Canvas のHierarchy構造

Dialog_Canvas
└ Panel
 ├ MessageText
 ├ YesButton
 │ └ Text
 └ NoButton
 └ Text

コンポーネント設定

Canvas
├ Render Mode → Screen Space – Overlay
└ Sort Order → 200(SaveLoad_Canvasより前面)

Sys_DialogManager.cs をアタッチ
├ Dialog Panel → Panelをアサイン
├ Message Text → MessageTextをアサイン
├ Yes Button → YesButtonをアサイン
└ No Button → NoButtonをアサイン

Dialog_Canvas → アクティブ
Panel → 非アクティブ

CanvasはアクティブのままでPanelだけ非アクティブにするのがポイントです。
スクリプトがAwakeで動作するために必要です。

とりあえず提案してくれる感じ好き(*´ω`*)

UIオブジェクト作成

SaveLoad_Canvas のHierarchy構造

SaveLoad_Canvas
└ Panel
├ TitleText
├ SlotArea
│ ├ Slot_01
│ │ ├ ScreenshotImage
│ │ ├ SaveTimeText
│ │ ├ PreviewText
│ │ ├ EmptyText
│ │ └ DeleteButton
│ │ └ Text
│ ├ Slot_02(同上)
│ └ Slot_03(同上)
└ CloseButton
└ Text

コンポーネント設定

Canvas
├ Render Mode → Screen Space – Overlay
└ Sort Order → 100

UI_SaveLoadPanel.cs をアタッチ
├ Panel → Panelをアサイン
├ Title Text → TitleTextをアサイン
├ Slots → Size 3
│ ├ Element0 → Slot_01をアサイン
│ ├ Element1 → Slot_02をアサイン
│ └ Element2 → Slot_03をアサイン
└ Close Button → CloseButtonをアサイン

Slot_01〜03それぞれに

UI_SaveSlot.cs をアタッチ
├ Screenshot Image → ScreenshotImageをアサイン
├ Save Time Text → SaveTimeTextをアサイン
├ Preview Text → PreviewTextをアサイン
├ Empty Text → EmptyTextをアサイン
├ Slot Button → Slot自身のButtonをアサイン
└ Delete Button → DeleteButtonをアサイン

Slotプレハブ化の手順

① Slot_01の設定が完成した状態で
② HierarchyのSlot_01をProjectウィンドウのPrefabsフォルダにドラッグ
③ 「Slot」という名前でプレハブとして保存
④ Slot_02・Slot_03を削除
⑤ プレハブをHierarchyにドラッグしてSlot_02・Slot_03として配置

ページ追加時
→ プレハブを複製して並べるだけ
→ スクリプトでSlotを動的生成することも可能

SaveLoad_Canvas → アクティブ
Panel → 非アクティブ

CanvasはアクティブのままでPanelだけ非アクティブにするのがポイントです。
スクリプトがAwakeで動作するために必要です。

挙動テスト

UI_SaveLoadButtonsのセーブボタンを押す
  → SaveLoad_Canvasが開く
  → タイトルが「セーブ」になっている
  → 空スロットに「Empty」が表示されている

空スロットをクリック
  → セーブされてパネルが閉じる

再度セーブ画面を開く
  → スクリーンショット・日時・セリフが表示されている

既存スロットをクリック
  → 「上書きしますか?」ダイアログが出る
  → はい → 上書きされてパネルが閉じる
  → いいえ → ダイアログが閉じるだけ

Deleteボタンをクリック
  → 「削除しますか?」ダイアログが出る
  → はい → スロットがEmptyに戻る

ロードボタンを押す
  → タイトルが「ロード」になっている
  → 既存スロットをクリックでロード開始
  → 暗幕が出てロードされる

表示・挙動の調整

操作調整

・セーブ画面表示中、「閉じる」ボタン以外(右クリック)でも閉じるように。
・セーブスロットを押したら即閉じではなく、画面は開いたままに
・画面表示中に一回何かの操作をしたら、保存はされるが、スロットに情報が表示されなくなる挙動の修正

記録年月日の「時間」を削除

Sys_SaveManager.cs 内記述
// 変更前
data.saveTime = DateTime.Now.ToString(“yyyy/MM/dd HH:mm“);
// 変更後
data.saveTime = DateTime.Now.ToString(“yyyy/MM/dd“);

個人的には情報過多表示は好み。
しかし、秒単位まで、いつプレイしたかバレる情報は、無いほうが世の中のためになると判断(^ω^)

「章」のテキスト保存追加

Hierarchyに追加するもの

System_set(もしくはルート)
ChapterManager ← 新規作成
 └ Sys_ChapterManager.cs をアタッチ
 ├ Chapter Text → 「第1章」
 └ Part Text → 「プロローグ」

Slot_00プレハブに追加するもの

Slot_00
ChapterText ← TextMeshProUGUIで新規作成
PartText  ← TextMeshProUGUIで新規作成

Chapter Text → ChapterTextをアサイン
Part Text → PartTextをアサイン

二段になる長いセリフを省略化する

TextMeshProの設定のみで対応可能。

オートセーブの実装

オートセーブの負荷の本質

JSON書き込み → ほぼ0負荷・一瞬で終わる
SceneManager → オートセーブでは不要
スクリーンショット → 画面全体をキャプチャするため負荷あり

確認

① オートセーブにスクリーンショットは必要かNo
② オートセーブのタイミングはCut単位でよいか。 (NextCut()が呼ばれるたびに自動保存)Yes
③ オートセーブはslot0固定で確定か。Yes

Hierarchyに追加するもの

SaveLoad_Canvas
└ Panel
└ SlotArea
Slot_Auto ← 新規作成(プレハブ化不要)
│ ├ SaveTimeText
│ ├ PreviewText
│ ├ ChapterText
│ └ PartText
│ ※ DeleteButtonは作らない
├ Slot_01(プレハブ)
└ Slot_02(プレハブ)

Inspectorでの設定

UI_SaveLoadPanel.cs
 ├ Auto Slot → Slot_Autoをアサイン
 ├ Slots → Size 2
 │ ├ Element0 → Slot_01をアサイン
 │ └ Element1 → Slot_02をアサイン
 └ Close Button → CloseButtonをアサイン

セーブ画面の方で、オートセーブ押せちゃう問題

解決セーブ画面時にボタンを非活性化する。(slotButton.interactable = false)
 → ボタンがグレーアウトして押せなくなる。
 → 見た目でも「押せない」とわかる。

セーブロードの実機挙動

想定していたセーブシステムの挙動がうまく実装されたと思います。
セーブのためでもあったCut進行構築をより理解する機会にもなりました。

1Cutに対して、そのCutでの表示情報をすべて(リアルタイムで過ぎ去った情報も改めて)与えておかないといけません。
被る情報(立ち絵の位置や消したオブジェクト)も記しておく必要があるので、構築の手間になる部分です。
(そもそもそういうことを理解していないでCut方式にしていた節もあります)
ここらへんの整理も課題として出てきたので、将来の自分に丸投げしておきましょう。

とにかく、一番の懸念点であった「セーブ・ロード」が実装できたのは大きな進捗です。
改善点や問題点は、若干まだありますが、素直に喜んでおくとします(*´ω`*)

保存データが決まり、テキスト設定音量設定の保存に対しての答えを示せるので、
次はコンフィグ周りを作っていけそうです。

システム第二段階まとめ

暗幕システム(Sys_FadeManager.cs)
  ├ CanvasGroup一括制御
  ├ FadeOut():パッとON
  ├ FadeIn():DOTweenでじわっとOFF
  └ Ease・時間はInspectorで調整可能

SaveSlot UI
  ├ Slot_Auto(slot0・オートセーブ専用)
  │ ├ セーブ画面では非活性化
  │ ├ スクリーンショットなし
  │ └ Deleteボタンなし
  ├ Slot_01・Slot_02(手動セーブ用プレハブ)
  │ ├ スクリーンショット表示
  │ ├ 日付表示(時分なし)
  │ ├ セリフプレビュー(…省略あり)
  │ ├ 章・パート表示
  │ ├ 上書き確認ダイアログ
  │ └ 削除確認ダイアログ
  └ 右クリックで閉じる

共通ダイアログ(Sys_DialogManager.cs)
  └ Show(message, onYes)で呼び出し

章テキスト・パートテキスト管理(Sys_ChapterManager.cs)
  └ Inspectorで任意テキストを設定

オートセーブ
  ├ slot0固定
  ├ スクリーンショットなし
  └ Cut冒頭・最初のセリフ表示時に実行

コメント