149 lines
4.5 KiB
C#
149 lines
4.5 KiB
C#
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;
|
||
|
||
/// <summary>视觉 Lock/Unlock(Timeline);true = 收起未开,false = 已打开。</summary>
|
||
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<Slider>();
|
||
playableDirector = GetComponent<PlayableDirector>();
|
||
|
||
_volume = volume;
|
||
IsFound = false;
|
||
slider.onValueChanged.RemoveAllListeners();
|
||
slider.onValueChanged.AddListener(HandleValueChanged);
|
||
slider.maxValue = sliderMaxValue;
|
||
slider.value = defaultValue;
|
||
UpdateVolumeWeight(slider.value);
|
||
|
||
isLocked = true;
|
||
SetInteractable(false);
|
||
}
|
||
|
||
/// <summary>仅控制是否可拖动,与 <see cref="UnlockSlider"/> / <see cref="LockSlider"/> 无关。</summary>
|
||
public void SetInteractable(bool interactable) => slider.interactable = interactable;
|
||
|
||
/// <summary>
|
||
/// 新一轮搜索前复位滑条与 IsFound(不播 Timeline、不改变视觉锁状态)。
|
||
/// 需先设置好 <see cref="TargetValue"/>。
|
||
/// </summary>
|
||
public void PrepareForSearch()
|
||
{
|
||
slider.value = defaultValue;
|
||
HandleValueChanged(slider.value);
|
||
}
|
||
|
||
/// <summary>仅播放打开 Timeline(若有),不碰 interactable。</summary>
|
||
public void UnlockSlider(Action onComplete = null)
|
||
{
|
||
if (!isLocked)
|
||
{
|
||
onComplete?.Invoke();
|
||
return;
|
||
}
|
||
|
||
PlayTimeline(openAsset, () =>
|
||
{
|
||
isLocked = false;
|
||
onComplete?.Invoke();
|
||
});
|
||
}
|
||
|
||
/// <summary>仅播放收起 Timeline(若有),不碰 interactable。</summary>
|
||
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<PlayableDirector> callback = null;
|
||
callback = pd =>
|
||
{
|
||
playableDirector.stopped -= callback;
|
||
onComplete?.Invoke();
|
||
};
|
||
playableDirector.stopped += callback;
|
||
playableDirector.Play();
|
||
}
|
||
}
|
||
}
|