using System; using System.Collections; using AibisDream.Framework; using AibisDream.Kit; using AibisDream.Utility; using DG.Tweening; using UnityEngine; using UnityEngine.ResourceManagement.AsyncOperations; namespace AibisDream { public class SpriteShowcase : Singleton { /// 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); // 大图初始状态:隐藏 if (_largeSpriteRenderer != null) { _largeSpriteRenderer.gameObject.SetActive(false); _largeSpriteRenderer.SetAlpha(0); } } 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; return picName.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ? picName.Substring(0, picName.Length - 4) : picName; } private IEnumerator LoadDreamSpriteInto(SpriteRenderer target, string picName) { var key = string.Format(DreamSpritePath, NormalizeDreamPicName(picName)); var handle = ResourceSystem.LoadAsync(key); yield return handle; if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null) target.sprite = handle.Result; else 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); } /// /// 显示物体(小图) /// /// 图片名(无扩展名,与 Yarn 中一致;Addressable Key 由 拼接) /// 淡入时长 /// 协程 public IEnumerator ShowObj(string picName, float duration = 1) { // 互斥:如果大图正在显示,先隐藏 if (IsLargeShowing) { 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); } /// /// 显示大图 /// /// 图片名(无扩展名,与 Yarn 中一致;Addressable Key 由 拼接) /// 淡入时长 /// 协程 public IEnumerator ShowLargeObj(string picName, float duration = 1) { // 互斥:小图立刻关掉,避免风筝层与底图不同步淡出造成的怪 fade if (IsSmallShowing) { yield return HideObj(0f); } if (_largeSpriteRenderer == null) yield break; _largeSpriteRenderer.gameObject.SetActive(true); yield return LoadDreamSpriteInto(_largeSpriteRenderer, picName); yield return _largeSpriteRenderer.DOFade(1, duration).WaitForCompletion(); } /// /// 隐藏物体(小图) /// /// 淡出时长 /// 协程 public IEnumerator HideObj(float duration = 1) { yield return HideObj(force: false, duration); } /// /// 隐藏物体(小图) /// /// 是否强制隐藏 /// 淡出时长 /// 协程 public IEnumerator HideObj(bool force, float duration = 1) { // force=false 时检查状态,未显示则直接返回 if (!force && !IsSmallShowing) yield break; 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; } } /// /// 隐藏大图 /// /// 是否强制隐藏 /// 淡出时长 /// 协程 public IEnumerator HideLargeObj(float duration = 1) { if (_largeSpriteRenderer == null) yield break; yield return _largeSpriteRenderer.DOFade(0, duration).WaitForCompletion(); _largeSpriteRenderer.gameObject.SetActive(false); } /// /// 隐藏所有(小图和大图) /// /// 淡出时长 /// 协程 public IEnumerator HideAll(float duration = 1) { if (IsSmallShowing) yield return HideObj(duration); if (IsLargeShowing) yield return HideLargeObj(duration); } /// /// 淡入(异步协程版本) /// /// 淡入时长 /// 协程 public IEnumerator FadeInAsync(float duration = 1) { if (_backgroundSpriteRenderer == null) yield break; _backgroundSpriteRenderer.gameObject.SetActive(true); yield return _backgroundSpriteRenderer.DOFade(1, duration).WaitForCompletion(); } /// /// 淡出(异步协程版本) /// /// 淡出时长 /// 协程 public IEnumerator FadeOutAsync(float duration = 1) { if (_backgroundSpriteRenderer == null) yield break; _backgroundSpriteRenderer.gameObject.SetActive(true); yield return _backgroundSpriteRenderer.DOFade(0, duration).WaitForCompletion(); _backgroundSpriteRenderer.gameObject.SetActive(false); } /// /// 淡入(同步版本,启动协程) /// /// 淡入时长 public void FadeInSync(float duration = 1) { StartCoroutine(FadeInAsync(duration)); } /// /// 淡出(同步版本,启动协程) /// /// 淡出时长 public void FadeOutSync(float duration = 1) { StartCoroutine(FadeOutAsync(duration)); } } }