Instantly share code, notes, and snippets.

# deebrol/FloatHelper.cs Last active May 31, 2018

Range with custom intervals – PropertyDrawer
 using System; namespace Helpers { public static class FloatHelper { /// /// Convert the number to the nearest value of a given multiple. /// Function from StackOverflow response HERE /// /// Number to convert /// Multiple for intervals /// Min number to avoid wrong numbers /// Max number to avoid wrong numbers public static float NearestRound(this float number, float multiple, float? min = null, float? max = null) { float val = number; if (multiple == 0) return val; if (multiple < 1) { float i = (float) Math.Floor(number); float x2 = i; while ((x2 += multiple) < number){} float x1 = x2 - multiple; val = (Math.Abs(number - x1) < Math.Abs(number - x2)) ? x1 : x2; } else { val = (float) Math.Round(number / multiple, MidpointRounding.AwayFromZero) * multiple; } if (min != null && val < min) val += multiple; else if (max != null && val > max) val -= multiple; return val; } /// /// Convert the number to the nearest value of a given multiple adjusting the return precision /// /// Number to convert /// Min number to avoid wrong numbers /// Max number to avoid wrong numbers /// Multiple for intervals /// Number of decimals /// Method to round the float public static float AdjustIntervalAndPrecision(this float number, float multiple, float? min = null, float? max = null, float floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) { float val = number.NearestRound(multiple, min, max); double mult = Math.Pow(10, floatPrecision); if (precisionMethod == PrecisionMethod.Round) val = (float) (Math.Round(mult * val) / mult); else if (precisionMethod == PrecisionMethod.Truncate) val = (float) (Math.Truncate(mult * val) / mult); return val; } } }
 // DONT PUT IN EDITOR FOLDER using System; using UnityEngine; /// /// Attribute used to make a float or int variable in a script be restricted to a specific range with a interval change /// [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)] public sealed class IntervalRangeAttribute : PropertyAttribute { public readonly float min; public readonly string minString; public readonly float max; public readonly string maxString; public readonly float interval; public readonly string intervalString; public readonly float floatPrecision; public readonly PrecisionMethod precisionMethod; /// /// Attribute used to make a float or int variable in a script be restricted to a specific range. /// /// The minimum allowed value. /// The maximum allowed value. /// The interval between two successive values. /// Number of decimals. /// Choose between round or truncate. public IntervalRangeAttribute(float min, float max, float interval = 0, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) { this.min = min; this.max = max; this.interval = interval; this.floatPrecision = floatPrecision; this.precisionMethod = precisionMethod; } /// /// Attribute used to make a float or int variable in a script be restricted to a specific range. /// /// The minimum allowed value. /// The maximum allowed value. /// The interval between two successive values. /// Number of decimals. /// Choose between round or truncate. public IntervalRangeAttribute(string minString, float max, float interval = 0, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) : this (0, max, interval, floatPrecision, precisionMethod) { this.minString = minString; } /// /// Attribute used to make a float or int variable in a script be restricted to a specific range. /// /// The minimum allowed value. /// The maximum allowed value. /// The interval between two successive values. /// Number of decimals. /// Choose between round or truncate. public IntervalRangeAttribute(float min, string maxString, float interval = 0, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) : this (min, 0, interval, floatPrecision, precisionMethod) { this.maxString = maxString; } /// /// Attribute used to make a float or int variable in a script be restricted to a specific range. /// /// The minimum allowed value. /// The maximum allowed value. /// The interval between two successive values. /// Number of decimals. /// Choose between round or truncate. public IntervalRangeAttribute(float min, float max, string intervalString, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) : this (min, max, 0, floatPrecision, precisionMethod) { this.intervalString = intervalString; } /// /// Attribute used to make a float or int variable in a script be restricted to a specific range. /// /// The minimum allowed value. /// The maximum allowed value. /// The interval between two successive values. /// Number of decimals. /// Choose between round or truncate. public IntervalRangeAttribute(string minString, string maxString, float interval = 0, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) : this (0, 0, interval, floatPrecision, precisionMethod) { this.minString = minString; this.maxString = maxString; } /// /// Attribute used to make a float or int variable in a script be restricted to a specific range. /// /// The minimum allowed value. /// The maximum allowed value. /// The interval between two successive values. /// Number of decimals. /// Choose between round or truncate. public IntervalRangeAttribute(float min, string maxString, string intervalString, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) : this (min, 0, 0, floatPrecision, precisionMethod) { this.maxString = maxString; this.intervalString = intervalString; } /// /// Attribute used to make a float or int variable in a script be restricted to a specific range. /// /// The minimum allowed value. /// The maximum allowed value. /// The interval between two successive values. /// Number of decimals. /// Choose between round or truncate. public IntervalRangeAttribute(string minString, float max, string intervalString, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) : this (0, max, 0, floatPrecision, precisionMethod) { this.minString = minString; this.intervalString = intervalString; } /// /// Attribute used to make a float or int variable in a script be restricted to a specific range. /// /// The minimum allowed value. /// The maximum allowed value. /// The interval between two successive values. /// Number of decimals. /// Choose between round or truncate. public IntervalRangeAttribute(string minString, string maxString, string intervalString, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) : this (0, 0, 0, floatPrecision, precisionMethod) { this.minString = minString; this.maxString = maxString; this.intervalString = intervalString; } }
 // PUT IN EDITOR FOLDER using UnityEditor; using UnityEngine; using Helpers; [CustomPropertyDrawer(typeof(IntervalRangeAttribute))] internal sealed class IntervalRangeDrawer : PropertyDrawer { private bool error; private string errorMessage; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { IntervalRangeAttribute attribute = (IntervalRangeAttribute) this.attribute; error = false; errorMessage = ""; // Check the Field type. Range only works with Float and Integer. if (property.propertyType == SerializedPropertyType.Float) { // We get the values passed to the attribute float minValue = attribute.min; float maxValue = attribute.max; float intervalValue = attribute.interval; // Or check and assign if the values are linked to other fields GetValue(ref minValue, property, attribute.minString); GetValue(ref maxValue, property, attribute.maxString); GetValue(ref intervalValue, property, attribute.intervalString); if (!error) { // Block to paint the slider label = EditorGUI.BeginProperty(position, label, property); EditorGUI.BeginChangeCheck(); float num = EditorGUI.Slider(position, label, property.floatValue, minValue, maxValue); // VERY IMPORTANT LINE: We need to adjust the output to our interval num = num.AdjustIntervalAndPrecision(intervalValue, minValue, maxValue, attribute.floatPrecision, attribute.precisionMethod); if (EditorGUI.EndChangeCheck()) property.floatValue = num; EditorGUI.EndProperty(); } } else if (property.propertyType == SerializedPropertyType.Integer) { // We get the values passed to the attribute int minValue = (int)attribute.min; int maxValue = (int)attribute.max; float intervalValue = attribute.interval; // Or check and assign if the values are linked to other fields GetValue(ref minValue, property, attribute.minString); GetValue(ref maxValue, property, attribute.maxString); GetValue(ref intervalValue, property, attribute.intervalString); if (!error) { // Block to paint the slider label = EditorGUI.BeginProperty(position, label, property); EditorGUI.BeginChangeCheck(); int num = EditorGUI.IntSlider(position, label, property.intValue, minValue, maxValue); // VERY IMPORTANT LINE: We need to adjust the output to our interval num = num.NearestRound(intervalValue, minValue, maxValue); if (EditorGUI.EndChangeCheck()) property.intValue = num; EditorGUI.EndProperty(); } } else { error = true; errorMessage = "Use Range with float or int."; } if (error) EditorGUI.LabelField(position, label.text, errorMessage); } /// /// Assign to val the value of the field named with propertyName if is valid /// private void GetValue(ref int val, SerializedProperty property, string propertyName) { if (propertyName == null) return; SerializedProperty p = property.serializedObject.FindProperty(propertyName); if (p != null) { switch (p.propertyType) { case SerializedPropertyType.Float: error = true; errorMessage = "Float property params not valid for int properties."; break; case SerializedPropertyType.Integer: val = p.intValue; break; default: error = true; errorMessage = "Use Range with float or int param values."; break; } } else { error = true; errorMessage = "Invalid param name or privated property."; } } /// /// Assign to val the value of the field named with propertyName if is valid /// private void GetValue(ref float val, SerializedProperty property, string propertyName) { if (propertyName == null) return; SerializedProperty p = property.serializedObject.FindProperty(propertyName); if (p != null) { switch (p.propertyType) { case SerializedPropertyType.Float: val = p.floatValue; break; case SerializedPropertyType.Integer: val = p.intValue; break; default: error = true; errorMessage = "Use Range with float or int param values."; break; } } else { error = true; errorMessage = "Invalid param name or privated property."; } } }
 using Helpers; using UnityEngine; public class IntervalRangeExample : MonoBehaviour { [Header("Unity Range")] [SerializeField] [Range(0, 10)] private float valueFloat0To10; [SerializeField] [Range(10, 0)] private float valueFloat10To0; [SerializeField] [Range(0, 0)] private float valueFloat0To0; [SerializeField] [Range(0, 10)] private int valueInt0To10; [SerializeField] [Range(0, 10)] private string valueString0To10; [Header("Custom Interval Range")] [SerializeField] [IntervalRangeAttribute(0, 10)] private float valueIntervalFloat0To10; [SerializeField] [IntervalRangeAttribute(10, 0)] private float valueIntervalFloat10To0; [SerializeField] [IntervalRangeAttribute(0, 0)] private float valueIntervalFloat0To0; [SerializeField] [IntervalRangeAttribute(0, 10, 3)] private float valueIntervalFloat0To10Interval3; [SerializeField] [IntervalRangeAttribute(0, 10, 0.5f)] private float valueIntervalFloat0To10IntervalHalf; [SerializeField] [IntervalRangeAttribute(10, 0, 1f/3f)] private float valueIntervalFloat10To0Interval1Third; [SerializeField] [IntervalRangeAttribute(10, 0, 1f/3f, precisionMethod:PrecisionMethod.Truncate)] private float valueIntervalFloat10To0Interval1ThirdTruncate; [SerializeField] [IntervalRangeAttribute(10, 0, 1f/3f, 10)] private float valueIntervalFloat10To0Interval1Third10Precision; [SerializeField] [IntervalRangeAttribute(0, 10, 0.1f)] private float valueIntervalFloat0To10Interval1Tenth; [SerializeField] [IntervalRangeAttribute(0, 10, 0.1f, 10)] private float valueIntervalFloat0To10Interval1Tenth10Precision; [SerializeField] [IntervalRangeAttribute(0, 10)] private int valueIntervalInt0To10; [SerializeField] [IntervalRangeAttribute(0, 10, 3)] private int valueIntervalInt0To10Interval3; [SerializeField] [IntervalRangeAttribute(0, 10, 0.5f)] private int valueIntervalInt0To10IntervalHalf; [SerializeField] [IntervalRangeAttribute(0, 10)] private string valueIntervalString0To10; [Header("Custom Interval Range With Variables References")] [SerializeField] private float min; [SerializeField] private int max; [SerializeField] private float interval; private string varForError; [SerializeField] [IntervalRangeAttribute("min", "max", "interval")] private float valueWithVariableReferences; [SerializeField] [IntervalRangeAttribute(5, "max", 3)] private int valueWithMaxVariableReferencesMin5Interval3; [SerializeField] [IntervalRangeAttribute("varForError", "max", "interval")] private float valueWithVariableReferencesErrorType; [SerializeField] [IntervalRangeAttribute(5, "min", "interval")] private int valueWithVariableReferencesErrorTypeForInt; private void Start() { Debug.Log("Note: Only for Editor changes."); Debug.Log("If we modify the values internally, they don't respect the interval property"); Debug.Log("Example ------>"); Debug.Log("What we expect:"); Debug.Log("5.9 , 5.899999 , 3"); Debug.Log("The real values:"); valueIntervalFloat0To10Interval1Tenth = 5.94f; valueIntervalFloat0To10Interval1Tenth10Precision = 5.94f; valueIntervalInt0To10Interval3 = 2; Debug.Log(valueIntervalFloat0To10Interval1Tenth + " , " + valueIntervalFloat0To10Interval1Tenth10Precision+ " , " + valueIntervalInt0To10Interval3); Debug.Log("You can use the extension methods to archive de same behaviour:"); valueIntervalFloat0To10Interval1Tenth = 5.94f.AdjustIntervalAndPrecision(0.1f); valueIntervalFloat0To10Interval1Tenth10Precision = 5.94f.AdjustIntervalAndPrecision(0.1f, floatPrecision:10); valueIntervalInt0To10Interval3 = 2.NearestRound(3); Debug.Log(valueIntervalFloat0To10Interval1Tenth + " , " + valueIntervalFloat0To10Interval1Tenth10Precision+ " , " + valueIntervalInt0To10Interval3); } }
 namespace Helpers { public static class IntHelper { /// /// Convert the number to the nearest value of a given multiple. /// Function from StackOverflow response HERE /// /// Number to convert /// Multiple /// Min number to avoid wrong numbers /// Max number to avoid wrong numbers public static int NearestRound(this int number, float multiple, float? min = null, float? max = null) { return (int)((float) number).NearestRound(multiple, min, max); } } }
 public enum PrecisionMethod { Round, Truncate, None }