Merge branch 'develop' into 记忆检查shader优化
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
---
|
||||
name: "source-command-split-commit"
|
||||
description: "将当前未提交改动拆分为多个符合规范的原子提交"
|
||||
---
|
||||
|
||||
# source-command-split-commit
|
||||
|
||||
Use this skill when the user asks to run the migrated source command `split-commit`.
|
||||
|
||||
## Command Template
|
||||
|
||||
将当前未提交改动拆分为多个原子提交。执行前会分析变更、生成分组方案,经用户确认后逐组提交。
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Step 1: 前置检查
|
||||
|
||||
1. **清理残留文件**:若存在 `msg.txt`,先 `rm -f msg.txt`
|
||||
2. **读取规范**:确认 `COMMIT_CONVENTION.md` 存在且可读
|
||||
|
||||
### Step 2: 保护分支检查
|
||||
|
||||
保护分支列表(按优先级):`AGENTS.md` > `.cursor/rules/` > `.git/config branch.protected` > 默认(`master|main|develop`)
|
||||
|
||||
若当前在保护分支,询问用户:
|
||||
- **A** - 创建 feature 分支后继续拆分(推荐)
|
||||
- **B** - 转移到新分支后压成单提交
|
||||
- **C** - 取消
|
||||
|
||||
### Step 3: 盘点变更
|
||||
|
||||
执行以下命令分析:
|
||||
- `git status` - 整体状态
|
||||
- `git diff --stat` - 文件统计
|
||||
- `git diff` - 详细改动
|
||||
- `git diff --cached` - 已暂存改动
|
||||
|
||||
### Step 4: 规则化分组
|
||||
|
||||
按"意图一致"原则分组,**优先按 type 分,再按 scope 分**。
|
||||
|
||||
**必须拆分的情况**:
|
||||
- 同一组可归属多个 type
|
||||
- 同文件包含多个独立目的(如功能+重构)
|
||||
- 资源替换与代码逻辑混合
|
||||
|
||||
**可以合并的情况**(最小拆分原则):
|
||||
- 同一功能的多文件改动(脚本+Prefab)
|
||||
- 同一模块的多个相关 Bug 修复
|
||||
|
||||
### Step 5: 生成方案表格
|
||||
|
||||
以表格形式展示拆分方案:
|
||||
|
||||
| 顺序 | 文件 | 变更目的 | 提交头 | 依据 | 方式 |
|
||||
|------|------|----------|--------|------|------|
|
||||
| 1 | `路径` | 描述 | `type(scope): subject` | 原因 | `git add`/`git add -p` |
|
||||
|
||||
**Type 选择优先级**:`fix` > `feat` > `art` > `audio` > `scene` > `yarn` > `perf|refactor|...`
|
||||
|
||||
**Scope 规则**:单模块用模块名(如 `blockpuzzle`),跨模块省略。
|
||||
|
||||
### Step 6: 用户确认
|
||||
|
||||
询问:`[Y 执行 / n 取消 / 提出调整]`
|
||||
|
||||
- 若调整:根据反馈修改方案,再次确认
|
||||
- 若执行:进入 Step 7
|
||||
- 若取消:清理并退出
|
||||
|
||||
### Step 7: 逐组提交
|
||||
|
||||
对每组执行:
|
||||
1. `git add <file>` 或 `git add -p <file>`
|
||||
2. 复查:`git diff --cached --stat` 和 `git diff --cached`
|
||||
3. 写入 `msg.txt` 并提交:`git commit -F msg.txt`
|
||||
4. 检查状态:`git status`
|
||||
|
||||
**失败处理**:
|
||||
- `git add -p` 跳过关键 hunk → 暂停,提示处理方案
|
||||
- pre-commit hook 失败 → 询问 [R]重试 [S]跳过 [A]中止
|
||||
|
||||
### Step 8: 结果校验
|
||||
|
||||
输出:
|
||||
1. `git log --oneline -<N>`
|
||||
2. 回放清单(验证 type/scope/subject 合规性)
|
||||
3. 若不合规,询问是否修正
|
||||
|
||||
### Step 9: 清理
|
||||
|
||||
`rm -f msg.txt`
|
||||
|
||||
---
|
||||
|
||||
## Unity 改动速查表
|
||||
|
||||
| 改动类型 | type | scope 示例 |
|
||||
|----------|------|-----------|
|
||||
| 脚本逻辑修复 | `fix` | `blockpuzzle`, `dialog` |
|
||||
| 脚本新功能 | `feat` | `huoshan`, `clue` |
|
||||
| 美术资源(贴图/模型/动画) | `art` | 对应模块或省略 |
|
||||
| 音频资源 | `audio` | `audio-kit` 或省略 |
|
||||
| 场景配置(灯光/镜头) | `scene` | 对应模块或省略 |
|
||||
| Yarn 对话 | `yarn` | `dialog` |
|
||||
| 测试代码 | `test` | 对应模块 |
|
||||
|
||||
---
|
||||
|
||||
## 规范校验清单(执行前检查)
|
||||
|
||||
- [ ] 格式:`<type>(<scope>): <subject>` 或 `<type>: <subject>`
|
||||
- [ ] type ∈ {`feat`,`fix`,`art`,`audio`,`scene`,`yarn`,`perf`,`refactor`,`style`,`docs`,`build`,`chore`,`test`}
|
||||
- [ ] subject:中文、动词开头、≤50字、无句号
|
||||
- [ ] 一次提交只做一件事
|
||||
|
||||
---
|
||||
|
||||
## 中文提交方法
|
||||
|
||||
由于 PowerShell 中文乱码问题,统一使用文件方式:
|
||||
|
||||
```bash
|
||||
echo "type(scope): 描述" > msg.txt
|
||||
git commit -F msg.txt
|
||||
rm -f msg.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关于 Pre-commit Hooks
|
||||
|
||||
若配置了 husky/lint-staged:
|
||||
- 每次提交都会触发检查
|
||||
- 如需跳过(**不推荐**):`git commit -F msg.txt --no-verify`
|
||||
- 跳过可能导致 CI 失败
|
||||
|
||||
---
|
||||
|
||||
开始执行请回复 **"开始"**,或告知需要的调整。
|
||||
Vendored
+62
-1
@@ -57,5 +57,66 @@
|
||||
"Temp/": true
|
||||
},
|
||||
"dotnet.defaultSolution": "aibis-dream.sln",
|
||||
"dotnet.preferCSharpExtension": true
|
||||
"dotnet.preferCSharpExtension": true,
|
||||
"cSpell.enableFiletypes": [
|
||||
"yarnspinner"
|
||||
],
|
||||
"cSpell.languageSettings": [
|
||||
{
|
||||
"languageId": "yarnspinner",
|
||||
"patterns": [
|
||||
{
|
||||
"name": "yarn-commands",
|
||||
"pattern": "/<<[^>]*>>/g"
|
||||
},
|
||||
{
|
||||
"name": "yarn-expressions",
|
||||
"pattern": "/\\{[^}]*\\}/g"
|
||||
},
|
||||
{
|
||||
"name": "yarn-variables",
|
||||
"pattern": "/\\$[a-zA-Z_][a-zA-Z0-9_]*/g"
|
||||
},
|
||||
{
|
||||
"name": "yarn-markup",
|
||||
"pattern": "/\\[[^\\]]*\\]/g"
|
||||
},
|
||||
{
|
||||
"name": "yarn-hashtags",
|
||||
"pattern": "/#[a-zA-Z_][a-zA-Z0-9_:-]*/g"
|
||||
},
|
||||
{
|
||||
"name": "yarn-node-markers",
|
||||
"pattern": "/^(---|===)$/gm"
|
||||
},
|
||||
{
|
||||
"name": "yarn-headers",
|
||||
"pattern": "/^(title|tags|position|color|colorID|tracking|custom|style):.*/gm"
|
||||
},
|
||||
{
|
||||
"name": "yarn-character-names",
|
||||
"pattern": "/^\\s*[A-Za-z_][A-Za-z0-9_]*(?=:)/gm"
|
||||
},
|
||||
{
|
||||
"name": "yarn-comments",
|
||||
"pattern": "/\\/\\/.*$/gm"
|
||||
}
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"yarn-commands",
|
||||
"yarn-expressions",
|
||||
"yarn-variables",
|
||||
"yarn-markup",
|
||||
"yarn-hashtags",
|
||||
"yarn-node-markers",
|
||||
"yarn-headers",
|
||||
"yarn-character-names",
|
||||
"yarn-comments"
|
||||
]
|
||||
}
|
||||
],
|
||||
"cSpell.words": [
|
||||
"Aibis",
|
||||
"Newtonsoft"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
AIBIS Dream (also known as "AllOurBrokenParts") is a Unity-based narrative adventure game with investigative elements. The project uses Yarn Spinner for dialogue, features a custom framework with modular systems, and includes multiple mini-games and a modular repair/fix system.
|
||||
|
||||
**Unity Version:** 2022.3.7f1c1 (Universal Render Pipeline)
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Framework (Assets/Scripts/Framework/)
|
||||
|
||||
Modular framework with Kit-pattern utilities:
|
||||
|
||||
- **ActionKit**: Action sequencing and command pattern
|
||||
- **AudioKit**: Audio management (includes FMOD integration)
|
||||
- **Config / Core**: Configuration and core utilities
|
||||
- **EventSystemKit**: Decoupled event system
|
||||
- **LogKit**: Structured logging with categories
|
||||
- **PoolKit**: Object pooling
|
||||
- **ResourceKit**: Asset loading via `ResourceSystem` (Addressables-based)
|
||||
- **Note**: New code must use `ResourceSystem`, never `Resources.Load`. See [Resource Loading Best Practices](Docs/ResourceLoadingBestPractices.md). Asset placement rules in [Resource Management Spec](Docs/ResourceManagementSpec.md).
|
||||
- **SelectorKit**: Weighted/batch/non-repeat random selection
|
||||
- **SingletonKit**: Singleton base classes
|
||||
- **StateMachineKit**: State management
|
||||
- **OtherKit**: Miscellaneous utilities (`CameraKit`, `AnimatorKit`, `FadeKit`, `LocalizationKit`, etc.)
|
||||
- **Attribute**: Custom editor attributes
|
||||
|
||||
### Key Game Systems
|
||||
|
||||
- **Game Loop** (Scripts/Game Loop/): `GameManager` (Singleton entry), `YarnVariableStorage` (Yarn variables), `SaveRestoreOrchestrator` / `SaveSystem` (save snapshots), `SceneLoader`, `ChapterController`, `TalkSceneSO`
|
||||
- **Dialog System** (Scripts/Dialog System/): Yarn Spinner integration — `DialogController`, `LineRunner`, `LocalisedLineProvider`, `BaseYarnCommand`
|
||||
- **SceneManagement** (Scripts/SceneManagement/): `ActorKit/`, `AnimatorKit/`, `EnvironmentKit/`, `SpawnerKit/`, `TimelineKit/`, `SceneCenter/`, `DreamDoorSystem`
|
||||
- **UI** (Scripts/UI/): `DialogUI/`, `Panel/`, `Form/`, `Components/`, `Cursor/`, `UIManager`
|
||||
- **MiniGame** (Scripts/MiniGame/): `HuoShan/` (language particles, EmotionWave), `BlockPuzzle/`, `Peipei/` (Eye, CutLine, Memory)
|
||||
- **FixSystem** (Scripts/FixSystem/): Legacy — HeatMap, PunchTape, Memory, screen effects
|
||||
- **FixSystemNew** (Scripts/FixSystemNew/): Active repair system — `FixSystemCenter`, `BodyModule/`, `Cable/`, `WhackMole/`, `Screen System/`, `Open/`, `Clinic/`
|
||||
- **AssetRefs** (Scripts/AssetRefs/): Addressable key constants (`ConstRef`, `FixAssetRef`, `TimelineAssetRef`)
|
||||
|
||||
### Content & Localization
|
||||
|
||||
- **Assets/GameContent/**: Runtime assets by scope (`Common/`, `UI/`, `Scene_{Name}/`, `Feature_{Name}/`)
|
||||
- **Assets/Language/**: Unity Localization tables (runtime active: `UIText/`, `Locales/`, `Sprite/`; `Dialog/`、`ActorName/`、`Params/` currently kept as migration backups)
|
||||
- **Assets/Resources/Yarn/**: Current Yarn dialogue files (target migration to `Assets/Narrative/` per spec, not yet complete)
|
||||
- **Assets/StreamingAssets/Config/**: CSV-driven localized gameplay data (`character.csv` for actor names, `params.csv` for `l10n.*` substitution terms)
|
||||
- **Assets/Prototype/**: HTML/JS prototypes for mechanic testing
|
||||
|
||||
## Critical Dependencies
|
||||
|
||||
### Unity Packages
|
||||
- **Yarn Spinner** 3.2.2 (OpenUPM), **Addressables** 1.21.21, **Cinemachine** 2.9.7
|
||||
- **URP** 14.0.8, **TextMesh Pro** 3.0.6, **Timeline** 1.7.5, **Localization** 1.4.5
|
||||
- **Visual Effect Graph** 14.0.8, **Visual Scripting** 1.8.0
|
||||
|
||||
### Third-Party
|
||||
- **FMOD** (audio engine), **DOTween** (tweening), **Shapes** (vector graphics)
|
||||
- **More Mountains Feedbacks** (game feel), **Destructible 2D**
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Code Organization
|
||||
1. Each major system self-contained in its own folder under `Assets/Scripts/`
|
||||
2. Kit-pattern utilities in `Framework/` — reusable across systems
|
||||
3. Singleton pattern for global managers (derived from `SingletonKit`)
|
||||
4. Favor composition over inheritance
|
||||
5. All runtime asset loading via `ResourceSystem` — never `Resources.Load`
|
||||
|
||||
### Scene Structure
|
||||
- Scenes in `Assets/Scenes/`; `Persistence` scene hosts global managers and Persistent Loader
|
||||
- Fix scenes: `{Character}FixScene` (e.g. `HuoShanFixScene`, `PeipeiFixScene`, `ExpressFixScene`)
|
||||
- Test scenes: `languageTest`, `HuoShanExpressionTest`, `wavetest`, etc.
|
||||
|
||||
### Addressables
|
||||
- Config in `Assets/AddressableAssetsData/`; Groups: `Core`, `Scene_*`, `Feature_*`, `Shared_*`, `Localization-*`
|
||||
- Key format: `Category/Path` (e.g. `Animation/Peipei`, `Timeline/BlockPuzzle/PanelOpen`)
|
||||
- Scene keys use bare PascalCase names (e.g. `ClinicOut`)
|
||||
- Details in [Resource Loading Best Practices](Docs/ResourceLoadingBestPractices.md)
|
||||
|
||||
### Yarn Dialogue Files
|
||||
- Current path: `Assets/Resources/Yarn/` (migration to `Assets/Narrative/Yarn/` planned)
|
||||
- Pattern: `{Character}_{Stage}.yarn`; UTF-8 encoding required for Chinese
|
||||
|
||||
#### Yarn Line Tag Maintenance
|
||||
- **Line tags** (`#line:xxxxxxx`) must be unique across the entire file. Duplicate tags break Yarn Spinner localization.
|
||||
- Duplicates typically occur when copying an existing node to create a variant (e.g. `地铁ver1` → `地铁ver3`).
|
||||
- **Use the dedup tool** (`Tools/dedup_yarn_tags.py`) for bulk cleanup:
|
||||
```bash
|
||||
# Preview duplicates from a specific node onward
|
||||
python Tools/dedup_yarn_tags.py Assets/Resources/Yarn/Fiction_Day1_begin/Fiction_Day1_begin.yarn --from-node 地铁ver4
|
||||
# Fix duplicates from a specific line onward
|
||||
python Tools/dedup_yarn_tags.py Assets/Resources/Yarn/Fiction_Day1_begin/Fiction_Day1_begin.yarn --from-line 535 --fix
|
||||
# Scan an entire directory (preview only)
|
||||
python Tools/dedup_yarn_tags.py Assets/Resources/Yarn/ --scan-all
|
||||
```
|
||||
|
||||
#### Yarn Script Generation
|
||||
- **Do NOT add `#line:` tags when generating or editing Yarn dialogue.** Leave all line-tag generation to the user's localization workflow (Unity Localization + Yarn Spinner import tools).
|
||||
- When creating new `.yarn` files, write plain dialogue lines without any `#line:` suffixes.
|
||||
|
||||
### Asset Naming
|
||||
- **Scripts**: PascalCase (e.g. `DialogController.cs`)
|
||||
- **Addressable Keys**: `Category/Path` — no spaces, no extensions, ≤3 levels
|
||||
- **Addressable Groups**: `{Type}_{PascalCaseName}`
|
||||
|
||||
### Git & Commits
|
||||
- LFS for large binaries; ignored: Library/, Temp/, Build/, *.csproj, *.sln
|
||||
- **Commit format**: `<type>(<scope>): <subject>` — subject in Chinese, type/scope in English
|
||||
- Extended types: `feat`, `fix`, `art`, `audio`, `scene`, `yarn`, `perf`, `refactor`, `style`, `docs`, `build`, `chore`, `test`
|
||||
- See [COMMIT_CONVENTION.md](COMMIT_CONVENTION.md) for full details
|
||||
|
||||
## Key Build Commands
|
||||
|
||||
```bash
|
||||
# Windows build
|
||||
"C:\Program Files\Unity\Hub\Editor\2022.3.7f1c1\Editor\Unity.exe" -batchmode -quit -projectPath . -buildWindows64Player "Build/AIBIS_Dream.exe" -logFile Build/log.txt
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a New Mini-Game
|
||||
1. Create `Assets/Scripts/MiniGame/{GameName}/`
|
||||
2. Add assets to `Assets/GameContent/Feature_{GameName}/`
|
||||
3. Create Addressable Group `Feature_{GameName}`
|
||||
4. Create test scene in `Assets/Scenes/`
|
||||
|
||||
### Adding Yarn Dialogue
|
||||
1. Create `.yarn` in `Assets/Resources/Yarn/{Module}/`
|
||||
2. Ensure YarnProject localization assets/CSV are updated (Unity `Assets/Language/Dialog/` is backup-only during migration)
|
||||
3. Reference in scene's `DialogueRunner`; test with `DialogController`
|
||||
|
||||
## Debugging & Performance
|
||||
- `LogKit` for structured logging; Addressables Event Viewer for resource debugging
|
||||
- Monitor `SceneResourceLoader.TotalHandleCount` for leak detection
|
||||
- Always use PoolKit for frequent instantiation; `LoadUncachedAsync` + `EarlyRelease` for large Timeline assets
|
||||
- Use `LoadAsync<GameObject>` + `Instantiate` for prefabs, never `InstantiateAsync`
|
||||
|
||||
## Important Notes
|
||||
- Narrative-focused game — dialogue and story take precedence
|
||||
- `FixSystemNew` is the active repair architecture; `FixSystem` is legacy
|
||||
- `YarnVariableStorage` holds Yarn runtime variables; save/load flows through `SaveRestoreOrchestrator` and `SaveSystem`
|
||||
|
||||
## Reference Documents
|
||||
- [Resource Loading Best Practices](Docs/ResourceLoadingBestPractices.md) — `ResourceSystem` API, Key/Group/Label naming, lifecycle
|
||||
- [Resource Management Spec](Docs/ResourceManagementSpec.md) — Asset placement: `GameContent/`, `Narrative/`, `_Archive/`
|
||||
- [Commit Convention](COMMIT_CONVENTION.md) — Commit message format and branch naming
|
||||
- [HuoShan Docs](Docs/HuoShan/) — Narrative flow, resources, test flowcharts
|
||||
|
||||
---
|
||||
|
||||
*This document provides guidance for Codex when working with the AIBIS Dream codebase.*
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb2747e27b8dc1e4fac3df4d5edb520c
|
||||
guid: 8d4c5a2b82ac1ca459e228aaad3255c6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -0,0 +1,667 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using AibisDream;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.Utility;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.SaveSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// 存读档临时测试工具:触发存档、从槽位/测试归档读档、浏览 testsavs 历史。
|
||||
/// </summary>
|
||||
public class SaveSystemTestWindow : EditorWindow
|
||||
{
|
||||
private const string MenuPath = "AIBIS/存档测试工具";
|
||||
private const string WindowTitle = "SaveSystem Test";
|
||||
|
||||
private Vector2 _scrollPosition;
|
||||
private string _lastLog = string.Empty;
|
||||
private int _restoreSlotIndex;
|
||||
private int _manualSlotIndex = 1;
|
||||
private bool _testArchiveEnabled;
|
||||
private string _selectedArchiveEntryId;
|
||||
private Texture2D _selectedThumbnail;
|
||||
|
||||
private List<TestSaveArchiveEntry> _archiveEntries = new();
|
||||
private List<TestSaveArchiveEntry> _archiveEntriesSnapshot = new();
|
||||
private double _lastArchiveRefreshTime;
|
||||
private bool _repaintQueued;
|
||||
private List<string> _slotOverviewLabels = new();
|
||||
private List<string> _restoreLogSnapshot = new();
|
||||
private string _displaySelectedArchiveEntryId;
|
||||
private Texture2D _displayThumbnail;
|
||||
|
||||
private void RequestRepaint()
|
||||
{
|
||||
if (_repaintQueued)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_repaintQueued = true;
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
_repaintQueued = false;
|
||||
if (this != null)
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[MenuItem(MenuPath)]
|
||||
public static void Open()
|
||||
{
|
||||
var window = GetWindow<SaveSystemTestWindow>(WindowTitle);
|
||||
window.minSize = new Vector2(480, 560);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_testArchiveEnabled = EditorPrefs.GetBool(TestSaveArchive.EditorPrefsEnabledKey, true);
|
||||
RefreshArchiveEntries(requestRepaint: false);
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
ReleaseThumbnail();
|
||||
}
|
||||
|
||||
private void OnEditorUpdate()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (EditorApplication.timeSinceStartup - _lastArchiveRefreshTime < 1.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshArchiveEntries(requestRepaint: true);
|
||||
}
|
||||
|
||||
private void OnPlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
if (state == PlayModeStateChange.EnteredPlayMode || state == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
RefreshArchiveEntries();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
SnapshotDynamicGuiData();
|
||||
}
|
||||
|
||||
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
|
||||
|
||||
DrawHeader();
|
||||
GUILayout.Space(8);
|
||||
DrawTestArchiveSettings();
|
||||
GUILayout.Space(8);
|
||||
DrawSaveActions();
|
||||
GUILayout.Space(8);
|
||||
DrawSlotRestoreSection();
|
||||
GUILayout.Space(8);
|
||||
DrawTestArchiveSection();
|
||||
GUILayout.Space(8);
|
||||
DrawRestoreLogSection();
|
||||
GUILayout.Space(8);
|
||||
DrawUtilitySection();
|
||||
GUILayout.Space(8);
|
||||
DrawLogSection();
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 Layout/Repaint 之前快照可变列表,避免同一 GUI 帧内控件数量不一致。
|
||||
/// </summary>
|
||||
private void SnapshotDynamicGuiData()
|
||||
{
|
||||
_displaySelectedArchiveEntryId = _selectedArchiveEntryId;
|
||||
_displayThumbnail = _selectedThumbnail;
|
||||
|
||||
_archiveEntriesSnapshot.Clear();
|
||||
for (var i = 0; i < _archiveEntries.Count; i++)
|
||||
{
|
||||
_archiveEntriesSnapshot.Add(_archiveEntries[i]);
|
||||
}
|
||||
|
||||
_slotOverviewLabels.Clear();
|
||||
foreach (var vm in SlotManager.GetSlotViewModels())
|
||||
{
|
||||
if (vm.IsEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var meta = SlotManager.LoadMeta(vm.SlotIndex);
|
||||
_slotOverviewLabels.Add(
|
||||
$"slot_{vm.SlotIndex}" +
|
||||
(vm.IsAutoSlot ? " [自动]" : "") +
|
||||
$" | {vm.SavedAt} | {meta?.nodeName ?? "—"} | {vm.SceneName}");
|
||||
}
|
||||
|
||||
_restoreLogSnapshot.Clear();
|
||||
var restoreLog = SaveRestoreOrchestrator.LastRestoreLog;
|
||||
for (var i = 0; i < restoreLog.Count; i++)
|
||||
{
|
||||
_restoreLogSnapshot.Add(restoreLog[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHeader()
|
||||
{
|
||||
EditorGUILayout.LabelField("存读档测试工具", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"用于 Play Mode 下验证自动存档、槽位读档与 testsavs 测试归档。\n" +
|
||||
"正式存档目录: saves/ | 测试归档: testsavs/(每次自动存档独立子文件夹)",
|
||||
MessageType.Info);
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox("存档/读档操作需要在 Play Mode 下执行。", MessageType.Warning);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField(
|
||||
$"状态: IsRestoring={SaveRestoreOrchestrator.IsRestoring}, " +
|
||||
$"SuppressAutoSave={SaveRestoreOrchestrator.IsAutoSaveSuppressed}");
|
||||
}
|
||||
|
||||
private void DrawTestArchiveSettings()
|
||||
{
|
||||
EditorGUILayout.LabelField("测试存档模式", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
_testArchiveEnabled = EditorGUILayout.Toggle("启用测试存档模式", _testArchiveEnabled);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
EditorPrefs.SetBool(TestSaveArchive.EditorPrefsEnabledKey, _testArchiveEnabled);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField($"testsavs 路径: {ConstRef.TestAutoSaveArchivePath}", EditorStyles.miniLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"开启后:每次 AutoSave 仅在 testsavs 下新建一份独立存档,不写入、不覆盖 saves/slot_0。\n" +
|
||||
"存多少次就保留多少份;关闭后恢复正式行为(仅覆盖 slot_0)。",
|
||||
MessageType.None);
|
||||
}
|
||||
|
||||
private void DrawSaveActions()
|
||||
{
|
||||
EditorGUILayout.LabelField("存档", EditorStyles.boldLabel);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("触发自动存档 (完整流程)"))
|
||||
{
|
||||
TriggerAutoSave();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("仅 Capture (不写盘)"))
|
||||
{
|
||||
CaptureInMemory();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("手动档: 复制 slot_0 → 上方指定槽位"))
|
||||
{
|
||||
CopyAutoToManual();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSlotRestoreSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("从正式槽位读档", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField($"saves 路径: {ConstRef.SaveFilePath}", EditorStyles.miniLabel);
|
||||
|
||||
var latest = SlotManager.GetLatestSlotIndex();
|
||||
EditorGUILayout.LabelField($"最近槽位: {(latest.HasValue ? $"slot_{latest.Value}" : "无")}");
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("读档: Latest"))
|
||||
{
|
||||
RestoreLatestSlot();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("读档: slot_0"))
|
||||
{
|
||||
RestoreSlot(SlotIndex.Auto);
|
||||
}
|
||||
}
|
||||
|
||||
_restoreSlotIndex = EditorGUILayout.IntSlider("槽位", _restoreSlotIndex, 0, SlotIndex.ManualEnd);
|
||||
if (GUILayout.Button($"读档: slot_{_restoreSlotIndex}"))
|
||||
{
|
||||
RestoreSlot(_restoreSlotIndex);
|
||||
}
|
||||
|
||||
GUILayout.Space(4);
|
||||
_manualSlotIndex = EditorGUILayout.IntSlider(
|
||||
"复制到手动槽位",
|
||||
_manualSlotIndex,
|
||||
SlotIndex.ManualStart,
|
||||
SlotIndex.ManualEnd);
|
||||
|
||||
DrawSlotBriefOverview();
|
||||
}
|
||||
|
||||
private void DrawSlotBriefOverview()
|
||||
{
|
||||
foreach (var label in _slotOverviewLabels)
|
||||
{
|
||||
EditorGUILayout.LabelField(label, EditorStyles.miniLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTestArchiveSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("testsavs 测试归档", EditorStyles.boldLabel);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("刷新列表"))
|
||||
{
|
||||
RefreshArchiveEntries();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("清空 testsavs"))
|
||||
{
|
||||
ClearTestArchives();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("打开 testsavs 文件夹"))
|
||||
{
|
||||
OpenDirectory(ConstRef.TestAutoSaveArchivePath);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField($"共 {_archiveEntriesSnapshot.Count} 条", EditorStyles.miniLabel);
|
||||
|
||||
if (_archiveEntriesSnapshot.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"暂无测试归档。开启「testsavs 独立归档」后触发自动存档,或从正式 slot_0 手动归档。",
|
||||
MessageType.None);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
||||
{
|
||||
foreach (var entry in _archiveEntriesSnapshot)
|
||||
{
|
||||
DrawArchiveEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("将当前 slot_0 手动归档到 testsavs"))
|
||||
{
|
||||
ArchiveCurrentAutoSlot();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawArchiveEntry(TestSaveArchiveEntry entry)
|
||||
{
|
||||
var isSelected = _displaySelectedArchiveEntryId == entry.EntryId;
|
||||
var boxStyle = isSelected ? EditorStyles.helpBox : EditorStyles.textArea;
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(boxStyle))
|
||||
{
|
||||
EditorGUILayout.LabelField(entry.EntryId, EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField(
|
||||
$"时间: {entry.Meta?.savedAt ?? "—"} | 节点: {entry.Meta?.nodeName ?? "—"}",
|
||||
EditorStyles.miniLabel);
|
||||
EditorGUILayout.LabelField(
|
||||
$"场景: {entry.Meta?.sceneName ?? "—"} | SO: {entry.Meta?.sceneSoName ?? "—"}",
|
||||
EditorStyles.miniLabel);
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
var maxWidth = EditorGUIUtility.currentViewWidth - 48f;
|
||||
var height = 120f;
|
||||
if (_displayThumbnail != null)
|
||||
{
|
||||
height = Mathf.Min(120f, _displayThumbnail.height * maxWidth / _displayThumbnail.width);
|
||||
GUILayout.Label(_displayThumbnail, GUILayout.Width(maxWidth), GUILayout.Height(height));
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
"(无缩略图)",
|
||||
EditorStyles.miniLabel,
|
||||
GUILayout.Width(maxWidth),
|
||||
GUILayout.Height(height));
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("选中"))
|
||||
{
|
||||
SelectArchiveEntry(entry);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("读档"))
|
||||
{
|
||||
RestoreFromArchive(entry);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("删除"))
|
||||
{
|
||||
DeleteArchiveEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(4);
|
||||
}
|
||||
|
||||
private void DrawRestoreLogSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("读档 Pipeline 日志", EditorStyles.boldLabel);
|
||||
|
||||
if (_restoreLogSnapshot.Count == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("(暂无)", EditorStyles.miniLabel);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var line in _restoreLogSnapshot)
|
||||
{
|
||||
EditorGUILayout.LabelField(line, EditorStyles.wordWrappedMiniLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawUtilitySection()
|
||||
{
|
||||
EditorGUILayout.LabelField("工具", EditorStyles.boldLabel);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("打开 saves 文件夹"))
|
||||
{
|
||||
OpenDirectory(ConstRef.SaveFilePath);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("打开存档验证工具"))
|
||||
{
|
||||
SaveSystemValidationWindow.Open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLogSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("操作结果", EditorStyles.boldLabel);
|
||||
EditorGUILayout.SelectableLabel(_lastLog, EditorStyles.textArea, GUILayout.MinHeight(80));
|
||||
}
|
||||
|
||||
private void RefreshArchiveEntries(bool requestRepaint = true)
|
||||
{
|
||||
var newEntries = TestSaveArchive.ListEntries();
|
||||
var entriesChanged = !ArchiveEntriesEqual(newEntries, _archiveEntries);
|
||||
_archiveEntries = newEntries;
|
||||
_lastArchiveRefreshTime = EditorApplication.timeSinceStartup;
|
||||
|
||||
if (!string.IsNullOrEmpty(_selectedArchiveEntryId) &&
|
||||
!_archiveEntries.Exists(e => e.EntryId == _selectedArchiveEntryId))
|
||||
{
|
||||
_selectedArchiveEntryId = null;
|
||||
ReleaseThumbnail();
|
||||
entriesChanged = true;
|
||||
}
|
||||
|
||||
if (requestRepaint && entriesChanged)
|
||||
{
|
||||
RequestRepaint();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ArchiveEntriesEqual(List<TestSaveArchiveEntry> a, List<TestSaveArchiveEntry> b)
|
||||
{
|
||||
if (a.Count != b.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < a.Count; i++)
|
||||
{
|
||||
if (a[i].EntryId != b[i].EntryId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SelectArchiveEntry(TestSaveArchiveEntry entry)
|
||||
{
|
||||
_selectedArchiveEntryId = entry.EntryId;
|
||||
ReleaseThumbnail();
|
||||
|
||||
if (!string.IsNullOrEmpty(entry.ThumbnailPath) && File.Exists(entry.ThumbnailPath))
|
||||
{
|
||||
var bytes = File.ReadAllBytes(entry.ThumbnailPath);
|
||||
_selectedThumbnail = new Texture2D(2, 2);
|
||||
_selectedThumbnail.LoadImage(bytes);
|
||||
}
|
||||
|
||||
RequestRepaint();
|
||||
}
|
||||
|
||||
private void ReleaseThumbnail()
|
||||
{
|
||||
if (_selectedThumbnail != null)
|
||||
{
|
||||
DestroyImmediate(_selectedThumbnail);
|
||||
_selectedThumbnail = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerAutoSave()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Log("需要在 Play Mode 下执行。");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!SavePointEvaluator.CanAutoSave(out var reason))
|
||||
{
|
||||
Log($"CanAutoSave 拒绝: {reason}");
|
||||
return;
|
||||
}
|
||||
|
||||
var routine = SaveRestoreOrchestrator.AutoSaveRoutine();
|
||||
while (routine.MoveNext())
|
||||
{
|
||||
}
|
||||
|
||||
RefreshArchiveEntries();
|
||||
Log(TestSaveArchive.IsEnabled
|
||||
? "测试存档模式:已写入 testsavs(未覆盖 slot_0)。"
|
||||
: "正式模式:已写入 slot_0。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"自动存档失败: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CaptureInMemory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (YarnVariableStorage.Instance == null)
|
||||
{
|
||||
Log("YarnVariableStorage 未初始化。");
|
||||
return;
|
||||
}
|
||||
|
||||
var snapshot = SnapshotService.Capture();
|
||||
Log($"Capture 成功: 场景={snapshot.scene?.sceneName}, 节点={snapshot.anchor?.nodeName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Capture 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyAutoToManual()
|
||||
{
|
||||
if (!SlotDirectory.Exists(SlotIndex.Auto))
|
||||
{
|
||||
Log("slot_0 不存在。");
|
||||
return;
|
||||
}
|
||||
|
||||
SlotManager.CopyAutoToManual(_manualSlotIndex);
|
||||
Log($"已复制到 slot_{_manualSlotIndex}。");
|
||||
}
|
||||
|
||||
private void RestoreLatestSlot()
|
||||
{
|
||||
var latest = SlotManager.GetLatestSlotIndex();
|
||||
if (!latest.HasValue)
|
||||
{
|
||||
Log("没有 latest_slot。");
|
||||
return;
|
||||
}
|
||||
|
||||
RestoreSlot(latest.Value);
|
||||
}
|
||||
|
||||
private void RestoreSlot(int slotIndex)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Log("读档需要在 Play Mode 下执行。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameManager.Instance == null)
|
||||
{
|
||||
Log("GameManager 未初始化。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SlotDirectory.Exists(slotIndex))
|
||||
{
|
||||
Log($"slot_{slotIndex} 不存在。");
|
||||
return;
|
||||
}
|
||||
|
||||
GameManager.Instance.StartWithSlot(slotIndex);
|
||||
Log($"已启动读档 slot_{slotIndex}。");
|
||||
}
|
||||
|
||||
private void RestoreFromArchive(TestSaveArchiveEntry entry)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Log("读档需要在 Play Mode 下执行。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameManager.Instance == null)
|
||||
{
|
||||
Log("GameManager 未初始化。");
|
||||
return;
|
||||
}
|
||||
|
||||
GameManager.Instance.StartWithSaveFile(entry.SnapshotPath);
|
||||
Log($"已启动读档: {entry.EntryId}");
|
||||
}
|
||||
|
||||
private void ArchiveCurrentAutoSlot()
|
||||
{
|
||||
var snapshot = SlotManager.LoadSnapshot(SlotIndex.Auto);
|
||||
if (snapshot == null)
|
||||
{
|
||||
Log("slot_0 无快照。");
|
||||
return;
|
||||
}
|
||||
|
||||
var thumbnail = SlotManager.LoadThumbnail(SlotIndex.Auto);
|
||||
var entryId = TestSaveArchive.Archive(snapshot, thumbnail);
|
||||
RefreshArchiveEntries();
|
||||
Log($"已手动归档: {entryId}");
|
||||
}
|
||||
|
||||
private void DeleteArchiveEntry(TestSaveArchiveEntry entry)
|
||||
{
|
||||
var entryId = entry.EntryId;
|
||||
EditorApplication.delayCall += () => ConfirmDeleteArchiveEntry(entryId);
|
||||
}
|
||||
|
||||
private void ConfirmDeleteArchiveEntry(string entryId)
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("删除测试归档", $"删除 {entryId}?", "删除", "取消"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TestSaveArchive.DeleteEntry(entryId);
|
||||
if (_selectedArchiveEntryId == entryId)
|
||||
{
|
||||
_selectedArchiveEntryId = null;
|
||||
ReleaseThumbnail();
|
||||
}
|
||||
|
||||
RefreshArchiveEntries();
|
||||
Log($"已删除: {entryId}");
|
||||
}
|
||||
|
||||
private void ClearTestArchives()
|
||||
{
|
||||
EditorApplication.delayCall += ConfirmClearTestArchives;
|
||||
}
|
||||
|
||||
private void ConfirmClearTestArchives()
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("清空 testsavs", "删除所有测试归档?不可撤销。", "删除", "取消"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TestSaveArchive.ClearAll();
|
||||
_selectedArchiveEntryId = null;
|
||||
ReleaseThumbnail();
|
||||
RefreshArchiveEntries();
|
||||
Log("已清空 testsavs。");
|
||||
}
|
||||
|
||||
private static void OpenDirectory(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
EditorUtility.RevealInFinder(path);
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
_lastLog = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||
Debug.Log($"[SaveSystemTest] {message}");
|
||||
RequestRepaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27c49345ec3a4e849af3f9da7045f7a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,704 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AibisDream;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.SaveSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// 存档系统 P1/P2/P3 存盘行为验证工具。
|
||||
/// 不验证读档(P4),不验证复杂 tags 边界(P3 后续)。
|
||||
/// </summary>
|
||||
public class SaveSystemValidationWindow : EditorWindow
|
||||
{
|
||||
private const string MenuPath = "AIBIS/存档系统验证工具";
|
||||
private const string WindowTitle = "SaveSystem Validator";
|
||||
|
||||
private const float SaveHistoryBoxMinHeight = 400f;
|
||||
|
||||
private Vector2 _scrollPosition;
|
||||
private string _lastLog = string.Empty;
|
||||
private int _manualSlotIndex = 1;
|
||||
private int _deleteSlotIndex = 1;
|
||||
private string _testNodeName = "SomeNode";
|
||||
private string _testTags = "";
|
||||
private int _restoreSlotIndex;
|
||||
|
||||
private readonly List<SaveHistoryEntry> _saveHistory = new();
|
||||
private string _lastObservedSavedAt;
|
||||
|
||||
/// <summary>运行时自动存档历史条目,仅内存缓存。</summary>
|
||||
private class SaveHistoryEntry
|
||||
{
|
||||
public string SavedAt;
|
||||
public string NodeName;
|
||||
public string SceneName;
|
||||
}
|
||||
|
||||
[MenuItem(MenuPath)]
|
||||
public static void Open()
|
||||
{
|
||||
var window = GetWindow<SaveSystemValidationWindow>(WindowTitle);
|
||||
window.minSize = new Vector2(420, 640);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
InitializeLastObservedSavedAt();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
}
|
||||
|
||||
private void OnPlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
if (state == PlayModeStateChange.EnteredPlayMode)
|
||||
{
|
||||
_saveHistory.Clear();
|
||||
InitializeLastObservedSavedAt();
|
||||
TrySeedHistoryFromCurrentAutoSlot();
|
||||
Repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
_saveHistory.Clear();
|
||||
_lastObservedSavedAt = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void TrySeedHistoryFromCurrentAutoSlot()
|
||||
{
|
||||
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
|
||||
if (meta == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_saveHistory.Add(CreateHistoryEntry(meta));
|
||||
_lastObservedSavedAt = meta.savedAt;
|
||||
}
|
||||
|
||||
private static SaveHistoryEntry CreateHistoryEntry(SlotMeta meta)
|
||||
{
|
||||
return new SaveHistoryEntry
|
||||
{
|
||||
SavedAt = meta.savedAt,
|
||||
NodeName = string.IsNullOrEmpty(meta.nodeName) ? "(无节点)" : meta.nodeName,
|
||||
SceneName = meta.sceneName
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeLastObservedSavedAt()
|
||||
{
|
||||
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
|
||||
_lastObservedSavedAt = meta?.savedAt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 监听 slot_0 的 meta 变化,运行时每次自动存档都会记录到内存列表中。
|
||||
/// 纯 Editor 行为,不侵入运行时代码。
|
||||
/// </summary>
|
||||
private void OnEditorUpdate()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
|
||||
if (meta == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_lastObservedSavedAt) && _lastObservedSavedAt == meta.savedAt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastObservedSavedAt = meta.savedAt;
|
||||
_saveHistory.Add(CreateHistoryEntry(meta));
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
|
||||
|
||||
DrawHeader();
|
||||
DrawSlotOverview();
|
||||
GUILayout.Space(12);
|
||||
DrawSaveHistorySection();
|
||||
GUILayout.Space(12);
|
||||
DrawDirectCaptureSection();
|
||||
GUILayout.Space(12);
|
||||
DrawAutoSaveSimulationSection();
|
||||
GUILayout.Space(12);
|
||||
DrawManualSlotSection();
|
||||
GUILayout.Space(12);
|
||||
DrawRestoreSection();
|
||||
GUILayout.Space(12);
|
||||
DrawSavePointEvaluationSection();
|
||||
GUILayout.Space(12);
|
||||
DrawUtilitySection();
|
||||
GUILayout.Space(12);
|
||||
DrawLogSection();
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
#region Drawing
|
||||
|
||||
private void DrawHeader()
|
||||
{
|
||||
EditorGUILayout.LabelField("存档系统存盘验证", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"本工具仅验证 P1/P2/P3 的存盘行为:快照捕获、槽位落盘、元数据、可存点判定框架。\n" +
|
||||
"不验证读档(P4)和复杂 tags 边界(P3 后续)。\n" +
|
||||
"涉及运行时单例(DialogController / GameManager)的操作需要在 Play Mode 下执行。",
|
||||
MessageType.Info);
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox("当前不在 Play Mode。Capture / TryAutoSave 等依赖运行时单例的操作可能失败。", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSlotOverview()
|
||||
{
|
||||
EditorGUILayout.LabelField("槽位概览", EditorStyles.boldLabel);
|
||||
|
||||
var latest = SlotManager.GetLatestSlotIndex();
|
||||
EditorGUILayout.LabelField($"最近槽位: {(latest.HasValue ? latest.Value.ToString() : "无")}");
|
||||
EditorGUILayout.LabelField($"存档根目录: {ConstRef.SaveFilePath}");
|
||||
EditorGUILayout.LabelField(
|
||||
$"读档状态: IsRestoring={SaveRestoreOrchestrator.IsRestoring}, " +
|
||||
$"SuppressAutoSave={SaveRestoreOrchestrator.IsAutoSaveSuppressed}");
|
||||
|
||||
var viewModels = SlotManager.GetSlotViewModels();
|
||||
foreach (var vm in viewModels)
|
||||
{
|
||||
DrawSlotViewModel(vm, latest);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSlotViewModel(SlotViewModel vm, int? latest)
|
||||
{
|
||||
var isLatest = latest.HasValue && latest.Value == vm.SlotIndex;
|
||||
var nodeName = SlotManager.LoadMeta(vm.SlotIndex)?.nodeName;
|
||||
var label = vm.IsEmpty
|
||||
? $"[{vm.SlotIndex}] (空)"
|
||||
: $"[{vm.SlotIndex}] {(vm.IsAutoSlot ? "[自动档]" : "")} {(isLatest ? "[最新]" : "")}\n" +
|
||||
$" 场景: {vm.SceneName}\n" +
|
||||
$" SO: {vm.SceneSoName}\n" +
|
||||
$" 节点: {nodeName}\n" +
|
||||
$" 时间: {vm.SavedAt}\n" +
|
||||
$" 缩略图: {(string.IsNullOrEmpty(vm.ThumbnailPath) ? "无" : Path.GetFileName(vm.ThumbnailPath))}";
|
||||
|
||||
EditorGUILayout.HelpBox(label, vm.IsEmpty ? MessageType.None : MessageType.Info);
|
||||
}
|
||||
|
||||
private void DrawDirectCaptureSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("直接捕获快照", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"调用 SnapshotService.Capture() 组装快照,并在内存中检查结构。\n" +
|
||||
"此操作不写盘,不经过 SavePointEvaluator。",
|
||||
MessageType.None);
|
||||
|
||||
if (GUILayout.Button("Capture Snapshot (内存)"))
|
||||
{
|
||||
CaptureSnapshotInMemory();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAutoSaveSimulationSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("模拟自动存档", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"调用 SaveRestoreOrchestrator.TryAutoSave(),走完整的判定+捕获+落盘流程。\n" +
|
||||
"需要在 Play Mode 下且有 DialogController 运行时实例。",
|
||||
MessageType.None);
|
||||
|
||||
if (GUILayout.Button("TryAutoSave (完整流程)"))
|
||||
{
|
||||
TryAutoSaveFullFlow();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSaveHistorySection()
|
||||
{
|
||||
EditorGUILayout.LabelField("自动存档历史 (运行时)", EditorStyles.boldLabel);
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox, GUILayout.MinHeight(SaveHistoryBoxMinHeight)))
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"进入 Play Mode 后,每次 slot_0 自动存档都会在此记录节点名与时间。\n" +
|
||||
"数据仅保存在当前窗口内存中,退出 Play Mode 后清空。",
|
||||
MessageType.Info);
|
||||
}
|
||||
else if (_saveHistory.Count == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("等待自动存档…", EditorStyles.wordWrappedLabel);
|
||||
EditorGUILayout.LabelField(
|
||||
"触发 content / hub / linear 节点,或点击下方 TryAutoSave。",
|
||||
EditorStyles.miniLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField($"共 {_saveHistory.Count} 条记录", EditorStyles.miniLabel);
|
||||
|
||||
for (var i = 0; i < _saveHistory.Count; i++)
|
||||
{
|
||||
var entry = _saveHistory[i];
|
||||
EditorGUILayout.LabelField(
|
||||
$"{i + 1}. [{entry.SavedAt}] {entry.NodeName}",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
EditorGUILayout.LabelField(
|
||||
$" 场景: {entry.SceneName ?? "N/A"}",
|
||||
EditorStyles.miniLabel);
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button("清空历史记录"))
|
||||
{
|
||||
_saveHistory.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawManualSlotSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("手动档操作", EditorStyles.boldLabel);
|
||||
|
||||
_manualSlotIndex = EditorGUILayout.IntSlider("复制到槽位", _manualSlotIndex, 1, SlotIndex.ManualEnd);
|
||||
if (GUILayout.Button("Copy Auto → Manual"))
|
||||
{
|
||||
CopyAutoToManual(_manualSlotIndex);
|
||||
}
|
||||
|
||||
GUILayout.Space(8);
|
||||
|
||||
_deleteSlotIndex = EditorGUILayout.IntSlider("删除槽位", _deleteSlotIndex, 0, SlotIndex.ManualEnd);
|
||||
if (GUILayout.Button("Delete Slot"))
|
||||
{
|
||||
DeleteSlot(_deleteSlotIndex);
|
||||
}
|
||||
|
||||
GUILayout.Space(8);
|
||||
|
||||
if (GUILayout.Button("Clear All Saves"))
|
||||
{
|
||||
ClearAllSaves();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSavePointEvaluationSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("可存点判定框架检查", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"手动设置节点名和 tags,模拟 OnNodeStart 自动档判定(含 DetourResume)。\n" +
|
||||
"InProgress 来自运行时 DialogController;全局门控按实际 Play Mode 状态取值。",
|
||||
MessageType.None);
|
||||
|
||||
_testNodeName = EditorGUILayout.TextField("节点名", _testNodeName);
|
||||
_testTags = EditorGUILayout.TextField("Tags (逗号分隔)", _testTags);
|
||||
|
||||
if (GUILayout.Button("Evaluate CanAutoSave"))
|
||||
{
|
||||
EvaluateSavePoint();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRestoreSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("读档验证 (P4)", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"触发 SaveRestoreOrchestrator.RestoreFromSlot,验证 Phase + Barrier 编排、自动存档抑制与 Provider 还原日志。\n" +
|
||||
"需要在 Play Mode 下执行;不代表正式玩家 UI。",
|
||||
MessageType.None);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Restore Latest"))
|
||||
{
|
||||
RestoreLatestSlot();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Restore slot_0"))
|
||||
{
|
||||
RestoreSlot(SlotIndex.Auto);
|
||||
}
|
||||
}
|
||||
|
||||
_restoreSlotIndex = EditorGUILayout.IntSlider("Restore 槽位", _restoreSlotIndex, 0, SlotIndex.ManualEnd);
|
||||
if (GUILayout.Button($"Restore slot_{_restoreSlotIndex}"))
|
||||
{
|
||||
RestoreSlot(_restoreSlotIndex);
|
||||
}
|
||||
|
||||
var restoreLog = SaveRestoreOrchestrator.LastRestoreLog;
|
||||
if (restoreLog.Count > 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("Restore Pipeline Log", EditorStyles.boldLabel);
|
||||
foreach (var line in restoreLog)
|
||||
{
|
||||
EditorGUILayout.LabelField(line, EditorStyles.wordWrappedMiniLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawUtilitySection()
|
||||
{
|
||||
EditorGUILayout.LabelField("工具", EditorStyles.boldLabel);
|
||||
|
||||
if (GUILayout.Button("打开存档目录"))
|
||||
{
|
||||
OpenSaveDirectory();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("刷新"))
|
||||
{
|
||||
Repaint();
|
||||
Log("已刷新。");
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLogSection()
|
||||
{
|
||||
GUILayout.Space(12);
|
||||
EditorGUILayout.LabelField("最后结果", EditorStyles.boldLabel);
|
||||
EditorGUILayout.SelectableLabel(_lastLog, EditorStyles.textArea, GUILayout.MinHeight(120));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Actions
|
||||
|
||||
private void CaptureSnapshotInMemory()
|
||||
{
|
||||
try
|
||||
{
|
||||
var storage = YarnVariableStorage.Instance;
|
||||
if (storage == null)
|
||||
{
|
||||
Log("YarnVariableStorage 未初始化,尝试直接 new SaveSnapshot 仅做结构演示。");
|
||||
var demo = new SaveSnapshot
|
||||
{
|
||||
gameVersion = Application.version,
|
||||
savedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
scene = new SceneSnapshotDto { sceneName = "DemoScene" },
|
||||
anchor = new AnchorSnapshot { nodeName = "DemoNode" },
|
||||
yarnVariables = new YarnVariablesSnapshot()
|
||||
};
|
||||
PrintSnapshotSummary(demo, "演示快照(无运行时)");
|
||||
return;
|
||||
}
|
||||
|
||||
var snapshot = SnapshotService.Capture();
|
||||
PrintSnapshotSummary(snapshot, "内存快照");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Capture 失败: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void TryAutoSaveFullFlow()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Log("TryAutoSave 需要在 Play Mode 下执行。");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var beforeLatest = SlotManager.GetLatestSlotIndex();
|
||||
var beforeSnapshot = SlotManager.LoadSnapshot(0);
|
||||
|
||||
if (!SavePointEvaluator.CanAutoSave(out var rejectReason))
|
||||
{
|
||||
Log($"CanAutoSave 拒绝: {rejectReason}");
|
||||
return;
|
||||
}
|
||||
|
||||
var routine = SaveRestoreOrchestrator.AutoSaveRoutine();
|
||||
while (routine.MoveNext())
|
||||
{
|
||||
}
|
||||
|
||||
var afterSnapshot = SlotManager.LoadSnapshot(0);
|
||||
var afterLatest = SlotManager.GetLatestSlotIndex();
|
||||
|
||||
var summary = string.Empty;
|
||||
if (afterSnapshot == null)
|
||||
{
|
||||
summary = "无快照写入(可能被 SavePointEvaluator 拒绝)。";
|
||||
}
|
||||
else
|
||||
{
|
||||
summary = $"落盘成功。\n" +
|
||||
$"场景: {afterSnapshot.scene?.sceneName}\n" +
|
||||
$"节点: {afterSnapshot.anchor?.nodeName}\n" +
|
||||
$"SO: {afterSnapshot.anchor?.sceneSoName}\n" +
|
||||
$"YarnProject: {afterSnapshot.anchor?.yarnProjectId}\n" +
|
||||
$"Sections: {string.Join(", ", afterSnapshot.sections?.Keys ?? Enumerable.Empty<string>())}\n" +
|
||||
$"latest_slot: {(afterLatest.HasValue ? afterLatest.Value.ToString() : "无")}";
|
||||
}
|
||||
|
||||
Log(summary);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"TryAutoSave 失败: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyAutoToManual(int slotIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!SlotDirectory.Exists(SlotIndex.Auto))
|
||||
{
|
||||
Log("自动档不存在,无法复制手动档。");
|
||||
return;
|
||||
}
|
||||
|
||||
SlotManager.CopyAutoToManual(slotIndex);
|
||||
Log($"已复制自动档到 slot_{slotIndex}。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"复制失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteSlot(int slotIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
SlotManager.DeleteSlot(slotIndex);
|
||||
Log($"已删除/清空 slot_{slotIndex}。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"删除失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllSaves()
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("确认", "删除所有存档槽位?此操作不可撤销。", "删除", "取消"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for (var i = SlotIndex.Auto; i <= SlotIndex.ManualEnd; i++)
|
||||
{
|
||||
SlotManager.DeleteSlot(i);
|
||||
}
|
||||
|
||||
var latestPath = SlotDirectory.GetLatestSlotIndexPath();
|
||||
if (File.Exists(latestPath))
|
||||
{
|
||||
File.Delete(latestPath);
|
||||
}
|
||||
|
||||
Log("已清空所有槽位和 latest_slot 记录。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"清空失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void EvaluateSavePoint()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Log("SavePointEvaluator 需要运行时状态(IsRestoring / GameManager pause)。请在 Play Mode 下执行。");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var projectId = DialogController.Instance?.DialogueRunner?.YarnProject?.name;
|
||||
var tags = ParseTestTags(_testTags);
|
||||
var inProgress = SavePointEvaluator.InProgressNodeNames;
|
||||
|
||||
if (SaveRestoreOrchestrator.IsAutoSaveSuppressed)
|
||||
{
|
||||
Log("全局门控: AutoSaveSuppressed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SaveRestoreOrchestrator.IsRestoring)
|
||||
{
|
||||
Log("全局门控: Restoring(读档重进走 OnRestoreEnterNode,不写盘)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameManager.Instance != null && GameManager.Instance.state.isInPause)
|
||||
{
|
||||
Log("全局门控: GamePaused");
|
||||
return;
|
||||
}
|
||||
|
||||
var detourResume = !string.IsNullOrEmpty(_testNodeName)
|
||||
&& inProgress.Contains(_testNodeName);
|
||||
var tagOk = SavePointEvaluator.EvaluateNodeTagsForAutoSave(
|
||||
_testNodeName, tags, out var tagReason);
|
||||
var wouldSave = !detourResume && tagOk;
|
||||
|
||||
Log(
|
||||
$"Project = {projectId ?? "(null)"}\n" +
|
||||
$"InProgress = [{string.Join(", ", inProgress)}]\n" +
|
||||
$"模拟节点 = {_testNodeName}, tags = [{string.Join(", ", tags)}]\n" +
|
||||
$"DetourResume = {detourResume}\n" +
|
||||
$"Tag 判定 = {tagOk}, TagReason = {tagReason}\n" +
|
||||
$"OnNodeStart 会自动存 = {wouldSave}\n" +
|
||||
$"(DialogController 当前上下文 CanAutoSave = {SavePointEvaluator.CanAutoSave(out var ctxReason)}, Reason = {ctxReason})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"判定失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string[] ParseTestTags(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(tag => tag.Trim())
|
||||
.Where(tag => tag.Length > 0)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private void RestoreLatestSlot()
|
||||
{
|
||||
var latest = SlotManager.GetLatestSlotIndex();
|
||||
if (!latest.HasValue)
|
||||
{
|
||||
Log("没有 latest_slot,无法读档。");
|
||||
return;
|
||||
}
|
||||
|
||||
RestoreSlot(latest.Value);
|
||||
}
|
||||
|
||||
private void RestoreSlot(int slotIndex)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Log("Restore 需要在 Play Mode 下执行。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameManager.Instance == null)
|
||||
{
|
||||
Log("GameManager 未初始化,无法启动读档协程。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SlotDirectory.Exists(slotIndex))
|
||||
{
|
||||
Log($"slot_{slotIndex} 不存在,无法读档。");
|
||||
return;
|
||||
}
|
||||
|
||||
GameManager.Instance.StartWithSlot(slotIndex);
|
||||
Log($"已启动 Restore slot_{slotIndex}。请观察 Restore Pipeline Log 与场景状态。");
|
||||
}
|
||||
|
||||
private void OpenSaveDirectory()
|
||||
{
|
||||
var path = ConstRef.SaveFilePath;
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
EditorUtility.RevealInFinder(path);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private void PrintSnapshotSummary(SaveSnapshot snapshot, string label)
|
||||
{
|
||||
var json = SnapshotPersistence.Serialize(snapshot);
|
||||
var lines = new List<string>
|
||||
{
|
||||
$"=== {label} ===",
|
||||
$"gameVersion: {snapshot.gameVersion}",
|
||||
$"savedAt: {snapshot.savedAt}",
|
||||
$"schemaVersion: {snapshot.schemaVersion}",
|
||||
$"scene: {snapshot.scene?.sceneName}",
|
||||
$"anchor.sceneSoName: {snapshot.anchor?.sceneSoName}",
|
||||
$"anchor.yarnProjectId: {snapshot.anchor?.yarnProjectId}",
|
||||
$"anchor.nodeName: {snapshot.anchor?.nodeName}",
|
||||
$"yarnVariables.floats: {snapshot.yarnVariables?.floats?.Count ?? 0} entries",
|
||||
$"yarnVariables.strings: {snapshot.yarnVariables?.strings?.Count ?? 0} entries",
|
||||
$"yarnVariables.bools: {snapshot.yarnVariables?.bools?.Count ?? 0} entries",
|
||||
$"sections: {(snapshot.sections != null ? string.Join(", ", snapshot.sections.Keys) : "null")}",
|
||||
};
|
||||
|
||||
if (snapshot.sections != null)
|
||||
{
|
||||
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.PunchTape);
|
||||
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.Fix);
|
||||
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.FixPanel);
|
||||
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.BodyModule);
|
||||
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.Eye);
|
||||
}
|
||||
|
||||
lines.Add("JSON preview (first 1500 chars):");
|
||||
lines.Add(json.Length > 1500 ? json.Substring(0, 1500) + "..." : json);
|
||||
|
||||
Log(string.Join("\n", lines));
|
||||
}
|
||||
|
||||
private static void AppendFixSectionHint(List<string> lines, SaveSnapshot snapshot, string sectionId)
|
||||
{
|
||||
if (snapshot.sections.ContainsKey(sectionId))
|
||||
{
|
||||
lines.Add($" [fix-related] {sectionId}: captured");
|
||||
}
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
_lastLog = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||
Debug.Log($"[SaveSystemValidator] {message}");
|
||||
Repaint();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9719ab6f265683c4484a170f6c4dc843
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1489,8 +1489,8 @@ MonoBehaviour:
|
||||
m_ShadowVolumeIntensityEnabled: 0
|
||||
m_ShadowVolumeIntensity: 0.75
|
||||
m_LocalBounds:
|
||||
m_Center: {x: 0.69834995, y: -3.87525, z: 0}
|
||||
m_Extent: {x: 9.61685, y: 5.77315, z: 0}
|
||||
m_Center: {x: 0.69945, y: -3.8748999, z: 0}
|
||||
m_Extent: {x: 9.62075, y: 5.7716, z: 0}
|
||||
m_PointLightInnerAngle: 360
|
||||
m_PointLightOuterAngle: 360
|
||||
m_PointLightInnerRadius: 0
|
||||
@@ -1889,6 +1889,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 14
|
||||
data:
|
||||
dialogViewType: 7
|
||||
bubbleSlots: []
|
||||
@@ -2230,6 +2231,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 1964514168895772326}
|
||||
- component: {fileID: 6318711606202227434}
|
||||
- component: {fileID: 5359247006764324596}
|
||||
m_Layer: 0
|
||||
m_Name: Analysis Camera
|
||||
m_TagString: Untagged
|
||||
@@ -2292,6 +2294,19 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_LegacyBlendHint: 0
|
||||
m_ComponentOwner: {fileID: 7397998753940568990}
|
||||
--- !u!114 &5359247006764324596
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 9005043052954555028}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7432789ba6bd1a340b824676e1e56184, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
cameraType: 27
|
||||
--- !u!1 &9040823909663253672
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -119,13 +119,3 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 79080239c808e074cbbffada2dd2045b, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
actorData:
|
||||
onSave:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
actorName:
|
||||
slotName:
|
||||
actorType: 0
|
||||
stateName:
|
||||
alpha: 0
|
||||
faceParam: 0
|
||||
|
||||
@@ -1359,8 +1359,8 @@ MonoBehaviour:
|
||||
m_ShadowVolumeIntensityEnabled: 0
|
||||
m_ShadowVolumeIntensity: 0.75
|
||||
m_LocalBounds:
|
||||
m_Center: {x: 0.032999992, y: -0.060499907, z: 0}
|
||||
m_Extent: {x: 14.1184, y: 5.5629997, z: 0}
|
||||
m_Center: {x: 0.03494978, y: -0.07005, z: 0}
|
||||
m_Extent: {x: 14.116449, y: 5.57125, z: 0}
|
||||
m_PointLightInnerAngle: 360
|
||||
m_PointLightOuterAngle: 360
|
||||
m_PointLightInnerRadius: 0
|
||||
@@ -1959,6 +1959,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 8988582123737961170}
|
||||
- component: {fileID: 5690280413635320205}
|
||||
- component: {fileID: 3792378371869211096}
|
||||
m_Layer: 0
|
||||
m_Name: Clinic Camera
|
||||
m_TagString: Untagged
|
||||
@@ -2021,6 +2022,19 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_LegacyBlendHint: 0
|
||||
m_ComponentOwner: {fileID: 6713959257411942534}
|
||||
--- !u!114 &3792378371869211096
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6462107428085023161}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7432789ba6bd1a340b824676e1e56184, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
cameraType: 1
|
||||
--- !u!1 &6543365627926242830
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2352,6 +2366,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 1
|
||||
data:
|
||||
dialogViewType: 1
|
||||
bubbleSlots: []
|
||||
@@ -2618,6 +2633,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 1289813725535206033}
|
||||
- component: {fileID: 258174790319769401}
|
||||
- component: {fileID: 3093863673183708419}
|
||||
m_Layer: 0
|
||||
m_Name: Cooling Camera
|
||||
m_TagString: Untagged
|
||||
@@ -2680,3 +2696,16 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_LegacyBlendHint: 0
|
||||
m_ComponentOwner: {fileID: 7830223793077688558}
|
||||
--- !u!114 &3093863673183708419
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8940458651926956810}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7432789ba6bd1a340b824676e1e56184, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
cameraType: 2
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,99 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1001 &1360354125466394617
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 0}
|
||||
m_Modifications:
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 250
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 190
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: -795
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -263
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1347914344471040156, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8732203183804650455, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: "UF\u7ED8\u56FE"
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 5a170c0b6c933c540a86be691fa6745c, type: 3}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -431,6 +431,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 952162656386370984}
|
||||
- component: {fileID: 1423545933766743583}
|
||||
- component: {fileID: 3423442071072641733}
|
||||
m_Layer: 0
|
||||
m_Name: Sell Deep Camera
|
||||
m_TagString: Untagged
|
||||
@@ -493,6 +494,19 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_LegacyBlendHint: 0
|
||||
m_ComponentOwner: {fileID: 7541972663295669641}
|
||||
--- !u!114 &3423442071072641733
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2172269519109149815}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7432789ba6bd1a340b824676e1e56184, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
cameraType: 30
|
||||
--- !u!1 &2448689962436376099
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1887,8 +1901,8 @@ MonoBehaviour:
|
||||
m_ShadowVolumeIntensityEnabled: 0
|
||||
m_ShadowVolumeIntensity: 0.75
|
||||
m_LocalBounds:
|
||||
m_Center: {x: 0.69924974, y: -3.88405, z: 0}
|
||||
m_Extent: {x: 9.612249, y: 5.7785497, z: 0}
|
||||
m_Center: {x: 0.7031002, y: -3.88395, z: 0}
|
||||
m_Extent: {x: 9.6236, y: 5.77255, z: 0}
|
||||
m_PointLightInnerAngle: 360
|
||||
m_PointLightOuterAngle: 360
|
||||
m_PointLightInnerRadius: 0
|
||||
@@ -1950,6 +1964,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 7
|
||||
data:
|
||||
dialogViewType: 1
|
||||
bubbleSlots: []
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
title: EmoPlugIn
|
||||
tags: 待编辑
|
||||
colorID: 8
|
||||
@@ -53,7 +53,7 @@ position: 480,-2840
|
||||
<<jump 检查视觉>>
|
||||
===
|
||||
title: IntoEyeView
|
||||
tags:
|
||||
tags: event
|
||||
group:视觉检查
|
||||
colorID: 6
|
||||
position: 244,271
|
||||
@@ -108,26 +108,29 @@ position: 244,271
|
||||
===
|
||||
|
||||
title: MemoryDeepIn
|
||||
tags:
|
||||
tags: event
|
||||
---
|
||||
<<if visited("OnMemoryViewShow")==false>>
|
||||
<<jump 第一次进入记忆时>>
|
||||
<<endif>>
|
||||
===
|
||||
|
||||
title: UFDeepIn
|
||||
tags:
|
||||
tags: event
|
||||
---
|
||||
<<jump UF深入检查>>
|
||||
===
|
||||
|
||||
title: EyeDeepIn
|
||||
tags:
|
||||
tags: event
|
||||
position: -9,21
|
||||
---
|
||||
<<switch_fix_system_to Eye>>
|
||||
<<ModuleHightLight Eye false>>
|
||||
===
|
||||
|
||||
title: OnMemoryInsert
|
||||
tags:
|
||||
tags: event
|
||||
group:记忆
|
||||
colorID: 3
|
||||
position: -1580,-2168
|
||||
|
||||
@@ -976,6 +976,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 10
|
||||
data:
|
||||
dialogViewType: 1
|
||||
bubbleSlots: []
|
||||
|
||||
@@ -364,6 +364,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 10
|
||||
data:
|
||||
dialogViewType: 1
|
||||
bubbleSlots: []
|
||||
|
||||
+801
-918
File diff suppressed because it is too large
Load Diff
+879
-925
File diff suppressed because it is too large
Load Diff
+933
-922
File diff suppressed because it is too large
Load Diff
+860
-920
File diff suppressed because it is too large
Load Diff
@@ -518,6 +518,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 8
|
||||
data:
|
||||
dialogViewType: 1
|
||||
bubbleSlots: []
|
||||
@@ -568,8 +569,6 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
subwaySlots: {fileID: 143273129}
|
||||
tvSlots: {fileID: 494482151}
|
||||
subwayCam: {fileID: 233946549}
|
||||
tvCam: {fileID: 1263973096}
|
||||
newsSpriteRenderer: {fileID: 5352226562362177808}
|
||||
arriveStation: {fileID: 969974277}
|
||||
--- !u!1 &226184625
|
||||
@@ -698,6 +697,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 233946548}
|
||||
- component: {fileID: 233946549}
|
||||
- component: {fileID: 233946550}
|
||||
m_Layer: 0
|
||||
m_Name: Subway Camera
|
||||
m_TagString: Untagged
|
||||
@@ -760,6 +760,19 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_LegacyBlendHint: 0
|
||||
m_ComponentOwner: {fileID: 511564811}
|
||||
--- !u!114 &233946550
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 233946547}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7432789ba6bd1a340b824676e1e56184, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
cameraType: 23
|
||||
--- !u!1 &252705163
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1574,6 +1587,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 9
|
||||
data:
|
||||
dialogViewType: 1
|
||||
bubbleSlots: []
|
||||
@@ -2685,6 +2699,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 1263973095}
|
||||
- component: {fileID: 1263973096}
|
||||
- component: {fileID: 1263973097}
|
||||
m_Layer: 0
|
||||
m_Name: TV Camera
|
||||
m_TagString: Untagged
|
||||
@@ -2747,6 +2762,19 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_LegacyBlendHint: 0
|
||||
m_ComponentOwner: {fileID: 11266075}
|
||||
--- !u!114 &1263973097
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1263973094}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7432789ba6bd1a340b824676e1e56184, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
cameraType: 24
|
||||
--- !u!1 &1364179052
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -735,6 +735,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 10
|
||||
data:
|
||||
dialogViewType: 2
|
||||
bubbleSlots: []
|
||||
|
||||
@@ -785,6 +785,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 10
|
||||
data:
|
||||
dialogViewType: 1
|
||||
bubbleSlots: []
|
||||
@@ -1358,6 +1359,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 8225446392f64688813d628ffb56cc7c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
slotKey: 11
|
||||
data:
|
||||
dialogViewType: 1
|
||||
bubbleSlots: []
|
||||
|
||||
@@ -16,7 +16,7 @@ MonoBehaviour:
|
||||
- exitName: default
|
||||
targetScene: {fileID: 11400000, guid: 653fbfbb55fef7740a2360590e29d552, type: 2}
|
||||
yarnProject: {fileID: 7545569452688597077, guid: 29ebe788cfe2c084e88ffdd8e2f93378, type: 3}
|
||||
firstSceneName: Scene/ClinicOut
|
||||
firstSceneName: "Scene/\u9152\u5427\u573A\u666F"
|
||||
title: "\u9152\u676F\u548C\u724C"
|
||||
description: "\u9152\u676F\u548C\u724C"
|
||||
coverPic: {fileID: 21300000, guid: df86302e77350e34ea422cd24043dd93, type: 3}
|
||||
|
||||
@@ -23,6 +23,26 @@ namespace AibisDream.Utility
|
||||
|
||||
public static readonly string SaveFilePath = Path.Combine(Application.persistentDataPath, "AllOurBrokenParts", "saves");
|
||||
|
||||
/// <summary>测试阶段自动存档历史目录;每次自动存档独立子文件夹,不覆盖正式槽位。</summary>
|
||||
public static readonly string TestAutoSaveArchivePath =
|
||||
Path.Combine(Application.persistentDataPath, "AllOurBrokenParts", "testsavs");
|
||||
|
||||
/// <summary>P2 槽位相关常量。</summary>
|
||||
public const int SaveSlotCount = 8;
|
||||
public const int SaveThumbnailWidth = 640;
|
||||
public const int SaveThumbnailHeight = 360;
|
||||
public const string SaveSlotDirPrefix = "slot_";
|
||||
public const string SaveSnapshotFileName = "snapshot";
|
||||
public const string SaveMetaFileName = "meta";
|
||||
public const string SaveThumbnailFileName = "thumbnail";
|
||||
public const string LatestSlotFileName = "latest_slot";
|
||||
|
||||
/// <summary>P1 快照层自测落盘路径(P2 槽位层接管后迁移)。</summary>
|
||||
public static readonly string SnapshotTestPath =
|
||||
Path.Combine(Application.persistentDataPath, "AllOurBrokenParts", "snapshot_test");
|
||||
|
||||
public const string SnapshotTestFileName = "latest_snapshot";
|
||||
|
||||
public static readonly string ChapterProgressPath = Path.Combine(Application.persistentDataPath, "AllOurBrokenParts", "Config", "chapter.json");
|
||||
|
||||
public static readonly string CharacterConfigPath = Path.Combine(Application.streamingAssetsPath, "Config", "character.csv");
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace AibisDream
|
||||
public static void NextYarn(string exitName = null)
|
||||
{
|
||||
EnumEventSystem.Global.Send(EventEnum.NextYarn);
|
||||
StorageSystem.Instance.ClearLocal();
|
||||
YarnVariableStorage.Instance.ClearLocal();
|
||||
DialogController.Instance.StartCoroutine(GameManager.Instance.NextSceneSo(exitName));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using AibisDream.Framework;
|
||||
@@ -16,20 +17,30 @@ namespace AibisDream
|
||||
|
||||
[SerializeField] private LineAdvanceInput lineAdvanceInput;
|
||||
|
||||
private string _currentNodeName;
|
||||
private string[] _currentNodeTags;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_dialogueRunner = GetComponentInChildren<DialogueRunner>();
|
||||
_dialogueRunner.onDialogueStart ??= new UnityEvent();
|
||||
_dialogueRunner.onDialogueComplete ??= new UnityEvent();
|
||||
_dialogueRunner.onNodeStart ??= new UnityEventString();
|
||||
_dialogueRunner.onNodeComplete ??= new UnityEventString();
|
||||
|
||||
_dialogueRunner.onDialogueStart.AddListener(() =>
|
||||
{
|
||||
EnumEventSystem.Global.Send(InteractionEventEnum.DialogStart);
|
||||
});
|
||||
|
||||
_dialogueRunner.onDialogueComplete.AddListener(() =>
|
||||
{
|
||||
EnumEventSystem.Global.Send(InteractionEventEnum.DialogEnd);
|
||||
ClearCurrentNodeContext();
|
||||
});
|
||||
|
||||
_dialogueRunner.onNodeStart.AddListener(OnNodeStart);
|
||||
_dialogueRunner.onNodeComplete.AddListener(OnNodeComplete);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@@ -76,6 +87,7 @@ namespace AibisDream
|
||||
/// <param name="yarnProject">Yarn组</param>
|
||||
public void StartDialog(YarnProject yarnProject)
|
||||
{
|
||||
PrepareYarnProjectForDialog(yarnProject);
|
||||
_dialogueRunner.SetProject(yarnProject);
|
||||
_ = _dialogueRunner.StartDialogue("Start");
|
||||
}
|
||||
@@ -83,14 +95,21 @@ namespace AibisDream
|
||||
public void StopDialog()
|
||||
{
|
||||
_ = _dialogueRunner.Stop();
|
||||
StorageSystem.Instance.Clear();
|
||||
SavePointEvaluator.ResetForProject(null);
|
||||
YarnVariableStorage.Instance.Clear();
|
||||
DialogUIManager.Instance.HideDialog();
|
||||
}
|
||||
|
||||
public void LoadDialog(YarnProject yarnProject)
|
||||
{
|
||||
PrepareYarnProjectForDialog(yarnProject);
|
||||
_dialogueRunner.SetProject(yarnProject);
|
||||
_ = _dialogueRunner.StartDialogue("Center");
|
||||
}
|
||||
|
||||
private static void PrepareYarnProjectForDialog(YarnProject yarnProject)
|
||||
{
|
||||
var projectId = yarnProject != null ? yarnProject.name : null;
|
||||
SavePointEvaluator.ResetForProjectIfChanged(projectId);
|
||||
}
|
||||
|
||||
public void StartDialogNode(string nodeName)
|
||||
@@ -140,30 +159,66 @@ namespace AibisDream
|
||||
/// </summary>
|
||||
public (string nodeName, IReadOnlyList<string> tags) GetCurrentNodeContext()
|
||||
{
|
||||
if (_dialogueRunner == null)
|
||||
if (string.IsNullOrEmpty(_currentNodeName))
|
||||
{
|
||||
return (null, Array.Empty<string>());
|
||||
}
|
||||
|
||||
var dialogue = _dialogueRunner.Dialogue;
|
||||
if (dialogue == null)
|
||||
return (_currentNodeName, _currentNodeTags ?? Array.Empty<string>());
|
||||
}
|
||||
|
||||
private void OnNodeStart(string nodeName)
|
||||
{
|
||||
UpdateCurrentNodeContext(nodeName);
|
||||
|
||||
var projectId = _dialogueRunner?.YarnProject?.name;
|
||||
var tags = _currentNodeTags ?? Array.Empty<string>();
|
||||
|
||||
if (SaveRestoreOrchestrator.IsRestoring)
|
||||
{
|
||||
return (null, Array.Empty<string>());
|
||||
SavePointEvaluator.OnRestoreEnterNode(projectId, nodeName);
|
||||
return;
|
||||
}
|
||||
|
||||
var nodeName = dialogue.CurrentNode;
|
||||
if (string.IsNullOrEmpty(nodeName))
|
||||
if (!SavePointEvaluator.OnNodeStartForAutoSave(projectId, nodeName, tags, out var reason))
|
||||
{
|
||||
return (null, Array.Empty<string>());
|
||||
if (reason == SavePointRejectReason.DetourResume)
|
||||
{
|
||||
Debug.Log($"[DialogController] 跳过 detour 返回续跑的自动存档: {nodeName}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var tagsHeader = dialogue.GetHeaderValue(nodeName, "tags");
|
||||
if (string.IsNullOrWhiteSpace(tagsHeader))
|
||||
StartCoroutine(SaveRestoreOrchestrator.AutoSaveRoutine(nodeName));
|
||||
}
|
||||
|
||||
private void OnNodeComplete(string nodeName)
|
||||
{
|
||||
SavePointEvaluator.OnNodeComplete(_dialogueRunner?.YarnProject?.name, nodeName);
|
||||
ClearCurrentNodeContext();
|
||||
}
|
||||
|
||||
private void UpdateCurrentNodeContext(string nodeName)
|
||||
{
|
||||
if (_dialogueRunner == null || _dialogueRunner.Dialogue == null)
|
||||
{
|
||||
return (nodeName, Array.Empty<string>());
|
||||
ClearCurrentNodeContext();
|
||||
return;
|
||||
}
|
||||
|
||||
return (nodeName, tagsHeader.Split(Array.Empty<char>(), StringSplitOptions.RemoveEmptyEntries));
|
||||
_currentNodeName = nodeName;
|
||||
|
||||
var tagsHeader = _dialogueRunner.Dialogue.GetHeaderValue(nodeName, "tags");
|
||||
_currentNodeTags = string.IsNullOrWhiteSpace(tagsHeader)
|
||||
? Array.Empty<string>()
|
||||
: tagsHeader.Split(Array.Empty<char>(), StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
private void ClearCurrentNodeContext()
|
||||
{
|
||||
_currentNodeName = null;
|
||||
_currentNodeTags = null;
|
||||
}
|
||||
|
||||
#region 推进方式修改
|
||||
|
||||
@@ -4,7 +4,7 @@ using UnityEngine;
|
||||
using DG.Tweening;
|
||||
using Yarn.Unity;
|
||||
using AibisDream.Framework;
|
||||
using Cinemachine;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.UI;
|
||||
using AibisDream.Kit;
|
||||
@@ -37,8 +37,6 @@ namespace AibisDream.FixSystem
|
||||
private static readonly int EnableSaturationId = Shader.PropertyToID("_EnableSaturation");
|
||||
private static readonly int SaturationId = Shader.PropertyToID("_Saturation");
|
||||
|
||||
[SerializeField] private BubbleSlotGroup bubbleSlotGroup;
|
||||
public BubbleSlotGroup BubbleSlotGroup => bubbleSlotGroup;
|
||||
[SerializeField] private GameObject memoryView;
|
||||
[SerializeField] private GameObject projector;
|
||||
[SerializeField] private SpriteRenderer memoryFinishedSpriteRenderer; // 用于显示记忆处理结束的表现
|
||||
@@ -55,7 +53,6 @@ namespace AibisDream.FixSystem
|
||||
[SerializeField] private CanvasGroup playBackground;
|
||||
|
||||
[SerializeField] private GameObject memoryGroup;
|
||||
[SerializeField] private CinemachineVirtualCamera memoryCamera;
|
||||
|
||||
private bool isProcessing = false; // 新增变量来追踪是否正在处理记忆
|
||||
private bool isPlayingFaderSound = false; // 追踪音效是否正在播放
|
||||
@@ -163,8 +160,6 @@ namespace AibisDream.FixSystem
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
if (memoryGroup != null)
|
||||
memoryGroup.SetActive(false);
|
||||
if (memoryCamera != null)
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.Memory, memoryCamera);
|
||||
}
|
||||
|
||||
|
||||
@@ -363,7 +358,7 @@ namespace AibisDream.FixSystem
|
||||
|
||||
/// <summary>
|
||||
/// 单条记忆播放结束后的复位:不调用 <see cref="MemorySlider.Init"/>(避免再次锁条并触发打开 Timeline),
|
||||
/// 仅禁用拖动;完整 <see cref="MemorySlider.LockSlider"/> 仅在退出 <see cref="MemoryState"/> 时 <see cref="OnExit"/> 执行。
|
||||
/// 仅禁用拖动;完整 <see cref="MemorySlider.LockSlider"/> 仅在退出 <see cref="MemoryCue"/> 时 <see cref="OnExit"/> 执行。
|
||||
/// </summary>
|
||||
private void ResetAfterMemoryFinished()
|
||||
{
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using UnityEngine;
|
||||
using AibisDream.Framework;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 打孔带系统入口:收藏数据、存档注册、Yarn 命令、弹窗与收藏夹面板代理。
|
||||
/// 打孔带系统入口:收藏数据、Yarn 命令、弹窗与收藏夹面板代理。
|
||||
/// </summary>
|
||||
public class PunchTapeManager : Singleton<PunchTapeManager>
|
||||
{
|
||||
@@ -19,48 +19,54 @@ namespace AibisDream.FixSystem
|
||||
[SerializeField] private PunchTapePrinter punchTapePrinter;
|
||||
public PunchTapePrinter PunchTapePrinter => punchTapePrinter;
|
||||
|
||||
private readonly PunchTapeCollectionData _collectionData = new();
|
||||
private readonly List<PunchTape> _favorites = new();
|
||||
|
||||
private GameObject currentPunchTapeGO;
|
||||
|
||||
/// <summary>收藏列表变更时通知 <see cref="PunchTapeFavoritesPanel"/> 等订阅方。</summary>
|
||||
public event Action OnCollectionChanged;
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
StorageSystem.Instance.RegisterData(_collectionData);
|
||||
_collectionData.LoadEvent += OnCollectionDataLoaded;
|
||||
}
|
||||
|
||||
public override void OnSingletonDestroy()
|
||||
{
|
||||
_collectionData.LoadEvent -= OnCollectionDataLoaded;
|
||||
}
|
||||
|
||||
private void OnCollectionDataLoaded(PunchTapeCollectionData data)
|
||||
{
|
||||
OnCollectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public IReadOnlyList<PunchTape> GetFavorites()
|
||||
{
|
||||
return _collectionData.punchTapeFavorites;
|
||||
return _favorites;
|
||||
}
|
||||
|
||||
public PunchTapeSnapshotDto CapturePunchTapeSnapshot()
|
||||
{
|
||||
return new PunchTapeSnapshotDto
|
||||
{
|
||||
favorites = _favorites.Select(t => new PunchTape(t.Category, t.ClueName, t.MemoryIndex)
|
||||
{
|
||||
used = t.used
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public void ApplyPunchTapeSnapshot(PunchTapeSnapshotDto dto)
|
||||
{
|
||||
_favorites.Clear();
|
||||
if (dto?.favorites != null)
|
||||
{
|
||||
_favorites.AddRange(dto.favorites);
|
||||
}
|
||||
|
||||
OnCollectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void AddPunchTape(PunchTape punchTape)
|
||||
{
|
||||
if (punchTape == null) return;
|
||||
if (_collectionData.punchTapeFavorites.Contains(punchTape))
|
||||
if (_favorites.Contains(punchTape))
|
||||
return;
|
||||
|
||||
_collectionData.punchTapeFavorites.Add(punchTape);
|
||||
_favorites.Add(punchTape);
|
||||
OnCollectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void RemovePunchTape(PunchTape punchTape)
|
||||
{
|
||||
if (punchTape == null) return;
|
||||
if (!_collectionData.punchTapeFavorites.Remove(punchTape))
|
||||
if (!_favorites.Remove(punchTape))
|
||||
return;
|
||||
|
||||
OnCollectionChanged?.Invoke();
|
||||
@@ -129,20 +135,4 @@ namespace AibisDream.FixSystem
|
||||
currentPunchTapeGO = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打孔带收藏列表的存档载体;由 <see cref="PunchTapeManager"/> 注册到 <see cref="StorageSystem"/>。
|
||||
/// </summary>
|
||||
public class PunchTapeCollectionData : IData
|
||||
{
|
||||
public List<PunchTape> punchTapeFavorites = new();
|
||||
|
||||
public event Action<PunchTapeCollectionData> LoadEvent;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
LoadEvent?.Invoke(this);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using Cinemachine;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
@@ -25,10 +25,13 @@ namespace AibisDream.FixSystem
|
||||
|
||||
#region 系统状态
|
||||
|
||||
private readonly ModuleSystemData _data = new();
|
||||
private ModuleState _moduleState = ModuleState.Body;
|
||||
public bool inHotErr;
|
||||
private int _heatValue;
|
||||
|
||||
/// <summary>获取当前 BodyModule 分组(Body/Head)。</summary>
|
||||
public ModuleState GetCurrentModuleState() => _moduleState;
|
||||
|
||||
private int HeatValue
|
||||
{
|
||||
get => _heatValue;
|
||||
@@ -55,9 +58,6 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
// 注册数据
|
||||
StorageSystem.Instance.RegisterData(_data);
|
||||
|
||||
// 处理内部索引
|
||||
_bodyModuleGroup = transform.Find("Scalehandler")?.transform.Find("Body Module Group")?.gameObject;
|
||||
_headModuleGroup = transform.Find("Scalehandler")?.transform.Find("Head Module Group")?.gameObject;
|
||||
@@ -70,16 +70,73 @@ namespace AibisDream.FixSystem
|
||||
|
||||
// 注册到维修系统管理器
|
||||
RepairSystemManager.Register(this);
|
||||
|
||||
// 注册Camera
|
||||
var virtualCam2 = transform.Find("Body Module Camera Center").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.BodyModuleCenter, virtualCam2);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
/// <summary>捕获插线模块持久状态(分组 + UF + Chip + 高亮/报警)。</summary>
|
||||
public BodyModuleSnapshotDto CaptureBodyModuleSnapshot()
|
||||
{
|
||||
StorageSystem.Instance.UnregisterData<ModuleSystemData>();
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.BodyModuleCenter);
|
||||
var dto = new BodyModuleSnapshotDto
|
||||
{
|
||||
moduleState = _moduleState.ToString()
|
||||
};
|
||||
|
||||
var ufModule = FindBodyModuleByName("UF")?.GetComponent<UFModule>();
|
||||
ufModule?.CaptureState(dto);
|
||||
|
||||
var chipModule = FindBodyModuleByName("Color")?.GetComponent<ChipModule>();
|
||||
chipModule?.CaptureState(dto);
|
||||
|
||||
foreach (var module in EnumerateAllBodyModules())
|
||||
{
|
||||
var name = module.data.moduleName;
|
||||
if (module.IsHighlighted) dto.highlightedModules.Add(name);
|
||||
if (module.IsAlarmed) dto.alarmedModules.Add(name);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/// <summary>写回插线模块持久状态;须在 fix cue 还原之后调用。</summary>
|
||||
public void ApplyBodyModuleSnapshot(BodyModuleSnapshotDto dto)
|
||||
{
|
||||
if (dto == null) return;
|
||||
|
||||
if (!string.IsNullOrEmpty(dto.moduleState)
|
||||
&& Enum.TryParse<ModuleState>(dto.moduleState, out var moduleState))
|
||||
{
|
||||
SwitchModuleGroup(moduleState);
|
||||
}
|
||||
|
||||
var ufModule = FindBodyModuleByName("UF")?.GetComponent<UFModule>();
|
||||
ufModule?.ApplyState(dto);
|
||||
|
||||
var chipModule = FindBodyModuleByName("Color")?.GetComponent<ChipModule>();
|
||||
chipModule?.ApplyState(dto);
|
||||
|
||||
var highlighted = new HashSet<string>(dto.highlightedModules ?? Enumerable.Empty<string>());
|
||||
var alarmed = new HashSet<string>(dto.alarmedModules ?? Enumerable.Empty<string>());
|
||||
|
||||
foreach (var module in EnumerateAllBodyModules())
|
||||
{
|
||||
var name = module.data.moduleName;
|
||||
module.Highlight(highlighted.Contains(name));
|
||||
module.Alarm(alarmed.Contains(name));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<BodyModule> EnumerateAllBodyModules()
|
||||
{
|
||||
if (_bodyModuleGroup != null)
|
||||
{
|
||||
foreach (var module in _bodyModuleGroup.GetComponentsInChildren<BodyModule>(true))
|
||||
yield return module;
|
||||
}
|
||||
|
||||
if (_headModuleGroup != null)
|
||||
{
|
||||
foreach (var module in _headModuleGroup.GetComponentsInChildren<BodyModule>(true))
|
||||
yield return module;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -242,8 +299,7 @@ namespace AibisDream.FixSystem
|
||||
// 重新获取ModuleGroup
|
||||
var root = moduleState == ModuleState.Body ? _bodyModuleGroup : _headModuleGroup;
|
||||
BodyModules = root?.GetComponentsInChildren<BodyModule>().ToList();
|
||||
// 更改data
|
||||
_data.moduleState = moduleState;
|
||||
_moduleState = moduleState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -383,18 +439,6 @@ namespace AibisDream.FixSystem
|
||||
}
|
||||
}
|
||||
|
||||
[LoadIndex(2)]
|
||||
public class ModuleSystemData : IData
|
||||
{
|
||||
public ModuleState moduleState;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Get<BodyModuleSystem>().SwitchModuleGroup(moduleState);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ModuleState
|
||||
{
|
||||
Body,
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void LoadCableState()
|
||||
{
|
||||
if (StorageSystem.Instance.TryGetValue(CableRetractedKey, out bool savedRetractedState))
|
||||
if (YarnVariableStorage.Instance.TryGetValue(CableRetractedKey, out bool savedRetractedState))
|
||||
{
|
||||
if (isCableRetracted)
|
||||
{
|
||||
@@ -89,7 +89,7 @@ namespace AibisDream.FixSystem
|
||||
private void SaveCableState()
|
||||
{
|
||||
Debug.Log("Save cableRetractedstate" + isCableRetracted);
|
||||
StorageSystem.Instance.SetValue(CableRetractedKey, isCableRetracted);
|
||||
YarnVariableStorage.Instance.SetValue(CableRetractedKey, isCableRetracted);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using UnityEngine;
|
||||
using AibisDream.FixSystem;
|
||||
using Cinemachine;
|
||||
using AibisDream.Framework;
|
||||
using DG.Tweening;
|
||||
using System.Collections;
|
||||
using AibisDream.Kit;
|
||||
@@ -10,7 +9,6 @@ namespace AibisDream
|
||||
{
|
||||
public class CuttingManager : MonoBehaviour
|
||||
{
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
[Header("切割组件")]
|
||||
public GameObject gearFollowerPrefab;
|
||||
public GameObject entrancePanelPrefab; // 入场面板预制体
|
||||
@@ -30,21 +28,7 @@ namespace AibisDream
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 注册
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
var virtualCam1 = transform.Find("Cut Emo Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam2 = transform.Find("Cut Memory Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam3 = transform.Find("Cut Logic Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam4 = transform.Find("Cut Emo Deep Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam5 = transform.Find("Cut Memory Deep Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam6 = transform.Find("Cut Logic Deep Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutEmo, virtualCam1);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutMemory, virtualCam2);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutLogic, virtualCam3);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutEmoDeep, virtualCam4);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutMemoryDeep, virtualCam5);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutLogicDeep, virtualCam6);
|
||||
|
||||
_mainCamera = Camera.main;
|
||||
}
|
||||
|
||||
@@ -311,17 +295,8 @@ namespace AibisDream
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 从系统字典中注销
|
||||
FixSystemCenter.SystemDic.Unregister<CuttingManager>();
|
||||
|
||||
// 清理相机注册
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutEmo);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutMemory);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutLogic);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutEmoDeep);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutMemoryDeep);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutLogicDeep);
|
||||
|
||||
// 强制清理所有相关对象
|
||||
if (gearFollower != null)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,10 @@ namespace AibisDream.FixSystem
|
||||
/// Yarn 可能在 Start 里异步加载材质完成前就调用 ModuleHightLight,需记下来待加载后再套材质。
|
||||
/// </summary>
|
||||
private bool _wantsHighlight;
|
||||
private bool _wantsAlarm;
|
||||
|
||||
public bool IsHighlighted => _wantsHighlight;
|
||||
public bool IsAlarmed => _wantsAlarm;
|
||||
|
||||
[SerializeField]
|
||||
private SpriteRenderer targetSpriteRenderer; // 目标的 SpriteRenderer
|
||||
@@ -99,7 +103,7 @@ namespace AibisDream.FixSystem
|
||||
else
|
||||
Debug.LogError("[BodyModule] Failed to load AlarmMaterial");
|
||||
|
||||
ApplyHighlightVisual();
|
||||
ApplyModuleVisual();
|
||||
}
|
||||
|
||||
#region 插槽功能
|
||||
@@ -165,23 +169,25 @@ namespace AibisDream.FixSystem
|
||||
public void Highlight(bool enable)
|
||||
{
|
||||
_wantsHighlight = enable;
|
||||
ApplyHighlightVisual();
|
||||
}
|
||||
|
||||
private void ApplyHighlightVisual()
|
||||
{
|
||||
if (targetSpriteRenderer == null || _highlightMaterial == null) return;
|
||||
if (_alarmMaterial != null && targetSpriteRenderer.material == _alarmMaterial) return;
|
||||
|
||||
targetSpriteRenderer.material = _wantsHighlight ? _highlightMaterial : _originalMaterial;
|
||||
ApplyModuleVisual();
|
||||
}
|
||||
|
||||
public void Alarm(bool enable)
|
||||
{
|
||||
if (targetSpriteRenderer != null && _alarmMaterial != null)
|
||||
{
|
||||
targetSpriteRenderer.material = enable ? _alarmMaterial : _originalMaterial;
|
||||
}
|
||||
_wantsAlarm = enable;
|
||||
ApplyModuleVisual();
|
||||
}
|
||||
|
||||
private void ApplyModuleVisual()
|
||||
{
|
||||
if (targetSpriteRenderer == null) return;
|
||||
|
||||
if (_wantsAlarm && _alarmMaterial != null)
|
||||
targetSpriteRenderer.material = _alarmMaterial;
|
||||
else if (_wantsHighlight && _highlightMaterial != null)
|
||||
targetSpriteRenderer.material = _highlightMaterial;
|
||||
else
|
||||
targetSpriteRenderer.material = _originalMaterial;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream;
|
||||
using System;
|
||||
|
||||
using AibisDream.SaveSystem;
|
||||
|
||||
public class ChipModule : MonoBehaviour
|
||||
{
|
||||
private GameObject _chipModule;
|
||||
|
||||
private ChipData _chipData=new ();
|
||||
private bool _isChipRemoved;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 将数据注册到数据容器内
|
||||
StorageSystem.Instance.RegisterData(_chipData);
|
||||
// 添加加载逻辑
|
||||
_chipData.LoadEvent += LoadData;
|
||||
_chipModule = transform.Find("Chip").gameObject;
|
||||
}
|
||||
// Start is called before the first frame update
|
||||
|
||||
|
||||
public void RemoveChipModule()
|
||||
{
|
||||
@@ -30,7 +18,7 @@ public class ChipModule : MonoBehaviour
|
||||
_chipModule.SetActive(false);
|
||||
}
|
||||
|
||||
_chipData.isChipRemoved = true;
|
||||
_isChipRemoved = true;
|
||||
}
|
||||
|
||||
public void InstallChipModule()
|
||||
@@ -40,35 +28,29 @@ public class ChipModule : MonoBehaviour
|
||||
_chipModule.SetActive(true);
|
||||
}
|
||||
|
||||
_chipData.isChipRemoved = false;
|
||||
_isChipRemoved = false;
|
||||
}
|
||||
|
||||
public void CaptureState(BodyModuleSnapshotDto dto)
|
||||
{
|
||||
if (dto == null) return;
|
||||
dto.isChipRemoved = _isChipRemoved;
|
||||
}
|
||||
|
||||
public void ApplyState(BodyModuleSnapshotDto dto)
|
||||
{
|
||||
if (dto == null) return;
|
||||
_isChipRemoved = dto.isChipRemoved;
|
||||
Chipinit();
|
||||
}
|
||||
|
||||
public void Chipinit()
|
||||
{
|
||||
_chipModule.SetActive(!_chipData.isChipRemoved);
|
||||
_chipModule.SetActive(!_isChipRemoved);
|
||||
}
|
||||
|
||||
|
||||
private void LoadData(ChipData ufData)
|
||||
{
|
||||
Chipinit(); // 读取存档文件时触发,将当前系统恢复为目标状态
|
||||
}
|
||||
|
||||
void Start()
|
||||
private void Start()
|
||||
{
|
||||
Chipinit();
|
||||
}
|
||||
}
|
||||
|
||||
public class ChipData : IData
|
||||
{
|
||||
public bool isChipRemoved = false;
|
||||
|
||||
public event Action<ChipData> LoadEvent;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
LoadEvent?.Invoke(this);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,19 @@
|
||||
using UnityEngine;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.SaveSystem;
|
||||
|
||||
public class UFModule : MonoBehaviour
|
||||
{
|
||||
private GameObject _ufCover;
|
||||
private GameObject _ufModule;
|
||||
|
||||
private GameObject _ufSocket;
|
||||
|
||||
private GameObject _ufColor;
|
||||
private UFData _ufData = new();
|
||||
|
||||
private bool _isUFCoverRemoved;
|
||||
private bool _isUFRemoved;
|
||||
private bool _isColorRemoved;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 将数据注册到数据容器内
|
||||
StorageSystem.Instance.RegisterData(_ufData);
|
||||
// 添加加载逻辑
|
||||
_ufData.LoadEvent += LoadData;
|
||||
_ufModule = transform.Find("Module Pic").gameObject;
|
||||
_ufSocket = transform.Find("Socket").gameObject;
|
||||
_ufCover = _ufModule.transform.Find("Cover").gameObject;
|
||||
@@ -33,7 +27,7 @@ public class UFModule : MonoBehaviour
|
||||
_ufCover.SetActive(false);
|
||||
}
|
||||
|
||||
_ufData.isUFCoverRemoved = true;
|
||||
_isUFCoverRemoved = true;
|
||||
}
|
||||
|
||||
public void RemoveUFModule()
|
||||
@@ -42,10 +36,9 @@ public class UFModule : MonoBehaviour
|
||||
{
|
||||
_ufModule.SetActive(false);
|
||||
_ufSocket.SetActive(false);
|
||||
|
||||
}
|
||||
|
||||
_ufData.isUFRemoved = true;
|
||||
_isUFRemoved = true;
|
||||
}
|
||||
|
||||
public void InstallUFCover()
|
||||
@@ -55,7 +48,7 @@ public class UFModule : MonoBehaviour
|
||||
_ufCover.SetActive(true);
|
||||
}
|
||||
|
||||
_ufData.isUFCoverRemoved = false;
|
||||
_isUFCoverRemoved = false;
|
||||
}
|
||||
|
||||
public void InstallUFModule()
|
||||
@@ -66,8 +59,9 @@ public class UFModule : MonoBehaviour
|
||||
_ufSocket.SetActive(true);
|
||||
}
|
||||
|
||||
_ufData.isUFRemoved = false;
|
||||
_isUFRemoved = false;
|
||||
}
|
||||
|
||||
public void InstallUFColor()
|
||||
{
|
||||
if (!_ufColor.activeInHierarchy)
|
||||
@@ -75,8 +69,9 @@ public class UFModule : MonoBehaviour
|
||||
_ufColor.SetActive(true);
|
||||
}
|
||||
|
||||
_ufData.isColorRemoved = false;
|
||||
_isColorRemoved = false;
|
||||
}
|
||||
|
||||
public void RemoveUFColor()
|
||||
{
|
||||
if (_ufColor.activeInHierarchy)
|
||||
@@ -84,42 +79,36 @@ public class UFModule : MonoBehaviour
|
||||
_ufColor.SetActive(false);
|
||||
}
|
||||
|
||||
_ufData.isColorRemoved = true;
|
||||
_isColorRemoved = true;
|
||||
}
|
||||
|
||||
public void CaptureState(BodyModuleSnapshotDto dto)
|
||||
{
|
||||
if (dto == null) return;
|
||||
dto.isUFCoverRemoved = _isUFCoverRemoved;
|
||||
dto.isUFRemoved = _isUFRemoved;
|
||||
dto.isColorRemoved = _isColorRemoved;
|
||||
}
|
||||
|
||||
public void ApplyState(BodyModuleSnapshotDto dto)
|
||||
{
|
||||
if (dto == null) return;
|
||||
_isUFCoverRemoved = dto.isUFCoverRemoved;
|
||||
_isUFRemoved = dto.isUFRemoved;
|
||||
_isColorRemoved = dto.isColorRemoved;
|
||||
UFinit();
|
||||
}
|
||||
|
||||
public void UFinit()
|
||||
{
|
||||
_ufModule.SetActive(!_ufData.isUFRemoved);
|
||||
_ufSocket.SetActive(!_ufData.isUFRemoved);
|
||||
_ufCover.SetActive(!_ufData.isUFCoverRemoved);
|
||||
_ufColor.SetActive(!_ufData.isColorRemoved);
|
||||
_ufModule.SetActive(!_isUFRemoved);
|
||||
_ufSocket.SetActive(!_isUFRemoved);
|
||||
_ufCover.SetActive(!_isUFCoverRemoved);
|
||||
_ufColor.SetActive(!_isColorRemoved);
|
||||
}
|
||||
|
||||
|
||||
private void LoadData(UFData ufData)
|
||||
{
|
||||
UFinit(); // 读取存档文件时触发,将当前系统恢复为目标状态
|
||||
}
|
||||
|
||||
void Start()
|
||||
private void Start()
|
||||
{
|
||||
UFinit();
|
||||
}
|
||||
}
|
||||
|
||||
public class UFData : IData
|
||||
{
|
||||
public bool isUFCoverRemoved = false;
|
||||
public bool isUFRemoved = false;
|
||||
|
||||
public bool isColorRemoved = false;
|
||||
|
||||
public event Action<UFData> LoadEvent;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
LoadEvent?.Invoke(this);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,32 @@ namespace AibisDream.FixSystem
|
||||
OnPullUpSocket?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>读档/静默:仅恢复插头精灵可见,不触发 CablePanel 事件。</summary>
|
||||
public void PullUpSocketSilent()
|
||||
{
|
||||
_sprite.enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>读档:无动画回到初始位。</summary>
|
||||
public void RestoreAtStartPositionImmediate()
|
||||
{
|
||||
transform.rotation = _defaultRotation;
|
||||
transform.position = _startPos;
|
||||
}
|
||||
|
||||
/// <summary>读档:无动画插入指定 socket 的视觉终态。</summary>
|
||||
public void RestoreInsertedAt(Transform socketPos)
|
||||
{
|
||||
if (socketPos == null) return;
|
||||
transform.position = socketPos.position;
|
||||
_sprite.enabled = false;
|
||||
}
|
||||
|
||||
public void SetSpriteVisible(bool visible)
|
||||
{
|
||||
_sprite.enabled = visible;
|
||||
}
|
||||
|
||||
private void OnDrag(BaseEventData eventData)
|
||||
{
|
||||
if (!_isDragging) return;
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class ClinicSystem : MonoBehaviour
|
||||
{
|
||||
[HideInInspector] public BubbleSlotGroup bubbleSlotGroup;
|
||||
|
||||
{
|
||||
private void Awake()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
bubbleSlotGroup = transform.Find("Bubble Slot Group").GetComponent<BubbleSlotGroup>();
|
||||
|
||||
// 注册Camera
|
||||
var clinicCam = transform.Find("Clinic Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.Clinic, clinicCam);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Unregister<ClinicSystem>();
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.Clinic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using AibisDream.Framework;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Runtime registry for systems available to the Fix scene director and cues.
|
||||
/// The old SystemDic field remains as a compatibility bridge while call sites migrate.
|
||||
/// </summary>
|
||||
public class FixSceneContext
|
||||
{
|
||||
public IOCContainer Registry { get; }
|
||||
|
||||
public FixSceneContext(IOCContainer registry)
|
||||
{
|
||||
Registry = registry;
|
||||
}
|
||||
|
||||
public void Register<T>(T instance)
|
||||
{
|
||||
Registry.Register(instance);
|
||||
}
|
||||
|
||||
public T Get<T>() where T : class
|
||||
{
|
||||
return Registry.Get<T>();
|
||||
}
|
||||
|
||||
public bool TryGet<T>(out T instance) where T : class
|
||||
{
|
||||
instance = Registry.Get<T>();
|
||||
return instance != null;
|
||||
}
|
||||
|
||||
public T Unregister<T>() where T : class
|
||||
{
|
||||
return Registry.Unregister<T>();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Registry.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f04f3d09ebc4c7499d59fb3ab6c7f4a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Directs Fix scene mode transitions. This is a scene cue board, not a gameplay state machine:
|
||||
/// cues orchestrate cameras, timelines, UI masks, bubbles and interaction setup.
|
||||
/// </summary>
|
||||
public class FixSceneDirector
|
||||
{
|
||||
public FixSceneMode CurrentMode { get; private set; }
|
||||
public string CurrentArgs { get; private set; } = "";
|
||||
public bool HasMode { get; private set; }
|
||||
public bool IsTransitioning { get; private set; }
|
||||
|
||||
private IFixSceneCue _currentCue;
|
||||
|
||||
public IEnumerator Init(FixSceneMode initMode)
|
||||
{
|
||||
yield return TransitionInternal(initMode, "", FixTransitionMode.Animated);
|
||||
}
|
||||
|
||||
public IEnumerator TransitionTo(FixSceneMode nextMode, string args = "")
|
||||
{
|
||||
yield return TransitionInternal(nextMode, args, FixTransitionMode.Animated);
|
||||
}
|
||||
|
||||
public IEnumerator TransitionToImmediate(FixSceneMode nextMode, string args = "")
|
||||
{
|
||||
yield return TransitionInternal(nextMode, args, FixTransitionMode.Immediate);
|
||||
}
|
||||
|
||||
public IEnumerator QuitCurrentMode()
|
||||
{
|
||||
if (_currentCue != null)
|
||||
{
|
||||
var transition = new FixTransition(CurrentMode, FixSceneMode.Default, "", FixTransitionMode.Animated);
|
||||
yield return _currentCue.Exit(transition);
|
||||
}
|
||||
|
||||
CurrentArgs = "";
|
||||
_currentCue = null;
|
||||
CurrentMode = FixSceneMode.Default;
|
||||
HasMode = false;
|
||||
IsTransitioning = false;
|
||||
}
|
||||
|
||||
private IEnumerator TransitionInternal(FixSceneMode nextMode, string args, FixTransitionMode mode)
|
||||
{
|
||||
if (IsTransitioning && mode == FixTransitionMode.Animated)
|
||||
{
|
||||
Debug.LogWarning($"[FixSceneDirector] Transition ignored while another transition is running: {CurrentMode} -> {nextMode}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (HasMode && CurrentMode.Equals(nextMode))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
IsTransitioning = true;
|
||||
|
||||
var transition = new FixTransition(HasMode ? CurrentMode : null, nextMode, args, mode);
|
||||
|
||||
if (mode == FixTransitionMode.Animated && _currentCue != null)
|
||||
{
|
||||
yield return _currentCue.Exit(transition);
|
||||
}
|
||||
|
||||
_currentCue = CreateCue(nextMode);
|
||||
CurrentMode = nextMode;
|
||||
CurrentArgs = args ?? "";
|
||||
HasMode = true;
|
||||
|
||||
EnumEventSystem.Global.Send(EventEnum.FixSceneModeChanged, nextMode);
|
||||
|
||||
if (mode == FixTransitionMode.Immediate)
|
||||
{
|
||||
yield return _currentCue.EnterImmediate(transition);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return _currentCue.Enter(transition);
|
||||
}
|
||||
|
||||
IsTransitioning = false;
|
||||
}
|
||||
|
||||
private static IFixSceneCue CreateCue(FixSceneMode nextMode)
|
||||
{
|
||||
return nextMode switch
|
||||
{
|
||||
FixSceneMode.Default => new DefaultCue(),
|
||||
FixSceneMode.Clinic => new ClinicCue(),
|
||||
FixSceneMode.BodyModule => new BodyModuleCue(),
|
||||
FixSceneMode.Memory => new MemoryCue(),
|
||||
FixSceneMode.Eye => new EyeCue(),
|
||||
FixSceneMode.UF => new UfCue(),
|
||||
FixSceneMode.Expression => new ExpressionCue(),
|
||||
FixSceneMode.Emo => new EmoCue(),
|
||||
FixSceneMode.Op => new OpCue(),
|
||||
FixSceneMode.EmoCut => new EmoCutCue(),
|
||||
FixSceneMode.MemoryCut => new MemoryCutCue(),
|
||||
FixSceneMode.LogicCut => new LogicCutCue(),
|
||||
FixSceneMode.Dream => new DreamCue(),
|
||||
FixSceneMode.AnalysisMode => new AnalysisModeCue(),
|
||||
FixSceneMode.Sales => new SalesCue(),
|
||||
FixSceneMode.BlockPuzzle => new BlockPuzzleCue(),
|
||||
_ => new DefaultCue()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,793 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.UI;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class FixStateMachine
|
||||
{
|
||||
public FixState CurState => _curState.GetState;
|
||||
|
||||
private IState<FixState> _curState;
|
||||
|
||||
private StateMachine<FixState> _stateMachine;
|
||||
|
||||
private FixSystemData _data;
|
||||
|
||||
public FixStateMachine(FixSystemData data)
|
||||
{
|
||||
_data = data;
|
||||
_stateMachine = new StateMachine<FixState>(CreateState);
|
||||
_stateMachine.OnStateSwitch += state => EnumEventSystem.Global.Send(EventEnum.FixStateSwitch, state);
|
||||
}
|
||||
|
||||
public IEnumerator Init(FixState initState)
|
||||
{
|
||||
_data.curState = initState;
|
||||
_data.args = "";
|
||||
yield return _stateMachine.Init(initState);
|
||||
}
|
||||
|
||||
public IEnumerator SwitchState(FixState nextState, string args = "")
|
||||
{
|
||||
_data.curState = nextState;
|
||||
_data.args = args;
|
||||
yield return _stateMachine.SwitchState(nextState, args);
|
||||
}
|
||||
|
||||
public IEnumerator QuitState()
|
||||
{
|
||||
_data.curState = FixState.Default;
|
||||
yield return _stateMachine.QuitState();
|
||||
}
|
||||
|
||||
private static IState<FixState> CreateState(FixState nextState, string args)
|
||||
{
|
||||
IState<FixState> res = nextState switch
|
||||
{
|
||||
FixState.Default => new DefaultState(),
|
||||
FixState.Clinic => new ClinicState(),
|
||||
FixState.BodyModule => new BodyModuleState(),
|
||||
FixState.Memory => new MemoryState(),
|
||||
FixState.Eye => new EyeState(),
|
||||
FixState.UF => new UfState(),
|
||||
FixState.Expression => new ExpressionState(),
|
||||
FixState.Emo => new EmoState(),
|
||||
FixState.Op => new OpState(),
|
||||
FixState.EmoCut => new EmoCutState(),
|
||||
FixState.MemoryCut => new MemoryCutState(),
|
||||
FixState.LogicCut => new LogicCutState(),
|
||||
FixState.Dream => new DreamState(),
|
||||
FixState.AnalysisMode => new AnalysisModeState(),
|
||||
FixState.Sales => new SalesState(),
|
||||
FixState.BlockPuzzle => new BlockPuzzleState(),
|
||||
_ => new DefaultState()
|
||||
};
|
||||
|
||||
res.SetArgs(args);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FixState
|
||||
{
|
||||
Default = 0,
|
||||
Clinic = 1,
|
||||
BodyModule = 2,
|
||||
Memory = 3,
|
||||
Eye = 4,
|
||||
UF = 5,
|
||||
Expression = 6,
|
||||
Emo = 9,
|
||||
Op = 10,
|
||||
EmoCut = 11,
|
||||
MemoryCut = 12,
|
||||
LogicCut = 13,
|
||||
Dream = 14,
|
||||
AnalysisMode = 15,
|
||||
Sales = 16,
|
||||
BlockPuzzle = 17
|
||||
}
|
||||
|
||||
public struct DefaultState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.Default;
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
CameraKit.Instance.SwitchCamera(CameraEnum.Clinic, true);
|
||||
var clinicSystem = FixSystemCenter.SystemDic.Get<ClinicSystem>();
|
||||
var data = clinicSystem.bubbleSlotGroup.CollectData();
|
||||
DialogUIManager.Instance.LoadBubbles(data);
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ClinicState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.Clinic;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 无
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
// 先加载对话气泡,避免 Yarn 对话(如 测试 节点 me: 释放log)在相机切换完成前执行时找不到 Player 槽位
|
||||
var clinicSystem = FixSystemCenter.SystemDic.Get<ClinicSystem>();
|
||||
// 切换相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Clinic);
|
||||
var data = clinicSystem.bubbleSlotGroup.CollectData();
|
||||
DialogUIManager.Instance.LoadBubbles(data);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
// 无
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct BodyModuleState : IState<FixState>
|
||||
{
|
||||
private string _args;
|
||||
|
||||
public FixState GetState => FixState.BodyModule;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 设置参数
|
||||
_args = args;
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var cablePanel = FixPanelSystem.GetRepairPanel<CablePanel>();
|
||||
// 拔出插头
|
||||
// 切换Body或Head
|
||||
if (Enum.TryParse<ModuleState>(_args, out var moduleState))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(moduleState);
|
||||
}
|
||||
|
||||
if (cablePanel != null && !FixPanelSystem.Instance.IsSystemOn)
|
||||
{
|
||||
// 切换相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModuleCenter);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
yield return FixPanelSystem.Instance.StartAllSystems();
|
||||
}
|
||||
else
|
||||
{
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
// 加载气泡
|
||||
var data = FixPanelSystem.Instance.BubbleSlotGroup.CollectData();
|
||||
DialogUIManager.Instance.LoadBubbles(data);
|
||||
|
||||
// 开启交互
|
||||
bodyModuleSystem.EnableSystem();
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
// 拔出插头
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
// 关闭交互
|
||||
bodyModuleSystem.DisableSystem();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct UfState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.UF;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var ufSystem = FixSystemCenter.SystemDic.Get<UFSystem>();
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
// 先切BodyModule
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
// 插头回到初始插孔
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
// 切换相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.UF);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
ufSystem.OpenUFView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EyeDeep);
|
||||
eyeSystem.OnEnter();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
var data = eyeSystem.bubbleSlotGroup.CollectData();
|
||||
DialogUIManager.Instance.LoadBubbles(data);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var ufSystem = FixSystemCenter.SystemDic.Get<UFSystem>();
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
eyeSystem.OnExit();
|
||||
ufSystem.CloseUFView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.UF);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
}
|
||||
}
|
||||
|
||||
public struct EyeState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.Eye;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
// 先切BodyModule
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
//打开盖子
|
||||
var eyeModule = bodyModuleSystem.FindBodyModuleByType<EyeModule>();
|
||||
yield return eyeModule.OpenEye();
|
||||
|
||||
// 插头回到初始插孔
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
// 切换相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Eye);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EyeDeep);
|
||||
eyeSystem.OnEnter();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
|
||||
var data = eyeSystem.bubbleSlotGroup.CollectData();
|
||||
DialogUIManager.Instance.LoadBubbles(data);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
float duration = 0.5f;
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Eye);
|
||||
eyeSystem.OnExit();
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
//关上
|
||||
var eyeModule = bodyModuleSystem.FindBodyModuleByType<EyeModule>();
|
||||
yield return eyeModule.CloseEye();
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public struct MemoryState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.Memory;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var memorySystem = FixSystemCenter.SystemDic.Get<MemoryProcess>();
|
||||
// 先切BodyModule
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
yield return memorySystem.DropMemoryProjector();
|
||||
// 插头回到初始插孔
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
// 相机聚焦到目标位
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Memory);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
memorySystem.OnEnter();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
var data = memorySystem.BubbleSlotGroup.CollectData();
|
||||
DialogUIManager.Instance.LoadBubbles(data);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var memorySystem = FixSystemCenter.SystemDic.Get<MemoryProcess>();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
memorySystem.OnExit();
|
||||
memorySystem.CloseMemoryView();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
memorySystem.PullMemoryProjector();
|
||||
}
|
||||
}
|
||||
|
||||
public struct ExpressionState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.Expression;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 无
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
// 获取表达系统
|
||||
var expressionManager = FixSystemCenter.SystemDic.Get<ExpressionManager>();
|
||||
|
||||
// 先切 BodyModule(如果需要)
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
var bodyModule = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
bodyModule.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
// 开启表达系统视图,并播放火山表达深入
|
||||
// 切换到表达相机
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("透镜/透镜in");
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Expression);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
expressionManager.OpenView();
|
||||
// 切换到深度表达相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.ExpressionDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("火山表达模块/火山表达深入");
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
var expressionManager = FixSystemCenter.SystemDic.Get<ExpressionManager>();
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
|
||||
// 重置身体模块
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
|
||||
// 关闭表达系统视图
|
||||
expressionManager.CloseView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Expression);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("透镜/透镜out");
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct EmoState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.Emo;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var emoManager = FixSystemCenter.SystemDic.Get<EmoManager>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
var bodyModule = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
bodyModule.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Emo);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
//emoManager.OpenView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EmoDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct OpState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.Op;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 无
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.Op))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Op);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
// 无
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct DreamState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.Dream;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 无
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.DreamCamera))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.DreamCamera);
|
||||
|
||||
var openSystem = FixSystemCenter.SystemDic.Get<OpenSystem>();
|
||||
DialogUIManager.Instance.LoadBubbles(openSystem.bubbleSlotGroup.CollectData());
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
// 无
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct EmoCutState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.EmoCut;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 无
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
// Cut的气泡统一放在BodyModule下
|
||||
DialogUIManager.Instance.LoadBubbles(cuttingManager.bubbleSlotGroup.CollectData());
|
||||
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
var bodyModule = bodyModuleSystem.FindBodyModuleByName("Emo");
|
||||
if (bodyModule != null)
|
||||
{
|
||||
bodyModule.GetCutSpriteRenderer().sortingOrder = 100;
|
||||
bodyModule.GetCutTextSpriteRenderer().sortingOrder = 101;
|
||||
bodyModule.GetCutSpriteRenderer().color = new Color(1f, 1f, 1f, 1f);
|
||||
bodyModule.GetCutSpriteRenderer().GetComponentInChildren<SpriteRenderer>().color =
|
||||
new Color(1f, 1f, 1f, 1f);
|
||||
}
|
||||
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutEmo);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutEmoDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
yield return cuttingManager.StartCutting(bodyModule);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
// 无
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
cuttingManager.ClearCutLine();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutEmo);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
public struct MemoryCutState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.MemoryCut;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 无
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
// Cut的气泡统一放在BodyModule下
|
||||
DialogUIManager.Instance.LoadBubbles(cuttingManager.bubbleSlotGroup.CollectData());
|
||||
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
var bodyModule = bodyModuleSystem.FindBodyModuleByName("Memory");
|
||||
if (bodyModule != null)
|
||||
{
|
||||
bodyModule.GetCutSpriteRenderer().sortingOrder = 100;
|
||||
bodyModule.GetCutTextSpriteRenderer().sortingOrder = 101;
|
||||
bodyModule.GetCutSpriteRenderer().color = new Color(1f, 1f, 1f, 1f);
|
||||
bodyModule.GetCutSpriteRenderer().GetComponentInChildren<SpriteRenderer>().color =
|
||||
new Color(1f, 1f, 1f, 1f);
|
||||
}
|
||||
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutMemory);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutMemoryDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
yield return cuttingManager.StartCutting(bodyModule);
|
||||
}
|
||||
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
cuttingManager.ClearCutLine();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutMemory);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
public struct LogicCutState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.LogicCut;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 无
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
// Cut的气泡统一放在BodyModule下
|
||||
DialogUIManager.Instance.LoadBubbles(cuttingManager.bubbleSlotGroup.CollectData());
|
||||
|
||||
var bodyModule = bodyModuleSystem.FindBodyModuleByName("Logic");
|
||||
if (bodyModule != null)
|
||||
{
|
||||
bodyModule.GetCutSpriteRenderer().sortingOrder = 100;
|
||||
bodyModule.GetCutTextSpriteRenderer().sortingOrder = 101;
|
||||
bodyModule.GetCutSpriteRenderer().color = new Color(1f, 1f, 1f, 1f);
|
||||
bodyModule.GetCutSpriteRenderer().GetComponentInChildren<SpriteRenderer>().color =
|
||||
new Color(1f, 1f, 1f, 1f);
|
||||
}
|
||||
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutLogic);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutLogicDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
yield return cuttingManager.StartCutting(bodyModule);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
cuttingManager.ClearCutLine();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutLogic);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
public struct AnalysisModeState : IState<FixState>
|
||||
{
|
||||
public FixState GetState => FixState.AnalysisMode;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
// 无
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var analysisModeManager = FixSystemCenter.SystemDic.Get<MiniGame.Language.AnalysisModeManager>();
|
||||
|
||||
// 先切 BodyModule(如果需要)
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
// 插头回到初始插孔
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
|
||||
// 切换到分析模式相机
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.AnalysisMode);
|
||||
// 进入分析模式
|
||||
if (analysisModeManager != null)
|
||||
{
|
||||
analysisModeManager.OnEnter();
|
||||
}
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var analysisModeManager = FixSystemCenter.SystemDic.Get<MiniGame.Language.AnalysisModeManager>();
|
||||
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
|
||||
// 退出分析模式
|
||||
if (analysisModeManager != null)
|
||||
{
|
||||
analysisModeManager.OnExit();
|
||||
}
|
||||
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
}
|
||||
}
|
||||
|
||||
public struct SalesState : IState<FixState>
|
||||
{
|
||||
private string _systemView;
|
||||
|
||||
public FixState GetState => FixState.Sales;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
_systemView = args;
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
// 进入前先播放钳子摘盖子,完成后深入
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("钳子/摘走盖子");
|
||||
|
||||
// 获取销售系统管理器
|
||||
var salesManager = FixSystemCenter.SystemDic.Get<AibisDream.MiniGame.Language.SalesManager>();
|
||||
if (salesManager != null)
|
||||
{
|
||||
// FadeIn
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
|
||||
// 打开销售系统视图,传入systemView参数
|
||||
salesManager.OpenView();
|
||||
|
||||
// FadeOut
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
|
||||
// 销售模块使用自身的气泡槽位;缺失时再回退到 ScreenSystem,避免读到错误的槽位配置
|
||||
if (salesManager.bubbleSlotGroup != null)
|
||||
{
|
||||
var data = salesManager.bubbleSlotGroup.CollectData();
|
||||
Debug.Log("[SalesState] Loaded bubble slots from SalesManager.");
|
||||
DialogUIManager.Instance.LoadBubbles(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[SalesState] SalesManager.bubbleSlotGroup is null, fallback to ScreenSystem bubble slots.");
|
||||
var data = FixPanelSystem.Instance.BubbleSlotGroup.CollectData();
|
||||
DialogUIManager.Instance.LoadBubbles(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var salesManager = FixSystemCenter.SystemDic.Get<AibisDream.MiniGame.Language.SalesManager>();
|
||||
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
|
||||
// 关闭销售系统视图
|
||||
if (salesManager != null)
|
||||
{
|
||||
salesManager.CloseView();
|
||||
}
|
||||
// 关闭前先播放钳子放下盖子,完成后再结
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("钳子/放下盖子");
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct BlockPuzzleState : IState<FixState>
|
||||
{
|
||||
private const string puzzleName = "Game2";
|
||||
private const float blockPuzzleScale = 1.6f;
|
||||
private static BlockPuzzleSystem BlockPuzzleSystem => FixSystemCenter.SystemDic.Get<BlockPuzzleSystem>();
|
||||
private static BodyModuleSystem BodyModuleSystem => FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
|
||||
public FixState GetState => FixState.BlockPuzzle;
|
||||
|
||||
public void SetArgs(string args)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerator Enter()
|
||||
{
|
||||
// 基本操作(其实应该早就处理好了)
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
var bubbleData = FixPanelSystem.Instance.BubbleSlotGroup.CollectData();
|
||||
DialogUIManager.Instance.LoadBubbles(bubbleData);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync(TimelineAssetRef.EngineModuleEnter);
|
||||
// 视角移动
|
||||
var engineModule = BodyModuleSystem.FindBodyModuleByName("Heart");
|
||||
Tween tween = RepairSystemManager.Instance.ScaleCurrentSystem(blockPuzzleScale, engineModule.transform.position);
|
||||
if (tween != null)
|
||||
{
|
||||
yield return tween.WaitForCompletion();
|
||||
}
|
||||
// 初始化拼图
|
||||
BlockPuzzleSystem.Initialize(puzzleName);
|
||||
// 开启遮罩
|
||||
yield return FixPanelSystem.Instance.MaskFadeIn();
|
||||
RepairSystemManager.Instance.SwitchSystem<BlockPuzzleSystem>();
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
yield return FixPanelSystem.Instance.MaskFadeOut();
|
||||
|
||||
// 播放进场动画
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync(TimelineAssetRef.CablePanelExit);
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync(TimelineAssetRef.BlockPuzzleEnter);
|
||||
// 解锁
|
||||
BlockPuzzleSystem.SetLocked(false);
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,61 +2,81 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class FixSystemCenter : Singleton<FixSystemCenter>
|
||||
{
|
||||
public FixState CurState => StateMachine.CurState;
|
||||
public FixSceneMode CurrentMode => Director.CurrentMode;
|
||||
|
||||
public FixState defaultState = FixState.Default;
|
||||
|
||||
private readonly FixSystemData _data = new();
|
||||
public FixSceneMode defaultMode = FixSceneMode.Default;
|
||||
|
||||
public static IOCContainer SystemDic = new();
|
||||
public static FixSceneContext Context;
|
||||
public static FixSceneDirector Director;
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
SystemDic ??= new IOCContainer();
|
||||
// 初始化状态机
|
||||
StateMachine = new FixStateMachine(_data);
|
||||
StartCoroutine(StateMachine.Init(defaultState));
|
||||
// 注册
|
||||
StorageSystem.Instance.RegisterData(_data);
|
||||
Context = new FixSceneContext(SystemDic);
|
||||
Director = new FixSceneDirector();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (SaveRestoreOrchestrator.IsRestoring)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(Director.Init(defaultMode));
|
||||
}
|
||||
|
||||
public override void OnSingletonDestroy()
|
||||
{
|
||||
// 注销
|
||||
StorageSystem.Instance.UnregisterData<FixSystemData>();
|
||||
CameraKit.Instance.RemoveAllCam();
|
||||
GameManager.Instance.StartCoroutine(StateMachine.QuitState());
|
||||
SystemDic.Clear();
|
||||
BubbleSlotKit.Instance.RemoveAll();
|
||||
GameManager.Instance.StartCoroutine(Director.QuitCurrentMode());
|
||||
Context?.Clear();
|
||||
}
|
||||
|
||||
#region 系统索引
|
||||
|
||||
// 容纳System
|
||||
public static IOCContainer SystemDic = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 状态机相关
|
||||
|
||||
public static FixStateMachine StateMachine;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[LoadIndex(2)]
|
||||
public class FixSystemData : IData
|
||||
{
|
||||
public FixState curState;
|
||||
public string args;
|
||||
|
||||
public IEnumerator Load()
|
||||
public IEnumerator TransitionTo(FixSceneMode nextMode, string args = "")
|
||||
{
|
||||
yield return FixSystemCenter.StateMachine.SwitchState(curState, args);
|
||||
yield return Director.TransitionTo(nextMode, args);
|
||||
}
|
||||
|
||||
public IEnumerator TransitionToImmediate(FixSceneMode nextMode, string args = "")
|
||||
{
|
||||
yield return Director.TransitionToImmediate(nextMode, args);
|
||||
}
|
||||
|
||||
/// <summary>Captures the macro Fix scene cue snapshot.</summary>
|
||||
public FixSnapshotDto CaptureSnapshot()
|
||||
{
|
||||
return new FixSnapshotDto
|
||||
{
|
||||
state = Director.CurrentMode.ToString(),
|
||||
args = Director.CurrentArgs
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Restores the macro Fix scene snapshot by jumping directly to the target cue endpoint.</summary>
|
||||
public IEnumerator RestoreSnapshot(FixSnapshotDto dto)
|
||||
{
|
||||
if (dto == null || string.IsNullOrEmpty(dto.state))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<FixSceneMode>(dto.state, out var mode))
|
||||
{
|
||||
Debug.LogWarning($"[FixSystemCenter] Could not parse FixSceneMode: {dto.state}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return Director.TransitionToImmediate(mode, dto.args ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
|
||||
public static class FixSystemYarnCommand
|
||||
{
|
||||
[YarnCommand("switch_fix_system_to")]
|
||||
public static IEnumerator SwitchFixSystemTo(string targetStateStr, string extraArg = "")
|
||||
public static IEnumerator SwitchFixSystemTo(string targetModeStr, string extraArg = "")
|
||||
{
|
||||
// 字符串转枚举
|
||||
if (!Enum.TryParse<FixState>(targetStateStr, out var targetState))
|
||||
if (!Enum.TryParse<FixSceneMode>(targetModeStr, out var targetMode))
|
||||
{
|
||||
Debug.Log($"{targetStateStr}不在FixState枚举中");
|
||||
Debug.Log($"[Yarn] Unknown FixSceneMode: {targetModeStr}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return FixSystemCenter.StateMachine.SwitchState(targetState, extraArg);
|
||||
yield return FixSystemCenter.Instance.TransitionTo(targetMode, extraArg);
|
||||
}
|
||||
|
||||
#region 维修系统管理器命令
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定的维修系统(按类型名称)
|
||||
/// 用法: <<repair_switch_system "BlockPuzzleSystem">>
|
||||
/// </summary>
|
||||
[YarnCommand("repair_switch_system")]
|
||||
public static IEnumerator RepairSwitchSystem(string systemTypeName)
|
||||
{
|
||||
@@ -39,18 +31,15 @@ namespace AibisDream.FixSystem
|
||||
|
||||
if (systemType == null)
|
||||
{
|
||||
Debug.LogError($"[Yarn] 未知的系统类型: {systemTypeName}");
|
||||
Debug.LogError($"[Yarn] Unknown repair system type: {systemTypeName}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
RepairSystemManager.Instance.SwitchSystem(systemType);
|
||||
|
||||
// 等待切换动画
|
||||
yield return new WaitForSeconds(RepairSystemManager.Instance.GetComponent<RepairSystemManager>()
|
||||
?.GetType().GetField("switchDuration", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
?.GetValue(RepairSystemManager.Instance) is float duration ? duration : 0.5f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public enum FixTransitionMode
|
||||
{
|
||||
Animated,
|
||||
Immediate
|
||||
}
|
||||
|
||||
public readonly struct FixTransition
|
||||
{
|
||||
public readonly FixSceneMode? From;
|
||||
public readonly FixSceneMode To;
|
||||
public readonly string Args;
|
||||
public readonly FixTransitionMode Mode;
|
||||
|
||||
public FixTransition(FixSceneMode? from, FixSceneMode to, string args, FixTransitionMode mode)
|
||||
{
|
||||
From = from;
|
||||
To = to;
|
||||
Args = args ?? "";
|
||||
Mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cue is a directed scene orchestration unit for a Fix mode.
|
||||
/// It owns camera, timeline, UI and interaction setup for entering or leaving that mode.
|
||||
/// </summary>
|
||||
public interface IFixSceneCue
|
||||
{
|
||||
FixSceneMode Mode { get; }
|
||||
|
||||
IEnumerator Enter(FixTransition transition);
|
||||
|
||||
IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
IEnumerator EnterImmediate(FixTransition transition)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d5df9cd63e8db54da758ad165a5a6b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,4 @@
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using Cinemachine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using Yarn.Unity;
|
||||
@@ -18,18 +16,9 @@ namespace AibisDream
|
||||
|
||||
public SpriteRenderer dreamSprite;
|
||||
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 注册
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
var virtualCam = transform.Find("Op Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.Op, virtualCam);
|
||||
var breakCam = transform.Find("Break Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.BreakCamera, breakCam);
|
||||
var dreamCam = transform.Find("Dream Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.DreamCamera, dreamCam);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using UnityEngine;
|
||||
using Cinemachine;
|
||||
using System.Collections;
|
||||
using DG.Tweening;
|
||||
|
||||
@@ -126,14 +126,75 @@ namespace AibisDream.FixSystem
|
||||
|
||||
#endregion
|
||||
|
||||
#region 快照 Capture / Restore
|
||||
|
||||
public FixPanelSnapshotDto CapturePanelSnapshot()
|
||||
{
|
||||
var cablePanel = GetRepairPanel<CablePanel>();
|
||||
var dto = new FixPanelSnapshotDto
|
||||
{
|
||||
isSystemOn = isSystemOn,
|
||||
currentRepairSystemType = repairSystemManager?.GetCurrentSystemType()?.Name,
|
||||
isCableRetracted = cablePanel != null && cablePanel.isCableRetracted
|
||||
};
|
||||
|
||||
if (cablePanel != null && cablePanel.TryGetPluggedModuleName(out var moduleName))
|
||||
{
|
||||
dto.pluggedModuleName = moduleName;
|
||||
}
|
||||
|
||||
if (dto.isCableRetracted && !string.IsNullOrEmpty(dto.pluggedModuleName))
|
||||
{
|
||||
Debug.LogWarning("[FixPanelSystem] Capture: isCableRetracted 与 pluggedModuleName 冲突,忽略 pluggedModuleName。");
|
||||
dto.pluggedModuleName = null;
|
||||
}
|
||||
|
||||
if (taskPanel != null)
|
||||
{
|
||||
taskPanel.CaptureSnapshot(out dto.isTaskPanelVisible, dto.tasks);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
public void ApplyPanelSnapshot(FixPanelSnapshotDto dto)
|
||||
{
|
||||
if (dto == null) return;
|
||||
|
||||
ApplySystemOnImmediate(dto.isSystemOn);
|
||||
|
||||
if (repairSystemManager != null)
|
||||
{
|
||||
repairSystemManager.RestoreSystemType(dto.currentRepairSystemType);
|
||||
}
|
||||
|
||||
GetRepairPanel<CablePanel>()?.ApplyCableSnapshot(dto.isCableRetracted, dto.pluggedModuleName);
|
||||
|
||||
taskPanel?.ApplySnapshot(dto.isTaskPanelVisible, dto.tasks, immediate: true);
|
||||
}
|
||||
|
||||
/// <summary>读档终态:跳过 StartAllSystems 动画,直接写面板壳层状态。</summary>
|
||||
public void ApplySystemOnImmediate(bool isOn)
|
||||
{
|
||||
isSystemOn = isOn;
|
||||
|
||||
if (panelLight != null)
|
||||
{
|
||||
panelLight.Initialize(isOn);
|
||||
}
|
||||
|
||||
var cablePanel = GetRepairPanel<CablePanel>();
|
||||
cablePanel?.InitLight(isOn);
|
||||
|
||||
if (screenPanel != null)
|
||||
{
|
||||
screenPanel.gameObject.SetActive(isOn);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 屏幕相关操作
|
||||
[Header("默认相机")]
|
||||
[SerializeField] private CinemachineVirtualCamera fixPanelCamera;
|
||||
|
||||
[Header("默认气泡")]
|
||||
[SerializeField] private BubbleSlotGroup bubbleSlotGroup;
|
||||
public BubbleSlotGroup BubbleSlotGroup => bubbleSlotGroup;
|
||||
|
||||
[Header("默认画布")]
|
||||
[SerializeField] private Canvas canvas;
|
||||
|
||||
@@ -150,17 +211,12 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void RegisterScreenView()
|
||||
{
|
||||
// 注册相机
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.BodyModule, fixPanelCamera);
|
||||
|
||||
DialogUIManager.Instance.RegisterScreenView(DialogViewType.Screen, screenPanel);
|
||||
DialogUIManager.Instance.RegisterScreenView(DialogViewType.Task, taskPanel);
|
||||
}
|
||||
|
||||
private void UnRegisterScreenView()
|
||||
{
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.BodyModule);
|
||||
|
||||
DialogUIManager.Instance.UnRegisterScreenView(DialogViewType.Screen);
|
||||
DialogUIManager.Instance.UnRegisterScreenView(DialogViewType.Task);
|
||||
}
|
||||
|
||||
@@ -212,6 +212,37 @@ namespace AibisDream.FixSystem
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>读档:按已注册类型名切换中央维修视图(无动画)。</summary>
|
||||
public void RestoreSystemType(string typeName)
|
||||
{
|
||||
Type targetType = typeof(BodyModuleSystem);
|
||||
|
||||
if (!string.IsNullOrEmpty(typeName))
|
||||
{
|
||||
Type matched = null;
|
||||
foreach (var kv in _registeredSystems)
|
||||
{
|
||||
if (kv.Key.Name == typeName)
|
||||
{
|
||||
matched = kv.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched != null)
|
||||
{
|
||||
targetType = matched;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[RepairSystemManager] 未找到已注册系统: {typeName},保持当前类型。");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SwitchSystem(targetType);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public class CablePanel : MonoBehaviour, IOperatorPanel
|
||||
{
|
||||
private const string CableRetractedKey = "$global.cableRetracted";
|
||||
private const int SortingOrderRetracted = 41;
|
||||
|
||||
#region 组件索引
|
||||
@@ -52,9 +51,8 @@ namespace AibisDream.FixSystem
|
||||
private void Start()
|
||||
{
|
||||
InitComponentRefs();
|
||||
|
||||
InitSystem();
|
||||
LoadCableState();
|
||||
InitCableVisualFromPrefab();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@@ -82,37 +80,109 @@ namespace AibisDream.FixSystem
|
||||
plugRef.OnPlugPointerDown += OnPlugPoinerDown;
|
||||
}
|
||||
|
||||
private void LoadCableState()
|
||||
private void InitCableVisualFromPrefab()
|
||||
{
|
||||
// 从Prefab读取
|
||||
if (isCableRetracted)
|
||||
{
|
||||
SetRetractCable();
|
||||
}
|
||||
else
|
||||
{
|
||||
PlugRef.ReturnToStartPosition();
|
||||
}
|
||||
|
||||
// 从存储系统读取
|
||||
if (!StorageSystem.Instance.TryGetValue(CableRetractedKey, out bool savedRetractedState))
|
||||
return;
|
||||
|
||||
if (savedRetractedState)
|
||||
{
|
||||
SetRetractCable();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isCableRetracted)
|
||||
DropDownCable();
|
||||
DropDownCableImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveCableState()
|
||||
public bool TryGetPluggedModuleName(out string moduleName)
|
||||
{
|
||||
Debug.Log("Save cableRetractedstate" + isCableRetracted);
|
||||
StorageSystem.Instance.SetValue(CableRetractedKey, isCableRetracted);
|
||||
if (curSocket is BodyModule bodyModule)
|
||||
{
|
||||
moduleName = bodyModule.data.moduleName;
|
||||
return !string.IsNullOrEmpty(moduleName);
|
||||
}
|
||||
|
||||
moduleName = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>读档:同步写回线缆与插头终态,无动画、无 Yarn、无 PlugIn 事件。</summary>
|
||||
public void ApplyCableSnapshot(bool retracted, string pluggedModuleName)
|
||||
{
|
||||
SilentPullUpSocket();
|
||||
|
||||
if (retracted)
|
||||
{
|
||||
if (ShouldSkipRetractOnRestore())
|
||||
{
|
||||
if (isCableRetracted)
|
||||
{
|
||||
DropDownCableImmediate();
|
||||
}
|
||||
|
||||
curSocket = null;
|
||||
return;
|
||||
}
|
||||
|
||||
SetRetractCable();
|
||||
curSocket = null;
|
||||
return;
|
||||
}
|
||||
|
||||
DropDownCableImmediate();
|
||||
|
||||
if (!string.IsNullOrEmpty(pluggedModuleName))
|
||||
{
|
||||
RestorePlugToModule(pluggedModuleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
curSocket = null;
|
||||
SetCableMode(CableMode.Physic);
|
||||
}
|
||||
}
|
||||
|
||||
public void DropDownCableImmediate()
|
||||
{
|
||||
PlugRef.PullUpSocketSilent();
|
||||
CableReelRef.ResetRotation();
|
||||
PlugRef.RestoreAtStartPositionImmediate();
|
||||
SetCableMode(CableMode.Physic);
|
||||
isCableRetracted = false;
|
||||
}
|
||||
|
||||
private void RestorePlugToModule(string moduleName)
|
||||
{
|
||||
var module = BodyModuleSystem?.FindBodyModuleByName(moduleName);
|
||||
if (module == null)
|
||||
{
|
||||
Debug.LogWarning($"[CablePanel] RestorePlugToModule: 未找到模块 {moduleName},插头留在初始位。");
|
||||
curSocket = null;
|
||||
SetCableMode(CableMode.Physic);
|
||||
return;
|
||||
}
|
||||
|
||||
curSocket = module;
|
||||
var socketPos = module.GetSocketPos();
|
||||
PlugRef.RestoreInsertedAt(socketPos);
|
||||
CableRef.SetEndPos(socketPos);
|
||||
SetCableMode(CableMode.Visual);
|
||||
BodyModuleSystem.CloseModuleSlots(null);
|
||||
}
|
||||
|
||||
private void SilentPullUpSocket()
|
||||
{
|
||||
if (curSocket == null) return;
|
||||
|
||||
CableRef.SetEndPos(PlugRef.PlugRoot);
|
||||
curSocket.PlugOut();
|
||||
curSocket = null;
|
||||
PlugRef.SetSpriteVisible(true);
|
||||
}
|
||||
|
||||
private static bool ShouldSkipRetractOnRestore()
|
||||
{
|
||||
return FixSystemCenter.Instance != null
|
||||
&& FixSystemCenter.Director.HasMode
|
||||
&& FixSystemCenter.Director.CurrentMode == FixSceneMode.BodyModule;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@@ -126,10 +196,14 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public void ResetPlug()
|
||||
{
|
||||
// 不在初始插孔里,就从当前插孔拔出来,插回初始插孔
|
||||
PlugRef.PullUpSocket();
|
||||
if (IsPlugInSocket())
|
||||
{
|
||||
PlugRef.PullUpSocket();
|
||||
}
|
||||
|
||||
BodyModuleSystem.CloseModuleSlots(null);
|
||||
PlugRef.ReturnToStartPosition();
|
||||
DropDownCableImmediate();
|
||||
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mods_close");
|
||||
AudioManager.Instance.PlaySfx("event:/FollowInput/plugdrop");
|
||||
}
|
||||
@@ -245,7 +319,6 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
ApplyRetractedState();
|
||||
isCableRetracted = true;
|
||||
SaveCableState();
|
||||
});
|
||||
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mods_close");
|
||||
@@ -262,7 +335,6 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
SetCableMode(CableMode.Physic);
|
||||
isCableRetracted = false;
|
||||
SaveCableState();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.Utility;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
@@ -10,6 +11,9 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
public class TaskPanel : MonoBehaviour, ILineView
|
||||
{
|
||||
private const float HiddenLocalX = -10.32f;
|
||||
private const float ShownLocalX = -7.12f;
|
||||
|
||||
#region 索引
|
||||
|
||||
[SerializeField] private Transform taskRoot;
|
||||
@@ -26,8 +30,9 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public void ShowPanel()
|
||||
{
|
||||
taskRoot.DOLocalMoveX(-7.12f, 1f);
|
||||
taskRoot.DOLocalMoveX(ShownLocalX, 1f);
|
||||
}
|
||||
|
||||
public void CompleteTask(string lineId)
|
||||
{
|
||||
if (_taskDict.Remove(lineId, out var taskItem))
|
||||
@@ -54,15 +59,70 @@ namespace AibisDream.FixSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var taskItem = Instantiate(_taskItemPrefab, taskListParent);
|
||||
var task = taskItem.GetComponent<TMP_Text>();
|
||||
task.text = $"\u25cf {token.lineInfo.lineText}";
|
||||
AddTaskItem(token.lineInfo.lineId, $"\u25cf {token.lineInfo.lineText}");
|
||||
token.TextStart();
|
||||
|
||||
_taskDict[token.lineInfo.lineId] = taskItem;
|
||||
token.TextShown();
|
||||
token.ForceAdvance();
|
||||
}
|
||||
|
||||
public void CaptureSnapshot(out bool isPanelVisible, List<TaskEntrySnapshotDto> tasks)
|
||||
{
|
||||
isPanelVisible = Mathf.Approximately(taskRoot.localPosition.x, ShownLocalX);
|
||||
tasks.Clear();
|
||||
|
||||
foreach (var kv in _taskDict)
|
||||
{
|
||||
var text = kv.Value.GetComponent<TMP_Text>()?.text ?? string.Empty;
|
||||
tasks.Add(new TaskEntrySnapshotDto
|
||||
{
|
||||
lineId = kv.Key,
|
||||
text = text
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplySnapshot(bool isPanelVisible, List<TaskEntrySnapshotDto> tasks, bool immediate)
|
||||
{
|
||||
ClearTasks();
|
||||
|
||||
if (tasks != null)
|
||||
{
|
||||
foreach (var entry in tasks)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entry.lineId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddTaskItem(entry.lineId, entry.text);
|
||||
}
|
||||
}
|
||||
|
||||
if (immediate)
|
||||
{
|
||||
taskRoot.DOKill();
|
||||
SetPanelVisibleImmediate(isPanelVisible);
|
||||
}
|
||||
else if (isPanelVisible)
|
||||
{
|
||||
ShowPanel();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTaskItem(string lineId, string text)
|
||||
{
|
||||
var taskItem = Instantiate(_taskItemPrefab, taskListParent);
|
||||
var task = taskItem.GetComponent<TMP_Text>();
|
||||
task.text = text;
|
||||
_taskDict[lineId] = taskItem;
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(taskListParent.GetComponent<RectTransform>());
|
||||
}
|
||||
|
||||
private void SetPanelVisibleImmediate(bool visible)
|
||||
{
|
||||
var pos = taskRoot.localPosition;
|
||||
pos.x = visible ? ShownLocalX : HiddenLocalX;
|
||||
taskRoot.localPosition = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c41d75ab4cb4d338c0288b83f61c94e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public struct BlockPuzzleCue : IFixSceneCue
|
||||
{
|
||||
private const string puzzleName = "Game2";
|
||||
private const float blockPuzzleScale = 1.6f;
|
||||
private static BlockPuzzleSystem BlockPuzzleSystem => FixSystemCenter.SystemDic.Get<BlockPuzzleSystem>();
|
||||
private static BodyModuleSystem BodyModuleSystem => FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
|
||||
public FixSceneMode Mode => FixSceneMode.BlockPuzzle;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
// 基本操作(其实应该早就处理好了)
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.BodyModule);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync(TimelineAssetRef.EngineModuleEnter);
|
||||
// 视角移动
|
||||
var engineModule = BodyModuleSystem.FindBodyModuleByName("Heart");
|
||||
Tween tween = RepairSystemManager.Instance.ScaleCurrentSystem(blockPuzzleScale, engineModule.transform.position);
|
||||
if (tween != null)
|
||||
{
|
||||
yield return tween.WaitForCompletion();
|
||||
}
|
||||
// 初始化拼图
|
||||
BlockPuzzleSystem.Initialize(puzzleName);
|
||||
// 开启遮罩
|
||||
yield return FixPanelSystem.Instance.MaskFadeIn();
|
||||
RepairSystemManager.Instance.SwitchSystem<BlockPuzzleSystem>();
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
yield return FixPanelSystem.Instance.MaskFadeOut();
|
||||
|
||||
// 播放进场动画
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync(TimelineAssetRef.CablePanelExit);
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync(TimelineAssetRef.BlockPuzzleEnter);
|
||||
// 解锁
|
||||
BlockPuzzleSystem.SetLocked(false);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84691c3d316643b98c99ca16c4560013
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public struct DefaultCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Default;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Clinic, true);
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Clinic);
|
||||
}
|
||||
|
||||
public IEnumerator EnterImmediate(FixTransition transition)
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Clinic, true);
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Clinic);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ClinicCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Clinic;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
// 先加载对话气泡,避免 Yarn 对话(如 测试 节点 me: 释放log)在相机切换完成前执行时找不到 Player 槽位
|
||||
// 切换相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Clinic);
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Clinic);
|
||||
}
|
||||
|
||||
public IEnumerator EnterImmediate(FixTransition transition)
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Clinic, true);
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Clinic);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
// 无
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct BodyModuleCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.BodyModule;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var cablePanel = FixPanelSystem.GetRepairPanel<CablePanel>();
|
||||
// 拔出插头
|
||||
// 切换Body或Head
|
||||
if (Enum.TryParse<ModuleState>(transition.Args, out var moduleState))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(moduleState);
|
||||
}
|
||||
|
||||
if (cablePanel != null && !FixPanelSystem.Instance.IsSystemOn)
|
||||
{
|
||||
// 切换相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModuleCenter);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
yield return FixPanelSystem.Instance.StartAllSystems();
|
||||
}
|
||||
else
|
||||
{
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.BodyModule);
|
||||
|
||||
// 开启交互
|
||||
bodyModuleSystem.EnableSystem();
|
||||
}
|
||||
|
||||
public IEnumerator EnterImmediate(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
|
||||
// 切换Body或Head
|
||||
if (Enum.TryParse<ModuleState>(transition.Args, out var moduleState))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(moduleState);
|
||||
}
|
||||
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule, true);
|
||||
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.BodyModule);
|
||||
// 开启交互
|
||||
bodyModuleSystem.EnableSystem();
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
// 拔出插头
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
// 关闭交互
|
||||
bodyModuleSystem.DisableSystem();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct OpCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Op;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.Op))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Op);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator EnterImmediate(FixTransition transition)
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Op, true);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
// 无
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct DreamCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Dream;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.DreamCamera))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.DreamCamera);
|
||||
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Open);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator EnterImmediate(FixTransition transition)
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.DreamCamera, true);
|
||||
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Open);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
// 无
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e85329d3d1e47c18bec82ccfe25f6ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.UI;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public struct EmoCutCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.EmoCut;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
// Cut的气泡统一放在BodyModule下
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Cut);
|
||||
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
var bodyModule = bodyModuleSystem.FindBodyModuleByName("Emo");
|
||||
if (bodyModule != null)
|
||||
{
|
||||
bodyModule.GetCutSpriteRenderer().sortingOrder = 100;
|
||||
bodyModule.GetCutTextSpriteRenderer().sortingOrder = 101;
|
||||
bodyModule.GetCutSpriteRenderer().color = new Color(1f, 1f, 1f, 1f);
|
||||
bodyModule.GetCutSpriteRenderer().GetComponentInChildren<SpriteRenderer>().color =
|
||||
new Color(1f, 1f, 1f, 1f);
|
||||
}
|
||||
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutEmo);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutEmoDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
yield return cuttingManager.StartCutting(bodyModule);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
// 无
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
cuttingManager.ClearCutLine();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutEmo);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
public struct MemoryCutCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.MemoryCut;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
// Cut的气泡统一放在BodyModule下
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Cut);
|
||||
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
var bodyModule = bodyModuleSystem.FindBodyModuleByName("Memory");
|
||||
if (bodyModule != null)
|
||||
{
|
||||
bodyModule.GetCutSpriteRenderer().sortingOrder = 100;
|
||||
bodyModule.GetCutTextSpriteRenderer().sortingOrder = 101;
|
||||
bodyModule.GetCutSpriteRenderer().color = new Color(1f, 1f, 1f, 1f);
|
||||
bodyModule.GetCutSpriteRenderer().GetComponentInChildren<SpriteRenderer>().color =
|
||||
new Color(1f, 1f, 1f, 1f);
|
||||
}
|
||||
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutMemory);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutMemoryDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
yield return cuttingManager.StartCutting(bodyModule);
|
||||
}
|
||||
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
cuttingManager.ClearCutLine();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutMemory);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
public struct LogicCutCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.LogicCut;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
// Cut的气泡统一放在BodyModule下
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Cut);
|
||||
|
||||
var bodyModule = bodyModuleSystem.FindBodyModuleByName("Logic");
|
||||
if (bodyModule != null)
|
||||
{
|
||||
bodyModule.GetCutSpriteRenderer().sortingOrder = 100;
|
||||
bodyModule.GetCutTextSpriteRenderer().sortingOrder = 101;
|
||||
bodyModule.GetCutSpriteRenderer().color = new Color(1f, 1f, 1f, 1f);
|
||||
bodyModule.GetCutSpriteRenderer().GetComponentInChildren<SpriteRenderer>().color =
|
||||
new Color(1f, 1f, 1f, 1f);
|
||||
}
|
||||
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutLogic);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutLogicDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
yield return cuttingManager.StartCutting(bodyModule);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.1f);
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
cuttingManager.ClearCutLine();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.CutLogic);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f31682a5a8144cfa8ba7f2d8619feaf2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public enum FixSceneMode
|
||||
{
|
||||
Default = 0,
|
||||
Clinic = 1,
|
||||
BodyModule = 2,
|
||||
Memory = 3,
|
||||
Eye = 4,
|
||||
UF = 5,
|
||||
Expression = 6,
|
||||
Emo = 9,
|
||||
Op = 10,
|
||||
EmoCut = 11,
|
||||
MemoryCut = 12,
|
||||
LogicCut = 13,
|
||||
Dream = 14,
|
||||
AnalysisMode = 15,
|
||||
Sales = 16,
|
||||
BlockPuzzle = 17
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9dc0800bbef347d5810d82e445a872cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.UI;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public struct ExpressionCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Expression;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
// 获取表达系统
|
||||
var expressionManager = FixSystemCenter.SystemDic.Get<ExpressionManager>();
|
||||
|
||||
// 先切 BodyModule(如果需要)
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
var bodyModule = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
bodyModule.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
// 开启表达系统视图,并播放火山表达深入
|
||||
// 切换到表达相机
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("透镜/透镜in");
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Expression);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
expressionManager.OpenView();
|
||||
// 切换到深度表达相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.ExpressionDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("火山表达模块/火山表达深入");
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
var expressionManager = FixSystemCenter.SystemDic.Get<ExpressionManager>();
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
|
||||
// 重置身体模块
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
|
||||
// 关闭表达系统视图
|
||||
expressionManager.CloseView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Expression);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("透镜/透镜out");
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct EmoCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Emo;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var emoManager = FixSystemCenter.SystemDic.Get<EmoManager>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
var bodyModule = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
bodyModule.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Emo);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
//emoManager.OpenView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EmoDeep);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public struct AnalysisModeCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.AnalysisMode;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
var analysisModeManager = FixSystemCenter.SystemDic.Get<MiniGame.Language.AnalysisModeManager>();
|
||||
|
||||
// 先切 BodyModule(如果需要)
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
// 插头回到初始插孔
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
|
||||
// 切换到分析模式相机
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.AnalysisMode);
|
||||
// 进入分析模式
|
||||
if (analysisModeManager != null)
|
||||
{
|
||||
analysisModeManager.OnEnter();
|
||||
}
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var analysisModeManager = FixSystemCenter.SystemDic.Get<MiniGame.Language.AnalysisModeManager>();
|
||||
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
|
||||
// 退出分析模式
|
||||
if (analysisModeManager != null)
|
||||
{
|
||||
analysisModeManager.OnExit();
|
||||
}
|
||||
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
}
|
||||
}
|
||||
|
||||
public struct SalesCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Sales;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
// 进入前先播放钳子摘盖子,完成后深入
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("钳子/摘走盖子");
|
||||
|
||||
// 获取销售系统管理器
|
||||
var salesManager = FixSystemCenter.SystemDic.Get<AibisDream.MiniGame.Language.SalesManager>();
|
||||
if (salesManager != null)
|
||||
{
|
||||
// FadeIn
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
|
||||
// 打开销售系统视图,传入systemView参数
|
||||
salesManager.OpenView();
|
||||
|
||||
// FadeOut
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
|
||||
// 销售模块使用自身的气泡槽位;缺失时再回退到 ScreenSystem,避免读到错误的槽位配置
|
||||
if (BubbleSlotKit.Instance.TryGet(BubbleSlotEnum.Sales, out _))
|
||||
{
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Sales);
|
||||
Debug.Log("[SalesCue] Loaded bubble slots from BubbleSlotKit (Sales).");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[SalesCue] Sales 气泡槽组未注册,回退到 BodyModule。");
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.BodyModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var salesManager = FixSystemCenter.SystemDic.Get<AibisDream.MiniGame.Language.SalesManager>();
|
||||
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
|
||||
// 关闭销售系统视图
|
||||
if (salesManager != null)
|
||||
{
|
||||
salesManager.CloseView();
|
||||
}
|
||||
// 关闭前先播放钳子放下盖子,完成后再结
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
yield return TimelineCenter.Instance.PlayTimelineAsync("钳子/放下盖子");
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98f122636e424c2199a237d2850a03b8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.UI;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public struct UfCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.UF;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var ufSystem = FixSystemCenter.SystemDic.Get<UFSystem>();
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
// 先切BodyModule
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
// 插头回到初始插孔
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
// 切换相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.UF);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
ufSystem.OpenUFView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EyeDeep);
|
||||
eyeSystem.OnEnter();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Eye);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var ufSystem = FixSystemCenter.SystemDic.Get<UFSystem>();
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
eyeSystem.OnExit();
|
||||
ufSystem.CloseUFView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.UF);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
}
|
||||
}
|
||||
|
||||
public struct EyeCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Eye;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
bodyModuleSystem.SwitchModuleGroup(ModuleState.Head);
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
// 先切BodyModule
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
//打开盖子
|
||||
var eyeModule = bodyModuleSystem.FindBodyModuleByType<EyeModule>();
|
||||
yield return eyeModule.OpenEye();
|
||||
|
||||
// 插头回到初始插孔
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
// 切换相机
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Eye);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EyeDeep);
|
||||
eyeSystem.OnEnter();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Eye);
|
||||
}
|
||||
|
||||
public IEnumerator EnterImmediate(FixTransition transition)
|
||||
{
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EyeDeep, true);
|
||||
eyeSystem.OnEnter();
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Eye);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
float duration = 0.5f;
|
||||
var eyeSystem = FixSystemCenter.SystemDic.Get<EyeSystem>();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Eye);
|
||||
eyeSystem.OnExit();
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
//关上
|
||||
var eyeModule = bodyModuleSystem.FindBodyModuleByType<EyeModule>();
|
||||
yield return eyeModule.CloseEye();
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public struct MemoryCue : IFixSceneCue
|
||||
{
|
||||
public FixSceneMode Mode => FixSceneMode.Memory;
|
||||
|
||||
public IEnumerator Enter(FixTransition transition)
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var memorySystem = FixSystemCenter.SystemDic.Get<MemoryProcess>();
|
||||
// 先切BodyModule
|
||||
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
|
||||
{
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
yield return memorySystem.DropMemoryProjector();
|
||||
// 插头回到初始插孔
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
// 相机聚焦到目标位
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Memory);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
memorySystem.OnEnter();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Memory);
|
||||
}
|
||||
|
||||
public IEnumerator EnterImmediate(FixTransition transition)
|
||||
{
|
||||
var memorySystem = FixSystemCenter.SystemDic.Get<MemoryProcess>();
|
||||
|
||||
yield return memorySystem.DropMemoryProjector();
|
||||
FixPanelSystem.GetRepairPanel<CablePanel>().ResetPlug();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.Memory, true);
|
||||
memorySystem.OnEnter();
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Memory);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
{
|
||||
float duration = 0.5f;
|
||||
var memorySystem = FixSystemCenter.SystemDic.Get<MemoryProcess>();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(duration);
|
||||
memorySystem.OnExit();
|
||||
memorySystem.CloseMemoryView();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(duration);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
|
||||
memorySystem.PullMemoryProjector();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90a6a39492ca40edbf1d17ab821c5e01
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -58,17 +58,17 @@ namespace AibisDream.FixSystem
|
||||
_whackMoleHandler.EndWhackGame();
|
||||
var whackRes = _whackMoleHandler.res;
|
||||
Debug.Log($"结果:共计{whackRes.totalMoleNum}个,成功击中{whackRes.hitMoleNum}个");
|
||||
StorageSystem.Instance.SetValue("$whackRes.totalMoleNum", whackRes.totalMoleNum);
|
||||
StorageSystem.Instance.SetValue("$whackRes.hitMoleNum", whackRes.hitMoleNum);
|
||||
YarnVariableStorage.Instance.SetValue("$whackRes.totalMoleNum", whackRes.totalMoleNum);
|
||||
YarnVariableStorage.Instance.SetValue("$whackRes.hitMoleNum", whackRes.hitMoleNum);
|
||||
// 流水线结果
|
||||
if (_data.HasPipeline())
|
||||
{
|
||||
var pipelineRes = pipelineSystem.res;
|
||||
Debug.Log(
|
||||
$"流水线结果:共计{pipelineRes.totalProductNum}个,成功{pipelineRes.successNum}个,失败{pipelineRes.failNum}个");
|
||||
StorageSystem.Instance.SetValue("$whackRes.totalProductNum", pipelineRes.totalProductNum);
|
||||
StorageSystem.Instance.SetValue("$whackRes.successNum", pipelineRes.successNum);
|
||||
StorageSystem.Instance.SetValue("$whackRes.failNum", pipelineRes.failNum);
|
||||
YarnVariableStorage.Instance.SetValue("$whackRes.totalProductNum", pipelineRes.totalProductNum);
|
||||
YarnVariableStorage.Instance.SetValue("$whackRes.successNum", pipelineRes.successNum);
|
||||
YarnVariableStorage.Instance.SetValue("$whackRes.failNum", pipelineRes.failNum);
|
||||
}
|
||||
// 结束
|
||||
DialogController.Instance?.StartDialogNode("打地鼠结束");
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.SaveSystem;
|
||||
using FMOD;
|
||||
using FMOD.Studio;
|
||||
using FMODUnity;
|
||||
@@ -37,18 +38,14 @@ namespace AibisDream.Kit
|
||||
#region 事件索引
|
||||
|
||||
private List<IUnRegister> _removeTrigger = new();
|
||||
private readonly AudioData _audioData = new();
|
||||
private AmbState _currentAmbState = AmbState.Main;
|
||||
|
||||
#endregion
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 注册事件
|
||||
RegisterEvent();
|
||||
// 启动AMB
|
||||
InitAmb();
|
||||
// 注册数据
|
||||
RegisterData();
|
||||
}
|
||||
|
||||
private void RegisterEvent()
|
||||
@@ -58,16 +55,9 @@ namespace AibisDream.Kit
|
||||
_removeTrigger.Add(EnumEventSystem.Global.Register(DialogEventEnum.LineShown, OnLineShown));
|
||||
// 场景切换相关
|
||||
_removeTrigger.Add(EnumEventSystem.Global.Register<EventEnum, string>(EventEnum.SceneLoad, OnSceneLoad));
|
||||
_removeTrigger.Add(EnumEventSystem.Global.Register<EventEnum, FixState>(EventEnum.FixStateSwitch, OnFixStateChange));
|
||||
_removeTrigger.Add(EnumEventSystem.Global.Register<EventEnum, FixSceneMode>(EventEnum.FixSceneModeChanged, OnFixSceneModeChange));
|
||||
_removeTrigger.Add(EnumEventSystem.Global.Register(GameLoopEnum.GameQuit, OnGameQuit));
|
||||
}
|
||||
|
||||
private void RegisterData()
|
||||
{
|
||||
// 注册数据
|
||||
StorageSystem.Instance.RegisterData(_audioData);
|
||||
_audioData.LoadEvent += LoadData;
|
||||
}
|
||||
|
||||
public override void OnSingletonDestroy()
|
||||
{
|
||||
@@ -83,7 +73,29 @@ namespace AibisDream.Kit
|
||||
_removeTrigger.Clear();
|
||||
}
|
||||
|
||||
private void LoadData(AudioData data)
|
||||
public AudioSnapshotDto CaptureSnapshot()
|
||||
{
|
||||
var dto = new AudioSnapshotDto
|
||||
{
|
||||
ambState = _currentAmbState.ToString()
|
||||
};
|
||||
|
||||
foreach (var pair in _musicDict)
|
||||
{
|
||||
pair.Value.GetDescription().getPath(out var path);
|
||||
dto.musicPaths[pair.Key] = path;
|
||||
}
|
||||
|
||||
foreach (var pair in _sfxDict)
|
||||
{
|
||||
pair.Value.GetDescription().getPath(out var path);
|
||||
dto.sfxPaths[pair.Key] = path;
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
public void RestoreSnapshot(AudioSnapshotDto data)
|
||||
{
|
||||
foreach (var musicEvent in _musicDict.Values)
|
||||
{
|
||||
@@ -94,20 +106,36 @@ namespace AibisDream.Kit
|
||||
{
|
||||
sfxEvent.release();
|
||||
}
|
||||
|
||||
foreach (var music in data.musicNames)
|
||||
|
||||
_musicDict.Clear();
|
||||
_sfxDict.Clear();
|
||||
|
||||
if (data.musicPaths != null)
|
||||
{
|
||||
var instance = RuntimeManager.CreateInstance(music.Value);
|
||||
instance.start();
|
||||
_musicDict[music.Key] = instance;
|
||||
foreach (var music in data.musicPaths)
|
||||
{
|
||||
var instance = RuntimeManager.CreateInstance(music.Value);
|
||||
instance.start();
|
||||
_musicDict[music.Key] = instance;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sfx in data.sfxNames)
|
||||
|
||||
if (data.sfxPaths != null)
|
||||
{
|
||||
var instance = RuntimeManager.CreateInstance(sfx.Value);
|
||||
instance.start();
|
||||
_sfxDict[sfx.Key] = instance;
|
||||
foreach (var sfx in data.sfxPaths)
|
||||
{
|
||||
var instance = RuntimeManager.CreateInstance(sfx.Value);
|
||||
instance.start();
|
||||
_sfxDict[sfx.Key] = instance;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(data.ambState)
|
||||
&& Enum.TryParse<AmbState>(data.ambState, out var ambState))
|
||||
{
|
||||
ChangeAmb(ambState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void PauseAll()
|
||||
@@ -140,6 +168,7 @@ namespace AibisDream.Kit
|
||||
|
||||
public void ChangeAmb(AmbState state)
|
||||
{
|
||||
_currentAmbState = state;
|
||||
RuntimeManager.StudioSystem.setParameterByName(AmbStateName, (float)state);
|
||||
}
|
||||
|
||||
@@ -178,16 +207,14 @@ namespace AibisDream.Kit
|
||||
|
||||
_musicDict.Clear();
|
||||
_sfxDict.Clear();
|
||||
|
||||
_audioData.musicNames.Clear();
|
||||
_audioData.sfxNames.Clear();
|
||||
_currentAmbState = AmbState.Main;
|
||||
}
|
||||
|
||||
private void OnFixStateChange(FixState nextState)
|
||||
private void OnFixSceneModeChange(FixSceneMode nextState)
|
||||
{
|
||||
// 状态切换时加载
|
||||
var state = MatchAmbState(nextState);
|
||||
Debug.Log("OnFixStateChange: " + state);
|
||||
Debug.Log("OnFixSceneModeChange: " + state);
|
||||
ChangeAmb(state);
|
||||
}
|
||||
|
||||
@@ -233,14 +260,11 @@ namespace AibisDream.Kit
|
||||
private void RemoveMusic(string key)
|
||||
{
|
||||
_musicDict.Remove(key);
|
||||
_audioData.musicNames.Remove(key);
|
||||
}
|
||||
|
||||
private void AddMusic(string key, EventInstance eventInstance)
|
||||
{
|
||||
_musicDict[key] = eventInstance;
|
||||
eventInstance.GetDescription().getPath(out var path);
|
||||
_audioData.musicNames[key] = path;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -312,14 +336,11 @@ namespace AibisDream.Kit
|
||||
private void RemoveSfx(string key)
|
||||
{
|
||||
_sfxDict.Remove(key);
|
||||
_audioData.sfxNames.Remove(key);
|
||||
}
|
||||
|
||||
private void AddSfx(string key, EventInstance eventInstance)
|
||||
{
|
||||
_sfxDict[key] = eventInstance;
|
||||
eventInstance.GetDescription().getPath(out var path);
|
||||
_audioData.sfxNames[key] = path;
|
||||
}
|
||||
|
||||
#region 调整音量
|
||||
@@ -361,18 +382,18 @@ namespace AibisDream.Kit
|
||||
};
|
||||
}
|
||||
|
||||
private static AmbState MatchAmbState(FixState fixState)
|
||||
private static AmbState MatchAmbState(FixSceneMode fixState)
|
||||
{
|
||||
return fixState switch
|
||||
{
|
||||
FixState.Clinic => AmbState.Clinic,
|
||||
FixState.BodyModule => AmbState.Body,
|
||||
FixState.EmoCut => AmbState.Head,
|
||||
FixState.MemoryCut => AmbState.Head,
|
||||
FixState.LogicCut => AmbState.Head,
|
||||
FixState.UF => AmbState.UF,
|
||||
FixState.Eye => AmbState.Eye,
|
||||
FixState.Memory => AmbState.Memory,
|
||||
FixSceneMode.Clinic => AmbState.Clinic,
|
||||
FixSceneMode.BodyModule => AmbState.Body,
|
||||
FixSceneMode.EmoCut => AmbState.Head,
|
||||
FixSceneMode.MemoryCut => AmbState.Head,
|
||||
FixSceneMode.LogicCut => AmbState.Head,
|
||||
FixSceneMode.UF => AmbState.UF,
|
||||
FixSceneMode.Eye => AmbState.Eye,
|
||||
FixSceneMode.Memory => AmbState.Memory,
|
||||
_ => AmbState.Main
|
||||
};
|
||||
}
|
||||
@@ -440,19 +461,5 @@ namespace AibisDream.Kit
|
||||
public void PauseAll();
|
||||
public void UnPauseAll();
|
||||
}
|
||||
|
||||
public class AudioData : IData
|
||||
{
|
||||
public readonly Dictionary<string, string> sfxNames = new();
|
||||
public readonly Dictionary<string, string> musicNames = new();
|
||||
|
||||
public event Action<AudioData> LoadEvent;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
LoadEvent?.Invoke(this);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Utility;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.Framework
|
||||
@@ -24,15 +21,6 @@ namespace AibisDream.Framework
|
||||
public abstract void SaveLevel();
|
||||
}
|
||||
|
||||
public interface IData
|
||||
{
|
||||
IEnumerator Load();
|
||||
|
||||
void Save()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#region IOC容器
|
||||
|
||||
public class IOCContainer
|
||||
@@ -89,183 +77,6 @@ namespace AibisDream.Framework
|
||||
}
|
||||
}
|
||||
|
||||
public class DataContainer
|
||||
{
|
||||
private Dictionary<string, IData> _instances = new();
|
||||
|
||||
public int Count => _instances.Count;
|
||||
|
||||
public void Register<T>(T instance) where T : IData
|
||||
{
|
||||
var key = typeof(T).AssemblyQualifiedName;
|
||||
if (key != null) _instances[key] = instance;
|
||||
}
|
||||
|
||||
public T Get<T>() where T : class, IData
|
||||
{
|
||||
var key = typeof(T).AssemblyQualifiedName;
|
||||
if (key != null && _instances.TryGetValue(key, out var retInstance))
|
||||
{
|
||||
return retInstance as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public object Get(Type type)
|
||||
{
|
||||
var key = type.AssemblyQualifiedName;
|
||||
if (key != null && _instances.TryGetValue(key, out var retInstance))
|
||||
{
|
||||
return retInstance;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public T Unregister<T>() where T : class, IData
|
||||
{
|
||||
var res = Get<T>();
|
||||
var assemblyQualifiedName = typeof(T).AssemblyQualifiedName;
|
||||
if (res != null && assemblyQualifiedName != null)
|
||||
{
|
||||
_instances.Remove(assemblyQualifiedName);
|
||||
return res;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_instances.Clear();
|
||||
}
|
||||
|
||||
public JObject SaveAsJson()
|
||||
{
|
||||
foreach (var data in _instances.Values)
|
||||
{
|
||||
data.Save();
|
||||
}
|
||||
return JObject.FromObject(_instances);
|
||||
}
|
||||
|
||||
public IEnumerator LoadByJson(JObject dataJson)
|
||||
{
|
||||
// 从加载序号1到10加载数据
|
||||
for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
// 先筛选对应序号
|
||||
var curIdx = i;
|
||||
var idxInstances = _instances.Where(item => LoadIndex.GetLoadIdx(item.Value) == curIdx).ToArray();
|
||||
// 回填数据并加载
|
||||
foreach (var instancePair in idxInstances)
|
||||
{
|
||||
if (!dataJson.TryGetValue(instancePair.Key, out var data))
|
||||
{
|
||||
Debug.Log(instancePair.Key + "未注册");
|
||||
continue;
|
||||
}
|
||||
|
||||
var fixDataType = Type.GetType(instancePair.Key);
|
||||
if (fixDataType == null)
|
||||
{
|
||||
Debug.Log(instancePair.Key + "类型不存在");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 把Json中读到的参数填回Data
|
||||
var tempObj = data.ToObject(fixDataType);
|
||||
CommonUtil.CopyFields(tempObj, instancePair.Value);
|
||||
|
||||
// 回填完成后进行加载
|
||||
yield return instancePair.Value.Load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetFieldValue<T>(string variableName, out T fieldValue)
|
||||
{
|
||||
fieldValue = default;
|
||||
var parts = variableName.Split('.');
|
||||
if (parts.Length <= 3)
|
||||
{
|
||||
Debug.LogError($"{variableName}输入格式错误,应为 '$data.类名.字段名'");
|
||||
return false;
|
||||
}
|
||||
|
||||
var className = parts[1];
|
||||
var fieldName = parts[2];
|
||||
|
||||
var key = _instances.Keys.FirstOrDefault(key => key.Contains(className));
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
Debug.LogError($"未找到类名为 {className} 的实例");
|
||||
return false;
|
||||
}
|
||||
|
||||
var instance = _instances[key];
|
||||
var fieldInfo = instance.GetType().GetField(fieldName);
|
||||
if (fieldInfo == null)
|
||||
{
|
||||
Debug.LogError($"未找到字段名为 {fieldName} 的字段");
|
||||
return false;
|
||||
}
|
||||
|
||||
var value = fieldInfo.GetValue(instance);
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
// 单独处理字符串类型
|
||||
fieldValue = (T)(object)(value?.ToString() ?? string.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
// 处理数字类型转换为 float
|
||||
if (value is IConvertible)
|
||||
{
|
||||
try
|
||||
{
|
||||
fieldValue = (T)(object)Convert.ToSingle(value);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"无法将字段值转换为 float: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogError("字段值不是可转换为 float 的类型");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(bool))
|
||||
{
|
||||
// 处理布尔类型转换为 bool
|
||||
if (value is IConvertible)
|
||||
{
|
||||
fieldValue = (T)(object)Convert.ToBoolean(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
Debug.LogError("字段值不是可转换为 bool 的类型");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value is T typedValue)
|
||||
{
|
||||
// 处理其他类型
|
||||
fieldValue = typedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
Debug.LogError($"未找到类型为 {typeof(T).Name} 的转换方法");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件相关
|
||||
|
||||
@@ -264,6 +264,67 @@ namespace AibisDream.Framework
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 AnimatorController 构建 shortNameHash → 状态短名 映射,供 ActorAnima 快照 Capture 反查。
|
||||
/// Editor 下遍历完整状态机;运行时 fallback 为 animationClips 名称(与状态名一致时有效)。
|
||||
/// </summary>
|
||||
public static Dictionary<int, string> BuildStateHashToNameMap(RuntimeAnimatorController controller)
|
||||
{
|
||||
var map = new Dictionary<int, string>();
|
||||
if (controller == null)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (controller is AnimatorController animatorController)
|
||||
{
|
||||
foreach (var layer in animatorController.layers)
|
||||
{
|
||||
if (layer.stateMachine != null)
|
||||
{
|
||||
CollectStateNamesFromStateMachine(layer.stateMachine, map);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var clip in controller.animationClips)
|
||||
{
|
||||
if (clip == null) continue;
|
||||
var hash = Animator.StringToHash(clip.name);
|
||||
map.TryAdd(hash, clip.name);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static void CollectStateNamesFromStateMachine(
|
||||
AnimatorStateMachine stateMachine,
|
||||
Dictionary<int, string> map)
|
||||
{
|
||||
if (stateMachine == null) return;
|
||||
|
||||
foreach (var childState in stateMachine.states)
|
||||
{
|
||||
if (childState.state == null) continue;
|
||||
var hash = Animator.StringToHash(childState.state.name);
|
||||
map.TryAdd(hash, childState.state.name);
|
||||
}
|
||||
|
||||
foreach (var subStateMachine in stateMachine.stateMachines)
|
||||
{
|
||||
if (subStateMachine.stateMachine != null)
|
||||
{
|
||||
CollectStateNamesFromStateMachine(subStateMachine.stateMachine, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.Framework
|
||||
{
|
||||
public class BubbleSlotKit : SingletonBase<BubbleSlotKit>
|
||||
{
|
||||
private readonly Dictionary<BubbleSlotEnum, BubbleSlotGroup> _slotDict = new();
|
||||
|
||||
private BubbleSlotKit()
|
||||
{
|
||||
}
|
||||
|
||||
public void Register(BubbleSlotEnum slotType, BubbleSlotGroup group)
|
||||
{
|
||||
if (slotType == BubbleSlotEnum.Default)
|
||||
{
|
||||
Debug.LogWarning("[BubbleSlotKit] 不能注册 Default 类型的气泡槽组。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_slotDict.TryGetValue(slotType, out var existing) && existing != null && existing != group)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[BubbleSlotKit] {slotType} 已被 \"{existing.name}\" 注册,将被 \"{group.name}\" 覆盖");
|
||||
}
|
||||
|
||||
_slotDict[slotType] = group;
|
||||
}
|
||||
|
||||
public void Unregister(BubbleSlotEnum slotType)
|
||||
{
|
||||
_slotDict.Remove(slotType);
|
||||
}
|
||||
|
||||
public void RemoveAll()
|
||||
{
|
||||
_slotDict.Clear();
|
||||
}
|
||||
|
||||
public bool TryGet(BubbleSlotEnum slotType, out BubbleSlotGroup group)
|
||||
{
|
||||
if (_slotDict.TryGetValue(slotType, out group) && group != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
group = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public BubbleSlotGroupData CollectData(BubbleSlotEnum slotType)
|
||||
{
|
||||
if (!TryGet(slotType, out var group))
|
||||
{
|
||||
Debug.LogWarning($"[BubbleSlotKit] CollectData: {slotType} 气泡槽组未注册");
|
||||
return default;
|
||||
}
|
||||
|
||||
return group.CollectData();
|
||||
}
|
||||
|
||||
public void LoadBubbles(BubbleSlotEnum slotType)
|
||||
{
|
||||
if (!TryGet(slotType, out var group))
|
||||
{
|
||||
Debug.LogWarning($"[BubbleSlotKit] LoadBubbles: {slotType} 气泡槽组未注册");
|
||||
return;
|
||||
}
|
||||
|
||||
DialogUIManager.Instance.LoadBubbles(group.CollectData());
|
||||
}
|
||||
}
|
||||
|
||||
public enum BubbleSlotEnum
|
||||
{
|
||||
Default,
|
||||
Clinic,
|
||||
BodyModule,
|
||||
Memory,
|
||||
Eye,
|
||||
Cut,
|
||||
Open,
|
||||
Sales,
|
||||
Subway,
|
||||
SubwayTV,
|
||||
Outside,
|
||||
Tarot,
|
||||
Emo,
|
||||
Expression,
|
||||
Analysis
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8f3c2e1b94d4f6a9e7d5c3b2a10876f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -93,6 +93,18 @@ namespace AibisDream.Framework
|
||||
|
||||
public void RegisterCamera(CameraEnum camType, ICinemachineCamera virtualCamera)
|
||||
{
|
||||
if (_virtualCameraDict.TryGetValue(camType, out var existing) && existing != virtualCamera)
|
||||
{
|
||||
var existingName = existing?.VirtualCameraGameObject != null
|
||||
? existing.VirtualCameraGameObject.name
|
||||
: "unknown";
|
||||
var newName = virtualCamera?.VirtualCameraGameObject != null
|
||||
? virtualCamera.VirtualCameraGameObject.name
|
||||
: "unknown";
|
||||
Debug.LogWarning(
|
||||
$"RegisterCamera: {camType} 已被 \"{existingName}\" 注册,将被 \"{newName}\" 覆盖");
|
||||
}
|
||||
|
||||
_virtualCameraDict[camType] = virtualCamera;
|
||||
|
||||
var vcam = virtualCamera.VirtualCameraGameObject.GetComponent<CinemachineVirtualCamera>();
|
||||
@@ -170,11 +182,15 @@ namespace AibisDream.Framework
|
||||
var activeBrain = _orthoCameraBrain;
|
||||
|
||||
CinemachineBlendDefinition originalBlend = default;
|
||||
CinemachineBlenderSettings savedCustomBlends = null;
|
||||
if (immediate)
|
||||
{
|
||||
originalBlend = activeBrain.m_DefaultBlend;
|
||||
activeBrain.m_DefaultBlend = new CinemachineBlendDefinition(
|
||||
CinemachineBlendDefinition.Style.Cut, 0f);
|
||||
// Custom Blends 会覆盖 DefaultBlend;immediate 时必须临时禁用,否则仍走 1s 缓动。
|
||||
savedCustomBlends = activeBrain.m_CustomBlends;
|
||||
activeBrain.m_CustomBlends = null;
|
||||
}
|
||||
|
||||
_virtualCameraDict.TryGetValue(_curCameraEnum, out var fromVCam);
|
||||
@@ -193,8 +209,14 @@ namespace AibisDream.Framework
|
||||
if (immediate)
|
||||
{
|
||||
ResetVirCam(lastCamEnum);
|
||||
activeBrain.ManualUpdate();
|
||||
var blend = originalBlend;
|
||||
ActionKit.Delay(0f, () => activeBrain.m_DefaultBlend = blend).StartGlobal();
|
||||
var customBlends = savedCustomBlends;
|
||||
ActionKit.DelayFrame(2, () =>
|
||||
{
|
||||
activeBrain.m_DefaultBlend = blend;
|
||||
activeBrain.m_CustomBlends = customBlends;
|
||||
}).StartGlobal();
|
||||
return 0f;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.Framework
|
||||
{
|
||||
[RequireComponent(typeof(CinemachineVirtualCamera))]
|
||||
public class VirtualCamRegistry : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private CameraEnum cameraType;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (cameraType == CameraEnum.Default)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(VirtualCamRegistry)}] cameraType 不能为 Default,请在 Inspector 配置。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetComponent<ICinemachineCamera>(out var virtualCam))
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(VirtualCamRegistry)}] 未找到 ICinemachineCamera 组件。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
CameraKit.Instance.RegisterCamera(cameraType, virtualCam);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (cameraType == CameraEnum.Default)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CameraKit.Instance?.UnRegisterCamera(cameraType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7432789ba6bd1a340b824676e1e56184
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -6,11 +6,13 @@ namespace AibisDream.Framework
|
||||
public class StateMachine<T> where T : Enum
|
||||
{
|
||||
public T CurState => _curState.GetState;
|
||||
public bool HasState => _curState != null;
|
||||
public IState<T> CurrentStateObj => _curState;
|
||||
public event Action<T> OnStateSwitch;
|
||||
|
||||
private IState<T> _curState;
|
||||
private readonly Func<T, string, IState<T>> _createState;
|
||||
|
||||
|
||||
public StateMachine(Func<T, string, IState<T>> createState)
|
||||
{
|
||||
_createState = createState;
|
||||
@@ -30,7 +32,28 @@ namespace AibisDream.Framework
|
||||
OnStateSwitch?.Invoke(nextState);
|
||||
yield return _curState.Enter();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 强制设置内部状态对象,不触发 Exit / Enter / OnStateSwitch。
|
||||
/// 用于读档等需要绕过正常状态过渡的场景。
|
||||
/// </summary>
|
||||
public void SetStateImmediate(T state, string args = "")
|
||||
{
|
||||
_curState = _createState.Invoke(state, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读档恢复:跳过前一状态的 Exit,直接进入目标状态的 EnterImmediate。
|
||||
/// </summary>
|
||||
public IEnumerator SwitchStateImmediate(T nextState, string args = "")
|
||||
{
|
||||
if (_curState != null && CurState.Equals(nextState)) yield break;
|
||||
|
||||
_curState = _createState.Invoke(nextState, args);
|
||||
OnStateSwitch?.Invoke(nextState);
|
||||
yield return _curState.EnterImmediate();
|
||||
}
|
||||
|
||||
public IEnumerator QuitState()
|
||||
{
|
||||
yield return _curState?.Exit();
|
||||
@@ -44,6 +67,12 @@ namespace AibisDream.Framework
|
||||
public void SetArgs(string args) {}
|
||||
public IEnumerator Enter();
|
||||
|
||||
/// <summary>读档恢复:跳过过渡动画,直接到达终态。</summary>
|
||||
public IEnumerator EnterImmediate()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerator Exit()
|
||||
{
|
||||
yield break;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace AibisDream
|
||||
PlugIn,
|
||||
PlugOut,
|
||||
SceneLoad,
|
||||
FixStateSwitch,
|
||||
FixSceneModeChanged,
|
||||
CutEnd
|
||||
}
|
||||
|
||||
@@ -85,4 +85,4 @@ namespace AibisDream
|
||||
TextSpeed,
|
||||
LocaleChanged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.UI;
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
@@ -27,8 +28,6 @@ namespace AibisDream
|
||||
#endregion
|
||||
|
||||
private BindProperty<TalkSceneSO> _currentTalkSceneSo;
|
||||
private readonly GameLoopData _data = new();
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
LoadAllSceneSos();
|
||||
@@ -38,11 +37,9 @@ namespace AibisDream
|
||||
{
|
||||
// 管理游戏开始时各部分的初始化
|
||||
SettingLoader.Init();
|
||||
// 数据类注册
|
||||
StorageSystem.Instance.RegisterData(_data);
|
||||
SnapshotRegistry.EnsureInitialized();
|
||||
// 场景So事件注册
|
||||
_currentTalkSceneSo = new BindProperty<TalkSceneSO>();
|
||||
_currentTalkSceneSo.Register(item => _data.curSceneSoName = item.name);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (runMode == RunMode.SingleScene)
|
||||
@@ -114,7 +111,25 @@ namespace AibisDream
|
||||
// 游戏开始
|
||||
EnumEventSystem.Global.Send(GameLoopEnum.GameStart);
|
||||
|
||||
StorageSystem.Instance.Load(fileName);
|
||||
StartCoroutine(LoadFromSaveFile(fileName));
|
||||
}
|
||||
|
||||
public void StartWithSlot(int slotIndex)
|
||||
{
|
||||
// 游戏开始
|
||||
EnumEventSystem.Global.Send(GameLoopEnum.GameStart);
|
||||
|
||||
StartCoroutine(LoadFromSlot(slotIndex));
|
||||
}
|
||||
|
||||
private IEnumerator LoadFromSaveFile(string fileName)
|
||||
{
|
||||
yield return SaveRestoreOrchestrator.RestoreFromFile(fileName);
|
||||
}
|
||||
|
||||
private IEnumerator LoadFromSlot(int slotIndex)
|
||||
{
|
||||
yield return SaveRestoreOrchestrator.RestoreFromSlot(slotIndex);
|
||||
}
|
||||
|
||||
private IEnumerator LoadFirstScene()
|
||||
@@ -270,6 +285,11 @@ namespace AibisDream
|
||||
DialogController.Instance.LoadDialog(_currentTalkSceneSo.Value.yarnProject);
|
||||
}
|
||||
|
||||
public TalkSceneSO GetCurrentTalkSceneSo()
|
||||
{
|
||||
return _currentTalkSceneSo?.Value;
|
||||
}
|
||||
|
||||
public string GetSceneSoName()
|
||||
{
|
||||
return _currentTalkSceneSo.Value.name;
|
||||
@@ -336,29 +356,6 @@ namespace AibisDream
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[LoadIndex(10)]
|
||||
public class GameLoopData : IData
|
||||
{
|
||||
public string curSceneSoName;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
GameManager.Instance.SetSceneSoByName(curSceneSoName);
|
||||
GameManager.Instance.StartDialog();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GameLoopYarnCommand
|
||||
{
|
||||
[YarnCommand("auto_save")]
|
||||
public static void AutoSave()
|
||||
{
|
||||
StorageSystem.Instance.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public struct GameLoopState
|
||||
{
|
||||
public bool isInGame;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class LoadIndex : Attribute
|
||||
{
|
||||
private int Idx { get; }
|
||||
|
||||
public LoadIndex(int idx)
|
||||
{
|
||||
Idx = idx;
|
||||
}
|
||||
|
||||
public static int GetLoadIdx<T>(T instance)
|
||||
{
|
||||
// 先通过反射拿到属性
|
||||
var attribute = instance.GetType().GetCustomAttribute<LoadIndex>();
|
||||
return attribute?.Idx ?? 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbb1d63c733941879217082bda086ade
|
||||
timeCreated: 1742905792
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.FixSystem;
|
||||
@@ -19,11 +18,12 @@ namespace AibisDream
|
||||
private string _sceneToLoad;
|
||||
private bool _isLoading;
|
||||
|
||||
private readonly SceneData _data = new();
|
||||
private string _currentSceneName;
|
||||
|
||||
public string CurrentSceneName => _currentSceneName;
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
StorageSystem.Instance.RegisterData(_data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -56,7 +56,7 @@ namespace AibisDream
|
||||
_sceneToLoad = targetScene;
|
||||
|
||||
// 在卸载场景前清理所有系统
|
||||
if (!string.IsNullOrEmpty(_data.sceneName))
|
||||
if (!string.IsNullOrEmpty(_currentSceneName))
|
||||
{
|
||||
DOTween.KillAll();
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace AibisDream
|
||||
|
||||
// 场景加载完成,通知各系统
|
||||
EnumEventSystem.Global.Send(EventEnum.SceneLoad, _sceneToLoad);
|
||||
_data.sceneName = _sceneToLoad;
|
||||
_currentSceneName = _sceneToLoad;
|
||||
UIManager.Instance.GetPanel<InfoPanel>().HideLoading();
|
||||
_isLoading = false;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ namespace AibisDream
|
||||
ResourceSystem.ReleaseSceneLoader();
|
||||
|
||||
yield return Addressables.UnloadSceneAsync(_loadHandle);
|
||||
_data.sceneName = null;
|
||||
_currentSceneName = null;
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
@@ -108,16 +108,4 @@ namespace AibisDream
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[LoadIndex(1)]
|
||||
public class SceneData : IData
|
||||
{
|
||||
public string sceneName;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
// yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync();
|
||||
yield return SceneLoader.Instance.LoadSceneAsync(sceneName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using AibisDream.UI;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class StorageSystem : VariableStorageBehaviour
|
||||
{
|
||||
private const string VariablePrefix = "$";
|
||||
private const string GlobalPrefix = "$global_";
|
||||
private const string DataPrefix = "$data.";
|
||||
private const int MaxSaveNum = 200;
|
||||
|
||||
#region 存储部分
|
||||
|
||||
private readonly Dictionary<string, object> _variableDict = new();
|
||||
private readonly DataContainer _dataContainer = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 数据类注册和注销
|
||||
|
||||
public void RegisterData<T>(T data) where T : IData
|
||||
{
|
||||
_dataContainer.Register(data);
|
||||
}
|
||||
|
||||
public void UnregisterData<T>() where T : class, IData
|
||||
{
|
||||
_dataContainer.Unregister<T>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 变量设置
|
||||
|
||||
public override void SetValue(string variableName, bool boolValue)
|
||||
{
|
||||
SetValue(variableName, boolValue);
|
||||
}
|
||||
|
||||
public override void SetValue(string variableName, string stringValue)
|
||||
{
|
||||
SetValue(variableName, stringValue);
|
||||
}
|
||||
|
||||
public override void SetValue(string variableName, float floatValue)
|
||||
{
|
||||
SetValue(variableName, floatValue);
|
||||
}
|
||||
|
||||
private void SetValue<T>(string variableName, T value)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
Debug.LogError($"{variableName}: 缺少$前缀");
|
||||
return;
|
||||
}
|
||||
|
||||
if (variableType == VariableType.DataField)
|
||||
{
|
||||
Debug.LogError($"{variableName}: 暂时不支持直接设置系统数据");
|
||||
return;
|
||||
}
|
||||
|
||||
// Global和Local直接设置
|
||||
_variableDict[variableName] = value;
|
||||
|
||||
// 数据设置事件广播
|
||||
var variableItem = new VariableItem
|
||||
{
|
||||
variableName = variableName,
|
||||
value = value
|
||||
};
|
||||
EnumEventSystem.Global.Send(StorageEvent.VariableSet, variableItem);
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_variableDict.Clear();
|
||||
}
|
||||
|
||||
public void ClearLocal()
|
||||
{
|
||||
// 收集所有Local类型的变量
|
||||
var locals = _variableDict.Keys.Where(item => ValidateVariableName(item) == VariableType.Local).ToArray();
|
||||
// 清除
|
||||
foreach (var localKey in locals)
|
||||
{
|
||||
_variableDict.Remove(localKey);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 变量获取
|
||||
|
||||
public override bool Contains(string variableName)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return variableType == VariableType.DataField
|
||||
? _dataContainer.TryGetFieldValue<string>(variableName, out _)
|
||||
: _variableDict.ContainsKey(variableName);
|
||||
}
|
||||
|
||||
public override bool TryGetValue<T>(string variableName, out T result)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
// 非法的直接pass
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 在系统数据中查找
|
||||
if (variableType == VariableType.DataField)
|
||||
{
|
||||
return _dataContainer.TryGetFieldValue(variableName, out result);
|
||||
}
|
||||
|
||||
// 直接查找数据
|
||||
if (_variableDict.TryGetValue(variableName, out var value))
|
||||
{
|
||||
if (value is T target)
|
||||
{
|
||||
result = target;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (TryGetInitialValue(variableName, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetInitialValue<T>(string variableName, out T result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
if (Program == null || !Program.InitialValues.TryGetValue(variableName, out var initialValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitialValueMatchesType<T>(initialValue.ValueCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Program.TryGetInitialValue(variableName, out result);
|
||||
}
|
||||
|
||||
private static bool InitialValueMatchesType<T>(Yarn.Operand.ValueOneofCase valueCase)
|
||||
{
|
||||
var targetType = typeof(T);
|
||||
|
||||
return valueCase switch
|
||||
{
|
||||
Yarn.Operand.ValueOneofCase.BoolValue => targetType == typeof(bool),
|
||||
Yarn.Operand.ValueOneofCase.StringValue => targetType == typeof(string),
|
||||
Yarn.Operand.ValueOneofCase.FloatValue => targetType == typeof(float),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 序列化
|
||||
|
||||
public override void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings,
|
||||
Dictionary<string, bool> bools, bool clear = true)
|
||||
{
|
||||
if (clear)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
foreach (var value in floats)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
|
||||
foreach (var value in strings)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
|
||||
foreach (var value in bools)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public override (Dictionary<string, float> FloatVariables, Dictionary<string, string> StringVariables,
|
||||
Dictionary<string, bool> BoolVariables) GetAllVariables()
|
||||
{
|
||||
var floatDict = new Dictionary<string, float>();
|
||||
var stringDict = new Dictionary<string, string>();
|
||||
var boolDict = new Dictionary<string, bool>();
|
||||
|
||||
foreach (var variablePair in _variableDict)
|
||||
{
|
||||
switch (variablePair.Value)
|
||||
{
|
||||
case float:
|
||||
{
|
||||
var value = Convert.ToSingle(variablePair.Value);
|
||||
floatDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
case string:
|
||||
{
|
||||
var value = Convert.ToString(variablePair.Value);
|
||||
stringDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
case bool:
|
||||
{
|
||||
var value = Convert.ToBoolean(variablePair.Value);
|
||||
boolDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Debug.Log($"{variablePair.Key} is not a valid type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (floatDict, stringDict, boolDict);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 保存/加载
|
||||
|
||||
public void Save()
|
||||
{
|
||||
ActionKit.Sequence()
|
||||
.Callback(() => UIManager.Instance.GetPanel<InfoPanel>().ShowSaveLoading())
|
||||
.Delay(1f)
|
||||
.Callback(() => UIManager.Instance.GetPanel<InfoPanel>().HideSaveLoading())
|
||||
.Start(this);
|
||||
|
||||
using (new CodeTimer("Save"))
|
||||
{
|
||||
// 先序列化Yarn变量
|
||||
var saveFile = new JObject();
|
||||
|
||||
// 保存版本号
|
||||
saveFile["version"] = $"Ver.{Application.version}";
|
||||
saveFile["time"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// 保存系统数据
|
||||
saveFile["systemData"] = _dataContainer.SaveAsJson();
|
||||
|
||||
// 保存对话变量
|
||||
var (floatDict, stringDict, boolDict) = GetAllVariables();
|
||||
saveFile["floatDict"] = JObject.FromObject(floatDict);
|
||||
saveFile["stringDict"] = JObject.FromObject(stringDict);
|
||||
saveFile["boolDict"] = JObject.FromObject(boolDict);
|
||||
|
||||
// 存为Json
|
||||
JsonUtil.SaveJObject(saveFile, GetSavePath());
|
||||
#if !UNITY_EDITOR
|
||||
DeleteOldSave();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteOldSave()
|
||||
{
|
||||
if (Directory.Exists(ConstRef.SaveFilePath))
|
||||
{
|
||||
var fileList = Directory.GetFiles(ConstRef.SaveFilePath);
|
||||
if (fileList.Length > MaxSaveNum)
|
||||
{
|
||||
var sorted = fileList.OrderByDescending(f => f).ToArray();
|
||||
for (var i = MaxSaveNum; i < fileList.Length; i++)
|
||||
{
|
||||
File.Delete(sorted[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(ConstRef.SaveFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(string savePath)
|
||||
{
|
||||
// 先将存档文件转为JObject
|
||||
var saveData = JsonUtil.ReadJObject(savePath);
|
||||
// 加载Yarn数据
|
||||
var floatDict = saveData["floatDict"]?.ToObject<Dictionary<string, float>>();
|
||||
var stringDict = saveData["stringDict"]?.ToObject<Dictionary<string, string>>();
|
||||
var boolDict = saveData["boolDict"]?.ToObject<Dictionary<string, bool>>();
|
||||
SetAllVariables(floatDict, stringDict, boolDict);
|
||||
|
||||
// 加载系统数据
|
||||
if (saveData["systemData"] is not JObject systemDataJson)
|
||||
{
|
||||
Debug.Log("维修数据读取问题");
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(_dataContainer.LoadByJson(systemDataJson));
|
||||
}
|
||||
|
||||
private string GetSavePath()
|
||||
{
|
||||
Directory.CreateDirectory(ConstRef.SaveFilePath);
|
||||
|
||||
string gameStage;
|
||||
if (TryGetValue("$gameStage", out float gameStageFloat))
|
||||
{
|
||||
gameStage = gameStageFloat.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryGetValue("$gameStage", out string gameStageStr);
|
||||
gameStage = gameStageStr;
|
||||
}
|
||||
|
||||
return
|
||||
$"{ConstRef.SaveFilePath}/{DateTime.Now:yyyyMMdd-HHmmss}_{GameManager.Instance.GetSceneSoName()}_{gameStage}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static VariableType ValidateVariableName(string variableName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(variableName) || !variableName.StartsWith(VariablePrefix))
|
||||
{
|
||||
return VariableType.Illegal;
|
||||
}
|
||||
|
||||
if (variableName.StartsWith(GlobalPrefix))
|
||||
{
|
||||
return VariableType.Global;
|
||||
}
|
||||
|
||||
if (variableName.StartsWith(DataPrefix))
|
||||
{
|
||||
return VariableType.DataField;
|
||||
}
|
||||
|
||||
return VariableType.Local;
|
||||
}
|
||||
|
||||
#region 单例部分
|
||||
|
||||
public static StorageSystem Instance { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum VariableType
|
||||
{
|
||||
Local,
|
||||
Global,
|
||||
DataField,
|
||||
Illegal
|
||||
}
|
||||
|
||||
public struct VariableItem
|
||||
{
|
||||
public string variableName;
|
||||
public object value;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd2f44edfbd942b49bb7a37f80d7a5eb
|
||||
timeCreated: 1742802488
|
||||
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.SaveSystem;
|
||||
using UnityEngine;
|
||||
using Yarn;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// Yarn 运行时变量存储(VariableStorageBehaviour)。
|
||||
/// 存档快照、落盘、深度维修容器见 SaveSystem 下各类。
|
||||
/// </summary>
|
||||
public class YarnVariableStorage : VariableStorageBehaviour
|
||||
{
|
||||
private const string VariablePrefix = "$";
|
||||
private const string GlobalPrefix = "$global_";
|
||||
|
||||
private readonly Dictionary<string, object> _variableDict = new();
|
||||
|
||||
#region 变量设置
|
||||
|
||||
public override void SetValue(string variableName, bool boolValue)
|
||||
{
|
||||
SetValue(variableName, boolValue);
|
||||
}
|
||||
|
||||
public override void SetValue(string variableName, string stringValue)
|
||||
{
|
||||
SetValue(variableName, stringValue);
|
||||
}
|
||||
|
||||
public override void SetValue(string variableName, float floatValue)
|
||||
{
|
||||
SetValue(variableName, floatValue);
|
||||
}
|
||||
|
||||
private void SetValue<T>(string variableName, T value)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
Debug.LogError($"{variableName}: 缺少$前缀");
|
||||
return;
|
||||
}
|
||||
|
||||
_variableDict[variableName] = value;
|
||||
|
||||
var variableItem = new VariableItem
|
||||
{
|
||||
variableName = variableName,
|
||||
value = value
|
||||
};
|
||||
EnumEventSystem.Global.Send(StorageEvent.VariableSet, variableItem);
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_variableDict.Clear();
|
||||
}
|
||||
|
||||
public void ClearLocal()
|
||||
{
|
||||
var locals = _variableDict.Keys.Where(item => ValidateVariableName(item) == VariableType.Local).ToArray();
|
||||
foreach (var localKey in locals)
|
||||
{
|
||||
_variableDict.Remove(localKey);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 变量获取
|
||||
|
||||
public override bool Contains(string variableName)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _variableDict.ContainsKey(variableName);
|
||||
}
|
||||
|
||||
public override bool TryGetValue<T>(string variableName, out T result)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (GetVariableKind(variableName))
|
||||
{
|
||||
case VariableKind.Stored:
|
||||
if (_variableDict.TryGetValue(variableName, out var value) && value is T target)
|
||||
{
|
||||
result = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Program == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Can't get initial value for variable {variableName}, because {nameof(Program)} is not set");
|
||||
}
|
||||
|
||||
return Program.TryGetInitialValue(variableName, out result);
|
||||
|
||||
case VariableKind.Smart:
|
||||
if (SmartVariableEvaluator == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Can't get value for smart variable {variableName}, because {nameof(SmartVariableEvaluator)} is not set");
|
||||
}
|
||||
|
||||
return SmartVariableEvaluator.TryGetSmartVariable(variableName, out result);
|
||||
|
||||
default:
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 序列化(供 SnapshotCapture / SnapshotRestore 使用)
|
||||
|
||||
public override void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings,
|
||||
Dictionary<string, bool> bools, bool clear = true)
|
||||
{
|
||||
if (clear)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
if (floats != null)
|
||||
{
|
||||
foreach (var value in floats)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (strings != null)
|
||||
{
|
||||
foreach (var value in strings)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (bools != null)
|
||||
{
|
||||
foreach (var value in bools)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override (Dictionary<string, float> FloatVariables, Dictionary<string, string> StringVariables,
|
||||
Dictionary<string, bool> BoolVariables) GetAllVariables()
|
||||
{
|
||||
var floatDict = new Dictionary<string, float>();
|
||||
var stringDict = new Dictionary<string, string>();
|
||||
var boolDict = new Dictionary<string, bool>();
|
||||
|
||||
foreach (var variablePair in _variableDict)
|
||||
{
|
||||
switch (variablePair.Value)
|
||||
{
|
||||
case float:
|
||||
{
|
||||
var value = Convert.ToSingle(variablePair.Value);
|
||||
floatDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
case string:
|
||||
{
|
||||
var value = Convert.ToString(variablePair.Value);
|
||||
stringDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
case bool:
|
||||
{
|
||||
var value = Convert.ToBoolean(variablePair.Value);
|
||||
boolDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Debug.Log($"{variablePair.Key} is not a valid type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (floatDict, stringDict, boolDict);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static VariableType ValidateVariableName(string variableName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(variableName) || !variableName.StartsWith(VariablePrefix))
|
||||
{
|
||||
return VariableType.Illegal;
|
||||
}
|
||||
|
||||
if (variableName.StartsWith(GlobalPrefix))
|
||||
{
|
||||
return VariableType.Global;
|
||||
}
|
||||
|
||||
return VariableType.Local;
|
||||
}
|
||||
|
||||
#region 单例
|
||||
|
||||
public static YarnVariableStorage Instance { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
SnapshotRegistry.EnsureInitialized();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum VariableType
|
||||
{
|
||||
Local,
|
||||
Global,
|
||||
Illegal
|
||||
}
|
||||
|
||||
public struct VariableItem
|
||||
{
|
||||
public string variableName;
|
||||
public object value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd2f44edfbd942b49bb7a37f80d7a5eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -123,7 +123,7 @@ namespace AibisDream
|
||||
|
||||
var validateResult = validator.Validate(grid.GetShapes());
|
||||
var shapesInGrid = grid.GetShapes().Select(s => s.BlockShapeData.blockShapeInfo.shapeName).ToArray();
|
||||
StorageSystem.Instance.SetValue("$shapesInGrid", string.Join(",", shapesInGrid));
|
||||
YarnVariableStorage.Instance.SetValue("$shapesInGrid", string.Join(",", shapesInGrid));
|
||||
|
||||
// 如果验证通过, 则设置变量并弹出对话
|
||||
if (validateResult.isPass)
|
||||
@@ -236,7 +236,7 @@ namespace AibisDream
|
||||
// 点击形状
|
||||
blockPuzzlePanel.ShowShapeInScreen(blockShape.BlockShapeData.blockShapeInfo);
|
||||
// 播放事件节点
|
||||
StorageSystem.Instance.SetValue("$selectedShape", blockShape.BlockShapeData.blockShapeInfo.shapeName);
|
||||
YarnVariableStorage.Instance.SetValue("$selectedShape", blockShape.BlockShapeData.blockShapeInfo.shapeName);
|
||||
SingleCastEventSystem.Global.Trigger(DialogEventEnum.StartNode, "ShapeSelected");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ using System.Linq;
|
||||
using DG.Tweening;
|
||||
using Shapes;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using Cinemachine;
|
||||
|
||||
namespace AibisDream.MiniGame.Language
|
||||
{
|
||||
@@ -137,7 +135,6 @@ namespace AibisDream.MiniGame.Language
|
||||
/// </summary>
|
||||
public class AnalysisModeManager : MonoBehaviour
|
||||
{
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
[Header("Canvas 引用(共享)")]
|
||||
[SerializeField] private Canvas sharedCanvas; // 共享 LanguageParticleManager 的 canvas
|
||||
|
||||
@@ -225,25 +222,10 @@ namespace AibisDream.MiniGame.Language
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 注册到系统字典
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
|
||||
// 注册相机
|
||||
var virtualCam = transform.Find("Analysis Camera")?.GetComponent<ICinemachineCamera>();
|
||||
if (virtualCam != null)
|
||||
{
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.AnalysisMode, virtualCam);
|
||||
}
|
||||
|
||||
SetupUI();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CameraKit.Instance?.UnRegisterCamera(CameraEnum.AnalysisMode);
|
||||
CameraKit.Instance?.UnRegisterCamera(CameraEnum.AnalysisModeDeep);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入分析模式(由状态机调用)
|
||||
/// </summary>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
using UnityEngine;
|
||||
using AibisDream.FixSystem;
|
||||
using Cinemachine;
|
||||
using AibisDream.Framework;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class EmoManager : MonoBehaviour
|
||||
{
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
private GameObject emoView;
|
||||
|
||||
void Start()
|
||||
@@ -15,10 +12,6 @@ namespace AibisDream
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
emoView = transform.Find("Emo View").gameObject;
|
||||
emoView.SetActive(false);
|
||||
var virtualCam = transform.Find("Emo Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam2 = transform.Find("Emo Deep Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.Emo, virtualCam);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.EmoDeep, virtualCam2);
|
||||
}
|
||||
|
||||
public void OpenView()
|
||||
|
||||
@@ -2,8 +2,6 @@ using System.Collections;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using AibisDream.FixSystem;
|
||||
using Cinemachine;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.MiniGame.Language;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -14,7 +12,6 @@ namespace AibisDream
|
||||
/// </summary>
|
||||
public class ExpressionManager : MonoBehaviour
|
||||
{
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
[Header("系统组件")]
|
||||
[SerializeField] private LanguageParticleManager particleManager;
|
||||
[SerializeField] private GameObject expressionView;
|
||||
@@ -61,19 +58,6 @@ namespace AibisDream
|
||||
{
|
||||
glitchController = GetComponentInChildren<SpriteNoiseGlitchController>(true);
|
||||
}
|
||||
|
||||
// 注册相机
|
||||
var virtualCam = transform.Find("Expression Camera")?.GetComponent<ICinemachineCamera>();
|
||||
if (virtualCam != null)
|
||||
{
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.Expression, virtualCam);
|
||||
}
|
||||
|
||||
var virtualCam2 = transform.Find("Expression Deep Camera")?.GetComponent<ICinemachineCamera>();
|
||||
if (virtualCam2 != null)
|
||||
{
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.ExpressionDeep, virtualCam2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -251,10 +235,5 @@ namespace AibisDream
|
||||
yield return glitchController.StartCoroutine(glitchController.TransitionToAndWait(to, duration));
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CameraKit.Instance?.UnRegisterCamera(CameraEnum.Expression);
|
||||
CameraKit.Instance?.UnRegisterCamera(CameraEnum.ExpressionDeep);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -707,7 +707,7 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
private void TriggerFailure()
|
||||
{
|
||||
_failCount++;
|
||||
StorageSystem.Instance?.SetValue("$knobFailCount", (float)_failCount);
|
||||
YarnVariableStorage.Instance?.SetValue("$knobFailCount", (float)_failCount);
|
||||
_hasFailed = true;
|
||||
Debug.Log($"[KnobController] Failure #{_failCount} at {_currentPower:F1}%, waiting for snapback...");
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Cinemachine;
|
||||
using AibisDream;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
@@ -26,15 +24,6 @@ namespace AibisDream.MiniGame.Language
|
||||
[Tooltip("销售系统主视图对象(会自动查找名为'SaleView'或'SellView'的子对象)")]
|
||||
[SerializeField] private GameObject saleView;
|
||||
|
||||
[HideInInspector] public BubbleSlotGroup bubbleSlotGroup;
|
||||
|
||||
[Header("相机配置")]
|
||||
[Tooltip("主销售相机")]
|
||||
[SerializeField] private CameraEnum mainCamera = CameraEnum.SaleCamera;
|
||||
|
||||
[Tooltip("深度销售相机(可选)")]
|
||||
[SerializeField] private CameraEnum deepCamera = CameraEnum.SaleDeepCamera;
|
||||
|
||||
[Header("销售模块深入 - Crank 与 Slider 协调")]
|
||||
[Tooltip("发条控制器(不填则从 saleView 下查找)")]
|
||||
[SerializeField] private CrankController crankController;
|
||||
@@ -90,7 +79,6 @@ namespace AibisDream.MiniGame.Language
|
||||
[Tooltip("盖子淡出时长")]
|
||||
[SerializeField] private float coverFadeDuration = 0.5f;
|
||||
|
||||
private readonly List<CameraEnum> registeredCameras = new();
|
||||
private bool _isProcessorDeepMode;
|
||||
private Vector3 _saleViewOriginalPos;
|
||||
private Vector3 _saleViewOriginalScale;
|
||||
@@ -139,26 +127,9 @@ namespace AibisDream.MiniGame.Language
|
||||
sliderController?.SetControlActive(true);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 对话气泡槽组(SaleSystem 下名为 "Bubble Slots" 的 GameObject)
|
||||
var bubbleSlots = transform.Find("Bubble Slots");
|
||||
if (bubbleSlots != null)
|
||||
{
|
||||
bubbleSlotGroup = bubbleSlots.GetComponent<BubbleSlotGroup>();
|
||||
if (bubbleSlotGroup == null)
|
||||
Debug.LogWarning("[SalesManager] 找到 'Bubble Slots' 但无 BubbleSlotGroup 组件", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[SalesManager] 未找到名为 'Bubble Slots' 的子对象,对话气泡将无法显示", this);
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
RegisterToSystem();
|
||||
RegisterCameras();
|
||||
FindSaleView();
|
||||
FindSaleViewEnterAnimation();
|
||||
FindCrankController();
|
||||
@@ -171,7 +142,6 @@ namespace AibisDream.MiniGame.Language
|
||||
private void OnDestroy()
|
||||
{
|
||||
_deepModeSequence?.Kill();
|
||||
UnregisterCameras();
|
||||
UnsubscribeFromCrankSlider();
|
||||
}
|
||||
|
||||
@@ -307,7 +277,7 @@ namespace AibisDream.MiniGame.Language
|
||||
if (_currentTurnIndex > totalTurns) return;
|
||||
|
||||
// 滑片开始转动前,先触发 ChipModule 解释当前步骤(Stage3 等配合)
|
||||
StorageSystem.Instance?.SetValue("$salesTurnIndex", (float)_currentTurnIndex);
|
||||
YarnVariableStorage.Instance?.SetValue("$salesTurnIndex", (float)_currentTurnIndex);
|
||||
if (!string.IsNullOrEmpty(crankSlideStartNode))
|
||||
DialogController.Instance?.StartDialogNode(crankSlideStartNode);
|
||||
|
||||
@@ -327,7 +297,7 @@ namespace AibisDream.MiniGame.Language
|
||||
{
|
||||
if (string.IsNullOrEmpty(crankCompleteNode)) return;
|
||||
|
||||
StorageSystem.Instance?.SetValue("$salesTurnIndex", (float)_currentTurnIndex);
|
||||
YarnVariableStorage.Instance?.SetValue("$salesTurnIndex", (float)_currentTurnIndex);
|
||||
DialogController.Instance?.StartDialogNode(crankCompleteNode);
|
||||
}
|
||||
|
||||
@@ -347,59 +317,6 @@ namespace AibisDream.MiniGame.Language
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册所有相机到CameraKit
|
||||
/// </summary>
|
||||
private void RegisterCameras()
|
||||
{
|
||||
if (CameraKit.Instance == null)
|
||||
{
|
||||
Debug.LogError("[SalesManager] CameraKit.Instance 为 null,无法注册相机!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有相机
|
||||
var virtualCams = GetComponentsInChildren<ICinemachineCamera>();
|
||||
|
||||
if (virtualCams.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[SalesManager] 未找到任何 ICinemachineCamera 组件!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册主相机
|
||||
if (virtualCams.Length > 0)
|
||||
{
|
||||
CameraKit.Instance.RegisterCamera(mainCamera, virtualCams[0]);
|
||||
registeredCameras.Add(mainCamera);
|
||||
Debug.Log($"[SalesManager] 成功注册主相机: {mainCamera}");
|
||||
}
|
||||
|
||||
// 注册深度相机(如果有)
|
||||
if (virtualCams.Length > 1)
|
||||
{
|
||||
CameraKit.Instance.RegisterCamera(deepCamera, virtualCams[1]);
|
||||
registeredCameras.Add(deepCamera);
|
||||
Debug.Log($"[SalesManager] 成功注册深度相机: {deepCamera}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从CameraKit注销所有相机
|
||||
/// </summary>
|
||||
private void UnregisterCameras()
|
||||
{
|
||||
if (CameraKit.Instance == null) return;
|
||||
|
||||
foreach (var cameraType in registeredCameras)
|
||||
{
|
||||
CameraKit.Instance.UnRegisterCamera(cameraType);
|
||||
Debug.Log($"[SalesManager] 已注销相机: {cameraType}");
|
||||
}
|
||||
|
||||
registeredCameras.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开销售系统界面
|
||||
/// </summary>
|
||||
|
||||
@@ -90,7 +90,7 @@ public class LineInteraction : MonoBehaviour
|
||||
Destroy(gameObject);
|
||||
|
||||
// 调用对话控制器
|
||||
DialogController.Instance.StartDialogNode("CutLine");
|
||||
DialogController.Instance.StartDialogNode("OnCutLineComplete");
|
||||
}
|
||||
|
||||
public void ActivateInteraction()
|
||||
|
||||
@@ -7,9 +7,9 @@ using Yarn.Unity;
|
||||
using System.Linq;
|
||||
using UnityEngine.UI;
|
||||
using AibisDream.FixSystem;
|
||||
using Cinemachine;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using Sequence = DG.Tweening.Sequence;
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace AibisDream
|
||||
{
|
||||
private const string MemorySpritePath = "Sprite/Memory/{0}.png";
|
||||
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
[SerializeField] private List<EyeTarget> targets = new(); // 保存目标列表
|
||||
public List<EyeTarget> Targets => targets; // 提供目标列表的公共访问
|
||||
private EyeTarget currentTarget; // 当前选中的目标
|
||||
@@ -29,7 +28,20 @@ namespace AibisDream
|
||||
public GameObject eyeView;
|
||||
public ViewCameraManager cameraManager;
|
||||
private IEyeStateEffect eyeStateEffect;
|
||||
private EyeData _eyeData = new();
|
||||
private EyeColorState _currentState = EyeColorState.CannotImagineColor;
|
||||
|
||||
/// <summary>获取当前 Eye 颜色阶段。</summary>
|
||||
public EyeColorState GetCurrentColorState() => _currentState;
|
||||
|
||||
public enum EyeColorState
|
||||
{
|
||||
CanImagineColor,
|
||||
CannotImagineColor,
|
||||
EyeDisorder,
|
||||
CanSeeColor,
|
||||
HaveChip
|
||||
}
|
||||
|
||||
private Tween hsvShiftTween;
|
||||
private MouseFollowAndZoom mouseFollowAndZoom;
|
||||
private RenderTexture runtimeEyeRenderTexture;
|
||||
@@ -168,26 +180,29 @@ namespace AibisDream
|
||||
public void Init()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
var virtualCam = transform.Find("Eye Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCamDeep = transform.Find("Eye DeepCamera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.Eye, virtualCam);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.EyeDeep, virtualCamDeep);
|
||||
StorageSystem.Instance.RegisterData(_eyeData);
|
||||
// 添加加载逻辑
|
||||
_eyeData.LoadEvent += LoadData;
|
||||
|
||||
// 确保 ViewCamera 默认不激活
|
||||
|
||||
if (cameraManager != null)
|
||||
{
|
||||
cameraManager.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
EnsureMouseFollowAndZoom();
|
||||
}
|
||||
|
||||
private void LoadData(EyeData eyedata)
|
||||
public EyeSnapshotDto CaptureEyeSnapshot()
|
||||
{
|
||||
SetEyeColorState(eyedata.currentState);
|
||||
return new EyeSnapshotDto { eyeColorState = _currentState.ToString() };
|
||||
}
|
||||
|
||||
public void ApplyEyeSnapshot(EyeSnapshotDto dto)
|
||||
{
|
||||
if (dto == null || string.IsNullOrEmpty(dto.eyeColorState))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Enum.TryParse(dto.eyeColorState, true, out EyeColorState state))
|
||||
{
|
||||
SetEyeColorState(state);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEnter()
|
||||
@@ -267,7 +282,7 @@ namespace AibisDream
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SetEyeColorState(_eyeData.currentState);
|
||||
SetEyeColorState(_currentState);
|
||||
|
||||
EnsureMouseFollowAndZoom();
|
||||
}
|
||||
@@ -304,7 +319,7 @@ namespace AibisDream
|
||||
yield break;
|
||||
}
|
||||
|
||||
StorageSystem.Instance.SetValue("$currentEyeTarget", target.name);
|
||||
YarnVariableStorage.Instance.SetValue("$currentEyeTarget", target.name);
|
||||
StartCoroutine(EyeEffectFadeOut());
|
||||
|
||||
cameraManager.MoveCameraTo(target.targetTransform ? target.targetTransform : target.transform, 1f);
|
||||
@@ -401,7 +416,7 @@ namespace AibisDream
|
||||
public void SetEyeColorStateForYarn(string state)
|
||||
{
|
||||
// Try to parse the string as an EyeColorState
|
||||
if (!Enum.TryParse(state, true, out EyeData.EyeColorState eyeState))
|
||||
if (!Enum.TryParse(state, true, out EyeColorState eyeState))
|
||||
{
|
||||
throw new ArgumentException("Invalid eye state: " + state, nameof(state));
|
||||
}
|
||||
@@ -410,24 +425,24 @@ namespace AibisDream
|
||||
SetEyeColorState(eyeState);
|
||||
}
|
||||
|
||||
private void SetEyeColorState(EyeData.EyeColorState state)
|
||||
private void SetEyeColorState(EyeColorState state)
|
||||
{
|
||||
_eyeData.currentState = state;
|
||||
_currentState = state;
|
||||
switch (state)
|
||||
{
|
||||
case EyeData.EyeColorState.CanImagineColor:
|
||||
case EyeColorState.CanImagineColor:
|
||||
eyeStateEffect = new CanImagineColorEffect();
|
||||
break;
|
||||
case EyeData.EyeColorState.CannotImagineColor:
|
||||
case EyeColorState.CannotImagineColor:
|
||||
eyeStateEffect = new CannotImagineColorEffect();
|
||||
break;
|
||||
case EyeData.EyeColorState.EyeDisorder:
|
||||
case EyeColorState.EyeDisorder:
|
||||
eyeStateEffect = new EyeDisorderEffect();
|
||||
break;
|
||||
case EyeData.EyeColorState.CanSeeColor:
|
||||
case EyeColorState.CanSeeColor:
|
||||
eyeStateEffect = new CanSeeColorEffect();
|
||||
break;
|
||||
case EyeData.EyeColorState.HaveChip:
|
||||
case EyeColorState.HaveChip:
|
||||
eyeStateEffect = new HaveChip();
|
||||
break;
|
||||
default:
|
||||
@@ -607,7 +622,7 @@ namespace AibisDream
|
||||
[YarnCommand("EyeGetColor")]
|
||||
public void GetColor()
|
||||
{
|
||||
SetEyeColorState(EyeData.EyeColorState.CanImagineColor);
|
||||
SetEyeColorState(EyeColorState.CanImagineColor);
|
||||
// 对目标列表中的每个目标执行 ColorBack 方法
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
@@ -617,25 +632,4 @@ namespace AibisDream
|
||||
// 切换到 CanImagineColorEffect 状态
|
||||
}
|
||||
}
|
||||
|
||||
public class EyeData : IData
|
||||
{
|
||||
public enum EyeColorState
|
||||
{
|
||||
CanImagineColor,
|
||||
CannotImagineColor,
|
||||
EyeDisorder,
|
||||
CanSeeColor,
|
||||
HaveChip
|
||||
}
|
||||
|
||||
public EyeColorState currentState = EyeColorState.CannotImagineColor;
|
||||
public event Action<EyeData> LoadEvent;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
LoadEvent?.Invoke(this);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using AibisDream;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using Cinemachine;
|
||||
using UnityEngine.Rendering;
|
||||
using Yarn.Unity;
|
||||
using DG.Tweening;
|
||||
@@ -46,8 +44,6 @@ public class UFSystem : MonoBehaviour
|
||||
private void Awake()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
var virtualCam = transform.Find("UF Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.UF, virtualCam);
|
||||
eyeSystem = FindObjectOfType<EyeSystem>();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections;
|
||||
using AibisDream;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.MiniGame.Tarot
|
||||
@@ -16,9 +17,7 @@ namespace AibisDream.MiniGame.Tarot
|
||||
[SerializeField] private Sprite backSprite;
|
||||
|
||||
[Header("Dialogue Bubbles")]
|
||||
[Tooltip("塔罗流程中使用的气泡槽位;不指定则进入塔罗时不切换气泡。")]
|
||||
[SerializeField] private BubbleSlotGroup tarotBubbleSlotGroup;
|
||||
[Tooltip("本场景普通对话的气泡槽位,用于退出塔罗时恢复;不填则尝试 OutsideCenter / FixPanelSystem。")]
|
||||
[Tooltip("退出塔罗时恢复的气泡槽位;不填则尝试 Outside / BodyModule。")]
|
||||
[SerializeField] private BubbleSlotGroup restoreBubbleSlotGroup;
|
||||
|
||||
private TarotDeck _deck;
|
||||
@@ -164,7 +163,7 @@ namespace AibisDream.MiniGame.Tarot
|
||||
{
|
||||
_isCardSelected = true;
|
||||
_selectedIndex = card.Index;
|
||||
StorageSystem.Instance?.SetValue("$tarotSelectedIndex", (float)card.Index);
|
||||
YarnVariableStorage.Instance?.SetValue("$tarotSelectedIndex", (float)card.Index);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@@ -176,21 +175,21 @@ namespace AibisDream.MiniGame.Tarot
|
||||
|
||||
private void TryEnterTarotBubbleMode()
|
||||
{
|
||||
if (tarotBubbleSlotGroup == null)
|
||||
if (!BubbleSlotKit.Instance.TryGet(BubbleSlotEnum.Tarot, out _))
|
||||
return;
|
||||
|
||||
var snapshot = CollectRestoreSnapshot();
|
||||
if (snapshot.bubbleSlots == null || snapshot.bubbleSlots.Length == 0)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
"[TarotManager] 已配置塔罗气泡但无法取得有效的恢复快照(请指定 restoreBubbleSlotGroup 或保证 OutsideCenter/FixPanelSystem 气泡可用),跳过气泡切换。",
|
||||
"[TarotManager] 已配置 Tarot 气泡但无法取得有效的恢复快照(请指定 restoreBubbleSlotGroup 或保证 Outside/BodyModule 气泡已注册),跳过气泡切换。",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
|
||||
_savedBubbleData = snapshot;
|
||||
_bubbleSwapActive = true;
|
||||
DialogUIManager.Instance.LoadBubbles(tarotBubbleSlotGroup.CollectData());
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Tarot);
|
||||
}
|
||||
|
||||
private void RestoreTarotBubbleMode()
|
||||
@@ -206,11 +205,11 @@ namespace AibisDream.MiniGame.Tarot
|
||||
if (restoreBubbleSlotGroup != null)
|
||||
return restoreBubbleSlotGroup.CollectData();
|
||||
|
||||
if (OutsideCenter.Instance != null && OutsideCenter.Instance.bubbleSlotGroup != null)
|
||||
return OutsideCenter.Instance.bubbleSlotGroup.CollectData();
|
||||
if (BubbleSlotKit.Instance.TryGet(BubbleSlotEnum.Outside, out _))
|
||||
return BubbleSlotKit.Instance.CollectData(BubbleSlotEnum.Outside);
|
||||
|
||||
if (FixPanelSystem.Instance != null && FixPanelSystem.Instance.BubbleSlotGroup != null)
|
||||
return FixPanelSystem.Instance.BubbleSlotGroup.CollectData();
|
||||
if (BubbleSlotKit.Instance.TryGet(BubbleSlotEnum.BodyModule, out _))
|
||||
return BubbleSlotKit.Instance.CollectData(BubbleSlotEnum.BodyModule);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a170c0b6c933c540a86be691fa6745c
|
||||
PrefabImporter:
|
||||
guid: 460a9e992cac6da46b7f32b0bb5c5f4c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 520cc163c88e4c00895fe201004b4384
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.SaveSystem.CloudSave
|
||||
{
|
||||
/// <summary>
|
||||
/// 云存档协调器:负责选择当前平台的 backend,并在槽位写入/读取后触发同步钩子。
|
||||
/// <para>
|
||||
/// P2 不实现真实云同步逻辑,仅保留扩展点。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class CloudSaveManager
|
||||
{
|
||||
private static readonly List<ICloudSaveBackend> Backends = new();
|
||||
|
||||
/// <summary>注册一个云存档后端。应在游戏启动时调用一次。</summary>
|
||||
public static void RegisterBackend(ICloudSaveBackend backend)
|
||||
{
|
||||
if (backend == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Backends.Add(backend);
|
||||
}
|
||||
|
||||
/// <summary>清空已注册后端(主要用于测试)。</summary>
|
||||
public static void ClearBackends()
|
||||
{
|
||||
Backends.Clear();
|
||||
}
|
||||
|
||||
/// <summary>槽位写入后调用:触发所有可用后端的异步上传。</summary>
|
||||
public static void OnSlotWritten(int slotIndex)
|
||||
{
|
||||
_ = UploadSlotAsync(slotIndex);
|
||||
}
|
||||
|
||||
/// <summary>槽位读取前调用:可触发下载(P2 默认不自动下载,避免覆盖本地进度)。</summary>
|
||||
public static void OnSlotRestored(int slotIndex)
|
||||
{
|
||||
// P2 占位:未来可在此触发冲突检测与下载。
|
||||
}
|
||||
|
||||
/// <summary>手动触发指定槽位的云同步上传。</summary>
|
||||
public static async Task UploadSlotAsync(int slotIndex)
|
||||
{
|
||||
var filePaths = CollectSlotFilePaths(slotIndex);
|
||||
if (filePaths.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var backend in Backends)
|
||||
{
|
||||
if (!backend.IsAvailable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await backend.UploadSlotAsync(slotIndex, filePaths.ToArray());
|
||||
Debug.Log($"[CloudSaveManager] {backend.PlatformName} 上传槽位 {slotIndex} 完成。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[CloudSaveManager] {backend.PlatformName} 上传槽位 {slotIndex} 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>收集槽位下需要同步的文件路径。</summary>
|
||||
private static List<string> CollectSlotFilePaths(int slotIndex)
|
||||
{
|
||||
var paths = new List<string>();
|
||||
|
||||
var snapshotPath = SlotDirectory.GetSnapshotPath(slotIndex);
|
||||
if (File.Exists(snapshotPath))
|
||||
{
|
||||
paths.Add(snapshotPath);
|
||||
}
|
||||
|
||||
var metaPath = SlotDirectory.GetMetaPath(slotIndex);
|
||||
if (File.Exists(metaPath))
|
||||
{
|
||||
paths.Add(metaPath);
|
||||
}
|
||||
|
||||
var thumbnailPath = SlotDirectory.GetThumbnailPath(slotIndex);
|
||||
if (File.Exists(thumbnailPath))
|
||||
{
|
||||
paths.Add(thumbnailPath);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user