using UnityEngine; using UnityEngine.Playables; namespace AibisDream { /// /// Drives the deliberately unsuccessful focus pulls at the start of the /// starfield-to-sea Timeline. The effect is evaluated from Timeline time so /// pausing, frame drops and repeated playback remain deterministic. /// public sealed class StarfieldDefocusController : MonoBehaviour { private static readonly int BlurAmountId = Shader.PropertyToID("_BlurAmount"); private static readonly int BlurSizeId = Shader.PropertyToID("_BlurSize"); [Header("References")] [SerializeField] private PlayableDirector director; [SerializeField] private SpriteRenderer starfieldRenderer; [Header("Defocus Look")] [Range(0f, 1f)] [SerializeField] private float initialBlur = 0.78f; [Range(0f, 0.05f)] [SerializeField] private float blurSize = 0.009f; [Header("Focus Attempt 1")] [Min(0f)] [SerializeField] private float firstAttemptStart = 0.45f; [Min(0.01f)] [SerializeField] private float firstFocusDuration = 0.48f; [Range(0f, 1f)] [SerializeField] private float firstNearFocusBlur = 0.38f; [Min(0f)] [SerializeField] private float firstHoldDuration = 0.14f; [Min(0.01f)] [SerializeField] private float firstSlipDuration = 0.38f; [Range(0f, 1f)] [SerializeField] private float firstSlipBlur = 0.72f; [Header("Focus Attempt 2")] [Min(0f)] [SerializeField] private float secondAttemptStart = 1.72f; [Min(0.01f)] [SerializeField] private float secondFocusDuration = 0.55f; [Range(0f, 1f)] [SerializeField] private float secondNearFocusBlur = 0.28f; [Min(0f)] [SerializeField] private float secondHoldDuration = 0.18f; [Min(0.01f)] [SerializeField] private float secondSlipDuration = 0.46f; [Range(0f, 1f)] [SerializeField] private float unresolvedBlur = 0.62f; [Header("Downward Pan")] [Tooltip("Timeline time at which the starfield starts moving down toward the sea.")] [Min(0f)] [SerializeField] private float blurFadeStart = 4f; [Min(0.01f)] [SerializeField] private float blurFadeDuration = 3.2f; private MaterialPropertyBlock propertyBlock; private void Reset() { director = GetComponent(); starfieldRenderer = GetComponentInChildren(true); } private void Awake() { propertyBlock = new MaterialPropertyBlock(); ApplyAtTime(director != null ? (float)director.time : 0f); } private void OnEnable() { ApplyAtTime(director != null ? (float)director.time : 0f); } private void LateUpdate() { if (director == null || starfieldRenderer == null) { return; } ApplyAtTime((float)director.time); } private void ApplyAtTime(float time) { if (starfieldRenderer == null) { return; } propertyBlock ??= new MaterialPropertyBlock(); starfieldRenderer.GetPropertyBlock(propertyBlock); propertyBlock.SetFloat(BlurAmountId, EvaluateBlur(time)); propertyBlock.SetFloat(BlurSizeId, blurSize); starfieldRenderer.SetPropertyBlock(propertyBlock); } private float EvaluateBlur(float time) { float firstFocusEnd = firstAttemptStart + firstFocusDuration; float firstHoldEnd = firstFocusEnd + firstHoldDuration; float firstSlipEnd = firstHoldEnd + firstSlipDuration; float secondFocusEnd = secondAttemptStart + secondFocusDuration; float secondHoldEnd = secondFocusEnd + secondHoldDuration; float secondSlipEnd = secondHoldEnd + secondSlipDuration; if (time < firstAttemptStart) { return initialBlur; } if (time < firstFocusEnd) { return SmoothLerp(initialBlur, firstNearFocusBlur, InverseLerp(firstAttemptStart, firstFocusEnd, time)); } if (time < firstHoldEnd) { return firstNearFocusBlur; } if (time < firstSlipEnd) { return SmoothLerp(firstNearFocusBlur, firstSlipBlur, InverseLerp(firstHoldEnd, firstSlipEnd, time)); } if (time < secondAttemptStart) { return firstSlipBlur; } if (time < secondFocusEnd) { return SmoothLerp(firstSlipBlur, secondNearFocusBlur, InverseLerp(secondAttemptStart, secondFocusEnd, time)); } if (time < secondHoldEnd) { return secondNearFocusBlur; } if (time < secondSlipEnd) { return SmoothLerp(secondNearFocusBlur, unresolvedBlur, InverseLerp(secondHoldEnd, secondSlipEnd, time)); } if (time < blurFadeStart) { return unresolvedBlur; } return SmoothLerp(unresolvedBlur, 0f, InverseLerp(blurFadeStart, blurFadeStart + blurFadeDuration, time)); } private static float InverseLerp(float from, float to, float value) { return Mathf.Clamp01((value - from) / Mathf.Max(0.0001f, to - from)); } private static float SmoothLerp(float from, float to, float t) { t = Mathf.Clamp01(t); t = t * t * (3f - 2f * t); return Mathf.LerpUnclamped(from, to, t); } } }