feat(save): 新增梦境演出图与物品图的存档快照

新增 showcase(order 69) 与 playTool(order 70) 两个快照 section,
分别持久化 SpriteShowcase 小图/大图与 PlayToolPanel 物品图/全屏图
的终态,读档时直接还原静止画面。SpriteShowcase 与 PlayToolPanel
补充快照捕获/还原与立即清理逻辑,GameManager 在卸载场景前清理
演出效果。screen 还原顺序顺延至 80。
This commit is contained in:
2026-07-18 23:59:52 +08:00
parent 2eea496c4b
commit a5e417eeae
13 changed files with 597 additions and 49 deletions
+5 -1
View File
@@ -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
+5 -3
View File
@@ -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 场景加载(BarrierLoadSceneAsync)← 框架直管,不走 Provider
Phase 2 env / actor / audio / timeline / fix / screenProvider 按 RestoreOrder
Phase 2 env / actor / audio / timeline / fix / showcase / playTool / screenProvider 按 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 → fixcue)→ fixPanel → bodyModule → eye → screen。`fixPanel` 须在 `fix` 之后,以覆盖 Cue `EnterImmediate` 中的 `ResetPlug`
**还原顺序**punchTape → fixcue)→ fixPanel → bodyModule → eye → showcase → playTool → screen。`fixPanel` 须在 `fix` 之后,以覆盖 Cue `EnterImmediate` 中的 `ResetPlug`
**维修场景门控**`FixSceneSnapshotHelper`):上述 section 仅在 `FixSystemCenter.Instance != null` 时 Capture/Restore。
+30
View File
@@ -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(鱼式流体摆动)→ AccentDOTween 顿挫)
+164 -34
View File
@@ -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();
}