Last active
May 31, 2018 18:18
-
-
Save deebrol/2067237647b2f228becf1f871864d526 to your computer and use it in GitHub Desktop.
Range with custom intervals – PropertyDrawer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace Helpers | |
{ | |
public static class FloatHelper | |
{ | |
/// <summary> | |
/// Convert the number to the nearest value of a given multiple. | |
/// Function from StackOverflow response <see href="https://stackoverflow.com/a/34444056">HERE</see> | |
/// </summary> | |
/// <param name="number">Number to convert</param> | |
/// <param name="multiple">Multiple for intervals</param> | |
/// <param name="min">Min number to avoid wrong numbers</param> | |
/// <param name="max">Max number to avoid wrong numbers</param> | |
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; | |
} | |
/// <summary> | |
/// Convert the number to the nearest value of a given multiple adjusting the return precision | |
/// </summary> | |
/// <param name="number">Number to convert</param> | |
/// <param name="min">Min number to avoid wrong numbers</param> | |
/// <param name="max">Max number to avoid wrong numbers</param> | |
/// <param name="multiple">Multiple for intervals</param> | |
/// <param name="floatPrecision">Number of decimals</param> | |
/// <param name="precisionMethod">Method to round the float</param> | |
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// DONT PUT IN EDITOR FOLDER | |
using System; | |
using UnityEngine; | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range with a interval change</para> | |
/// </summary> | |
[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; | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range.</para> | |
/// </summary> | |
/// <param name="min">The minimum allowed value.</param> | |
/// <param name="max">The maximum allowed value.</param> | |
/// <param name="interval">The interval between two successive values.</param> | |
/// <param name="floatPrecision">Number of decimals.</param> | |
/// <param name="precisionMethod">Choose between round or truncate.</param> | |
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; | |
} | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range.</para> | |
/// </summary> | |
/// <param name="minString">The minimum allowed value.</param> | |
/// <param name="max">The maximum allowed value.</param> | |
/// <param name="interval">The interval between two successive values.</param> | |
/// <param name="floatPrecision">Number of decimals.</param> | |
/// <param name="precisionMethod">Choose between round or truncate.</param> | |
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; | |
} | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range.</para> | |
/// </summary> | |
/// <param name="min">The minimum allowed value.</param> | |
/// <param name="maxString">The maximum allowed value.</param> | |
/// <param name="interval">The interval between two successive values.</param> | |
/// <param name="floatPrecision">Number of decimals.</param> | |
/// <param name="precisionMethod">Choose between round or truncate.</param> | |
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; | |
} | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range.</para> | |
/// </summary> | |
/// <param name="min">The minimum allowed value.</param> | |
/// <param name="max">The maximum allowed value.</param> | |
/// <param name="intervalString">The interval between two successive values.</param> | |
/// <param name="floatPrecision">Number of decimals.</param> | |
/// <param name="precisionMethod">Choose between round or truncate.</param> | |
public IntervalRangeAttribute(float min, float max, string intervalString, int floatPrecision = 2, PrecisionMethod precisionMethod = PrecisionMethod.Round) | |
: this (min, max, 0, floatPrecision, precisionMethod) | |
{ | |
this.intervalString = intervalString; | |
} | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range.</para> | |
/// </summary> | |
/// <param name="minString">The minimum allowed value.</param> | |
/// <param name="maxString">The maximum allowed value.</param> | |
/// <param name="interval">The interval between two successive values.</param> | |
/// <param name="floatPrecision">Number of decimals.</param> | |
/// <param name="precisionMethod">Choose between round or truncate.</param> | |
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; | |
} | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range.</para> | |
/// </summary> | |
/// <param name="min">The minimum allowed value.</param> | |
/// <param name="maxString">The maximum allowed value.</param> | |
/// <param name="intervalString">The interval between two successive values.</param> | |
/// <param name="floatPrecision">Number of decimals.</param> | |
/// <param name="precisionMethod">Choose between round or truncate.</param> | |
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; | |
} | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range.</para> | |
/// </summary> | |
/// <param name="minString">The minimum allowed value.</param> | |
/// <param name="max">The maximum allowed value.</param> | |
/// <param name="intervalString">The interval between two successive values.</param> | |
/// <param name="floatPrecision">Number of decimals.</param> | |
/// <param name="precisionMethod">Choose between round or truncate.</param> | |
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; | |
} | |
/// <summary> | |
/// <para>Attribute used to make a float or int variable in a script be restricted to a specific range.</para> | |
/// </summary> | |
/// <param name="minString">The minimum allowed value.</param> | |
/// <param name="maxString">The maximum allowed value.</param> | |
/// <param name="intervalString">The interval between two successive values.</param> | |
/// <param name="floatPrecision">Number of decimals.</param> | |
/// <param name="precisionMethod">Choose between round or truncate.</param> | |
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
/// <summary> | |
/// Assign to val the value of the field named with propertyName if is valid | |
/// </summary> | |
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."; | |
} | |
} | |
/// <summary> | |
/// Assign to val the value of the field named with propertyName if is valid | |
/// </summary> | |
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."; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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("<color=red>Note: Only for Editor changes.</color>"); | |
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Helpers | |
{ | |
public static class IntHelper | |
{ | |
/// <summary> | |
/// Convert the number to the nearest value of a given multiple. | |
/// Function from StackOverflow response <see href="https://stackoverflow.com/a/34444056">HERE</see> | |
/// </summary> | |
/// <param name="number">Number to convert</param> | |
/// <param name="multiple">Multiple</param> | |
/// <param name="min">Min number to avoid wrong numbers</param> | |
/// <param name="max">Max number to avoid wrong numbers</param> | |
public static int NearestRound(this int number, float multiple, float? min = null, float? max = null) | |
{ | |
return (int)((float) number).NearestRound(multiple, min, max); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public enum PrecisionMethod | |
{ | |
Round, | |
Truncate, | |
None | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment