Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

FancyLabel - Multicolor and Multifont label

Discussion in 'Immediate Mode GUI (IMGUI)' started by Dafu, Mar 16, 2008.

Thread Status:
Not open for further replies.
  1. Dafu

    Dafu

    Joined:
    Aug 22, 2006
    Posts:
    124
    Hey guys,

    I needed a multi-color, multi-font label for my game Dungeon Sketch (http://forum.unity3d.com/viewtopic.php?t=9808). So I threw together a custom control this morning to do just that. I know this feature has been asked for by some people (me being one of them), so I thought I'd share it with you guys. Let me know if it works for you.

    With this control you can draw a multi-line label that:
    - Can use multiple colors to highlight words.
    - Can use multiple fonts to accent words (ex. normal, bold, italic)
    - Can align the resulting multi-line fancy text to left, right or center.

    To use the function you simply mark up your text strings with the proper escape sequences, which are:

    #aabbccdd - Change current color to the one specified by the hex string red green blue alpha (much like HTML color codes, but also with an alpha channel)
    #! - Revert back to the original color that was used before this function call
    #n - normal font
    #x - bold font
    #i - italic font

    And then call FancyLabel() in OnGUI() passing the rect to draw the string in, the text, the fonts, and the text alignment.

    As for how it works. The function parses the marked up text string and breaks it up into separate labels every time it encounters a color or font change. GUILayout is used to line labels up nicely. There is more to it (its a lengthy function as you can see), but thats the jist of it.

    See below for an example.

    C# code:
    Code (csharp):
    1.  
    2. int HexStringToInt( string hexString ) {
    3.     int value = 0;
    4.     int digitValue = 1;
    5.     hexString = hexString.ToUpper();
    6.     char[] hexDigits = hexString.ToCharArray(0, hexString.Length);
    7.    
    8.     for ( int i = hexString.Length - 1; i >= 0; i-- ) {
    9.         int digit = 0;
    10.         if ( hexDigits[i] >= '0'  hexDigits[i] <= '9' ) {
    11.             digit = hexDigits[i] - '0';
    12.         } else if ( hexDigits[i] >= 'A'  hexDigits[i] <= 'F' ) {
    13.             digit = hexDigits[i] - 'A' + 10;               
    14.         } else {
    15.             // Not a hex string
    16.             return -1;
    17.         }
    18.  
    19.         value += digit * digitValue;
    20.         digitValue *= 16;
    21.     }
    22.    
    23.     return value;
    24. }
    25.  
    26. // Recognized color string sequences:
    27. // #aabbccdd - Change current color to the one specified by the hex string
    28. //             red green blue alpha
    29. // #!        - Revert back to the original color that was used before this function call
    30. // #n        - normal font
    31. // #x        - bold font
    32. // #i        - italic font
    33. public void FancyLabel( Rect rect, string text,
    34.                         Font normalFont, Font boldFont, Font italicFont,
    35.                         TextAlignment alignment ) {
    36.     int     i1 = 0, i2 = 0;
    37.     bool    done = false;
    38.     bool    newLine = false;
    39.     Color   originalColor = GUI.contentColor;
    40.     Color   textColor = new Color( originalColor.r,
    41.                                    originalColor.g,
    42.                                    originalColor.b,
    43.                                    originalColor.a );
    44.    
    45.     bool    leftSpace = false, rightSpace = false, topSpace = false, bottomSpace = false;
    46.  
    47.     Font    defaultFont = GUI.skin.font;
    48.     Font    newFont = null;
    49.            
    50.     GUIStyle fontStyle = new GUIStyle(testStyle);
    51.    
    52.     // Start with normal font
    53.     if ( normalFont != null ) {
    54.         fontStyle.font = normalFont;
    55.     } else {
    56.         fontStyle.font = defaultFont;          
    57.     }
    58.  
    59.     // NOTE: Lowering this padding reduces the line spacing
    60.     // May need to adjust per font
    61.     fontStyle.padding.bottom = -5;
    62.    
    63.     GUILayout.BeginArea( rect );
    64.     GUILayout.BeginVertical( GUILayout.ExpandHeight( true ),
    65.                              GUILayout.Width( rect.height ),
    66.                              GUILayout.MinWidth( rect.height ) );
    67.     GUILayout.BeginHorizontal( GUILayout.ExpandWidth( true ),
    68.                                GUILayout.Width( rect.width ),
    69.                                GUILayout.MinWidth( rect.width ) );
    70.     // Insert flexible space on the left if Center or Right aligned
    71.     if ( alignment == TextAlignment.Right || alignment == TextAlignment.Center) {
    72.          GUILayout.FlexibleSpace();
    73.     }
    74.            
    75.     while ( !done ) {
    76.  
    77.         int skipChars = 0;
    78.         int firstEscape, firstDoubleEscape, firstNewline;
    79.        
    80.         firstEscape = text.IndexOf("#", i2);
    81.         firstNewline = text.IndexOf("\n", i2);
    82.  
    83.         if ( firstEscape != -1  ( firstNewline == -1 || firstEscape < firstNewline ) ) {
    84.             i1 = firstEscape;
    85.         } else {
    86.             i1 = firstNewline;
    87.         }
    88.        
    89.         // We're at the end, set the index to the end of the
    90.         // string and signal an end
    91.         if ( i1 == -1 ) {
    92.             i1 = text.Length - 1;
    93.             done = true;
    94.         }
    95.  
    96.         fontStyle.normal.textColor = textColor;
    97.         if ( newFont != null ) {
    98.             fontStyle.font = newFont;
    99.             newFont = null;
    100.         }
    101.        
    102.         // If the next character is # then we have a ## sequence
    103.         // We want to point one of the # so advance the index by
    104.         // one to include the first #
    105.         if ( !done ) {
    106.             if ( text.Substring( i1, 1 ) == "#" ) {
    107.                 if ( (text.Length - i1) >= 2  text.Substring(i1 + 1, 1) == "#" ) {
    108.                     skipChars = 2;
    109.                 }
    110.                 // Revert to original color sequence
    111.                 else if (  (text.Length - i1) >= 2  text.Substring(i1 + 1, 1) == "!" ) {
    112.                     textColor = new Color( originalColor.r,
    113.                                            originalColor.g,
    114.                                            originalColor.b,
    115.                                            originalColor.a );
    116.                     i1--;
    117.                     skipChars = 3;
    118.                 }
    119.                 // Set normal font
    120.                 else if (  (text.Length - i1) >= 2  text.Substring(i1 + 1, 1) == "n" ) {
    121.                     if ( normalFont != null ) {
    122.                         newFont = normalFont;
    123.                     } else {
    124.                         newFont = defaultFont;
    125.                     }
    126.                     i1--;
    127.                     skipChars = 3;
    128.                 }
    129.                 // Set bold font
    130.                 else if (  (text.Length - i1) >= 2  text.Substring(i1 + 1, 1) == "x" ) {
    131.                     if ( boldFont != null ) {
    132.                         newFont = boldFont;
    133.                     } else {
    134.                         newFont = defaultFont;
    135.                     }
    136.                     i1--;
    137.                     skipChars = 3;
    138.                 }
    139.                 // Set italic font
    140.                 else if (  (text.Length - i1) >= 2  text.Substring(i1 + 1, 1) == "i" ) {
    141.                     if ( italicFont != null ) {
    142.                         newFont = italicFont;
    143.                     } else {
    144.                         newFont = defaultFont;
    145.                     }
    146.                     i1--;
    147.                     skipChars = 3;
    148.                 }
    149.                 //  New color sequence
    150.                 else if ( (text.Length - i1) >= 10 ) {
    151.                     string rText = text.Substring( i1 + 1, 2 );
    152.                     string gText = text.Substring( i1 + 3, 2 );
    153.                     string bText = text.Substring( i1 + 5, 2 );
    154.                     string aText = text.Substring( i1 + 7, 2 );
    155.            
    156.                     float r = HexStringToInt( rText ) / 255.0f;
    157.                     float g = HexStringToInt( gText ) / 255.0f;
    158.                     float b = HexStringToInt( bText ) / 255.0f;
    159.                     float a = HexStringToInt( aText ) / 255.0f;
    160.                    
    161.                     if ( r < 0 || g < 0 || b < 0 || a < 0 ) {
    162.                         Debug.Log("Invalid color sequence");
    163.                         return;
    164.                     }
    165.                    
    166.                     textColor = new Color( r, g, b, a );
    167.                     skipChars = 10;
    168.                     // Move back one character so that we don't print the #
    169.                     i1--;
    170.                 } else {
    171.                     Debug.Log("Invalid # escape sequence");
    172.                     return;
    173.                 }
    174.             } else if ( (text.Length - i1) >= 1  text.Substring( i1, 1 ) == "\n" ) {
    175.                 newLine = true;
    176.                 i1--;
    177.                 skipChars = 2;
    178.             } else {
    179.                 Debug.Log("Invalid escape sequence");
    180.                 return;
    181.             }
    182.         }
    183.        
    184.         string textPiece = text.Substring( i2, i1 - i2 + 1 );          
    185.         GUILayout.Label( textPiece, fontStyle );
    186.        
    187.         // Unity seems to cut off the trailing spaces in the label, he have
    188.         // to add them manually here
    189.         // Figure out how many trailing spaces there are
    190.         int spaces = textPiece.Length - textPiece.TrimEnd(' ').Length;
    191.        
    192.         // NOTE: Add the proper amount of gap for trailing spaces.
    193.         // the length of space is a questimate here,
    194.         // may need to be adjusted for different fonts
    195.         GUILayout.Space( spaces * 5.0f );
    196.        
    197.         if ( newLine ) {
    198.             // Create a new line by ending the horizontal layout
    199.             if ( alignment == TextAlignment.Left || alignment == TextAlignment.Center) {
    200.                 GUILayout.FlexibleSpace();
    201.             }
    202.             GUILayout.EndHorizontal();
    203.             GUILayout.BeginHorizontal( GUILayout.ExpandWidth( true ),
    204.                                        GUILayout.Width( rect.width ),
    205.                                        GUILayout.MinWidth( rect.width ) );         
    206.             if ( alignment == TextAlignment.Right || alignment == TextAlignment.Center) {
    207.                 GUILayout.FlexibleSpace();
    208.             }
    209.             newLine = false;
    210.         }
    211.        
    212.         // Store the last index
    213.         i2 = i1 + skipChars;
    214.     }
    215.     if ( alignment == TextAlignment.Left || alignment == TextAlignment.Center) {
    216.         GUILayout.FlexibleSpace();
    217.     }
    218.     GUILayout.EndHorizontal();
    219.     GUILayout.FlexibleSpace();
    220.     GUILayout.EndVertical();
    221.     GUILayout.EndArea();       
    222. }
    223.  
    To accomplish the text seen in this screenshot from Dungeon Sketch I simply used the FancyLabel like this:

    Code (csharp):
    1.  
    2.         string text = "";
    3.         text += "#FFDDDDFF"; // Light red
    4.         text += "Fireball";
    5.         text += "#!"; // revert to default color
    6.         text += "\n\nHurls a ball of fire that ";
    7.         text += "#x"; // bold font
    8.         text += "explodes";
    9.         text += "#n"; // normal font
    10.         text += " on contact\n";
    11.         text +="and damages all nearby enemies.\n\n";
    12.         text += "#FF6666FF"; // red
    13.         text += "#x"; // bold font
    14.         text += "8";
    15.         text += "#!"; // revert to default color
    16.         text += "#n"; // normal font
    17.         text += " to ";
    18.         text += "#FF6666FF"; // red
    19.         text += "#x"; // bold font
    20.         text += "12";
    21.         text += "#n"; // normal font
    22.         text += "#!"; // revert to default color
    23.         text += "#i"; // italic font
    24.         text += " fire";
    25.         text += "#n"; // normal font
    26.         text += " damage";
    27.  
    28.         Globals.gGUIGlobal().FancyLabel( labelRect,
    29.                                          text,
    30.                                          Globals.gGUIGlobal().defaultFont,                                       Globals.gGUIGlobal().defaultBoldFont,
    31.                                          Globals.gGUIGlobal().defaultItalicFont,
    32.                                          TextAlignment.Center);
    33.  
    34.  
    (I spaced the string out like this so I could comment on what is happening, in practice this whole string can be easily written in one or two lines of code)
     

    Attached Files:

  2. benblo

    benblo

    Joined:
    Aug 14, 2007
    Posts:
    476
    Very very cool!
    I made a very crude HTML parser the other day to make just that, but didn't go nearly as far (didn't do italic and bold for example).
    Maybe we could merge the 2 solutions so you could input pseudo-HTML instead of your #stuff... someday.
     
  3. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Wow, that's really cool! And it also shows how powerful UnityGUI 2.0 is!!! :)
     
  4. Luke-Houlihan

    Luke-Houlihan

    Joined:
    Jun 26, 2007
    Posts:
    303
    I get 2 errors when your script compiles,
    A namespace can only contain types and namespace declarations
    on lines 1 and 32.

    Other than that it looks amazing! This is something I have want for a long time but dont have the coding ability yet. Thank you so much Dafu!
    -Luke
     
  5. Dafu

    Dafu

    Joined:
    Aug 22, 2006
    Posts:
    124
    Luke,

    You'll have to put the two functions inside some class. They can't be sitting alone in the source code file.

    I'll look into throwing together a stand alone example project that uses the FancyLabel.
     
  6. AngryAnt

    AngryAnt

    Keyboard Operator

    Joined:
    Oct 25, 2005
    Posts:
    3,045
    Nice. You should toss this at the Unify Wiki - its a nice place to have stuff like this.
     
  7. Luke-Houlihan

    Luke-Houlihan

    Joined:
    Jun 26, 2007
    Posts:
    303
    Awsome! Got it to work, Im a javascripter myself so c# stuff is just close enough that I can understand whats happening, but just dissimilar enough that I miss much. Is there an easy way to find the aabbccdd of a given color?
    Awsome Script
    -Luke
     
  8. Dafu

    Dafu

    Joined:
    Aug 22, 2006
    Posts:
    124
    Luke,

    Yes there is an easy way to find the code.

    The code is in the hex RGBA format.

    If you use Photoshop the color picker will give you the first 6 hex digits, I think it's refered to as HTML color or such.

    You're probably used to creating colors in Unity, so here is how you could translate between the two by hand:

    Color myColor = new Color( 0.8f, 0.05f, 0.3f, 1.0f );

    This color is:
    0.8f Red (R)
    0.05f Green (G)
    0.3f Blue (B)
    1.0f Alpha (A)

    These values are in the range of 0.0f to 1.0f, the hex values in the hex color string that FancyLabel uses are in range 0 to 255 decimal, or 0 to FF hex. So to translate that color into a hex string do some simple math:

    First figure out the decimal values in 0 to 255 range
    0.8f * 255 = 204
    0.05f * 255 = 12.75
    0.3f * 255 = 76.5
    1.0f * 255 = 255

    Then convert them to hex (any calculator can do this for you)
    204 -> CC
    12.75 -> 0C (ignore fractions, and pad with a 0 if its a single digit)
    76.5 -> 4C
    255 -> FF

    Put them together and you have:
    #CC0C4CFF

    That's a rather long-winded explanation:)
     
  9. attilam

    attilam

    Joined:
    Jun 2, 2007
    Posts:
    86
    Awesome Dafu, just what I was looking for; thanks for sharing!
     
  10. monark

    monark

    Joined:
    May 2, 2008
    Posts:
    1,598
    This is so cool. Did you get any further with this adding new features or anything.
    Good work though, thank you.
     
  11. monark

    monark

    Joined:
    May 2, 2008
    Posts:
    1,598
    Any ideas how to add word wrapping to this? I've turned it on for the style used but then if say for example a single bold word follows a word wrapped normal font line the bolded word gets placed next to the normal line on the first line of text and not after the end of the word wrapped lines, thus making the text unreadable.
     

    Attached Files:

  12. the_gnoblin

    the_gnoblin

    Joined:
    Jan 10, 2009
    Posts:
    722
    Dafu, thanks for sharing!
    Very useful).

    When I try using your code with Arial 16t font the word 'explodes' gets cut down to 'explode'.

    Any idea why this might happen? :eek:
     
  13. grimmy

    grimmy

    Joined:
    Feb 2, 2009
    Posts:
    409
    Anytips for us Javascripters? I tried created a separate c# file and pasting in the code but no matter how many congigurations I tried I just kept getting errors. More to do with my terrible knowledge of C# than anything else though.
     
  14. minevr

    minevr

    Joined:
    Mar 4, 2008
    Posts:
    1,018
    Great!!

    Get it,Thanks. :p
     
  15. wisly

    wisly

    Joined:
    Jan 18, 2010
    Posts:
    12
    I have the same problem.
    maybe the size is of label is not suitable.

     
  16. onedong

    onedong

    Joined:
    Aug 27, 2009
    Posts:
    32
    was there any update on this for being put into javascript?
     
  17. nandre

    nandre

    Joined:
    Aug 26, 2010
    Posts:
    2
    @onedong/Grimmy
    You can call this function from JavaScript if you put the code in the standard assets folder. The reason for this is because scripts in the standard assets folder get compiled before scripts in folders created by you. Just instantiate the object you put the functions in, and call the function with the object in your OnGUI function.

    @wisly/gnoblin
    The reason it gets cut down is because the size of the Rect you passed is not big enough.

    @monark
    I also needed a version of this with word wrapping as part of the research project I'm working on (don't ask), so I went ahead and wrote one:

    Code (csharp):
    1.  
    2.  
    3. //**************************************************************************
    4. //
    5. // Project Name: FancyLabel2
    6. //
    7. // Purpose: To allow Unity users to create GUI elements that can change
    8. //          style partway through. Modified from another script to
    9. //          include word wrap.
    10. //
    11. // URL of original program: [url]http://forum.unity3d.com/viewtopic.php?t=10175&start=0&postdays=0&postorder=asc&highlight=[/url]
    12. //
    13. // Notes: There is a variable called avgPpC (AVErage Pixels Per Character)
    14. //        that needs to be changed to suit your font size/style.
    15. //
    16. // Author: Nathan Andre
    17. //
    18. // Email: na203602@ohio.edu
    19. //
    20. //**************************************************************************
    21.  
    22.  
    23. using UnityEngine;
    24. using System.Collections;
    25.  
    26. public class atextscript : MonoBehaviour {
    27.    
    28.     public GUISkin skin;
    29.  
    30.     // Use this for initialization
    31.     void Start()
    32.     {
    33.    
    34.     }
    35.    
    36.     // Update is called once per frame
    37.     void Update()
    38.     {
    39.    
    40.     }
    41.    
    42.     int HexStringToInt(string hexString)
    43.     {
    44.         int value = 0;
    45.         int digitValue = 1;
    46.         hexString = hexString.ToUpper();
    47.         char[] hexDigits = hexString.ToCharArray(0, hexString.Length);
    48.    
    49.         for(int i = hexString.Length - 1; i >= 0; i--)
    50.         {
    51.             int digit = 0;
    52.             if (hexDigits[i] >= '0'  hexDigits[i] <= '9')
    53.             {
    54.                 digit = hexDigits[i] - '0';
    55.             }
    56.             else if(hexDigits[i] >= 'A'  hexDigits[i] <= 'F')
    57.             {
    58.                 digit = hexDigits[i] - 'A' + 10;            
    59.             }
    60.             else
    61.             {
    62.                 // Not a hex string
    63.                 return -1;
    64.             }
    65.            
    66.             value += digit * digitValue;
    67.             digitValue *= 16;
    68.         }
    69.    
    70.         return value;
    71.     }
    72.  
    73.  
    74.  
    75. public void FancyLabel2(Rect rect, string text, Font normalFont, Font boldFont, Font italicFont, TextAlignment alignment)
    76. {
    77.     //bool   leftSpace = false, rightSpace = false, topSpace = false, bottomSpace = false;
    78.  
    79.     Color    textColor = GUI.skin.GetStyle("Label").normal.textColor;
    80.     Font    defaultFont = GUI.skin.font;
    81.     Font   newFont = null;
    82.  
    83.     //GUIStyle fontStyle = new GUIStyle(testStyle);
    84.     GUIStyle fontStyle = new GUIStyle();
    85.     fontStyle.normal.textColor = textColor;
    86.    
    87.     // Start with normal font
    88.     if(normalFont != null)
    89.     {
    90.         fontStyle.font = normalFont;
    91.     }
    92.     else
    93.     {
    94.         fontStyle.font = defaultFont;          
    95.     }
    96.  
    97.     // NOTE: Lowering this padding reduces the line spacing
    98.     // May need to adjust per font
    99.     fontStyle.padding.bottom = -5;
    100.    
    101.     GUILayout.BeginArea(rect);
    102.     GUILayout.BeginVertical(GUILayout.ExpandHeight(true),
    103.                             GUILayout.Width(rect.height),
    104.                             GUILayout.MinWidth(rect.height));
    105.     GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true),
    106.                                 GUILayout.Width(rect.width),
    107.                                 GUILayout.MinWidth(rect.width));
    108.                                
    109.     // Insert flexible space on the left if Center or Right aligned
    110.     if(alignment == TextAlignment.Right || alignment == TextAlignment.Center)
    111.     {
    112.         GUILayout.FlexibleSpace();
    113.     }
    114.    
    115.     int newline = 0;
    116.     bool clearBuffer = false;
    117.     double pixelsPerLine = 0;
    118.     double avgPpC = 8.5;
    119.    
    120.     text = text.Replace("\n", "");
    121.     text = text.Replace("\r", "");
    122.    
    123.     string[] toks = text.Split(' ');
    124.     string output = "";
    125.  
    126.     for(int i=0; i<toks.Length; ++i)
    127.     {
    128.         //Add a leading space if you need one
    129.         if(i != 0)
    130.         {
    131.             output += " ";
    132.             pixelsPerLine += avgPpC;
    133.         }
    134.                
    135.         int index = 0;
    136.         while(index < toks[i].Length)
    137.         {
    138.             if(toks[i][index] == '\\')
    139.             {
    140.                 //Must be an escape character
    141.                 if(toks[i][index+1] == 'n')
    142.                 {
    143.                     ++newline;
    144.                 }
    145.                 else if(toks[i][index+1] == '#')
    146.                 {
    147.                     output += "#";
    148.                 }
    149.                
    150.                 index += 2;
    151.             }
    152.             else if(toks[i][index] == '#')
    153.             {
    154.                 //Must be a style sequence
    155.                 //The style will probably change, so might as well start a new label
    156.                 clearBuffer = true;
    157.                
    158.                 if(toks[i][index+1] == '!')
    159.                 {
    160.                     //Original Color
    161.                     textColor = GUI.skin.GetStyle("Label").normal.textColor;
    162.                 }
    163.                 else if(toks[i][index+1] == 'n')
    164.                 {
    165.                     //Normal Font
    166.                     if (normalFont != null)
    167.                         newFont = normalFont;
    168.                 }
    169.                 else if(toks[i][index+1] == 'x')
    170.                 {
    171.                     //Bold Font
    172.                     if (boldFont != null)
    173.                         newFont = boldFont;
    174.                 }
    175.                 else if(toks[i][index+1] == 'i')
    176.                 {
    177.                     //Italic Font
    178.                     if (italicFont != null)
    179.                         newFont = italicFont;
    180.                 }
    181.                 else
    182.                 {
    183.                     //Must be a color change
    184.                     string rText = toks[i].Substring(index + 1, 2);
    185.                     string gText = toks[i].Substring(index + 3, 2);
    186.                     string bText = toks[i].Substring(index + 5, 2);
    187.                     string aText = toks[i].Substring(index + 7, 2);
    188.  
    189.                     float r = HexStringToInt(rText) / 255.0f;
    190.                     float g = HexStringToInt(gText) / 255.0f;
    191.                     float b = HexStringToInt(bText) / 255.0f;
    192.                     float a = HexStringToInt(aText) / 255.0f;
    193.                
    194.                     if(r < 0 || g < 0 || b < 0 || a < 0)
    195.                     {
    196.                         Debug.Log("Invalid color sequence");
    197.                         return;
    198.                     }
    199.                
    200.                     textColor = new Color(r, g, b, a);
    201.                     index += 7;
    202.                 }
    203.  
    204.                 index += 2;
    205.             }
    206.             else
    207.             {
    208.                 //Must just be a regular string
    209.                 //Check to see if a new line is needed, then go ahead and add the text to the string
    210.                 int index2, firstFormat, firstEscape = 0;
    211.                 firstFormat = toks[i].IndexOf("#", index);
    212.                 firstEscape = toks[i].IndexOf("\\", index);
    213.                 //if(firstFormat != -1  (firstFormat < firstEscape  firstEscape != -1))
    214.                 if(firstFormat != -1  (firstEscape!=-1?firstFormat<firstEscape:true))
    215.                 {
    216.                     index2 = firstFormat;
    217.                 }
    218.                 else if(firstEscape != -1)
    219.                 {
    220.                     index2 = firstEscape;
    221.                 }
    222.                 else
    223.                 {
    224.                     index2 = toks[i].Length;
    225.                 }
    226.                
    227.                 //Check to see if the words need to wrap
    228.                 if((pixelsPerLine + (index2 - index)*avgPpC) >= rect.width)
    229.                 {
    230.                     if(newline == 0) newline = 1;
    231.                 }
    232.                
    233.                 //Check to see if you need to make a label
    234.                 if(clearBuffer || newline > 0)
    235.                 {
    236.                     //Clear the buffer if the style changes or there is a newline
    237.                     GUILayout.Label(output, fontStyle);
    238.  
    239.                     //Add in trailing spaces
    240.                     int spaces = output.Length - output.TrimEnd(' ').Length;
    241.                     //Might have to adjust this constant
    242.                     GUILayout.Space(spaces * 5.0f);
    243.                     //And also count that space in the pixel size of the buffer...
    244.                     pixelsPerLine += spaces*avgPpC;
    245.                    
    246.                     //Clear the buffer and cleanup
    247.                     output = "";
    248.                     clearBuffer = false;
    249.                     fontStyle.normal.textColor = textColor;
    250.                     if(newFont != null)
    251.                     {
    252.                         fontStyle.font = newFont;
    253.                         newFont = null;
    254.                     }
    255.                 }
    256.  
    257.                 //You might have multiple newlines since the last label was created
    258.                 //ie if you do multiple newlines in a row
    259.                 while(newline > 0)
    260.                 {
    261.                     //Create a new line by ending the horizontal layout
    262.                     if(alignment == TextAlignment.Left || alignment == TextAlignment.Center)
    263.                     {
    264.                         GUILayout.FlexibleSpace();
    265.                     }
    266.                     GUILayout.EndHorizontal();
    267.                     GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true),
    268.                                                 GUILayout.Width(rect.width),
    269.                                                 GUILayout.MinWidth(rect.width));          
    270.                     if(alignment == TextAlignment.Right || alignment == TextAlignment.Center)
    271.                     {
    272.                         GUILayout.FlexibleSpace();
    273.                     }
    274.                    
    275.                     //You have to include this label, otherwise the newline will be
    276.                     //at the same place as the last one.
    277.                     if(newline > 1) GUILayout.Label(" ", fontStyle);
    278.                    
    279.                     --newline;
    280.                     pixelsPerLine = 0;
    281.                 }
    282.                
    283.                 //Write the new stuff to the buffer
    284.                 output += toks[i].Substring(index, index2-index);
    285.                 pixelsPerLine += (index2-index)*avgPpC;
    286.                 index += index2-index;
    287.             }
    288.         }
    289.     }
    290.    
    291.    
    292.     //Clear the buffer one last time
    293.     GUILayout.Label(output, fontStyle );
    294.    
    295.     if(alignment == TextAlignment.Left || alignment == TextAlignment.Center)
    296.     {
    297.         GUILayout.FlexibleSpace();
    298.     }
    299.     GUILayout.EndHorizontal();
    300.     GUILayout.FlexibleSpace();
    301.     GUILayout.EndVertical();
    302.     GUILayout.EndArea();  
    303. }
    304.  
    305. }
    306.  
    307.  
    I have not done a lot of testing on this, but it works well enough for my purposes. If you try it out, shoot me a private message to tell me how it goes. One thing to mention is that it is a very crude form of word wrap. It doesn't take the particular font/font size into account, so there is a variable you have to change called avgPpC (AVErage Pixels Per Character). Right now it is at 8.5, which is the value that works for my purposes.

    EDIT 1: Changed the code a little bit. Did some testing and found a bug. Will fix tomorrow.
    EDIT 2: Fixed the code to change fonts and do trailing spaces. Also added some comments.
     
  18. tonyd

    tonyd

    Joined:
    Jun 2, 2009
    Posts:
    1,224
    Could you post a sample project?

    I'm trying to reverse engineer this code, but I'm having a hard time figuring out what is going on.

    At what point is the text drawn on screen, and is there a speed penalty for multiple font styles and colors?
     
  19. nandre

    nandre

    Joined:
    Aug 26, 2010
    Posts:
    2
    I tried uploading a sample project as an attachment, but it wouldn't let me for some reason... Any idea why? Is it possible that I don't have the proper permissions?

    Text is drawn to the screen any time you see a GUILayout.Label function call. In the comments I usually call this "clearing the buffer". Sorry if that caused confusion.

    Yes, there are performance penalties to using new colors, but it's usually pretty insignificant unless you're changing style every other word or something.[/quote]
     
  20. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    You should be able to post a project if you archive it as a zip file and if it isn't too large. A large project can often be stripped down to a minimal setup that displays the problem (eg, remove unused stuff from Standard Assets, etc).
     
  21. Dover8

    Dover8

    Joined:
    Aug 20, 2010
    Posts:
    94
    To work around the issue of unity clipping spaces from the end of a string, you can instead take the space into account at the start of the next string. I was doing something similar with a chat window that detects if there is a web-link contained in the chat or not, and if so, turning the link-text into a click-able button that launches the web-link.

    Code (csharp):
    1.  
    2. GUILayout.BeginHorizontal();
    3.     // Shadow
    4.     GUI.color.r = 0; GUI.color.g = 0; GUI.color.b = 0;
    5.     GUILayout.Label(text, "MyStyle");
    6.     r = GUILayoutUtility.GetLastRect();
    7.     r.x--;
    8.     r.y--;
    9.    
    10.     GUI.color.r = color.r; GUI.color.g = color.g; GUI.color.b = color.b;
    11.     GUI.Label(r, text.Substring(0, startofLink-1), "MyStyle");
    12.     //change to blue
    13.     GUI.color.r = 0; GUI.color.g = 0; GUI.color.b = 1;
    14.     //position the link
    15.     r.x += GUI.skin.GetStyle("MyStyle").CalcSize(GUIContent(text.Substring(0, startofLink-1))).x;
    16.     //print the link over the text
    17.     if(GUI.Button(r, text.Substring(startofLink-1, endofLink - (startofLink-1)), "MyStyle")  GUI.color.a == 1) {
    18.         Settings.OpenLink(link);//link was stored earlier in code
    19.         alpha = 0;
    20.     }
    21.     if(endofLink < text.length)//more text after link
    22.     {
    23.         GUI.color.r = color.r; GUI.color.g = color.g; GUI.color.b = color.b;
    24.         r.x += GUI.skin.GetStyle("MyStyle").CalcSize(GUIContent(text.Substring(startofLink-1, endofLink - (startofLink-1)))).x;
    25.         GUI.Label(r, text.Substring(endofLink, text.length-endofLink),"MyStyle");
    26.     }
    27. GUILayout.EndHorizontal();
    28.  
    the difference that I refer to is the: text.Substring(startofLink-1, endofLink - (startofLink-1) whereas you have Substring(index1, index2 - index1). For the first case you don't -1 off the start as that would obviously be bad, but otherwise you're spaces will then be the first character in the next Substring, removeing the need to "add" space.

    However I also add my code here as I look for help. If I try to put the above into a GUILayout.Window then I encounter the sitaution of wordwrap. For an ordinary message (in a GUILayout.Label) I want wordwrap so it fits in the window, but for a link, I want the wrapping to take into account all three elements (label, button, label) as for now, it is wrapping each element individually resulting in a bit of a mess. You get something like this
    Code (csharp):
    1.  
    2. | my first    and then   followed   |
    3. | label fills  my link   by the end |
    4. | this space   goes here label here |
    5.  
    what is desired is:
    Code (csharp):
    1.  
    2. | my first label fills this space   |
    3. | and the my link goes here followed|
    4. |by the end label here              |
    If anyone has any insights on this it would be much appreciated.
     
  22. Stephane.Bessette

    Stephane.Bessette

    Joined:
    Nov 18, 2010
    Posts:
    9
    I've expanded on the code provided by Dafu and Nandre to provide the following features:
    • Background color
    • Text color
    • Font name
    • Font size
    • Font attributes: underline and strikethrough
    • Hyperlink
    • Horizontal alignment: left, right, center
    • Horizontal space (pixels)
    • Vertical alignment: bottom or Unity's almost top

    An explanatory text is available on my blog, as well as the up to date source code for FormattedLabel, GuiHelper - DrawLine, and HexUtil - HexToColor.

    The relevant source code has been added as attachment; they are too large to be included in the message text.
     

    Attached Files:

    Last edited: Nov 29, 2010
  23. RyuMaster

    RyuMaster

    Joined:
    Sep 13, 2010
    Posts:
    468
    Thank you very much! Your code is most useful and amazing!
     
  24. liverolA

    liverolA

    Joined:
    Feb 10, 2009
    Posts:
    347
    Thanks for the great work.
     
    Last edited: Dec 15, 2010
  25. jedy

    jedy

    Joined:
    Aug 1, 2010
    Posts:
    579
    Stephane.Bessette - strange thing in HexUtil.cs - instead of "8)" there is some HTML code for a smiley :D

    Edit : Finished testing! Awesome work and thanks! All of you - Dafu, Nandre and Stephane.Bessette !!!
     
    Last edited: Dec 15, 2010
  26. afalk

    afalk

    Joined:
    Jun 21, 2010
    Posts:
    164
    Outstanding code and a nice share - thanks!
     
  27. scarpelius

    scarpelius

    Joined:
    Aug 19, 2007
    Posts:
    966
    Hey, this is a great resource. Thanks to authors and to andeee to point me here.
     
  28. Yann

    Yann

    Joined:
    Oct 20, 2007
    Posts:
    432
    Indeed, very interesting ! Stephane's blog being unreachable, could anybody post a simple example project ?
     
  29. cr0ybot

    cr0ybot

    Joined:
    Apr 29, 2011
    Posts:
    7
    This is impressive work; I haven't seen such a range of text formatting options in Unity until now, but... I ran Stephanie's demo, and 49 draw calls? 12 fps for just a small box of text? Isn't there a way to condense all of this text rendering into fewer draw calls?

    I wouldn't know the first thing about that, but I thought I'd ask anyways...
     
  30. StefanoCecere

    StefanoCecere

    Joined:
    Jun 10, 2011
    Posts:
    210
    i'm working on Stephane code improving it to have built in bold and italic styles (so to not waste more fonts) and custom default GUIStyle
    i'll post my updated version soon!
     
  31. jedy

    jedy

    Joined:
    Aug 1, 2010
    Posts:
    579
    Wonderful! Many people will find it useful !
     
  32. ufo

    ufo

    Joined:
    Aug 6, 2011
    Posts:
    13
    this is a sewing machine and you call it onGUI. the unity devs really should come up with something better than all these static functions.

    easy example:
    Code (csharp):
    1.  
    2.  FormattedLabel _formattedLabelText = new FormattedLabel(new Rect(100, 60, 300, 200).width, "[formatted text]");
    3. _formattedLabelText.draw();
    4.  
     
  33. Adam-Buckner

    Adam-Buckner

    Joined:
    Jun 27, 2007
    Posts:
    5,664
    Watching... Sad S.B's site is down... Those could use some explanation.
     
  34. unitydevist

    unitydevist

    Joined:
    Feb 3, 2009
    Posts:
    45
    Anyone have docs on this now?
     
  35. Deoxyz

    Deoxyz

    Joined:
    Dec 13, 2009
    Posts:
    38
    I have tried this script together with iGUI. But, when I use font changes, my newlines are messed up.
    I added an example. I have to change font, because iOS cannot use dynamic fonts.

    It is used in an OnGUI, so that cannot be the problem. Without the font changes in the text, it works.
    Also, my text has to go further than on the image. Almost to the end.

    Can someone help me with this? It's a bit urgent.

    Text:
    #666666ff#xWhat?\n\n(Enter)#nThis is an example#xTo show this#n and as you can see, it is not like it should be.#xBla bla bla#n some more text and now an enter.\n\n#xEnter!#nBla bla bla



    Thanks in advance.
     

    Attached Files:

  36. healyj28

    healyj28

    Joined:
    May 24, 2011
    Posts:
    4
    Will you be posting your demo soon?
     
  37. healyj28

    healyj28

    Joined:
    May 24, 2011
    Posts:
    4
    Will you be posting your unity 3d exmple for the Formatted Label? Your blog seems to be down.
     
  38. Stephane.Bessette

    Stephane.Bessette

    Joined:
    Nov 18, 2010
    Posts:
    9
    I've been away quite a while now and my site is being rebuilt. In the meantime, here's the corrected release as well as a full project, to make it easier to see it in action.

    I did not include the library folder so you'll have to supply one missing information after opening the project. Select the "Main Camera" within the Hierarchy and specify "FormattedLabel > FormattedLabelTest.cs" as the script within the Inspector. The mouse cursor fields are optional and have good fallback values i.e. they use an arrow and a hand image.

    Hit the "Play" button and select one of the four options: Demo, Fireball, Hyperlink, and SpecialText. The text for these is specified within FormattedLabel > FormattedLabel.cs, within the GetTestText() function (at the end of the file). And the supported commands are specified within that same file, at the beginning. Each command (for example, Color) can be specified either in short (C) or long (Color) format.
     

    Attached Files:

  39. Adam-Buckner

    Adam-Buckner

    Joined:
    Jun 27, 2007
    Posts:
    5,664
    Very nice. Good to have you back! This is a very useful solution for everyone with Unity!

    And thanks so much for putting the effort into maintaining this!
     
  40. Stephane.Bessette

    Stephane.Bessette

    Joined:
    Nov 18, 2010
    Posts:
    9
  41. hound0999

    hound0999

    Joined:
    Jun 30, 2009
    Posts:
    124
    It works on the web player.

    Also I made some changes to the formattedlabel file. This is being used for a chat window for me and forcing double [[ wasn't an option. I've now allowed the option to remove, call attention to or pass through what would be an invalid command. It's all commented in the code. The lines I added are 398-411, 697-709. I also added a getting started section in the summary. It's really easy to use and should only take about a minute to implement with the getting started section now. I wasn't sure how to use it at first and anyone not versed in C# may be lost.

    Also here is a example of its use for chat: Chat Web Player
     

    Attached Files:

    Last edited: Dec 4, 2011
  42. hound0999

    hound0999

    Joined:
    Jun 30, 2009
    Posts:
    124
    Fixed a bug with word wrap.

    Bug:
    When ever a super long word is typed in it will not wrap to a new line.

    Fix:
    New Lines 435 - 465 of FormattedLabel.cs. You only need to replace your old FormattedLabel with the new one in the zip.

    I've added a check to see if a word is larger then the screen width. If it is it will break down the word into multiple words instead of passing it through as one word.

    This was probably out of the intended use of this file, but it now has applications for dynamic chat sessions. Let me know if there are any bugs that occur with the code.

    Testing Link: Chat

    As robust of a game engine that Unity is I can't believe that the current GUI is simple to use to get started but becomes more of a pain in the ass if you want to do anything fancy. This all seems like stuff that should be built in. Thank you to the creators of Fancy and Formatted Label.
     

    Attached Files:

    Last edited: Dec 7, 2011
  43. jedy

    jedy

    Joined:
    Aug 1, 2010
    Posts:
    579
    Good job mate.
    I'm still not using the formatted label code, cuz it takes alot of resources. When I have time I'll add some kind of caching the text in some variable, because right now if I recall correctly it reparses it on OnGui call.
     
  44. hound0999

    hound0999

    Joined:
    Jun 30, 2009
    Posts:
    124
    Yeah I don't think that I'd ever use it for anything in game, but it makes for a decent dedicated chat room.
     
  45. charmandermon

    charmandermon

    Joined:
    Dec 4, 2011
    Posts:
    352
    Hey guys Is it possible to update this plugin to support subscripts and superscripts? Thanks in advanced
     
  46. charmandermon

    charmandermon

    Joined:
    Dec 4, 2011
    Posts:
    352
    Hey Stephane, Is there a way to update to plugin to support subscripts and superscripts? Thanks!
     
  47. Stephane.Bessette

    Stephane.Bessette

    Joined:
    Nov 18, 2010
    Posts:
    9
    It should be possible to add superscript and subscript.

    Look at drawText() within FormattedLabel.cs. That code uses fillerHeight to implement bottom alignment. The easiest implementation for super- and subscript would be in the bottom-alignment mode, where you'd only have to add a value to fillerHeight to get a subscript, and possibly put fillerHeight to zero for a superscript.

    Regarding processing load/speed, FancyLabel has two passes. In the first pass it validates the commands and standardizes all commands to their short form (all color specifications use C and not Color). That removes some code from the actual OnGUI loop. However more could be done I'm certain.

    I've been thinking of changing all that code into classes rather than a huge if...else. That way a lot of parsing, to separate the commands from the text would only be performed once, as well as the various conversion (hex colors into a Color).
     
  48. jedy

    jedy

    Joined:
    Aug 1, 2010
    Posts:
    579
    Yeah I was thinking into separating it into different classes. It should bump the performance.
    When I get into detail with the GUI of my game I'll try caching most of the data to make the GUI as efficient as it could be.
     
  49. Stephane.Bessette

    Stephane.Bessette

    Joined:
    Nov 18, 2010
    Posts:
    9
    Good to know.


    You're quite right. Programmers and documentation...
     
  50. Stephane.Bessette

    Stephane.Bessette

    Joined:
    Nov 18, 2010
    Posts:
    9
    I would make a change in the sample within the GettingStarted.txt documentation. FancyLabel has two passes. The first to parse and measure the text, and the second to draw the parsed text. This approach should be faster.

    Code (csharp):
    1. private FormattedLabel fLabel;
    2.  
    3. void Start ()
    4. {
    5.     // The text to draw
    6.     string textToFormat = "[ffffffff][ha c]Fireball\n";
    7.  
    8.     // Parse the text to format
    9.     fLabel = new FormattedLabel(screen.Width, textToFormat);
    10. }
    11.  
    12.  
    13. void OnGUI()
    14. {
    15.     // Draw the parsed text
    16.     fLabel.draw();
    17. }
    18.  
     
Thread Status:
Not open for further replies.