chore(merge): 合并 develop 并整合 Day2 梦境剧情与演出

This commit is contained in:
2026-07-19 21:25:30 +08:00
291 changed files with 60545 additions and 5128 deletions
@@ -5,6 +5,7 @@ using AibisDream.SaveSystem;
using AibisDream.Utility;
using DG.Tweening;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;
namespace AibisDream
@@ -27,6 +28,7 @@ namespace AibisDream
public string ActorName => _actorName;
public string SlotName => _slotName;
public ActorType ActorType => _actorType;
public virtual bool IsReady => true;
public void Init(string actorName, ActorSlot slot, ActorType actorType)
{
@@ -61,6 +63,11 @@ namespace AibisDream
spriteRenderer.SetAlpha(0);
}
public virtual IEnumerator InitializeAsync()
{
yield break;
}
public abstract ActorEntrySnapshotDto CaptureEntry();
public abstract IEnumerator ApplyEntryAsync(ActorEntrySnapshotDto entry);
@@ -226,6 +233,102 @@ namespace AibisDream
return actor;
}
public IEnumerator CreateActorAsync(
string actorName,
ActorSlot slot,
ActorType actorType,
Action<AbsActor> onCompleted)
{
if (actorType != ActorType.FrameAnimation)
{
onCompleted?.Invoke(CreateActor(actorName, slot, actorType));
yield break;
}
yield return CreateFrameAnimationActorAsync(
actorName,
slot,
actorType,
null,
onCompleted);
}
public IEnumerator CreateFromEntryAsync(
ActorEntrySnapshotDto entry,
ActorSlot slot,
Action<AbsActor> onCompleted)
{
if (!Enum.TryParse<ActorType>(entry.actorType, out var actorType))
{
actorType = ActorType.Anima;
}
if (actorType != ActorType.FrameAnimation)
{
onCompleted?.Invoke(CreateFromEntry(entry, slot));
yield break;
}
yield return CreateFrameAnimationActorAsync(
entry.actorName,
slot,
actorType,
entry,
onCompleted);
}
private IEnumerator CreateFrameAnimationActorAsync(
string actorName,
ActorSlot slot,
ActorType actorType,
ActorEntrySnapshotDto entry,
Action<AbsActor> onCompleted)
{
var prefabKey = ConstRef.FrameAnimationActorPrefabName;
var handle = ResourceSystem.LoadPersistentAsync<GameObject>(prefabKey);
yield return handle;
if (handle.Status != AsyncOperationStatus.Succeeded || handle.Result == null)
{
Debug.LogError(
$"[ActorFactory] 角色 '{actorName}' 的帧动画 Prefab 加载失败,key='{prefabKey}'。");
onCompleted?.Invoke(null);
yield break;
}
var instance = Object.Instantiate(handle.Result, _actorRoot);
var actor = instance.GetComponent<AbsActor>();
if (actor == null)
{
Debug.LogError(
$"[ActorFactory] Prefab '{prefabKey}' 缺少 AbsActor 组件,角色='{actorName}'。");
Object.Destroy(instance);
onCompleted?.Invoke(null);
yield break;
}
if (entry == null)
{
actor.Init(actorName, slot, actorType);
}
else
{
actor.InitFromEntry(entry, slot);
}
yield return actor.InitializeAsync();
if (!actor.IsReady)
{
Debug.LogError(
$"[ActorFactory] 帧动画角色 '{actorName}' 初始化失败,Prefab='{prefabKey}'。");
Object.Destroy(actor.gameObject);
onCompleted?.Invoke(null);
yield break;
}
onCompleted?.Invoke(actor);
}
private AbsActor InstantiateByType(ActorType actorType)
{
return actorType switch
@@ -242,6 +345,7 @@ namespace AibisDream
{
Sprite,
Anima,
AnimaEx
AnimaEx,
FrameAnimation = 3
}
}
@@ -49,7 +49,17 @@ namespace AibisDream
{
foreach (var slot in _slotDict.Values.Where(slot => slot.hasDefaultActor))
{
InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType);
if (slot.defaultActorType == ActorType.FrameAnimation)
{
StartCoroutine(InitActorAsync(
slot.defaultActorName,
slot.slotName,
slot.defaultActorType));
}
else
{
InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType);
}
}
}
@@ -94,7 +104,11 @@ namespace AibisDream
continue;
}
var actor = _actorFactory.CreateFromEntry(entry, slot);
AbsActor actor = null;
yield return _actorFactory.CreateFromEntryAsync(
entry,
slot,
created => actor = created);
if (actor == null) continue;
UpsertActor(entry.actorName, actor);
@@ -121,8 +135,26 @@ namespace AibisDream
{
if (!string.IsNullOrEmpty(slot.defaultActorName))
{
var actor = InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType);
actor.Show();
if (slot.defaultActorType == ActorType.FrameAnimation)
{
if (Application.isPlaying)
{
StartCoroutine(InitActorAsync(
slot.defaultActorName,
slot.slotName,
slot.defaultActorType,
actor => actor?.Show()));
}
else
{
Debug.LogWarning("FrameAnimation 默认角色只能在 Play Mode 中异步加载。");
}
}
else
{
var actor = InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType);
actor?.Show();
}
}
else
{
@@ -134,6 +166,13 @@ namespace AibisDream
public AbsActor InitActor(string actorName, string slotName, ActorType actorType)
{
if (actorType == ActorType.FrameAnimation)
{
Debug.LogError(
$"FrameAnimation 角色 '{actorName}' 必须通过 InitActorAsync 初始化。");
return null;
}
var slot = _slotDict[slotName];
var actor = _actorFactory.CreateActor(actorName, slot, actorType);
UpsertActor(actorName, actor);
@@ -141,6 +180,36 @@ namespace AibisDream
return actor;
}
public IEnumerator InitActorAsync(
string actorName,
string slotName,
ActorType actorType,
Action<AbsActor> onCompleted = null)
{
if (!_slotDict.TryGetValue(slotName, out var slot))
{
Debug.LogError(
$"[ActorManager] 初始化角色 '{actorName}' 失败:slot '{slotName}' 不存在。");
onCompleted?.Invoke(null);
yield break;
}
AbsActor actor = null;
yield return _actorFactory.CreateActorAsync(
actorName,
slot,
actorType,
created => actor = created);
if (actor == null)
{
onCompleted?.Invoke(null);
yield break;
}
UpsertActor(actorName, actor);
onCompleted?.Invoke(actor);
}
public bool TryFindActor(string actorOrSlot, out AbsActor actor)
{
if (_actorDict.TryGetValue(actorOrSlot, out actor))
@@ -165,11 +234,11 @@ namespace AibisDream
private const string ClinicSlotName = "clinic";
[YarnCommand("init_actor")]
public static void InitActor(string actorName, string slotName = ClinicSlotName, string actorType = "Anima")
public static IEnumerator InitActor(string actorName, string slotName = ClinicSlotName, string actorType = "Anima")
{
if (Enum.TryParse<ActorType>(actorType, out var actorTypeEnum))
{
ActorManager.Instance.InitActor(actorName, slotName, actorTypeEnum);
yield return ActorManager.Instance.InitActorAsync(actorName, slotName, actorTypeEnum);
}
else
{
@@ -0,0 +1,187 @@
using System.Collections;
using System.Linq;
using AibisDream.FrameAnimation;
using AibisDream.Framework;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace AibisDream
{
/// <summary>
/// 由 FrameAnimationGraph 驱动的 ActorManager 角色实现。
/// Graph 使用 FrameAnimation/{ActorName} 的场景级 Addressable key。
/// </summary>
public sealed class FrameAnimationActor : AbsActor
{
private FrameAnimationPlayer _player;
private string _currentStateName = string.Empty;
private bool _isReady;
public override bool IsReady => _isReady;
protected override void InitInternal()
{
_player = GetComponent<FrameAnimationPlayer>();
base.InitInternal();
_actorType = ActorType.FrameAnimation;
_isReady = false;
}
public override IEnumerator InitializeAsync()
{
if (_player == null)
{
Debug.LogError(
$"[FrameAnimationActor] 角色 '{ActorName}' 缺少 FrameAnimationPlayer。",
this);
yield break;
}
var graphKey = ConstRef.FrameAnimationGraphPrefix + ActorName;
var handle = ResourceSystem.LoadSceneAsync<FrameAnimationGraph>(graphKey);
if (!handle.IsValid())
{
Debug.LogError(
$"[FrameAnimationActor] 角色 '{ActorName}' 无法开始加载 Graphkey='{graphKey}'。",
this);
yield break;
}
yield return handle;
if (handle.Status != AsyncOperationStatus.Succeeded || handle.Result == null)
{
Debug.LogError(
$"[FrameAnimationActor] 角色 '{ActorName}' 的 Graph 加载失败,key='{graphKey}'。",
this);
yield break;
}
var validation = FrameAnimationGraphValidator.Validate(handle.Result);
if (validation.HasErrors)
{
var errors = string.Join(
" | ",
validation.Issues
.Where(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
.Select(issue => $"{issue.Code}: {issue.Message}"));
Debug.LogError(
$"[FrameAnimationActor] 角色 '{ActorName}' 的 Graph 校验失败,key='{graphKey}'{errors}",
this);
yield break;
}
if (!_player.TryBindGraph(handle.Result, out var bindError))
{
Debug.LogError(
$"[FrameAnimationActor] 角色 '{ActorName}' 绑定 Graph 失败,key='{graphKey}', error={bindError}",
this);
yield break;
}
_isReady = true;
}
public override void ChangeState(string stateName)
{
if (!TryPlay(stateName, false, out var handle))
{
return;
}
handle.RegisterCompleted(result => LogFailedResult(stateName, result));
}
public override IEnumerator ChangeStateAsync(string stateName)
{
if (!TryPlay(stateName, false, out var handle))
{
yield break;
}
yield return handle;
LogFailedResult(stateName, handle.Result);
}
public override ActorEntrySnapshotDto CaptureEntry()
{
var entry = new ActorEntrySnapshotDto();
FillBaseEntry(entry);
entry.stateName = _currentStateName;
return entry;
}
public override IEnumerator ApplyEntryAsync(ActorEntrySnapshotDto entry)
{
ApplyAlphaFromEntry(entry);
if (string.IsNullOrWhiteSpace(entry.stateName))
{
yield break;
}
if (!TryPlay(entry.stateName, true, out var handle))
{
yield break;
}
// 非循环终态会同步完成;Loop 会继续播放,读档 Barrier 不等待它。
if (handle.IsCompleted)
{
LogFailedResult(entry.stateName, handle.Result);
}
}
private bool TryPlay(
string stateName,
bool restoreTerminalState,
out FrameAnimationPlaybackHandle handle)
{
handle = null;
if (!_isReady || _player == null)
{
Debug.LogError(
$"[FrameAnimationActor] 角色 '{ActorName}' 尚未完成初始化,playable='{stateName}'。",
this);
return false;
}
if (string.IsNullOrWhiteSpace(stateName))
{
Debug.LogWarning(
$"[FrameAnimationActor] 角色 '{ActorName}' 收到空 playable id。",
this);
return false;
}
handle = restoreTerminalState
? _player.RestoreTerminalState(stateName)
: _player.Play(stateName);
if (handle.IsCompleted &&
handle.Result.Reason == FrameAnimationCompletionReason.Failed)
{
LogFailedResult(stateName, handle.Result);
return false;
}
_currentStateName = stateName;
return true;
}
private void LogFailedResult(
string stateName,
FrameAnimationPlaybackResult result)
{
if (result.Reason != FrameAnimationCompletionReason.Failed)
{
return;
}
var graphKey = ConstRef.FrameAnimationGraphPrefix + ActorName;
Debug.LogError(
$"[FrameAnimationActor] 角色 '{ActorName}' 播放失败,Graph='{graphKey}', " +
$"playable='{stateName}', error={result.Error}",
this);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2b56f5f150d4fc486d9c68615bd57dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,115 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace AibisDream
{
public sealed class SceneReadinessResult
{
public bool Success { get; internal set; }
public bool Cancelled { get; internal set; }
public string Error { get; internal set; }
}
public static class SceneReadiness
{
public const float DefaultTimeoutSeconds = 15f;
public static IEnumerator WaitUntilReady(
Scene scene,
Func<bool> isCancellationRequested,
Action<SceneReadinessResult> completed,
float timeoutSeconds = DefaultTimeoutSeconds)
{
var result = new SceneReadinessResult();
// 等待场景内 Awake/Start 完成,再收集当前场景自己的 Gate。
yield return null;
if (isCancellationRequested?.Invoke() == true)
{
result.Cancelled = true;
result.Error = "Scene readiness was cancelled.";
completed?.Invoke(result);
yield break;
}
if (!scene.IsValid() || !scene.isLoaded)
{
result.Error = "Loaded scene is invalid or not loaded.";
completed?.Invoke(result);
yield break;
}
var gates = CollectGates(scene);
if (gates.Count == 0)
{
result.Success = true;
completed?.Invoke(result);
yield break;
}
var deadline = Time.unscaledTime + Mathf.Max(0f, timeoutSeconds);
while (true)
{
if (isCancellationRequested?.Invoke() == true)
{
result.Cancelled = true;
result.Error = "Scene readiness was cancelled.";
break;
}
var allReady = true;
foreach (var gate in gates)
{
if (gate is UnityEngine.Object unityObject && unityObject == null)
{
result.Error = "A scene dialogue gate was destroyed while waiting.";
completed?.Invoke(result);
yield break;
}
if (!gate.IsDialogueReady)
{
allReady = false;
}
}
if (allReady)
{
result.Success = true;
break;
}
if (Time.unscaledTime >= deadline)
{
result.Error = $"Scene readiness timed out after {timeoutSeconds:0.#} seconds.";
break;
}
yield return null;
}
completed?.Invoke(result);
}
private static List<ISceneDialogueGate> CollectGates(Scene scene)
{
var gates = new List<ISceneDialogueGate>();
foreach (var root in scene.GetRootGameObjects())
{
foreach (var behaviour in root.GetComponentsInChildren<MonoBehaviour>(true))
{
if (behaviour is ISceneDialogueGate gate)
{
gates.Add(gate);
}
}
}
return gates;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa605d25316143c690f4e165e48ed832
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -2,6 +2,7 @@
using System.Collections;
using AibisDream.Framework;
using AibisDream.Kit;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using DG.Tweening;
using UnityEngine;
@@ -28,6 +29,8 @@ namespace AibisDream
private Vector3 _smallBaseLocalScale;
private Vector3 _smallMotionBaseOffset;
private string _currentSmallPicName;
private string _currentLargePicName;
private bool _currentSmallUsesSplitLayers;
private DG.Tweening.Sequence _largePresentationSequence;
private Vector3 _largeBaseLocalPosition;
@@ -183,15 +186,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()
@@ -464,6 +479,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>
@@ -501,8 +582,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();
}
@@ -523,7 +612,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();
@@ -533,7 +623,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);
@@ -547,7 +647,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);
}
@@ -563,6 +672,8 @@ namespace AibisDream
}
}
_currentSmallPicName = normalizedName;
_currentSmallUsesSplitLayers = useSplitMotion;
PlaySmallMotion(motionProfile);
}
@@ -591,8 +702,15 @@ namespace AibisDream
_largeSpriteRenderer.gameObject.SetActive(true);
_largeSpriteRenderer.SetAlpha(0f);
yield return LoadDreamSpriteInto(_largeSpriteRenderer, picName);
var loaded = false;
yield return LoadDreamSpriteInto(_largeSpriteRenderer, picName, value => loaded = value);
if (!loaded)
{
ClearLargeImmediate();
yield break;
}
_currentLargePicName = NormalizeDreamPicName(picName);
if (duration <= 0f)
{
_largeSpriteRenderer.SetAlpha(1f);
@@ -656,6 +774,7 @@ namespace AibisDream
_spriteRenderer.gameObject.SetActive(false);
_currentSmallPicName = null;
_currentSmallUsesSplitLayers = false;
if (_smallMotionRenderer != null)
{
@@ -691,6 +810,7 @@ namespace AibisDream
}
_largeSpriteRenderer.gameObject.SetActive(false);
_currentLargePicName = null;
ResetLargePresentationImmediate();
}
@@ -708,6 +828,185 @@ 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();
if (_backgroundSpriteRenderer != null)
{
_backgroundSpriteRenderer.DOKill();
_backgroundSpriteRenderer.SetAlpha(0f);
_backgroundSpriteRenderer.gameObject.SetActive(false);
}
}
#region Rig
// Kite Camera(虚拟镜头完全补偿)→ Rig(高度+漂移)→ Sway(鱼式流体摆动)→ AccentDOTween 顿挫)