diff --git a/Assets/Scripts/Dialog System/BaseYarnCommand.cs b/Assets/Scripts/Dialog System/BaseYarnCommand.cs
index 3ea567548..e8cbf2ba4 100644
--- a/Assets/Scripts/Dialog System/BaseYarnCommand.cs
+++ b/Assets/Scripts/Dialog System/BaseYarnCommand.cs
@@ -113,6 +113,56 @@ namespace AibisDream
}
}
+ [YarnCommand("kite_show")]
+ public static IEnumerator KiteShow(float height = 0, int wind = 0, float duration = 0.35f)
+ {
+ var showcase = SpriteShowcase.Instance;
+ if (showcase == null) yield break;
+ yield return showcase.ShowKite(height, wind, duration);
+ }
+
+ [YarnCommand("kite_state")]
+ public static IEnumerator KiteState(float height, int wind, float duration = 0.9f, float leadDuration = 0.35f)
+ {
+ var showcase = SpriteShowcase.Instance;
+ if (showcase == null) yield break;
+
+ showcase.SetKiteState(height, wind, duration);
+ if (leadDuration > 0f)
+ {
+ yield return new WaitForSeconds(Mathf.Min(leadDuration, duration));
+ }
+ }
+
+ [YarnCommand("kite_wind")]
+ public static void KiteWind(int wind, float turbulence = -1f)
+ {
+ var showcase = SpriteShowcase.Instance;
+ if (showcase == null) return;
+ showcase.SetKiteWind(wind, turbulence);
+ }
+
+ [YarnCommand("kite_accent")]
+ public static IEnumerator KiteAccent(string profile, float leadDuration = 0.18f)
+ {
+ var showcase = SpriteShowcase.Instance;
+ if (showcase == null) yield break;
+
+ showcase.PlayKiteAccent(profile);
+ if (leadDuration > 0f)
+ {
+ yield return new WaitForSeconds(leadDuration);
+ }
+ }
+
+ [YarnCommand("kite_hide")]
+ public static IEnumerator KiteHide(float duration = 1)
+ {
+ var showcase = SpriteShowcase.Instance;
+ if (showcase == null) yield break;
+ yield return showcase.HideKite(duration);
+ }
+
[YarnCommand("hide_sprite")]
public static IEnumerator HideSprite(float duration = 1)
{
diff --git a/Assets/Scripts/SceneManagement/SpriteShowcase.cs b/Assets/Scripts/SceneManagement/SpriteShowcase.cs
index e13fb24c1..c19bc3faa 100644
--- a/Assets/Scripts/SceneManagement/SpriteShowcase.cs
+++ b/Assets/Scripts/SceneManagement/SpriteShowcase.cs
@@ -172,13 +172,9 @@ namespace AibisDream
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)
+ // 风筝图已改由风筝 Rig(ShowKite 等)连续驱动,分层底图+遮罩通路仅保留给轱辘特写
+ if (normalized == "D2S轱辘")
{
basePicName = $"{normalized}{KiteBaseSuffix}";
motionPicName = $"{normalized}{KiteMotionSuffix}";
@@ -206,102 +202,26 @@ namespace AibisDream
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;
-
+ // 轱辘:持续拉扯里的几次失控顿挫。单向循环,避免 Yoyo 反放造成“被倒带”的怪感。
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));
+ 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:
@@ -332,6 +252,12 @@ namespace AibisDream
yield return HideLargeObj(duration);
}
+ // 互斥:风筝 Rig 正在显示时先隐藏
+ if (IsKiteShowing)
+ {
+ yield return HideKite(Mathf.Min(duration, 0.25f));
+ }
+
KillSmallTweenState();
ResetSmallTransform();
@@ -363,6 +289,12 @@ namespace AibisDream
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);
@@ -422,6 +354,11 @@ namespace AibisDream
yield return HideObj(0f);
}
+ if (IsKiteShowing)
+ {
+ yield return HideKite(0f);
+ }
+
if (_largeSpriteRenderer == null) yield break;
_largeSpriteRenderer.gameObject.SetActive(true);
@@ -449,6 +386,12 @@ namespace AibisDream
/// 协程
public IEnumerator HideObj(bool force, float duration = 1)
{
+ // 风筝 Rig 视作小图的一种,hide_sprite 时一并隐藏
+ if (IsKiteShowing)
+ {
+ yield return HideKite(duration);
+ }
+
// force=false 时检查状态,未显示则直接返回
if (!force && !IsSmallShowing) yield break;
@@ -507,13 +450,391 @@ namespace AibisDream
/// 协程
public IEnumerator HideAll(float duration = 1)
{
- if (IsSmallShowing)
+ 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
+
///
/// 淡入(异步协程版本)
///