using System; using UnityEngine; using UnityEngine.UI; using UnityEngine.Rendering; using UnityEngine.Playables; using UnityEngine.Timeline; namespace AibisDream.FixSystem { [RequireComponent(typeof(Slider))] public class MemorySlider : MonoBehaviour { [SerializeField] private float defaultValue = 0f; [SerializeField] private float sliderMaxValue = 6f; [SerializeField] private float lockThreshold = 0.06f; [SerializeField] private TimelineAsset openAsset; [SerializeField] private TimelineAsset closeAsset; private Slider slider; private PlayableDirector playableDirector; private Volume _volume; /// 视觉 Lock/Unlock(Timeline);true = 收起未开,false = 已打开。 private bool isLocked; public float TargetValue { get; set; } public bool IsFound { get; private set; } public event Action OnFound; public event Action OnLost; public void Init(Volume volume) { slider = GetComponent(); playableDirector = GetComponent(); _volume = volume; IsFound = false; slider.onValueChanged.RemoveAllListeners(); slider.onValueChanged.AddListener(HandleValueChanged); slider.maxValue = sliderMaxValue; slider.value = defaultValue; UpdateVolumeWeight(slider.value); isLocked = true; SetInteractable(false); } /// 仅控制是否可拖动,与 / 无关。 public void SetInteractable(bool interactable) => slider.interactable = interactable; /// /// 新一轮搜索前复位滑条与 IsFound(不播 Timeline、不改变视觉锁状态)。 /// 需先设置好 。 /// public void PrepareForSearch() { slider.value = defaultValue; HandleValueChanged(slider.value); } /// 仅播放打开 Timeline(若有),不碰 interactable。 public void UnlockSlider(Action onComplete = null) { if (!isLocked) { onComplete?.Invoke(); return; } PlayTimeline(openAsset, () => { isLocked = false; onComplete?.Invoke(); }); } /// 仅播放收起 Timeline(若有),不碰 interactable。 public void LockSlider(Action onComplete = null) { if (isLocked) { onComplete?.Invoke(); return; } slider.value = 0f; UpdateVolumeWeight(0f); PlayTimeline(closeAsset, () => { isLocked = true; onComplete?.Invoke(); }); } private void HandleValueChanged(float value) { float t = UpdateVolumeWeight(value); bool wasFound = IsFound; IsFound = t < lockThreshold; if (IsFound && !wasFound) OnFound?.Invoke(); else if (!IsFound && wasFound) OnLost?.Invoke(); } private float UpdateVolumeWeight(float value) { float distance = Mathf.Abs(value - TargetValue); float maxDistance = Mathf.Max( Mathf.Abs(slider.maxValue - TargetValue), Mathf.Abs(slider.minValue - TargetValue) ); float t = Mathf.Clamp01(distance / maxDistance); if (_volume != null) _volume.weight = Mathf.Lerp(0, 1, t); return t; } private void PlayTimeline(TimelineAsset asset, Action onComplete) { if (asset == null || playableDirector == null) { onComplete?.Invoke(); return; } playableDirector.Stop(); playableDirector.playableAsset = asset; playableDirector.time = 0; playableDirector.extrapolationMode = DirectorWrapMode.None; Action callback = null; callback = pd => { playableDirector.stopped -= callback; onComplete?.Invoke(); }; playableDirector.stopped += callback; playableDirector.Play(); } } }