using System; using UnityEngine; using UnityEngine.UI; /// /// Reshapes a UI 's response with an so the value runs /// non-linearly between the slider's authored minValue/maxValue. The curve maps the handle /// position (x, 0..1 - linear in screen space) to a value fraction (y, 0..1) that is then scaled to the /// slider's range. The slider's own min/max stays the single source of truth for the range; this only /// bends the mapping inside it - so dropping the component onto an existing slider needs no range re-entry. /// /// is the curved output, positions the handle to show a given /// output (inverting the curve), and raises the curved value whenever the /// handle moves - so the component is usable both standalone (wire ) and from /// code that drives the raw slider (read ). /// /// The curve should be monotonic increasing (the default linear is); the inverse used by /// assumes that. /// [RequireComponent(typeof(Slider))] [DisallowMultipleComponent] public class SliderCurve : MonoBehaviour { [Tooltip("Maps the handle position (x, 0..1) to a value fraction (y, 0..1), scaled to the slider's " + "minValue/maxValue. Default linear = no change. Should be monotonic increasing.")] public AnimationCurve response = AnimationCurve.Linear(0f, 0f, 1f, 1f); /// Raised with the curved output whenever the handle moves. public event Action ValueChanged; private Slider _slider; private Slider Slider => _slider ? _slider : (_slider = GetComponent()); /// The curved output value for the current handle position. public float Value => Slider ? Curve(Slider.normalizedValue) : 0f; private void OnEnable() { if (Slider) Slider.onValueChanged.AddListener(OnSliderChanged); } private void OnDisable() { if (_slider) _slider.onValueChanged.RemoveListener(OnSliderChanged); } private void OnSliderChanged(float _) => ValueChanged?.Invoke(Value); /// Positions the handle so reads , raising the /// slider's onValueChanged (and thus ). public void SetOutput(float output) { if (Slider) Slider.normalizedValue = Position(output); // normalizedValue setter notifies } /// As but without raising any change event (for silent seeding). public void SetOutputWithoutNotify(float output) { if (Slider) Slider.SetValueWithoutNotify(Mathf.Lerp(Slider.minValue, Slider.maxValue, Position(output))); } // handle position (0..1) -> curved output value in [minValue, maxValue] private float Curve(float t) { float y = Mathf.Clamp01(response.Evaluate(Mathf.Clamp01(t))); float v = Mathf.Lerp(Slider.minValue, Slider.maxValue, y); return Slider && Slider.wholeNumbers ? Mathf.Round(v) : v; } // curved output value -> handle position (0..1), inverting the (monotonic) curve via bisection private float Position(float output) { var s = Slider; if (!s) return 0f; float target = Mathf.Clamp01(Mathf.InverseLerp(s.minValue, s.maxValue, output)); // desired y float lo = 0f, hi = 1f; for (int i = 0; i < 24; i++) { float mid = 0.5f * (lo + hi); if (Mathf.Clamp01(response.Evaluate(mid)) < target) lo = mid; else hi = mid; } return 0.5f * (lo + hi); } }