Files

330 lines
13 KiB
C#

using System.Collections;
using AibisDream.Utility;
using DG.Tweening;
using UnityEngine;
namespace AibisDream
{
internal sealed class LargeSpriteEffectsPresenter
{
private static readonly int BlurAmountId = Shader.PropertyToID("_BlurAmount");
private static readonly int BlurSizeId = Shader.PropertyToID("_BlurSize");
private static readonly int UseSoftFocusId = Shader.PropertyToID("_UseSoftFocus");
private readonly MonoBehaviour _host;
private readonly SpriteRenderer _renderer;
private readonly Material _focusMaterial;
private readonly Vector3 _baseLocalPosition;
private readonly Quaternion _baseLocalRotation;
private readonly Vector3 _baseLocalScale;
private readonly float _baseAlpha;
private readonly Material _baseMaterial;
private MaterialPropertyBlock _propertyBlock;
private Coroutine _animationCoroutine;
private Sequence _presentationSequence;
private int _blurTweenVersion;
private int _presentationTweenVersion;
public bool IsAnimationPlaying => _animationCoroutine != null;
public string AnimationPrefix { get; private set; }
public int AnimationFrameCount { get; private set; }
public float AnimationSecondsPerFrame { get; private set; } = 0.2f;
public float TargetBlurAmount { get; private set; }
public float TargetBlurSize { get; private set; } = 0.012f;
public float TargetScaleMultiplier { get; private set; } = 1f;
public float TargetAlpha { get; private set; } = 1f;
public LargeSpriteEffectsPresenter(
MonoBehaviour host,
SpriteRenderer renderer,
Material focusMaterial)
{
_host = host;
_renderer = renderer;
_focusMaterial = focusMaterial;
if (_renderer == null) return;
var transform = _renderer.transform;
_baseLocalPosition = transform.localPosition;
_baseLocalRotation = transform.localRotation;
_baseLocalScale = transform.localScale;
// SpriteShowcase 在自己的 Awake 中会先隐藏 Renderer;D2 的稳定基准仍然是完全可见。
_baseAlpha = 1f;
_baseMaterial = _renderer.sharedMaterial;
}
public IEnumerator ShowAnimation(
string picNamePrefix,
int frameCount,
float secondsPerFrame = 0.2f,
float duration = 1f)
{
if (_renderer == null || _host == null || frameCount <= 0) yield break;
ResetEffectsImmediate();
var frames = new Sprite[frameCount];
var normalizedPrefix = Day2SleepSpriteLoader.NormalizeName(picNamePrefix);
for (var index = 0; index < frameCount; index++)
{
Sprite loadedFrame = null;
yield return Day2SleepSpriteLoader.Load(
$"{normalizedPrefix}{index + 1}",
value => loadedFrame = value);
if (loadedFrame == null)
{
ResetEffectsImmediate();
yield break;
}
frames[index] = loadedFrame;
}
_renderer.gameObject.SetActive(true);
_renderer.sprite = frames[0];
_renderer.SetAlpha(0f);
AnimationPrefix = normalizedPrefix;
AnimationFrameCount = frameCount;
AnimationSecondsPerFrame = Mathf.Max(0.01f, secondsPerFrame);
_animationCoroutine = _host.StartCoroutine(LoopAnimation(frames, AnimationSecondsPerFrame));
if (duration <= 0f)
_renderer.SetAlpha(1f);
else
yield return _renderer.DOFade(1f, duration).WaitForCompletion();
TargetAlpha = 1f;
}
public void SetBlur(float amount, float blurSize = 0.012f)
{
_blurTweenVersion++;
TargetBlurAmount = Mathf.Clamp01(amount);
TargetBlurSize = Mathf.Max(0f, blurSize);
ApplyBlur(TargetBlurAmount, TargetBlurSize);
}
public IEnumerator TweenBlur(float targetAmount, float duration, float blurSize = 0.012f)
{
if (_renderer == null) yield break;
var tweenVersion = ++_blurTweenVersion;
var startAmount = ReadBlurAmount();
TargetBlurAmount = Mathf.Clamp01(targetAmount);
TargetBlurSize = Mathf.Max(0f, blurSize);
duration = Mathf.Max(0f, duration);
if (duration <= 0f)
{
ApplyBlur(TargetBlurAmount, TargetBlurSize);
yield break;
}
var elapsed = 0f;
while (elapsed < duration)
{
if (tweenVersion != _blurTweenVersion) yield break;
elapsed += Time.deltaTime;
var progress = Mathf.Clamp01(elapsed / duration);
progress = progress * progress * (3f - 2f * progress);
ApplyBlur(Mathf.Lerp(startAmount, TargetBlurAmount, progress), TargetBlurSize);
yield return null;
}
if (tweenVersion != _blurTweenVersion) yield break;
ApplyBlur(TargetBlurAmount, TargetBlurSize);
}
public IEnumerator AnimatePresentation(float scaleMultiplier, float targetAlpha, float duration)
{
if (_renderer == null) yield break;
var tweenVersion = ++_presentationTweenVersion;
KillPresentationTweens();
_renderer.gameObject.SetActive(true);
TargetScaleMultiplier = Mathf.Max(0.01f, scaleMultiplier);
TargetAlpha = Mathf.Clamp01(targetAlpha);
duration = Mathf.Max(0f, duration);
var targetScale = ScaleFor(TargetScaleMultiplier);
if (duration <= 0f)
{
if (tweenVersion != _presentationTweenVersion) yield break;
ApplyPresentationImmediate(TargetScaleMultiplier, TargetAlpha);
yield break;
}
_presentationSequence = DOTween.Sequence();
_presentationSequence.Join(
_renderer.transform.DOScale(targetScale, duration).SetEase(Ease.InOutSine));
_presentationSequence.Join(
_renderer.transform.DOLocalMove(_baseLocalPosition, duration).SetEase(Ease.InOutSine));
_presentationSequence.Join(
_renderer.DOFade(TargetAlpha, duration).SetEase(Ease.InOutSine));
yield return _presentationSequence.WaitForCompletion();
// 被 set_door_closeup / 新 tween 打断后,不要回写 scale。
if (tweenVersion != _presentationTweenVersion) yield break;
_presentationSequence = null;
var transform = _renderer.transform;
transform.localPosition = _baseLocalPosition;
transform.localRotation = _baseLocalRotation;
transform.localScale = targetScale;
_renderer.SetAlpha(TargetAlpha);
}
public void ApplyPresentationImmediate(float scaleMultiplier, float alpha)
{
if (_renderer == null) return;
_presentationTweenVersion++;
KillPresentationTweens();
TargetScaleMultiplier = Mathf.Max(0.01f, scaleMultiplier);
TargetAlpha = Mathf.Clamp01(alpha);
var transform = _renderer.transform;
transform.localPosition = _baseLocalPosition;
transform.localRotation = _baseLocalRotation;
transform.localScale = ScaleFor(TargetScaleMultiplier);
_renderer.SetAlpha(TargetAlpha);
}
public void SetSpriteImmediate(Sprite sprite)
{
if (_renderer == null || sprite == null) return;
// Null-assign forces URP SpriteRenderer to refresh _MainTex in its property block.
_renderer.sprite = null;
_renderer.sprite = sprite;
}
public void StopPresentationAnimation()
{
_presentationTweenVersion++;
KillPresentationTweens();
}
public void ResetPresentationImmediate()
{
if (_renderer == null) return;
_presentationTweenVersion++;
KillPresentationTweens();
TargetScaleMultiplier = 1f;
TargetAlpha = _baseAlpha;
var transform = _renderer.transform;
transform.localPosition = _baseLocalPosition;
transform.localRotation = _baseLocalRotation;
transform.localScale = _baseLocalScale;
_renderer.SetAlpha(_baseAlpha);
}
public void HidePresentationImmediate()
{
if (_renderer == null) return;
StopAnimation(hideRenderer: true);
ApplyBlur(0f, 0f);
ResetPresentationImmediate();
_renderer.SetAlpha(0f);
_renderer.gameObject.SetActive(false);
}
public void ResetEffectsImmediate()
{
_blurTweenVersion++;
var animationWasPlaying = IsAnimationPlaying;
StopAnimation(hideRenderer: animationWasPlaying);
TargetBlurAmount = 0f;
TargetBlurSize = 0.012f;
ApplyBlur(0f, 0f);
ResetPresentationImmediate();
}
private IEnumerator LoopAnimation(Sprite[] frames, float secondsPerFrame)
{
var index = 0;
var wait = new WaitForSeconds(Mathf.Max(0.01f, secondsPerFrame));
while (frames.Length > 0)
{
if (_renderer != null)
_renderer.sprite = frames[index];
index = (index + 1) % frames.Length;
yield return wait;
}
}
private void StopAnimation(bool hideRenderer)
{
if (_animationCoroutine != null && _host != null)
{
_host.StopCoroutine(_animationCoroutine);
_animationCoroutine = null;
}
AnimationPrefix = null;
AnimationFrameCount = 0;
if (hideRenderer && _renderer != null)
{
_renderer.SetAlpha(0f);
_renderer.gameObject.SetActive(false);
}
}
private float ReadBlurAmount()
{
if (_renderer == null || _renderer.sharedMaterial != _focusMaterial) return 0f;
_propertyBlock ??= new MaterialPropertyBlock();
_renderer.GetPropertyBlock(_propertyBlock);
return _propertyBlock.GetFloat(BlurAmountId);
}
private void ApplyBlur(float amount, float blurSize)
{
if (_renderer == null) return;
amount = Mathf.Clamp01(amount);
if (amount <= 0.001f)
{
// SetPropertyBlock(null) can drop SpriteRenderer's internal _MainTex on URP.
// Clear blur overrides, restore base material, then force a sprite rebind.
_renderer.sharedMaterial = _baseMaterial;
_propertyBlock ??= new MaterialPropertyBlock();
_renderer.GetPropertyBlock(_propertyBlock);
_propertyBlock.SetFloat(BlurAmountId, 0f);
_propertyBlock.SetFloat(BlurSizeId, 0f);
_propertyBlock.SetFloat(UseSoftFocusId, 0f);
_renderer.SetPropertyBlock(_propertyBlock);
_renderer.SetPropertyBlock(null);
RebindSpriteTexture(_renderer);
return;
}
if (_focusMaterial == null)
{
Debug.LogWarning("[LargeSpriteEffectsPresenter] 未配置全屏图片对焦材质。");
return;
}
_renderer.sharedMaterial = _focusMaterial;
_propertyBlock ??= new MaterialPropertyBlock();
_renderer.GetPropertyBlock(_propertyBlock);
_propertyBlock.SetFloat(BlurAmountId, amount);
_propertyBlock.SetFloat(BlurSizeId, Mathf.Max(0f, blurSize));
_propertyBlock.SetFloat(UseSoftFocusId, 1f);
// Keep sprite texture in the block so URP SpriteRenderer does not fall back to white.
if (_renderer.sprite != null && _renderer.sprite.texture != null)
_propertyBlock.SetTexture("_MainTex", _renderer.sprite.texture);
_renderer.SetPropertyBlock(_propertyBlock);
}
private static void RebindSpriteTexture(SpriteRenderer renderer)
{
if (renderer == null) return;
var sprite = renderer.sprite;
if (sprite == null) return;
renderer.sprite = null;
renderer.sprite = sprite;
}
private Vector3 ScaleFor(float multiplier)
{
return new Vector3(
_baseLocalScale.x * multiplier,
_baseLocalScale.y * multiplier,
_baseLocalScale.z);
}
private void KillPresentationTweens()
{
_presentationSequence?.Kill();
_presentationSequence = null;
if (_renderer == null) return;
DOTween.Kill(_renderer);
DOTween.Kill(_renderer.transform);
}
}
}