feat(save): 新增梦境演出图与物品图的存档快照
新增 showcase(order 69) 与 playTool(order 70) 两个快照 section, 分别持久化 SpriteShowcase 小图/大图与 PlayToolPanel 物品图/全屏图 的终态,读档时直接还原静止画面。SpriteShowcase 与 PlayToolPanel 补充快照捕获/还原与立即清理逻辑,GameManager 在卸载场景前清理 演出效果。screen 还原顺序顺延至 80。
This commit is contained in:
@@ -543,6 +543,10 @@ namespace AibisDream
|
||||
yield return DialogController.Instance.StopDialogRoutine();
|
||||
}
|
||||
|
||||
// The showcase belongs to the loaded gameplay scene, so clear it
|
||||
// while that scene is still alive and hidden behind the fade.
|
||||
ResetPresentationEffects();
|
||||
|
||||
SceneOperationResult unloadResult = null;
|
||||
const int maxUnloadAttempts = 2;
|
||||
for (var attempt = 1; attempt <= maxUnloadAttempts; attempt++)
|
||||
@@ -568,7 +572,6 @@ namespace AibisDream
|
||||
yield break;
|
||||
}
|
||||
|
||||
ResetPresentationEffects();
|
||||
UIManager.Instance.HidePanel<EndPanel>();
|
||||
yield return LocalizationKit.WaitUntilReady();
|
||||
|
||||
@@ -703,6 +706,7 @@ namespace AibisDream
|
||||
{
|
||||
ScreenEffectManager.Instance?.ResetSaturation();
|
||||
UIManager.Instance.GetPanel<PlayToolPanel>()?.ClearPresentationEffects();
|
||||
SpriteShowcase.Instance?.ClearPresentationEffects();
|
||||
CameraKit.Instance?.ResetCamera();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AibisDream.SaveSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// sections["fix"]:Fix 场景宏观模式(FixSceneDirector)。
|
||||
/// RestoreOrder=65,在 timeline(60) 之后、screen(70) 之前。
|
||||
/// RestoreOrder=65,在 timeline(60) 之后、showcase(69) 之前。
|
||||
/// </summary>
|
||||
public class FixSnapshotProvider : IAsyncSnapshotProvider
|
||||
{
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections;
|
||||
using AibisDream.UI;
|
||||
|
||||
namespace AibisDream.SaveSystem
|
||||
{
|
||||
/// <summary>sections["playTool"]:物品图与全屏图。RestoreOrder=70。</summary>
|
||||
public sealed class PlayToolSnapshotProvider : IAsyncSnapshotProvider
|
||||
{
|
||||
public string SaveId => SnapshotProviderIds.PlayTool;
|
||||
public int RestoreOrder => 70;
|
||||
|
||||
public object Capture()
|
||||
{
|
||||
var panel = UIManager.Instance?.GetPanel<PlayToolPanel>();
|
||||
return panel != null ? panel.CaptureSnapshot() : null;
|
||||
}
|
||||
|
||||
public IEnumerator RestoreAsync(object dto, SnapshotRestoreContext context)
|
||||
{
|
||||
if (dto is not PlayToolSnapshotDto snapshot)
|
||||
{
|
||||
context.Warn("[PlayToolSnapshotProvider] DTO 类型不匹配,跳过还原。");
|
||||
yield break;
|
||||
}
|
||||
|
||||
var panel = UIManager.Instance?.GetPanel<PlayToolPanel>();
|
||||
if (panel == null)
|
||||
{
|
||||
context.Warn("[PlayToolSnapshotProvider] PlayToolPanel 未初始化,跳过还原。");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return panel.RestoreSnapshotAsync(snapshot, context.Warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c8b98755ccb44089e8ac66b30c02c68
|
||||
@@ -2,12 +2,12 @@ namespace AibisDream.SaveSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// sections["screen"]:屏幕饱和度与演出淡入淡出遮罩。
|
||||
/// 委托 <see cref="ScreenSnapshotHelper"/>;RestoreOrder=70,通常在表现类最后还原。
|
||||
/// 委托 <see cref="ScreenSnapshotHelper"/>;RestoreOrder=80,在表现类最后还原。
|
||||
/// </summary>
|
||||
public class ScreenSnapshotProvider : ISyncSnapshotProvider
|
||||
{
|
||||
public string SaveId => SnapshotProviderIds.Screen;
|
||||
public int RestoreOrder => 70;
|
||||
public int RestoreOrder => 80;
|
||||
|
||||
public object Capture()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace AibisDream.SaveSystem
|
||||
{
|
||||
/// <summary>sections["showcase"]:梦境普通小图或大图。RestoreOrder=69。</summary>
|
||||
public sealed class ShowcaseSnapshotProvider : IAsyncSnapshotProvider
|
||||
{
|
||||
public string SaveId => SnapshotProviderIds.Showcase;
|
||||
public int RestoreOrder => 69;
|
||||
|
||||
public object Capture()
|
||||
{
|
||||
return SpriteShowcase.Instance != null
|
||||
? SpriteShowcase.Instance.CaptureSnapshot()
|
||||
: null;
|
||||
}
|
||||
|
||||
public IEnumerator RestoreAsync(object dto, SnapshotRestoreContext context)
|
||||
{
|
||||
if (dto is not ShowcaseSnapshotDto snapshot)
|
||||
{
|
||||
context.Warn("[ShowcaseSnapshotProvider] DTO 类型不匹配,跳过还原。");
|
||||
yield break;
|
||||
}
|
||||
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null)
|
||||
{
|
||||
context.Warn("[ShowcaseSnapshotProvider] SpriteShowcase 未初始化,跳过还原。");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return showcase.RestoreSnapshotAsync(snapshot, context.Warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83cccdfaca2a42a89ed515c16c6adfad
|
||||
@@ -100,7 +100,9 @@ SaveSystem/
|
||||
├── TimelineSnapshotProvider.cs order 60
|
||||
├── FixSnapshotProvider.cs order 65
|
||||
├── FixPanelSnapshotProvider.cs order 66
|
||||
└── ScreenSnapshotProvider.cs order 70
|
||||
├── ShowcaseSnapshotProvider.cs order 69
|
||||
├── PlayToolSnapshotProvider.cs order 70
|
||||
└── ScreenSnapshotProvider.cs order 80
|
||||
```
|
||||
|
||||
## 存盘 / 读盘流程
|
||||
@@ -133,7 +135,7 @@ Phase 0 Yarn 变量(sync)
|
||||
↓
|
||||
Phase 1 场景加载(Barrier:LoadSceneAsync)← 框架直管,不走 Provider
|
||||
↓
|
||||
Phase 2 env / actor / audio / timeline / fix / screen(Provider 按 RestoreOrder)
|
||||
Phase 2 env / actor / audio / timeline / fix / showcase / playTool / screen(Provider 按 RestoreOrder)
|
||||
└─ timeline:`Stopped` = untouched(从未 Evaluate),读档仅还原 `isActive`,不 Reset/Evaluate
|
||||
↓
|
||||
Phase 2′ 可选 Barrier(如 Timeline Addressable 须显式等待)
|
||||
@@ -200,7 +202,7 @@ Fix 场景各 State 的 `Enter()` 通常包含相机过渡、Timeline 播放、F
|
||||
| `bodyModule` | `BodyModuleSnapshotDto` | 67 | 插线模块物理态 |
|
||||
| `eye` | `EyeSnapshotDto` | 68 | Eye 叙事阶段 |
|
||||
|
||||
**还原顺序**:punchTape → fix(cue)→ fixPanel → bodyModule → eye → screen。`fixPanel` 须在 `fix` 之后,以覆盖 Cue `EnterImmediate` 中的 `ResetPlug`。
|
||||
**还原顺序**:punchTape → fix(cue)→ fixPanel → bodyModule → eye → showcase → playTool → screen。`fixPanel` 须在 `fix` 之后,以覆盖 Cue `EnterImmediate` 中的 `ResetPlug`。
|
||||
|
||||
**维修场景门控**(`FixSceneSnapshotHelper`):上述 section 仅在 `FixSystemCenter.Instance != null` 时 Capture/Restore。
|
||||
|
||||
|
||||
@@ -180,6 +180,36 @@ namespace AibisDream.SaveSystem
|
||||
public bool isFadeScreenCovered;
|
||||
}
|
||||
|
||||
/// <summary>sections["playTool"]:常驻演出 UI 中的物品图与全屏图终态。</summary>
|
||||
[Serializable]
|
||||
public class PlayToolSnapshotDto
|
||||
{
|
||||
public bool isObjVisible;
|
||||
public string objPicName;
|
||||
public bool isFullScreenVisible;
|
||||
public string fullScreenPicName;
|
||||
}
|
||||
|
||||
/// <summary>Showcase 当前持久化的图片类型;不包含风筝及其他瞬时演出。</summary>
|
||||
public enum ShowcaseDisplayMode
|
||||
{
|
||||
None,
|
||||
Small,
|
||||
Large
|
||||
}
|
||||
|
||||
/// <summary>sections["showcase"]:梦境 Showcase 普通小图或大图终态。</summary>
|
||||
[Serializable]
|
||||
public class ShowcaseSnapshotDto
|
||||
{
|
||||
/// <summary><see cref="ShowcaseDisplayMode"/> 枚举名。</summary>
|
||||
public string displayMode;
|
||||
public string picName;
|
||||
|
||||
/// <summary>小图是否由分层资源组成;只恢复静止画面,不恢复运动。</summary>
|
||||
public bool usesSplitLayers;
|
||||
}
|
||||
|
||||
/// <summary>sections["subway"]:地铁场景宏观状态(SubwayCenter / OutSideState)。</summary>
|
||||
[Serializable]
|
||||
public class SubwaySnapshotDto
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace AibisDream.SaveSystem
|
||||
public const string Audio = "audio";
|
||||
public const string Timeline = "timeline";
|
||||
public const string Screen = "screen";
|
||||
public const string PlayTool = "playTool";
|
||||
public const string Showcase = "showcase";
|
||||
public const string Subway = "subway";
|
||||
public const string PunchTape = "punchTape";
|
||||
public const string Fix = "fix";
|
||||
@@ -29,7 +31,7 @@ namespace AibisDream.SaveSystem
|
||||
/// </summary>
|
||||
public static readonly string[] RequiredForCapture =
|
||||
{
|
||||
Environment, Actor, Audio, Timeline, Subway, Fix, Screen
|
||||
Environment, Actor, Audio, Timeline, Subway, Fix, Showcase, PlayTool, Screen
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,6 +61,8 @@ namespace AibisDream.SaveSystem
|
||||
Register(new FixPanelSnapshotProvider());
|
||||
Register(new BodyModuleSnapshotProvider());
|
||||
Register(new EyeSnapshotProvider());
|
||||
Register(new ShowcaseSnapshotProvider());
|
||||
Register(new PlayToolSnapshotProvider());
|
||||
Register(new ScreenSnapshotProvider());
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +257,8 @@ namespace AibisDream.SaveSystem
|
||||
SnapshotProviderIds.FixPanel => jobj.ToObject<FixPanelSnapshotDto>(),
|
||||
SnapshotProviderIds.BodyModule => jobj.ToObject<BodyModuleSnapshotDto>(),
|
||||
SnapshotProviderIds.Eye => jobj.ToObject<EyeSnapshotDto>(),
|
||||
SnapshotProviderIds.Showcase => jobj.ToObject<ShowcaseSnapshotDto>(),
|
||||
SnapshotProviderIds.PlayTool => jobj.ToObject<PlayToolSnapshotDto>(),
|
||||
SnapshotProviderIds.Screen => jobj.ToObject<ScreenSnapshotDto>(),
|
||||
_ => jobj
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
@@ -55,6 +56,8 @@ namespace AibisDream
|
||||
private Vector3 _smallBaseLocalScale;
|
||||
private Vector3 _smallMotionBaseOffset;
|
||||
private string _currentSmallPicName;
|
||||
private string _currentLargePicName;
|
||||
private bool _currentSmallUsesSplitLayers;
|
||||
|
||||
// 通过 activeSelf 判断当前状态
|
||||
private bool IsSmallShowing => _spriteRenderer.gameObject.activeSelf;
|
||||
@@ -221,15 +224,27 @@ namespace AibisDream
|
||||
: picName;
|
||||
}
|
||||
|
||||
private IEnumerator LoadDreamSpriteInto(SpriteRenderer target, string picName)
|
||||
private IEnumerator LoadDreamSpriteInto(
|
||||
SpriteRenderer target,
|
||||
string picName,
|
||||
Action<bool> completed = null,
|
||||
Action<string> warning = null)
|
||||
{
|
||||
var key = string.Format(DreamSpritePath, NormalizeDreamPicName(picName));
|
||||
var handle = ResourceSystem.LoadAsync<Sprite>(key);
|
||||
yield return handle;
|
||||
if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null)
|
||||
{
|
||||
target.sprite = handle.Result;
|
||||
completed?.Invoke(true);
|
||||
}
|
||||
else
|
||||
Debug.LogError($"[SpriteShowcase] Failed to load sprite: {key}");
|
||||
{
|
||||
var message = $"[SpriteShowcase] Failed to load sprite: {key}";
|
||||
Debug.LogError(message);
|
||||
warning?.Invoke(message);
|
||||
completed?.Invoke(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSmallMotionRenderer()
|
||||
@@ -503,6 +518,72 @@ namespace AibisDream
|
||||
PlaySmallMotion(motionProfile);
|
||||
}
|
||||
|
||||
private void ClearSmallImmediate()
|
||||
{
|
||||
KillSmallTweenState();
|
||||
ResetSmallTransform();
|
||||
|
||||
if (_spriteRenderer != null)
|
||||
{
|
||||
_spriteRenderer.DOKill();
|
||||
_spriteRenderer.SetAlpha(0f);
|
||||
_spriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
_currentSmallPicName = null;
|
||||
_currentSmallUsesSplitLayers = false;
|
||||
|
||||
if (_smallMotionRenderer != null)
|
||||
{
|
||||
_smallMotionRenderer.DOKill();
|
||||
_smallMotionRenderer.SetAlpha(0f);
|
||||
_smallMotionRenderer.gameObject.SetActive(false);
|
||||
_smallMotionRenderer.maskInteraction = SpriteMaskInteraction.None;
|
||||
}
|
||||
|
||||
if (_smallMotionMask != null)
|
||||
{
|
||||
_smallMotionMask.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearLargeImmediate()
|
||||
{
|
||||
if (_largeSpriteRenderer != null)
|
||||
{
|
||||
_largeSpriteRenderer.DOKill();
|
||||
_largeSpriteRenderer.SetAlpha(0f);
|
||||
_largeSpriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
_currentLargePicName = null;
|
||||
}
|
||||
|
||||
private void ClearKiteImmediate()
|
||||
{
|
||||
KillKiteTweens();
|
||||
if (_kiteRenderer != null)
|
||||
{
|
||||
_kiteRenderer.SetAlpha(0f);
|
||||
}
|
||||
|
||||
if (_kiteBaseRenderer != null)
|
||||
{
|
||||
_kiteBaseRenderer.SetAlpha(0f);
|
||||
_kiteBaseRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (_kiteCameraNode != null)
|
||||
{
|
||||
_kiteCameraNode.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (_kiteMask != null)
|
||||
{
|
||||
_kiteMask.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示物体(小图)
|
||||
/// </summary>
|
||||
@@ -540,8 +621,16 @@ namespace AibisDream
|
||||
|
||||
_spriteRenderer.gameObject.SetActive(true);
|
||||
|
||||
yield return LoadDreamSpriteInto(_spriteRenderer, picName);
|
||||
var loaded = false;
|
||||
yield return LoadDreamSpriteInto(_spriteRenderer, picName, value => loaded = value);
|
||||
if (!loaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_currentSmallPicName = NormalizeDreamPicName(picName);
|
||||
_currentSmallUsesSplitLayers = false;
|
||||
|
||||
yield return _spriteRenderer.DOFade(1, duration).WaitForCompletion();
|
||||
}
|
||||
@@ -562,7 +651,8 @@ namespace AibisDream
|
||||
|
||||
var normalizedName = NormalizeDreamPicName(picName);
|
||||
var useSplitMotion = TryGetSplitMotionPicNames(picName, out var basePicName, out var motionPicName);
|
||||
var isSamePic = string.Equals(_currentSmallPicName, normalizedName, StringComparison.OrdinalIgnoreCase);
|
||||
var isSamePic = string.Equals(_currentSmallPicName, normalizedName, StringComparison.OrdinalIgnoreCase)
|
||||
&& _currentSmallUsesSplitLayers == useSplitMotion;
|
||||
_smallMotionBaseOffset = normalizedName == "D2S轱辘" ? new Vector3(0f, -0.32f, 0f) : Vector3.zero;
|
||||
|
||||
KillSmallTweenState();
|
||||
@@ -572,7 +662,17 @@ namespace AibisDream
|
||||
|
||||
if (!isSamePic || _spriteRenderer.sprite == null)
|
||||
{
|
||||
yield return LoadDreamSpriteInto(_spriteRenderer, useSplitMotion ? basePicName : picName);
|
||||
var baseLoaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_spriteRenderer,
|
||||
useSplitMotion ? basePicName : picName,
|
||||
value => baseLoaded = value);
|
||||
if (!baseLoaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_currentSmallPicName = normalizedName;
|
||||
}
|
||||
_spriteRenderer.SetAlpha(1f);
|
||||
@@ -586,7 +686,16 @@ namespace AibisDream
|
||||
ResetSmallTransform(resetMotionLayer: !isSamePic);
|
||||
if (!isSamePic || _smallMotionRenderer.sprite == null)
|
||||
{
|
||||
yield return LoadDreamSpriteInto(_smallMotionRenderer, motionPicName);
|
||||
var motionLoaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_smallMotionRenderer,
|
||||
motionPicName,
|
||||
value => motionLoaded = value);
|
||||
if (!motionLoaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
_smallMotionRenderer.SetAlpha(1f);
|
||||
}
|
||||
@@ -602,6 +711,8 @@ namespace AibisDream
|
||||
}
|
||||
}
|
||||
|
||||
_currentSmallPicName = normalizedName;
|
||||
_currentSmallUsesSplitLayers = useSplitMotion;
|
||||
PlaySmallMotion(motionProfile);
|
||||
}
|
||||
|
||||
@@ -628,8 +739,15 @@ namespace AibisDream
|
||||
|
||||
_largeSpriteRenderer.gameObject.SetActive(true);
|
||||
|
||||
yield return LoadDreamSpriteInto(_largeSpriteRenderer, picName);
|
||||
var loaded = false;
|
||||
yield return LoadDreamSpriteInto(_largeSpriteRenderer, picName, value => loaded = value);
|
||||
if (!loaded)
|
||||
{
|
||||
ClearLargeImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_currentLargePicName = NormalizeDreamPicName(picName);
|
||||
yield return _largeSpriteRenderer.DOFade(1, duration).WaitForCompletion();
|
||||
}
|
||||
|
||||
@@ -680,6 +798,7 @@ namespace AibisDream
|
||||
|
||||
_spriteRenderer.gameObject.SetActive(false);
|
||||
_currentSmallPicName = null;
|
||||
_currentSmallUsesSplitLayers = false;
|
||||
|
||||
if (_smallMotionRenderer != null)
|
||||
{
|
||||
@@ -706,6 +825,7 @@ namespace AibisDream
|
||||
|
||||
yield return _largeSpriteRenderer.DOFade(0, duration).WaitForCompletion();
|
||||
_largeSpriteRenderer.gameObject.SetActive(false);
|
||||
_currentLargePicName = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -722,6 +842,186 @@ namespace AibisDream
|
||||
yield return HideLargeObj(duration);
|
||||
}
|
||||
|
||||
public ShowcaseSnapshotDto CaptureSnapshot()
|
||||
{
|
||||
if (IsKiteShowing)
|
||||
{
|
||||
return HiddenShowcaseSnapshot();
|
||||
}
|
||||
|
||||
if (IsSmallShowing && !string.IsNullOrEmpty(_currentSmallPicName))
|
||||
{
|
||||
return new ShowcaseSnapshotDto
|
||||
{
|
||||
displayMode = nameof(ShowcaseDisplayMode.Small),
|
||||
picName = _currentSmallPicName,
|
||||
usesSplitLayers = _currentSmallUsesSplitLayers
|
||||
};
|
||||
}
|
||||
|
||||
if (IsLargeShowing && !string.IsNullOrEmpty(_currentLargePicName))
|
||||
{
|
||||
return new ShowcaseSnapshotDto
|
||||
{
|
||||
displayMode = nameof(ShowcaseDisplayMode.Large),
|
||||
picName = _currentLargePicName
|
||||
};
|
||||
}
|
||||
|
||||
return HiddenShowcaseSnapshot();
|
||||
}
|
||||
|
||||
private static ShowcaseSnapshotDto HiddenShowcaseSnapshot()
|
||||
{
|
||||
return new ShowcaseSnapshotDto
|
||||
{
|
||||
displayMode = nameof(ShowcaseDisplayMode.None)
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerator RestoreSnapshotAsync(ShowcaseSnapshotDto snapshot, Action<string> warning = null)
|
||||
{
|
||||
ClearPresentationEffects();
|
||||
if (snapshot == null) yield break;
|
||||
|
||||
if (!Enum.TryParse(snapshot.displayMode, true, out ShowcaseDisplayMode mode))
|
||||
{
|
||||
warning?.Invoke($"[SpriteShowcase] Unknown display mode: {snapshot.displayMode}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case ShowcaseDisplayMode.None:
|
||||
yield break;
|
||||
|
||||
case ShowcaseDisplayMode.Small:
|
||||
if (string.IsNullOrWhiteSpace(snapshot.picName))
|
||||
{
|
||||
warning?.Invoke("[SpriteShowcase] Small snapshot is missing picName.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return RestoreSmallImmediate(snapshot.picName, snapshot.usesSplitLayers, warning);
|
||||
yield break;
|
||||
|
||||
case ShowcaseDisplayMode.Large:
|
||||
if (string.IsNullOrWhiteSpace(snapshot.picName))
|
||||
{
|
||||
warning?.Invoke("[SpriteShowcase] Large snapshot is missing picName.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return RestoreLargeImmediate(snapshot.picName, warning);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator RestoreSmallImmediate(string picName, bool usesSplitLayers, Action<string> warning)
|
||||
{
|
||||
if (_spriteRenderer == null) yield break;
|
||||
|
||||
var normalizedName = NormalizeDreamPicName(picName);
|
||||
_smallMotionBaseOffset = normalizedName == "D2S轱辘" ? new Vector3(0f, -0.32f, 0f) : Vector3.zero;
|
||||
_spriteRenderer.gameObject.SetActive(true);
|
||||
|
||||
if (usesSplitLayers)
|
||||
{
|
||||
if (!TryGetSplitMotionPicNames(picName, out var basePicName, out var motionPicName))
|
||||
{
|
||||
warning?.Invoke($"[SpriteShowcase] Split-layer snapshot is unsupported: {picName}");
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
var baseLoaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_spriteRenderer,
|
||||
basePicName,
|
||||
value => baseLoaded = value,
|
||||
warning);
|
||||
if (!baseLoaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
EnsureSmallMotionRenderer();
|
||||
_smallMotionRenderer.gameObject.SetActive(true);
|
||||
CopySmallRendererSettings(_smallMotionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
|
||||
ConfigureSmallMotionMask();
|
||||
ResetSmallTransform();
|
||||
|
||||
var motionLoaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_smallMotionRenderer,
|
||||
motionPicName,
|
||||
value => motionLoaded = value,
|
||||
warning);
|
||||
if (!motionLoaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_smallMotionRenderer.SetAlpha(1f);
|
||||
}
|
||||
else
|
||||
{
|
||||
var loaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_spriteRenderer,
|
||||
picName,
|
||||
value => loaded = value,
|
||||
warning);
|
||||
if (!loaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
_spriteRenderer.SetAlpha(1f);
|
||||
_currentSmallPicName = normalizedName;
|
||||
_currentSmallUsesSplitLayers = usesSplitLayers;
|
||||
}
|
||||
|
||||
private IEnumerator RestoreLargeImmediate(string picName, Action<string> warning)
|
||||
{
|
||||
if (_largeSpriteRenderer == null) yield break;
|
||||
|
||||
_largeSpriteRenderer.gameObject.SetActive(true);
|
||||
var loaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_largeSpriteRenderer,
|
||||
picName,
|
||||
value => loaded = value,
|
||||
warning);
|
||||
if (!loaded)
|
||||
{
|
||||
ClearLargeImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_largeSpriteRenderer.SetAlpha(1f);
|
||||
_currentLargePicName = NormalizeDreamPicName(picName);
|
||||
}
|
||||
|
||||
public void ClearPresentationEffects()
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
ClearLargeImmediate();
|
||||
ClearKiteImmediate();
|
||||
ResetDreamOverflow();
|
||||
|
||||
if (_backgroundSpriteRenderer != null)
|
||||
{
|
||||
_backgroundSpriteRenderer.DOKill();
|
||||
_backgroundSpriteRenderer.SetAlpha(0f);
|
||||
_backgroundSpriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
#region 风筝 Rig
|
||||
|
||||
// Kite Camera(虚拟镜头完全补偿)→ Rig(高度+漂移)→ Sway(鱼式流体摆动)→ Accent(DOTween 顿挫)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using RainbowArt.CleanFlatUI;
|
||||
using UnityEngine;
|
||||
@@ -35,6 +37,12 @@ namespace AibisDream.UI
|
||||
private SignBoard _signBoard;
|
||||
private TimeTurner _timeTurner;
|
||||
private int _timeFlowsVersion;
|
||||
private int _objOperationVersion;
|
||||
private int _fullScreenOperationVersion;
|
||||
private string _currentObjPicName;
|
||||
private string _currentFullScreenPicName;
|
||||
private bool _isObjVisible;
|
||||
private bool _isFullScreenVisible;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -63,70 +71,197 @@ namespace AibisDream.UI
|
||||
: picName;
|
||||
}
|
||||
|
||||
private IEnumerator LoadObjSprite(
|
||||
string picName,
|
||||
Action<Sprite> completed,
|
||||
Action<string> warning = null)
|
||||
{
|
||||
var normalizedName = NormalizeObjPicName(picName);
|
||||
var key = string.Format(ObjSpritePath, normalizedName);
|
||||
var handle = ResourceSystem.LoadAsync<Sprite>(key);
|
||||
yield return handle;
|
||||
if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null)
|
||||
{
|
||||
completed?.Invoke(handle.Result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var message = $"[PlayToolPanel] Failed to load sprite: {key}";
|
||||
Debug.LogError(message);
|
||||
warning?.Invoke(message);
|
||||
completed?.Invoke(null);
|
||||
}
|
||||
|
||||
private void ApplyObjSprite(Sprite sprite)
|
||||
{
|
||||
var imageAspect = (float)sprite.texture.width / sprite.texture.height;
|
||||
if (imageAspect > 1f)
|
||||
{
|
||||
_showObjImage.rectTransform.sizeDelta = new Vector2(objWidth, objWidth / imageAspect);
|
||||
}
|
||||
else
|
||||
{
|
||||
_showObjImage.rectTransform.sizeDelta = new Vector2(objHeight * imageAspect, objHeight);
|
||||
}
|
||||
|
||||
_showObjImage.sprite = sprite;
|
||||
}
|
||||
|
||||
private void ClearObjImmediate()
|
||||
{
|
||||
_objOperationVersion++;
|
||||
_showObjImage.DOKill();
|
||||
_showObjImage.SetAlpha(0f);
|
||||
_showObjPanel.SetActive(false);
|
||||
_currentObjPicName = null;
|
||||
_isObjVisible = false;
|
||||
}
|
||||
|
||||
private void ClearFullScreenImmediate()
|
||||
{
|
||||
_fullScreenOperationVersion++;
|
||||
_fullScreenImage.DOKill();
|
||||
_fullScreenImage.SetAlpha(0f);
|
||||
_fullScreenImage.gameObject.SetActive(false);
|
||||
_currentFullScreenPicName = null;
|
||||
_isFullScreenVisible = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示物体
|
||||
/// </summary>
|
||||
/// <returns>协程</returns>
|
||||
public IEnumerator ShowObj(string picName, float duration = 1)
|
||||
{
|
||||
var version = ++_objOperationVersion;
|
||||
_showObjImage.DOKill();
|
||||
_showObjPanel.gameObject.SetActive(true);
|
||||
|
||||
var key = string.Format(ObjSpritePath, NormalizeObjPicName(picName));
|
||||
var handle = ResourceSystem.LoadAsync<Sprite>(key);
|
||||
yield return handle;
|
||||
if (handle.Status != AsyncOperationStatus.Succeeded || handle.Result == null)
|
||||
Sprite newSprite = null;
|
||||
yield return LoadObjSprite(picName, value => newSprite = value);
|
||||
if (version != _objOperationVersion)
|
||||
{
|
||||
Debug.LogError($"[PlayToolPanel] Failed to load sprite: {key}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
var newSprite = handle.Result;
|
||||
// 获取图片的长宽比
|
||||
var imageAspect = (float)newSprite.texture.width / newSprite.texture.height;
|
||||
|
||||
// 计算新的尺寸
|
||||
if (imageAspect > 1) // 宽图
|
||||
if (newSprite == null)
|
||||
{
|
||||
var newHeight = objWidth / imageAspect;
|
||||
_showObjImage.rectTransform.sizeDelta = new Vector2(objWidth, newHeight);
|
||||
}
|
||||
else // 高图
|
||||
{
|
||||
var newWidth = objHeight * imageAspect;
|
||||
_showObjImage.rectTransform.sizeDelta = new Vector2(newWidth, objHeight);
|
||||
ClearObjImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 淡入
|
||||
_showObjImage.sprite = newSprite;
|
||||
ApplyObjSprite(newSprite);
|
||||
yield return _showObjImage.FadeInAsync(duration);
|
||||
if (version != _objOperationVersion) yield break;
|
||||
|
||||
_currentObjPicName = NormalizeObjPicName(picName);
|
||||
_isObjVisible = true;
|
||||
}
|
||||
|
||||
public IEnumerator HideObj(float duration = 1)
|
||||
{
|
||||
var version = ++_objOperationVersion;
|
||||
_currentObjPicName = null;
|
||||
_isObjVisible = false;
|
||||
_showObjImage.DOKill();
|
||||
yield return _showObjImage.FadeOutAsync(duration);
|
||||
if (version != _objOperationVersion) yield break;
|
||||
_showObjPanel.SetActive(false);
|
||||
}
|
||||
|
||||
public IEnumerator ShowFullScreen(string picName, float duration = 1)
|
||||
{
|
||||
var key = string.Format(ObjSpritePath, NormalizeObjPicName(picName));
|
||||
var handle = ResourceSystem.LoadAsync<Sprite>(key);
|
||||
yield return handle;
|
||||
if (handle.Status != AsyncOperationStatus.Succeeded || handle.Result == null)
|
||||
var version = ++_fullScreenOperationVersion;
|
||||
_fullScreenImage.DOKill();
|
||||
_fullScreenImage.gameObject.SetActive(true);
|
||||
|
||||
Sprite newSprite = null;
|
||||
yield return LoadObjSprite(picName, value => newSprite = value);
|
||||
if (version != _fullScreenOperationVersion)
|
||||
{
|
||||
Debug.LogError($"[PlayToolPanel] Failed to load sprite: {key}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
_fullScreenImage.sprite = handle.Result;
|
||||
if (newSprite == null)
|
||||
{
|
||||
ClearFullScreenImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_fullScreenImage.sprite = newSprite;
|
||||
|
||||
// 淡入
|
||||
yield return _fullScreenImage.FadeInAsync(duration);
|
||||
if (version != _fullScreenOperationVersion) yield break;
|
||||
|
||||
_currentFullScreenPicName = NormalizeObjPicName(picName);
|
||||
_isFullScreenVisible = true;
|
||||
}
|
||||
|
||||
public IEnumerator HideFullScreen(float duration = 1)
|
||||
{
|
||||
var version = ++_fullScreenOperationVersion;
|
||||
_currentFullScreenPicName = null;
|
||||
_isFullScreenVisible = false;
|
||||
_fullScreenImage.DOKill();
|
||||
yield return _fullScreenImage.FadeOutAsync(duration);
|
||||
if (version != _fullScreenOperationVersion) yield break;
|
||||
_fullScreenImage.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public PlayToolSnapshotDto CaptureSnapshot()
|
||||
{
|
||||
return new PlayToolSnapshotDto
|
||||
{
|
||||
isObjVisible = _isObjVisible,
|
||||
objPicName = _currentObjPicName,
|
||||
isFullScreenVisible = _isFullScreenVisible,
|
||||
fullScreenPicName = _currentFullScreenPicName
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerator RestoreSnapshotAsync(PlayToolSnapshotDto snapshot, Action<string> warning = null)
|
||||
{
|
||||
ClearObjImmediate();
|
||||
ClearFullScreenImmediate();
|
||||
if (snapshot == null) yield break;
|
||||
|
||||
if (snapshot.isObjVisible && !string.IsNullOrWhiteSpace(snapshot.objPicName))
|
||||
{
|
||||
var version = ++_objOperationVersion;
|
||||
Sprite sprite = null;
|
||||
yield return LoadObjSprite(snapshot.objPicName, value => sprite = value, warning);
|
||||
if (version == _objOperationVersion && sprite != null)
|
||||
{
|
||||
ApplyObjSprite(sprite);
|
||||
_showObjPanel.SetActive(true);
|
||||
_showObjImage.SetAlpha(1f);
|
||||
_currentObjPicName = NormalizeObjPicName(snapshot.objPicName);
|
||||
_isObjVisible = true;
|
||||
}
|
||||
}
|
||||
else if (snapshot.isObjVisible)
|
||||
{
|
||||
warning?.Invoke("[PlayToolPanel] Obj snapshot is missing picName.");
|
||||
}
|
||||
|
||||
if (snapshot.isFullScreenVisible && !string.IsNullOrWhiteSpace(snapshot.fullScreenPicName))
|
||||
{
|
||||
var version = ++_fullScreenOperationVersion;
|
||||
Sprite sprite = null;
|
||||
yield return LoadObjSprite(snapshot.fullScreenPicName, value => sprite = value, warning);
|
||||
if (version == _fullScreenOperationVersion && sprite != null)
|
||||
{
|
||||
_fullScreenImage.sprite = sprite;
|
||||
_fullScreenImage.gameObject.SetActive(true);
|
||||
_fullScreenImage.SetAlpha(1f);
|
||||
_currentFullScreenPicName = NormalizeObjPicName(snapshot.fullScreenPicName);
|
||||
_isFullScreenVisible = true;
|
||||
}
|
||||
}
|
||||
else if (snapshot.isFullScreenVisible)
|
||||
{
|
||||
warning?.Invoke("[PlayToolPanel] Full-screen snapshot is missing picName.");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator OpenProgressWindow(string title, string description, float duration)
|
||||
@@ -197,13 +332,8 @@ namespace AibisDream.UI
|
||||
// SceneLoader unloads scenes with DOTween.KillAll(). Do not rely on a
|
||||
// tween completion callback to deactivate these raycast targets: if
|
||||
// that tween is killed, an invisible full-screen image blocks the UI.
|
||||
_fullScreenImage.DOKill();
|
||||
_fullScreenImage.color = Color.clear;
|
||||
_fullScreenImage.gameObject.SetActive(false);
|
||||
|
||||
_showObjImage.DOKill();
|
||||
_showObjImage.color = Color.clear;
|
||||
_showObjPanel.SetActive(false);
|
||||
ClearFullScreenImmediate();
|
||||
ClearObjImmediate();
|
||||
|
||||
_timeTurner.ForceStop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user