1+ // Must be placed within a folder named "Editor"
2+ using System ;
3+ using System . Globalization ;
4+ using System . Text ;
5+ using UnityEditor ;
6+ using UnityEngine ;
7+
8+ /// <summary> Use it to make large numbers appear more readable without use of exponents.
9+ /// <para>Supported types: int, long, float, double</para></summary>
10+ [ CustomPropertyDrawer ( typeof ( BigNumberAttribute ) ) ]
11+ public class BigNumberDrawer : PropertyDrawer
12+ {
13+ //Use those to tweak for your taste.
14+ #region Options
15+
16+ /// <summary> Symbol used to separate groups of digits inside a number. </summary>
17+ public const string NumberSeparator = " " ; // Can be anything except '.', since period is used as decimal separator in Unity.
18+
19+ /// <summary> If number is equals or bigger than this value, an abbreviated version of this number would appear in label. </summary>
20+ public const int ShowAbbreviationMin = 10_000 ;
21+
22+ /// <summary> Amount of digits shown after '.' in abbreviated values. </summary>
23+ public const int DecimalPlacesInAbbreviated = 0 ;
24+
25+ #endregion
26+
27+ private static readonly NumberFormatInfo SeparatorFormat = new NumberFormatInfo { NumberGroupSeparator = NumberSeparator , NumberDecimalDigits = 0 } ;
28+
29+ public override void OnGUI ( Rect position , SerializedProperty property , GUIContent label )
30+ {
31+ string toText = string . Empty ;
32+ double number = 0 ;
33+
34+ switch ( property . type )
35+ {
36+ case "int" :
37+ number = property . intValue ;
38+
39+ break ;
40+ case "long" :
41+ number = property . longValue ;
42+
43+ break ;
44+ case "float" :
45+ number = property . floatValue ;
46+
47+ break ;
48+ case "double" :
49+ number = property . doubleValue ;
50+ break ;
51+ }
52+
53+ toText = AddSeparators ( number ) ;
54+
55+ if ( Math . Abs ( number ) >= ShowAbbreviationMin ) label . text = $ "{ label . text } ({ Abbreviate ( number , DecimalPlacesInAbbreviated ) } )";
56+
57+
58+ EditorGUI . BeginProperty ( position , label , property ) ;
59+ toText = EditorGUI . TextField ( position , label , toText ) ;
60+ EditorGUI . EndProperty ( ) ;
61+
62+
63+ string parsable = toText . Replace ( NumberSeparator , string . Empty ) . Replace ( " " , string . Empty ) ;
64+ try
65+ {
66+ double value = double . Parse ( parsable , CultureInfo . InvariantCulture ) ;
67+
68+ switch ( property . type )
69+ {
70+ case "int" :
71+ value = Math . Clamp ( value , int . MinValue , int . MaxValue ) ;
72+ property . intValue = ( int ) value ;
73+
74+ break ;
75+ case "long" :
76+ value = Math . Clamp ( value , long . MinValue , long . MaxValue ) ;
77+ property . longValue = ( long ) value ;
78+
79+ break ;
80+ case "float" :
81+ value = Math . Clamp ( value , float . MinValue , float . MaxValue ) ;
82+ property . floatValue = ( float ) value ;
83+
84+ break ;
85+ case "double" :
86+ property . doubleValue = value ;
87+
88+ break ;
89+ }
90+ property . serializedObject . ApplyModifiedProperties ( ) ;
91+ }
92+ catch ( System . Exception )
93+ {
94+ //Any formatting errors will be ignored, Unity will automatically revert to last correct input.
95+ }
96+ }
97+
98+ private string AddSeparators ( double value )
99+ {
100+ StringBuilder sb = new StringBuilder ( ) ;
101+
102+ sb . Append ( value . ToString ( "N" , SeparatorFormat ) ) ;
103+
104+ if ( ( decimal ) value % 1 != 0 ) //Adding separators for the decimal part.
105+ {
106+ string decimals = ( ( decimal ) value % 1 ) . ToString ( ) . Remove ( 0 , 2 ) ;
107+ sb . Append ( '.' ) ;
108+
109+ for ( int i = 0 ; i < decimals . Length ; i ++ )
110+ {
111+ if ( i != 0 && i % 3 == 0 ) sb . Append ( NumberSeparator ) ;
112+
113+ sb . Append ( decimals [ i ] ) ;
114+ }
115+ }
116+ return sb . ToString ( ) ;
117+ }
118+
119+ /// <summary> Abbreviates a number into more pleasant to read (123 456 789 123 -> 123.45B) </summary>
120+ /// <param name="decimalPlaces"> Amount of digits after point. </param>
121+ public static string Abbreviate ( double number , int decimalPlaces = 2 )
122+ {
123+ bool negative = number < 0 ;
124+ number = Math . Abs ( number ) ;
125+
126+ string numberString = number . ToString ( ) ;
127+
128+ foreach ( NumberSuffix suffix in Enum . GetValues ( typeof ( NumberSuffix ) ) )
129+ {
130+ // Assign the amount of digits to base 10.
131+ double currentDigitValue = 1 * Math . Pow ( 10 , ( int ) suffix * 3 ) ;
132+
133+ string suffixText = Enum . GetName ( typeof ( NumberSuffix ) , ( int ) suffix ) ;
134+ if ( suffix == 0 ) { suffixText = string . Empty ; }
135+
136+ // Set the return value to a rounded value with the suffix.
137+ if ( number >= currentDigitValue )
138+ {
139+ numberString = $ "{ Math . Round ( number / currentDigitValue , decimalPlaces , MidpointRounding . ToEven ) } { suffixText } ";
140+ }
141+ }
142+ if ( negative ) numberString = numberString . Insert ( 0 , "-" ) ;
143+
144+ return numberString ;
145+ }
146+
147+ /// <summary> Suffixes for numbers based on how many digits they have left of the decimal point. </summary>
148+ /// <remarks> Must be ordered from small to large. </remarks>
149+ private enum NumberSuffix
150+ {
151+ /// <summary> Thousand = 1_000 </summary>
152+ K = 1 ,
153+ /// <summary> Million = 1_000_000 </summary>
154+ M = 2 ,
155+ /// <summary> Billion = 1_000_000_000 </summary>
156+ B = 3 ,
157+ /// <summary> Trillion = 1_000_000_000_000 </summary>
158+ T = 4 ,
159+ /// <summary> Quadrillion = 1_000_000_000_000_000</summary>
160+ Q = 5
161+ }
162+ }
0 commit comments