From 18d6d970f9e4bd8d21807ff772ee90db67bc40ec Mon Sep 17 00:00:00 2001 From: bottlefish <781230111@qq.com> Date: Fri, 10 Jul 2026 22:32:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(scene-mgmt):=20=E5=AE=9E=E7=8E=B0=E9=A3=8E?= =?UTF-8?q?=E7=AD=9D=E7=B2=BE=E7=81=B5=E5=88=86=E5=B1=82=E5=8A=A8=E6=95=88?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../Scripts/SceneManagement/SpriteShowcase.cs | 368 +++++++++++++++++- 1 file changed, 365 insertions(+), 3 deletions(-) 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; + } } ///