329 lines
13 KiB
C#
329 lines
13 KiB
C#
using System.Collections;
|
|
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);
|
|
}
|
|
}
|
|
}
|