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); // 风筝图已改由风筝 Rig(ShowKite 等)连续驱动,分层底图+遮罩通路仅保留给轱辘特写 if (normalized == "D2S轱辘") { 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) { // 轱辘:持续拉扯里的几次失控顿挫。单向循环,避免 Yoyo 反放造成“被倒带”的怪感。 case "reel_tension": seq.Append(tr.DOLocalMove(BaseOffset(0.03f, -0.05f), 0.28f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -1.5f), 0.28f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(BaseOffset(0.13f, -0.18f), 0.16f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -4.8f), 0.16f).SetEase(Ease.OutCubic)); seq.Append(tr.DOLocalMove(BaseOffset(0.08f, -0.11f), 0.48f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -2.4f), 0.48f).SetEase(Ease.InOutSine)); seq.AppendInterval(0.12f); seq.Append(tr.DOLocalMove(BaseOffset(0.19f, -0.25f), 0.13f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -6.5f), 0.13f).SetEase(Ease.OutCubic)); seq.Append(tr.DOLocalMove(BaseOffset(0.11f, -0.15f), 0.62f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -3.2f), 0.62f).SetEase(Ease.InOutSine)); seq.AppendInterval(0.2f); seq.Append(tr.DOLocalMove(BaseOffset(0.15f, -0.21f), 0.18f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -5.2f), 0.18f).SetEase(Ease.OutCubic)); seq.Append(tr.DOLocalMove(BaseOffset(0.06f, -0.09f), 0.75f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -2f), 0.75f).SetEase(Ease.InOutSine)); seq.AppendInterval(0.18f); seq.SetLoops(-1, LoopType.Restart); 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); } // 互斥:风筝 Rig 正在显示时先隐藏 if (IsKiteShowing) { yield return HideKite(Mathf.Min(duration, 0.25f)); } 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); } // 互斥:风筝 Rig 正在显示时先隐藏 if (IsKiteShowing) { yield return HideKite(Mathf.Min(duration, 0.25f)); } 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 (IsKiteShowing) { yield return HideKite(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) { // 风筝 Rig 视作小图的一种,hide_sprite 时一并隐藏 if (IsKiteShowing) { yield return HideKite(duration); } // 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 || IsKiteShowing) yield return HideObj(duration); if (IsLargeShowing) yield return HideLargeObj(duration); } #region 风筝 Rig // 风筝段落弃用切图(D2S风筝1/2/3),改为单张风筝图 + 三层 Transform 连续驱动: // Kite Rig(高度补间:缩放+位移)→ Sway(常驻风况循环)→ Accent(一次性顿挫 + Renderer) // 三层各持有独立 Tween,互不覆盖。白色底图挂在独立的静态 renderer 上,不随高度缩放。 private const string KiteSpritePicName = "D2S风筝1_风筝层"; private const string KiteBasePicName = "D2S风筝1_底图"; private const float KiteMaxHeight = 13f; /// 每升高 1 点高度,缩放乘以该系数。 private const float KiteScalePerHeight = 0.84f; /// 缩放下限:最远处的风筝也不小于该比例。 private const float KiteMinScaleFactor = 0.12f; /// 高度最大时风筝相对基准位置的漂移量(向上飘 + 少量横移)。 private static readonly Vector3 KiteTopDrift = new Vector3(0.5f, 1.2f, 0f); private Transform _kiteRootPivot; private Transform _kiteSwayPivot; private SpriteRenderer _kiteRenderer; private SpriteRenderer _kiteBaseRenderer; private SpriteMask _kiteMask; private DG.Tweening.Sequence _kiteHeightSequence; private DG.Tweening.Sequence _kiteSwaySequence; private DG.Tweening.Sequence _kiteAccentSequence; private Tween _kiteFadeTween; private Tween _kiteBaseFadeTween; private float _kiteHeight; /// 当前乱度 0~1,由 kite_wind 显式设定;kite_state 沿用上次的值(粘滞)。 private float _kiteTurbulence; private bool _kiteSpriteLoaded; private bool _kiteBaseSpriteLoaded; private bool IsKiteShowing => _kiteRootPivot != null && _kiteRootPivot.gameObject.activeSelf; private void EnsureKiteRig() { if (_kiteRootPivot != null || _spriteRenderer == null) return; var root = new GameObject("Kite Rig"); root.transform.SetParent(_spriteRenderer.transform.parent); root.transform.localPosition = _smallBaseLocalPosition; root.transform.localRotation = _smallBaseLocalRotation; root.transform.localScale = _smallBaseLocalScale; var sway = new GameObject("Kite Sway"); sway.transform.SetParent(root.transform, false); var accent = new GameObject("Kite Accent"); accent.transform.SetParent(sway.transform, false); // 白色底图:独立静态节点,不随高度层缩放 var baseObject = new GameObject("Kite Base"); baseObject.transform.SetParent(_spriteRenderer.transform.parent); baseObject.transform.localPosition = _smallBaseLocalPosition; baseObject.transform.localRotation = _smallBaseLocalRotation; baseObject.transform.localScale = _smallBaseLocalScale; // 遮罩:把风筝层限制在底图(天空)范围内,不会飘出画框 var maskObject = new GameObject("Kite Mask"); maskObject.transform.SetParent(_spriteRenderer.transform.parent); maskObject.transform.localPosition = _smallBaseLocalPosition; maskObject.transform.localRotation = _smallBaseLocalRotation; maskObject.transform.localScale = _smallBaseLocalScale; _kiteRootPivot = root.transform; _kiteSwayPivot = sway.transform; _kiteRenderer = accent.AddComponent(); CopySmallRendererSettings(_kiteRenderer, 1, SpriteMaskInteraction.VisibleInsideMask); _kiteBaseRenderer = baseObject.AddComponent(); CopySmallRendererSettings(_kiteBaseRenderer, 0, SpriteMaskInteraction.None); _kiteMask = maskObject.AddComponent(); _kiteMask.alphaCutoff = 0.01f; _kiteMask.isCustomRangeActive = true; _kiteMask.enabled = false; root.SetActive(false); baseObject.SetActive(false); _kiteRenderer.SetAlpha(0); _kiteBaseRenderer.SetAlpha(0); } private static float KiteScaleFactor(float height) => Mathf.Max(Mathf.Pow(KiteScalePerHeight, height), KiteMinScaleFactor); private Vector3 KitePosition(float height) => _smallBaseLocalPosition + KiteTopDrift * (height / KiteMaxHeight); private Vector3 KiteScale(float height) => _smallBaseLocalScale * KiteScaleFactor(height); /// 极高处降低不透明度,配合“几乎看不见”的文本。 private static float KiteAlpha(float height) => height <= 8f ? 1f : Mathf.Lerp(1f, 0.75f, (height - 8f) / (KiteMaxHeight - 8f)); private void KillKiteTweens() { _kiteHeightSequence?.Kill(); _kiteHeightSequence = null; _kiteSwaySequence?.Kill(); _kiteSwaySequence = null; _kiteAccentSequence?.Kill(); _kiteAccentSequence = null; _kiteFadeTween?.Kill(); _kiteFadeTween = null; _kiteBaseFadeTween?.Kill(); _kiteBaseFadeTween = null; } private void ApplyKiteStateImmediate(float height) { _kiteRootPivot.localPosition = KitePosition(height); _kiteRootPivot.localRotation = _smallBaseLocalRotation; _kiteRootPivot.localScale = KiteScale(height); _kiteSwayPivot.localPosition = Vector3.zero; _kiteSwayPivot.localRotation = Quaternion.identity; _kiteRenderer.transform.localPosition = Vector3.zero; _kiteRenderer.transform.localRotation = Quaternion.identity; } /// /// 显示风筝 Rig。已显示时等价于 (幂等)。 /// /// 风筝高度(对应 Yarn 变量 $h) /// 风向(对应 Yarn 变量 $wind:1 强 / 0 无 / -1 弱) /// 淡入时长 public IEnumerator ShowKite(float height, int wind, float duration = 0.35f) { if (IsLargeShowing) { yield return HideLargeObj(duration); } if (IsSmallShowing) { yield return HideObj(Mathf.Min(duration, 0.25f)); } EnsureKiteRig(); if (_kiteRenderer == null) yield break; height = Mathf.Clamp(height, 0f, KiteMaxHeight); if (IsKiteShowing) { SetKiteState(height, wind, duration); yield break; } KillKiteTweens(); ApplyKiteStateImmediate(height); _kiteHeight = height; _kiteRootPivot.gameObject.SetActive(true); _kiteBaseRenderer.gameObject.SetActive(true); if (!_kiteSpriteLoaded) { yield return LoadDreamSpriteInto(_kiteRenderer, KiteSpritePicName); _kiteSpriteLoaded = _kiteRenderer.sprite != null; } if (!_kiteBaseSpriteLoaded) { yield return LoadDreamSpriteInto(_kiteBaseRenderer, KiteBasePicName); _kiteBaseSpriteLoaded = _kiteBaseRenderer.sprite != null; } if (_kiteBaseSpriteLoaded) { _kiteMask.sprite = _kiteBaseRenderer.sprite; _kiteMask.frontSortingLayerID = _kiteRenderer.sortingLayerID; _kiteMask.backSortingLayerID = _kiteRenderer.sortingLayerID; _kiteMask.frontSortingOrder = _spriteRenderer.sortingOrder + 2; _kiteMask.backSortingOrder = _spriteRenderer.sortingOrder; _kiteMask.enabled = true; } _kiteTurbulence = 0f; PlayKiteSway(wind); _kiteBaseFadeTween = _kiteBaseRenderer.DOFade(1f, duration); _kiteFadeTween = _kiteRenderer.DOFade(KiteAlpha(height), duration); yield return _kiteFadeTween.WaitForCompletion(); } /// /// 高度补间(每次选择的主反馈)+ 切换风况循环。 /// public void SetKiteState(float height, int wind, float duration = 0.9f) { if (!IsKiteShowing) return; height = Mathf.Clamp(height, 0f, KiteMaxHeight); _kiteHeight = height; _kiteHeightSequence?.Kill(); var seq = DOTween.Sequence(); seq.Append(_kiteRootPivot.DOLocalMove(KitePosition(height), duration).SetEase(Ease.InOutSine)); seq.Join(_kiteRootPivot.DOScale(KiteScale(height), duration).SetEase(Ease.InOutSine)); _kiteHeightSequence = seq; _kiteFadeTween?.Kill(); _kiteFadeTween = _kiteRenderer.DOFade(KiteAlpha(height), duration); PlayKiteSway(wind); } /// 仅切换风况循环(选择前 telegraph 风向)。 /// 风向:1 强 / 0 无 / -1 弱 /// 乱度 0~1;负数或省略表示平稳。设定后粘滞,kite_state 沿用。 public void SetKiteWind(int wind, float turbulence = -1f) { if (!IsKiteShowing) return; _kiteTurbulence = turbulence >= 0f ? Mathf.Clamp01(turbulence) : 0f; PlayKiteSway(wind); } /// /// 常驻风况循环。位移在高度层的子空间里会随缩放变小,用 comp 做部分补偿保证远处仍有可读的移动。 /// 平稳(乱度 0):大而缓的漫游位移,自由自在地飘,绝不抖。 /// 乱流(乱度 >0):一阵一阵——静一下,猛地被拽走一截(位移+旋转),慢慢荡回来,再来一阵。 /// private void PlayKiteSway(int wind) { _kiteSwaySequence?.Kill(); _kiteSwaySequence = null; var tr = _kiteSwayPivot; var storm = _kiteTurbulence; // 屏幕可读性补偿:高度层缩小时适度放大局部位移(开方衰减),避免远处几乎不动 var comp = Mathf.Clamp(1f / Mathf.Sqrt(KiteScaleFactor(_kiteHeight)), 1f, 2.2f); var seq = DOTween.Sequence(); var loopType = LoopType.Yoyo; if (wind > 0 && storm > 0.01f) { // 乱流:先被阵风突然带走,再慢慢回撑;间隔不等,让“失控”读出来但不变成抖屏。 loopType = LoopType.Restart; var amp = comp * (0.95f + storm * 0.95f); seq.AppendInterval(Mathf.Lerp(0.35f, 0.12f, storm)); seq.Append(tr.DOLocalMove(new Vector3(0.46f, 0.08f, 0f) * amp, 0.22f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -13f - storm * 8f), 0.22f).SetEase(Ease.OutCubic)); seq.Append(tr.DOLocalMove(new Vector3(0.2f, -0.08f, 0f) * amp, 0.95f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 5f + storm * 2f), 0.95f).SetEase(Ease.InOutSine)); seq.AppendInterval(0.08f); seq.Append(tr.DOLocalMove(new Vector3(-0.3f, 0.16f, 0f) * amp, 0.18f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 12f + storm * 7f), 0.18f).SetEase(Ease.OutCubic)); seq.Append(tr.DOLocalMove(new Vector3(-0.04f, 0.02f, 0f) * amp, 0.72f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -2f), 0.72f).SetEase(Ease.InOutSine)); seq.AppendInterval(Mathf.Lerp(0.28f, 0.08f, storm)); seq.Append(tr.DOLocalMove(new Vector3(0.36f, 0.2f, 0f) * amp, 0.16f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -10f - storm * 5f), 0.16f).SetEase(Ease.OutCubic)); seq.Append(tr.DOLocalMove(new Vector3(0.1f, 0.04f, 0f) * amp, 1.05f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 2f), 1.05f).SetEase(Ease.InOutSine)); } else if (wind > 0) { // 稳定的风:顶着风侧倾,大幅度缓慢巡游(位移为主,平稳无抖) seq.Append(tr.DOLocalMove(new Vector3(0.3f, 0.18f, 0f) * comp, 2.6f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -7f), 2.6f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(new Vector3(0.42f, 0.06f, 0f) * comp, 2.2f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -4f), 2.2f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(new Vector3(0.12f, 0.12f, 0f) * comp, 2.8f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -8.5f), 2.8f).SetEase(Ease.InOutSine)); } else if (wind == 0) { // 平稳无风:自由自在地漫游,大而缓的位移 seq.Append(tr.DOLocalMove(new Vector3(0.26f, 0.14f, 0f) * comp, 3.2f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 4f), 3.2f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(new Vector3(-0.2f, 0.24f, 0f) * comp, 3.6f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -4.5f), 3.6f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(new Vector3(-0.3f, 0.04f, 0f) * comp, 3f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 3f), 3f).SetEase(Ease.InOutSine)); } else { // 风弱:缓慢的下坠漂移 seq.Append(tr.DOLocalMove(new Vector3(0.08f, -0.22f, 0f) * comp, 2.6f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 3.5f), 2.6f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(new Vector3(-0.14f, -0.1f, 0f) * comp, 2.2f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -2f), 2.2f).SetEase(Ease.InOutSine)); } seq.SetLoops(-1, loopType); _kiteSwaySequence = seq; } /// /// 一次性顿挫动效,叠加在 Accent 层上,结束回到原位,不打断高度/风况补间。 /// 支持:lift(上冲回落)/ sink(下坠沉降)/ taut(前扯抖动)/ drop(猛地下坠)/ flutter(原地挣扎扑腾)。 /// public void PlayKiteAccent(string profile) { if (!IsKiteShowing) return; _kiteAccentSequence?.Kill(); _kiteAccentSequence = null; var tr = _kiteRenderer.transform; // 位移在高度层的子空间里,随缩放自动变小,无需额外乘缩放系数 var seq = DOTween.Sequence(); switch (NormalizeMotionProfile(profile)) { case "lift": seq.Append(tr.DOLocalMove(new Vector3(0.14f, 0.3f, 0f), 0.35f).SetEase(Ease.OutQuad)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -6f), 0.35f).SetEase(Ease.OutQuad)); break; case "sink": seq.Append(tr.DOLocalMove(new Vector3(-0.06f, -0.28f, 0f), 0.4f).SetEase(Ease.OutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 5f), 0.4f).SetEase(Ease.OutSine)); break; // 绷紧:猛地被扯直,然后绷住缓慢微颤(小幅慢速拉锯,不是震动) case "taut": seq.Append(tr.DOLocalMove(new Vector3(0.3f, 0.06f, 0f), 0.2f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -9f), 0.2f).SetEase(Ease.OutCubic)); seq.Append(tr.DOLocalMove(new Vector3(0.26f, 0.03f, 0f), 0.3f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(new Vector3(0.31f, 0.07f, 0f), 0.26f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(new Vector3(0.27f, 0.04f, 0f), 0.3f).SetEase(Ease.InOutSine)); break; case "drop": seq.Append(tr.DOLocalMove(new Vector3(0.04f, -0.42f, 0f), 0.55f).SetEase(Ease.InQuad)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 7f), 0.55f).SetEase(Ease.InQuad)); break; // 挣扎:左右交替的几下小拉扯,力度递减 case "flutter": seq.Append(tr.DOLocalMove(new Vector3(0.1f, 0.14f, 0f), 0.2f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -5f), 0.2f).SetEase(Ease.OutCubic)); seq.Append(tr.DOLocalMove(new Vector3(-0.08f, 0.06f, 0f), 0.26f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, 4.5f), 0.26f).SetEase(Ease.InOutSine)); seq.Append(tr.DOLocalMove(new Vector3(0.06f, 0.1f, 0f), 0.22f).SetEase(Ease.OutCubic)); seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -3.5f), 0.22f).SetEase(Ease.OutCubic)); break; default: seq.Kill(); return; } // 回到原位,保证可与高度/风况补间叠加 seq.Append(tr.DOLocalMove(Vector3.zero, 0.5f).SetEase(Ease.InOutSine)); seq.Join(tr.DOLocalRotate(Vector3.zero, 0.5f).SetEase(Ease.InOutSine)); _kiteAccentSequence = seq; } /// /// 隐藏风筝 Rig /// /// 淡出时长 public IEnumerator HideKite(float duration = 1) { if (!IsKiteShowing) yield break; KillKiteTweens(); if (duration <= 0f) { _kiteRenderer.SetAlpha(0); _kiteBaseRenderer.SetAlpha(0); } else { _kiteBaseFadeTween = _kiteBaseRenderer.DOFade(0, duration); _kiteFadeTween = _kiteRenderer.DOFade(0, duration); yield return _kiteFadeTween.WaitForCompletion(); } _kiteRootPivot.gameObject.SetActive(false); _kiteBaseRenderer.gameObject.SetActive(false); if (_kiteMask != null) { _kiteMask.enabled = false; } } #endregion /// /// 淡入(异步协程版本) /// /// 淡入时长 /// 协程 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)); } } }