Skip to content

Instantly share code, notes, and snippets.

@khadzhynov
Created August 31, 2025 12:00
Show Gist options
  • Select an option

  • Save khadzhynov/455779a8b2d1fb714140d8c68b922f98 to your computer and use it in GitHub Desktop.

Select an option

Save khadzhynov/455779a8b2d1fb714140d8c68b922f98 to your computer and use it in GitHub Desktop.
RandomRange - inspector helper
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace GG.Utils
{
/// <summary>
/// A small helper to define a range of random values (int or float) in the inspector
/// Property drawer included
/// https://gist.github.com/khadzhynov
/// </summary>
/// <typeparam name="T">Int or Float</typeparam>
public abstract class RandomRange<T> where T : struct
{
protected abstract T GetMin();
protected abstract T GetMax();
private T? _cached;
/// <summary>
/// Each access will generate a new random value within the specified range.
/// </summary>
public T New => Random(GetMin(), GetMax());
/// <summary>
/// Will generate and cache a new value on first access.
/// Subsequent accesses will return the cached value.
/// </summary>
public T Cached => _cached ??= Random(GetMin(), GetMax());
public void ResetCache() => _cached = null;
private static T Random(T min, T max)
{
if (typeof(T) == typeof(int))
return (T)(object)UnityEngine.Random.Range((int)(object)min, (int)(object)max);
if (typeof(T) == typeof(float))
return (T)(object)UnityEngine.Random.Range((float)(object)min, (float)(object)max);
throw new NotSupportedException();
}
}
[Serializable]
public class RandomRangeFloat : RandomRange<float>
{
[SerializeField] private float _min, _max;
protected override float GetMin() => _min;
protected override float GetMax() => _max;
}
[Serializable]
public class RandomRangeInt : RandomRange<int>
{
[SerializeField] private int _min, _max;
protected override int GetMin() => _min;
protected override int GetMax() => _max;
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(RandomRangeFloat))]
[CustomPropertyDrawer(typeof(RandomRangeInt))]
public sealed class RandomRangeFloatDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
=> EditorGUIUtility.singleLineHeight;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
var labelRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth, position.height);
var fieldsRect = new Rect(position.x + EditorGUIUtility.labelWidth, position.y,
position.width - EditorGUIUtility.labelWidth, position.height);
EditorGUI.LabelField(labelRect, label);
var minProp = property.FindPropertyRelative("_min");
var maxProp = property.FindPropertyRelative("_max");
if (minProp == null || maxProp == null)
{
EditorGUI.LabelField(fieldsRect, "Expected _min and _max");
EditorGUI.EndProperty();
return;
}
const float spacing = 4f;
float half = (fieldsRect.width - spacing) * 0.5f;
var minRect = new Rect(fieldsRect.x, fieldsRect.y, half, fieldsRect.height);
var maxRect = new Rect(fieldsRect.x + half + spacing, fieldsRect.y, half, fieldsRect.height);
float oldLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 30f;
EditorGUI.BeginChangeCheck();
if (minProp.propertyType == SerializedPropertyType.Integer)
{
SetIntProperty(minRect, minProp, maxRect, maxProp);
}
else if (minProp.propertyType == SerializedPropertyType.Float)
{
SetFloatProperty(minRect, minProp, maxRect, maxProp);
}
else
{
EditorGUI.EndChangeCheck();
EditorGUI.LabelField(fieldsRect, "Expected int or float.");
}
EditorGUIUtility.labelWidth = oldLabelWidth;
EditorGUI.EndProperty();
}
private static void SetFloatProperty(Rect minRect, SerializedProperty minProp, Rect maxRect, SerializedProperty maxProp)
{
float min = EditorGUI.FloatField(minRect, "Min", minProp.floatValue);
float max = EditorGUI.FloatField(maxRect, "Max", maxProp.floatValue);
if (EditorGUI.EndChangeCheck())
{
if (min > max) (min, max) = (max, min);
minProp.floatValue = min;
maxProp.floatValue = max;
}
}
private static void SetIntProperty(Rect minRect, SerializedProperty minProp, Rect maxRect, SerializedProperty maxProp)
{
int min = EditorGUI.IntField(minRect, "Min", minProp.intValue);
int max = EditorGUI.IntField(maxRect, "Max", maxProp.intValue);
if (EditorGUI.EndChangeCheck())
{
if (min > max) (min, max) = (max, min);
minProp.intValue = min;
maxProp.intValue = max;
}
}
}
#endif
}
using UnityEngine;
namespace GG.Utils
{
public class RandomRangeDemo : MonoBehaviour
{
[SerializeField] private RandomRangeInt _testInt;
[SerializeField] private RandomRangeFloat _testFloat;
private void Start()
{
Debug.Log(
$"Test <_testInt>; calling [New] 4 times: {_testInt.New}, {_testInt.New}, {_testInt.New}, {_testInt.New}; " +
$"\ncalling [Cached] 4 times: {_testInt.Cached}, {_testInt.Cached}, {_testInt.Cached}, {_testInt.Cached}");
_testInt.ResetCache();
Debug.Log($"Cache reset - call [Cached] again 4 more times: {_testInt.Cached}, {_testInt.Cached}, {_testInt.Cached}, {_testInt.Cached}");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment