diff --git a/Assets/Scripts/SceneManagement/SpriteShowcase.cs b/Assets/Scripts/SceneManagement/SpriteShowcase.cs
index c444291c8..e13fb24c1 100644
--- a/Assets/Scripts/SceneManagement/SpriteShowcase.cs
+++ b/Assets/Scripts/SceneManagement/SpriteShowcase.cs
@@ -13,17 +13,30 @@ namespace AibisDream
{
/// Addressable Key 模板,与 Scene_Dream 中梦境图 Address 一致。
private const string DreamSpritePath = "Sprite/梦境/{0}.png";
+ private const string KiteBaseSuffix = "_底图";
+ private const string KiteMotionSuffix = "_风筝层";
[SerializeField] private SpriteRenderer _spriteRenderer;
[SerializeField] private SpriteRenderer _backgroundSpriteRenderer;
[SerializeField] private SpriteRenderer _largeSpriteRenderer;
+ private SpriteRenderer _smallMotionRenderer;
+ private SpriteMask _smallMotionMask;
+ private DG.Tweening.Sequence _smallMotionSequence;
+ private Vector3 _smallBaseLocalPosition;
+ private Quaternion _smallBaseLocalRotation;
+ private Vector3 _smallBaseLocalScale;
+ private Vector3 _smallMotionBaseOffset;
+ private string _currentSmallPicName;
+
// 通过 activeSelf 判断当前状态
private bool IsSmallShowing => _spriteRenderer.gameObject.activeSelf;
private bool IsLargeShowing => _largeSpriteRenderer != null && _largeSpriteRenderer.gameObject.activeSelf;
public override void OnSingletonInit()
{
+ CaptureSmallBaseTransform();
+
// 初始状态:隐藏
_spriteRenderer.gameObject.SetActive(false);
_spriteRenderer.SetAlpha(0);
@@ -36,6 +49,16 @@ namespace AibisDream
}
}
+ private void CaptureSmallBaseTransform()
+ {
+ if (_spriteRenderer == null) return;
+
+ var tr = _spriteRenderer.transform;
+ _smallBaseLocalPosition = tr.localPosition;
+ _smallBaseLocalRotation = tr.localRotation;
+ _smallBaseLocalScale = tr.localScale;
+ }
+
private static string NormalizeDreamPicName(string picName)
{
if (string.IsNullOrEmpty(picName)) return picName;
@@ -55,6 +78,246 @@ namespace AibisDream
Debug.LogError($"[SpriteShowcase] Failed to load sprite: {key}");
}
+ private void EnsureSmallMotionRenderer()
+ {
+ if (_smallMotionRenderer != null || _spriteRenderer == null) return;
+
+ var motionObject = new GameObject($"{_spriteRenderer.name} Motion");
+ motionObject.transform.SetParent(_spriteRenderer.transform.parent);
+ _smallMotionRenderer = motionObject.AddComponent();
+ CopySmallRendererSettings(_smallMotionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
+ _smallMotionRenderer.gameObject.SetActive(false);
+ _smallMotionRenderer.SetAlpha(0);
+ }
+
+ private void CopySmallRendererSettings(SpriteRenderer target, int sortingOrderOffset, SpriteMaskInteraction maskInteraction)
+ {
+ if (target == null || _spriteRenderer == null) return;
+
+ target.sharedMaterial = _spriteRenderer.sharedMaterial;
+ target.sortingLayerID = _spriteRenderer.sortingLayerID;
+ target.sortingOrder = _spriteRenderer.sortingOrder + sortingOrderOffset;
+ target.drawMode = _spriteRenderer.drawMode;
+ target.size = _spriteRenderer.size;
+ target.flipX = _spriteRenderer.flipX;
+ target.flipY = _spriteRenderer.flipY;
+ target.maskInteraction = maskInteraction;
+ }
+
+ private void EnsureSmallMotionMask()
+ {
+ if (_smallMotionMask != null || _spriteRenderer == null) return;
+
+ var maskObject = new GameObject($"{_spriteRenderer.name} Motion Mask");
+ maskObject.transform.SetParent(_spriteRenderer.transform.parent);
+ _smallMotionMask = maskObject.AddComponent();
+ _smallMotionMask.alphaCutoff = 0.01f;
+ _smallMotionMask.isCustomRangeActive = true;
+ _smallMotionMask.enabled = false;
+ }
+
+ private void ConfigureSmallMotionMask()
+ {
+ EnsureSmallMotionMask();
+ if (_smallMotionMask == null) return;
+
+ _smallMotionMask.sprite = _spriteRenderer.sprite;
+ _smallMotionMask.frontSortingLayerID = _spriteRenderer.sortingLayerID;
+ _smallMotionMask.backSortingLayerID = _spriteRenderer.sortingLayerID;
+ _smallMotionMask.frontSortingOrder = _spriteRenderer.sortingOrder + 2;
+ _smallMotionMask.backSortingOrder = _spriteRenderer.sortingOrder;
+
+ var maskTr = _smallMotionMask.transform;
+ maskTr.localPosition = _smallBaseLocalPosition;
+ maskTr.localRotation = _smallBaseLocalRotation;
+ maskTr.localScale = _smallBaseLocalScale;
+ _smallMotionMask.enabled = true;
+ }
+
+ private void KillSmallTweenState()
+ {
+ _smallMotionSequence?.Kill();
+ _smallMotionSequence = null;
+ }
+
+ private void ResetSmallTransform(bool resetMotionLayer = true)
+ {
+ if (_spriteRenderer == null) return;
+
+ var tr = _spriteRenderer.transform;
+ tr.localPosition = _smallBaseLocalPosition;
+ tr.localRotation = _smallBaseLocalRotation;
+ tr.localScale = _smallBaseLocalScale;
+
+ if (!resetMotionLayer || _smallMotionRenderer == null) return;
+
+ var motionTr = _smallMotionRenderer.transform;
+ motionTr.localPosition = _smallBaseLocalPosition + _smallMotionBaseOffset;
+ motionTr.localRotation = _smallBaseLocalRotation;
+ motionTr.localScale = _smallBaseLocalScale;
+ }
+
+ private static string NormalizeMotionProfile(string motionProfile)
+ {
+ return string.IsNullOrEmpty(motionProfile)
+ ? string.Empty
+ : motionProfile.Trim().ToLowerInvariant();
+ }
+
+ private Vector3 BaseOffset(float x, float y)
+ {
+ return _smallBaseLocalPosition + _smallMotionBaseOffset + new Vector3(x, y, 0f);
+ }
+
+ private static bool TryGetSplitMotionPicNames(string picName, out string basePicName, out string motionPicName)
+ {
+ var normalized = NormalizeDreamPicName(picName);
+ var isSplitMotionPic = !string.IsNullOrEmpty(normalized)
+ && (normalized.StartsWith("D2S风筝", StringComparison.Ordinal)
+ || normalized == "D2S轱辘")
+ && !normalized.EndsWith(KiteBaseSuffix, StringComparison.Ordinal)
+ && !normalized.EndsWith(KiteMotionSuffix, StringComparison.Ordinal);
+
+ if (isSplitMotionPic)
+ {
+ basePicName = $"{normalized}{KiteBaseSuffix}";
+ motionPicName = $"{normalized}{KiteMotionSuffix}";
+ return true;
+ }
+
+ basePicName = picName;
+ motionPicName = null;
+ return false;
+ }
+
+ private void PlaySmallMotion(string motionProfile)
+ {
+ _smallMotionSequence?.Kill();
+ _smallMotionSequence = null;
+
+ var profile = NormalizeMotionProfile(motionProfile);
+ if (string.IsNullOrEmpty(profile) || profile == "none") return;
+
+ var target = _smallMotionRenderer != null && _smallMotionRenderer.gameObject.activeSelf
+ ? _smallMotionRenderer
+ : _spriteRenderer;
+ var tr = target.transform;
+ var seq = DOTween.Sequence();
+
+ switch (profile)
+ {
+ // 近景微风:位移/倾角更明显,多段不对称节奏模拟阵风
+ case "kite_low_breeze":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.32f, 0.21f), 2.8f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 6.4f), 2.8f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(-0.13f, 0.06f), 1.35f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -3.6f), 1.35f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.25f, 0.17f), 2.45f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 4.8f), 2.45f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.06f, 0.12f), 0.95f).SetEase(Ease.OutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 1.2f), 0.95f).SetEase(Ease.OutSine));
+ seq.SetLoops(-1, LoopType.Yoyo);
+ break;
+
+ case "kite_mid_breeze":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.3f, 0.19f), 2.35f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 6f), 2.35f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(-0.14f, 0.07f), 1.18f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -3.5f), 1.18f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.27f, 0.16f), 2.05f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 4.8f), 2.05f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.02f, 0.1f), 0.82f).SetEase(Ease.OutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -0.8f), 0.82f).SetEase(Ease.OutSine));
+ seq.SetLoops(-1, LoopType.Yoyo);
+ break;
+
+ case "kite_far_breeze":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.2f, 0.12f), 3.2f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 4f), 3.2f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(-0.08f, 0.04f), 2.2f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -2.2f), 2.2f).SetEase(Ease.InOutSine));
+ seq.SetLoops(-1, LoopType.Yoyo);
+ break;
+
+ case "kite_tension":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.48f, 0.15f), 0.92f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -9f), 0.92f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.1f, -0.13f), 0.58f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 4.2f), 0.58f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.39f, 0.11f), 0.9f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -6.6f), 0.9f).SetEase(Ease.InOutSine));
+ seq.SetLoops(-1, LoopType.Yoyo);
+ break;
+
+ case "kite_drop":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.1f, -0.34f), 1.55f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -5.5f), 1.55f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(-0.08f, -0.18f), 1.2f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 2.8f), 1.2f).SetEase(Ease.InOutSine));
+ seq.SetLoops(-1, LoopType.Yoyo);
+ break;
+
+ case "kite_breakaway":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.58f, 0.2f), 0.58f).SetEase(Ease.OutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -12f), 0.58f).SetEase(Ease.OutSine));
+ seq.Append(tr.DOShakePosition(0.28f, new Vector3(0.075f, 0.035f, 0f), 10, 78f, false, false)
+ .SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.14f, -0.16f), 0.46f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 5.8f), 0.46f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.5f, 0.16f), 0.66f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -9f), 0.66f).SetEase(Ease.InOutSine));
+ seq.SetLoops(-1, LoopType.Yoyo);
+ break;
+
+ case "reel_tension":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.14f, -0.18f), 0.5f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -3.8f), 0.5f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOShakePosition(0.22f, new Vector3(0.04f, 0.02f, 0f), 8, 82f, false, false)
+ .SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(-0.06f, -0.06f), 0.7f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 1.6f), 0.7f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.08f, -0.14f), 0.92f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -2.2f), 0.92f).SetEase(Ease.InOutSine));
+ seq.SetLoops(-1, LoopType.Yoyo);
+ break;
+
+ case "kite_lift":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.28f, 0.34f), 0.68f).SetEase(Ease.OutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -6.4f), 0.68f).SetEase(Ease.OutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.16f, 0.22f), 0.78f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -3.2f), 0.78f).SetEase(Ease.InOutSine));
+ break;
+
+ case "kite_sink":
+ seq.Append(tr.DOLocalMove(BaseOffset(-0.14f, -0.34f), 0.76f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 5.2f), 0.76f).SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(-0.06f, -0.22f), 0.62f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 2.4f), 0.62f).SetEase(Ease.InOutSine));
+ break;
+
+ case "kite_taut":
+ seq.Append(tr.DOLocalMove(BaseOffset(0.4f, 0.08f), 0.42f).SetEase(Ease.OutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -8f), 0.42f).SetEase(Ease.OutSine));
+ seq.Append(tr.DOShakePosition(0.24f, new Vector3(0.06f, 0.025f, 0f), 8, 76f, false, false)
+ .SetEase(Ease.InOutSine));
+ seq.Append(tr.DOLocalMove(BaseOffset(0.24f, 0.03f), 0.62f).SetEase(Ease.InOutSine));
+ seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -4.2f), 0.62f).SetEase(Ease.InOutSine));
+ break;
+
+ default:
+ seq.Kill();
+ return;
+ }
+
+ _smallMotionSequence = seq;
+ }
+
+ public void PlayCurrentMotion(string motionProfile)
+ {
+ if (_spriteRenderer == null || !IsSmallShowing) return;
+ PlaySmallMotion(motionProfile);
+ }
+
///
/// 显示物体(小图)
///
@@ -69,13 +332,82 @@ namespace AibisDream
yield return HideLargeObj(duration);
}
+ KillSmallTweenState();
+ ResetSmallTransform();
+
+ if (_smallMotionRenderer != null)
+ {
+ _smallMotionRenderer.gameObject.SetActive(false);
+ _smallMotionRenderer.SetAlpha(0);
+ _smallMotionRenderer.maskInteraction = SpriteMaskInteraction.None;
+ }
+
+ if (_smallMotionMask != null)
+ {
+ _smallMotionMask.enabled = false;
+ }
+
_spriteRenderer.gameObject.SetActive(true);
yield return LoadDreamSpriteInto(_spriteRenderer, picName);
+ _currentSmallPicName = NormalizeDreamPicName(picName);
yield return _spriteRenderer.DOFade(1, duration).WaitForCompletion();
}
+ public IEnumerator ShowObjWithMotion(string picName, string motionProfile, float duration)
+ {
+ // 互斥:如果大图正在显示,先隐藏
+ if (IsLargeShowing)
+ {
+ yield return HideLargeObj(duration);
+ }
+
+ var normalizedName = NormalizeDreamPicName(picName);
+ var useSplitMotion = TryGetSplitMotionPicNames(picName, out var basePicName, out var motionPicName);
+ var isSamePic = string.Equals(_currentSmallPicName, normalizedName, StringComparison.OrdinalIgnoreCase);
+ _smallMotionBaseOffset = normalizedName == "D2S轱辘" ? new Vector3(0f, -0.32f, 0f) : Vector3.zero;
+
+ KillSmallTweenState();
+
+ ResetSmallTransform(resetMotionLayer: !isSamePic);
+ _spriteRenderer.gameObject.SetActive(true);
+
+ if (!isSamePic || _spriteRenderer.sprite == null)
+ {
+ yield return LoadDreamSpriteInto(_spriteRenderer, useSplitMotion ? basePicName : picName);
+ _currentSmallPicName = normalizedName;
+ }
+ _spriteRenderer.SetAlpha(1f);
+
+ if (useSplitMotion)
+ {
+ EnsureSmallMotionRenderer();
+ _smallMotionRenderer.gameObject.SetActive(true);
+ CopySmallRendererSettings(_smallMotionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
+ ConfigureSmallMotionMask();
+ ResetSmallTransform(resetMotionLayer: !isSamePic);
+ if (!isSamePic || _smallMotionRenderer.sprite == null)
+ {
+ yield return LoadDreamSpriteInto(_smallMotionRenderer, motionPicName);
+ }
+ _smallMotionRenderer.SetAlpha(1f);
+ }
+ else if (_smallMotionRenderer != null)
+ {
+ _smallMotionRenderer.gameObject.SetActive(false);
+ _smallMotionRenderer.SetAlpha(0);
+ _smallMotionRenderer.maskInteraction = SpriteMaskInteraction.None;
+
+ if (_smallMotionMask != null)
+ {
+ _smallMotionMask.enabled = false;
+ }
+ }
+
+ PlaySmallMotion(motionProfile);
+ }
+
///
/// 显示大图
///
@@ -84,10 +416,10 @@ namespace AibisDream
/// 协程
public IEnumerator ShowLargeObj(string picName, float duration = 1)
{
- // 互斥:如果小图正在显示,先隐藏
+ // 互斥:小图立刻关掉,避免风筝层与底图不同步淡出造成的怪 fade
if (IsSmallShowing)
{
- yield return HideObj(duration);
+ yield return HideObj(0f);
}
if (_largeSpriteRenderer == null) yield break;
@@ -120,8 +452,38 @@ namespace AibisDream
// force=false 时检查状态,未显示则直接返回
if (!force && !IsSmallShowing) yield break;
- yield return _spriteRenderer.DOFade(0, duration).WaitForCompletion();
+ KillSmallTweenState();
+ ResetSmallTransform();
+
+ var motionActive = _smallMotionRenderer != null && _smallMotionRenderer.gameObject.activeSelf;
+ if (duration <= 0f)
+ {
+ _spriteRenderer.SetAlpha(0);
+ if (motionActive)
+ _smallMotionRenderer.SetAlpha(0);
+ }
+ else
+ {
+ var fade = _spriteRenderer.DOFade(0, duration);
+ if (motionActive)
+ _smallMotionRenderer.DOFade(0, duration);
+ yield return fade.WaitForCompletion();
+ }
+
_spriteRenderer.gameObject.SetActive(false);
+ _currentSmallPicName = null;
+
+ if (_smallMotionRenderer != null)
+ {
+ _smallMotionRenderer.gameObject.SetActive(false);
+ _smallMotionRenderer.SetAlpha(0);
+ _smallMotionRenderer.maskInteraction = SpriteMaskInteraction.None;
+ }
+
+ if (_smallMotionMask != null)
+ {
+ _smallMotionMask.enabled = false;
+ }
}
///