342 lines
14 KiB
C#
342 lines
14 KiB
C#
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<SpriteRenderer>();
|
|
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<SpriteMask>();
|
|
_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;
|
|
}
|
|
}
|
|
}
|