using System.Collections; using AibisDream.Utility; using DG.Tweening; using UnityEngine; namespace AibisDream { internal sealed class ReelMotionPresenter { private const string BaseSuffix = "_底图"; private const string MotionSuffix = "_风筝层"; private const float MotionSpeedMultiplier = 5.2f; private const float MotionAmplitude = 0.22f; private readonly SpriteRenderer _baseRenderer; private readonly Vector3 _baseLocalPosition; private readonly Quaternion _baseLocalRotation; private readonly Vector3 _baseLocalScale; private SpriteRenderer _motionRenderer; private SpriteMask _motionMask; private Sequence _motionSequence; private Vector3 _motionBaseOffset; public bool IsShowing => _baseRenderer != null && _baseRenderer.gameObject.activeSelf; public string CurrentPicName { get; private set; } public string CurrentMotionProfile { get; private set; } public ReelMotionPresenter(SpriteRenderer baseRenderer) { _baseRenderer = baseRenderer; if (_baseRenderer == null) return; var transform = _baseRenderer.transform; _baseLocalPosition = transform.localPosition; _baseLocalRotation = transform.localRotation; _baseLocalScale = transform.localScale; } public IEnumerator Show(string picName, string motionProfile) { ClearImmediate(); if (_baseRenderer == null) yield break; var normalizedName = Day2SleepSpriteLoader.NormalizeName(picName); if (!IsReelPic(normalizedName)) { Debug.LogWarning($"[ReelMotionPresenter] Unsupported reel sprite: {picName}"); yield break; } _motionBaseOffset = new Vector3(0f, -0.32f, 0f); _baseRenderer.gameObject.SetActive(true); var baseLoaded = false; yield return Day2SleepSpriteLoader.LoadInto( _baseRenderer, normalizedName + BaseSuffix, value => baseLoaded = value); if (!baseLoaded) { ClearImmediate(); yield break; } EnsureMotionRenderer(); _motionRenderer.gameObject.SetActive(true); CopyRendererSettings(_motionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask); ConfigureMotionMask(); ResetTransforms(); var motionLoaded = false; yield return Day2SleepSpriteLoader.LoadInto( _motionRenderer, normalizedName + MotionSuffix, value => motionLoaded = value); if (!motionLoaded) { ClearImmediate(); yield break; } _baseRenderer.SetAlpha(1f); _motionRenderer.SetAlpha(1f); CurrentPicName = normalizedName; PlayMotion(motionProfile); } public void PlayCurrentMotion(string motionProfile) { if (!IsShowing) return; PlayMotion(motionProfile); } public void ClearImmediate() { KillTweenState(); ResetTransforms(); CurrentPicName = null; CurrentMotionProfile = null; if (_baseRenderer != null) { _baseRenderer.DOKill(); _baseRenderer.SetAlpha(0f); _baseRenderer.gameObject.SetActive(false); } if (_motionRenderer != null) { _motionRenderer.DOKill(); _motionRenderer.SetAlpha(0f); _motionRenderer.gameObject.SetActive(false); _motionRenderer.maskInteraction = SpriteMaskInteraction.None; } if (_motionMask != null) _motionMask.enabled = false; } private void EnsureMotionRenderer() { if (_motionRenderer != null || _baseRenderer == null) return; var motionObject = new GameObject($"{_baseRenderer.name} Motion"); motionObject.transform.SetParent(_baseRenderer.transform.parent); _motionRenderer = motionObject.AddComponent(); CopyRendererSettings(_motionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask); _motionRenderer.gameObject.SetActive(false); _motionRenderer.SetAlpha(0f); } private void EnsureMotionMask() { if (_motionMask != null || _baseRenderer == null) return; var maskObject = new GameObject($"{_baseRenderer.name} Motion Mask"); maskObject.transform.SetParent(_baseRenderer.transform.parent); _motionMask = maskObject.AddComponent(); _motionMask.alphaCutoff = 0.01f; _motionMask.isCustomRangeActive = true; _motionMask.enabled = false; } private void CopyRendererSettings( SpriteRenderer target, int sortingOrderOffset, SpriteMaskInteraction maskInteraction) { if (target == null || _baseRenderer == null) return; target.sharedMaterial = _baseRenderer.sharedMaterial; target.sortingLayerID = _baseRenderer.sortingLayerID; target.sortingOrder = _baseRenderer.sortingOrder + sortingOrderOffset; target.drawMode = _baseRenderer.drawMode; target.size = _baseRenderer.size; target.flipX = _baseRenderer.flipX; target.flipY = _baseRenderer.flipY; target.maskInteraction = maskInteraction; } private void ConfigureMotionMask() { EnsureMotionMask(); if (_motionMask == null) return; _motionMask.sprite = _baseRenderer.sprite; _motionMask.frontSortingLayerID = _baseRenderer.sortingLayerID; _motionMask.backSortingLayerID = _baseRenderer.sortingLayerID; _motionMask.frontSortingOrder = _baseRenderer.sortingOrder + 2; _motionMask.backSortingOrder = _baseRenderer.sortingOrder; var transform = _motionMask.transform; transform.localPosition = _baseLocalPosition; transform.localRotation = _baseLocalRotation; transform.localScale = _baseLocalScale; _motionMask.enabled = true; } private void ResetTransforms() { if (_baseRenderer == null) return; var baseTransform = _baseRenderer.transform; baseTransform.localPosition = _baseLocalPosition; baseTransform.localRotation = _baseLocalRotation; baseTransform.localScale = _baseLocalScale; if (_motionRenderer == null) return; var motionTransform = _motionRenderer.transform; motionTransform.localPosition = _baseLocalPosition + _motionBaseOffset; motionTransform.localRotation = _baseLocalRotation; motionTransform.localScale = _baseLocalScale; } private static bool IsReelPic(string picName) { return picName == "D2S轱辘" || picName == "D2S轱辘没线"; } private static string NormalizeProfile(string motionProfile) { return string.IsNullOrEmpty(motionProfile) ? string.Empty : motionProfile.Trim().ToLowerInvariant(); } private Vector3 BaseOffset(float x, float y) { return _baseLocalPosition + _motionBaseOffset + new Vector3(x, y, 0f); } private void PlayMotion(string motionProfile) { KillTweenState(); var profile = NormalizeProfile(motionProfile); CurrentMotionProfile = profile; if (string.IsNullOrEmpty(profile) || profile == "none") return; if (profile != "reel_tension") { Debug.LogWarning($"[ReelMotionPresenter] Unknown motion profile: {motionProfile}"); return; } var target = _motionRenderer != null && _motionRenderer.gameObject.activeSelf ? _motionRenderer.transform : _baseRenderer.transform; var sequence = DOTween.Sequence(); sequence.Append(target.DOLocalMove( BaseOffset(0.13f * MotionAmplitude, -0.32f * MotionAmplitude), 0.38f) .SetEase(Ease.OutCubic)); sequence.Join(target.DOLocalRotate( new Vector3(0f, 0f, -2.8f * MotionAmplitude), 0.38f) .SetEase(Ease.OutCubic)); sequence.OnComplete(() => PlayReelHold(target)); sequence.timeScale = MotionSpeedMultiplier; _motionSequence = sequence; } private void PlayReelHold(Transform transform, float strain = 0f) { _motionSequence?.Kill(); var amp = MotionAmplitude; var deepX = strain * 0.03f; var deepY = strain * 0.07f; var speed = Mathf.Lerp(1f, 0.78f, strain); var rotBias = strain * 0.35f; var sequence = DOTween.Sequence(); var strainDuration = Random.Range(0.55f, 0.9f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.17f, 0.23f) + deepX) * amp, (Random.Range(-0.42f, -0.34f) - deepY) * amp), strainDuration) .SetEase(Ease.InOutSine)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-3.6f, -2.4f) - rotBias) * amp), strainDuration) .SetEase(Ease.InOutSine)); if (Random.value < 0.4f + strain * 0.25f) { var yankDuration = Random.Range(0.18f, 0.28f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.25f, 0.34f) + deepX) * amp, (Random.Range(-0.62f, -0.5f) - deepY) * amp), yankDuration) .SetEase(Ease.OutCubic)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-4.8f, -3.4f) - rotBias) * amp), yankDuration) .SetEase(Ease.OutCubic)); sequence.AppendInterval(Random.Range(0.1f, 0.18f) * speed); var biteDuration = Random.Range(0.2f, 0.32f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.19f, 0.27f) + deepX) * amp, (Random.Range(-0.48f, -0.4f) - deepY) * amp), biteDuration) .SetEase(Ease.OutCubic)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-3.4f, -2.2f) - rotBias) * amp), biteDuration) .SetEase(Ease.OutCubic)); } var recoverDuration = Random.Range(0.28f, 0.42f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.12f, 0.16f) + deepX) * amp, (Random.Range(-0.32f, -0.26f) - deepY * 0.55f) * amp), recoverDuration) .SetEase(Ease.OutCubic)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-2.3f, -1.3f) - rotBias * 0.7f) * amp), recoverDuration) .SetEase(Ease.OutCubic)); var slipDuration = Random.Range(0.16f, 0.26f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.2f, 0.27f) + deepX) * amp, (Random.Range(-0.5f, -0.4f) - deepY) * amp), slipDuration) .SetEase(Ease.OutQuad)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-4.1f, -2.8f) - rotBias) * amp), slipDuration) .SetEase(Ease.OutQuad)); if (Random.value < 0.3f) { var dipDuration = Random.Range(0.14f, 0.22f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.21f, 0.28f) + deepX) * amp, (Random.Range(-0.56f, -0.47f) - deepY) * amp), dipDuration) .SetEase(Ease.OutQuad)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-4.4f, -3f) - rotBias) * amp), dipDuration) .SetEase(Ease.OutQuad)); var braceDuration = Random.Range(0.18f, 0.28f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.16f, 0.22f) + deepX) * amp, (Random.Range(-0.4f, -0.33f) - deepY) * amp), braceDuration) .SetEase(Ease.OutCubic)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-3f, -1.8f) - rotBias) * amp), braceDuration) .SetEase(Ease.OutCubic)); } var pullDuration = Random.Range(0.4f, 0.58f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.13f, 0.18f) + deepX) * amp, (Random.Range(-0.35f, -0.28f) - deepY * 0.75f) * amp), pullDuration) .SetEase(Ease.InOutSine)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-2.8f, -1.6f) - rotBias) * amp), pullDuration) .SetEase(Ease.InOutSine)); if (Random.value < 0.25f) { var sagDuration = Random.Range(0.2f, 0.32f) * speed; sequence.Append(transform.DOLocalMove( BaseOffset((Random.Range(0.18f, 0.24f) + deepX) * amp, (Random.Range(-0.46f, -0.38f) - deepY) * amp), sagDuration) .SetEase(Ease.InOutSine)); sequence.Join(transform.DOLocalRotate( new Vector3(0f, 0f, (Random.Range(-3.5f, -2.2f) - rotBias) * amp), sagDuration) .SetEase(Ease.InOutSine)); } sequence.OnComplete(() => PlayReelHold(transform, Mathf.Min(1f, strain + 0.12f))); sequence.timeScale = MotionSpeedMultiplier; _motionSequence = sequence; } private void KillTweenState() { _motionSequence?.Kill(); _motionSequence = null; } } }