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

Adjustable Character Spacing - free script

Discussion in 'UGUI & TextMesh Pro' started by Deeperbeige, Dec 30, 2014.

  1. Deeperbeige

    Deeperbeige

    Joined:
    May 12, 2013
    Posts:
    17
    I was dismayed to find that the new 4.6 uGUI Text component supports line spacing, but not letter spacing. Especially since the later versions of NGUI had this option. I figured that when the UI source came out, I could probably add it pretty easily. Nope - the text layout engine isn't part of the open source stuff, and you can't get at the code or a suitable API for it.

    My best workaround was to simply modify existing fonts and add extra space to every character. This is as difficult to manage as it sounds, and impractical for a recent design I'm working to which has lots of differently spaced headings. I would need a different font for each different spacing.

    Then by chance I looked at the source for the Shadow effect, which uses a BaseVertexEffect to modify the list of vertices that make the geometry of the object being drawn. An hour or two of hacking, and I've got a component that spaces text out via much the same technique (although it doesn't generate any extra verts). Add the component to a UI textfield, adjust the spacing value, job done.



    I thought I should share the effect here, as I've seen a few other threads asking the same basic question.

    While this effect works in the editor, plays nicely on mobile and integrates well with the new UI system, it does have its limits. Full details in the comments at the top of the code, but don't expect miracles. If you can improve some part of it, feel free to contribute!

    Grab the effect from BitBucket. You can either clone the repo if you know your way around GIT, or you can just get a zip directly from the downloads section on the left. Thanks to Andrew Pavlovich for setting up the repo, and to everyone else who has contributed improvements.

    https://bitbucket.org/AcornGame/adjustable-character-spacing
     
    Last edited: Dec 31, 2016
  2. senritsu

    senritsu

    Joined:
    Dec 12, 2012
    Posts:
    37
    Very nice, just what i was looking for, since my fonts look like S*** in unity :D
    All the letters overlapping.. i really wonder what happens internally with the kerning.
     
  3. FredZvt81

    FredZvt81

    Joined:
    Apr 8, 2014
    Posts:
    24
    Man, you are awesome! It works perfectly! Thank you very much!
     
  4. DrBlort

    DrBlort

    Joined:
    Nov 14, 2012
    Posts:
    72
    Thank you! Saved me from having to use images with the separation set by a graphic designer.
     
  5. Dudledok

    Dudledok

    Joined:
    Oct 24, 2013
    Posts:
    110
    Thank you, very simple and nice.
     
  6. Perceive

    Perceive

    Joined:
    Aug 2, 2013
    Posts:
    10
    This works wonderfully, thanks!
     
  7. kromenak

    kromenak

    Joined:
    Feb 9, 2011
    Posts:
    270
    Thanks for the component - works great! I did notice that it doesn't play nicely with the shadow component at first, but I found that if you apply this letter spacing before the shadow (make sure Letter Spacer comes before Shadow in the component list on the GameObject), everything works fine.
     
  8. cesarpo

    cesarpo

    Joined:
    Jun 8, 2013
    Posts:
    97
    This is awesome! Thanks!
     
  9. tomph

    tomph

    Joined:
    Nov 6, 2013
    Posts:
    33
    Thank you for sharing! :)
     
  10. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    In case anyone needs a solution that works with rich text and word wrapping, take a look at TextMesh Pro. It includes control over character spacing, line spacing and paragraph spacing as well as kerning.

    TextMesh Pro also offers more text styles like superscript, subscript, underline, strikethrough and several additional rich text tags. Below are a few examples.


    The size tag supports different formats like <size=36> or <size=+12> or <size=-12> or <size=%150>.






    This last example uses the Rich Text Tag <align=left> or <align=center> or <align=right> or <align=justified> and </align>

    Again, these are just some of the rich text tags available. These features all work with the New UI as well as the normal Mesh Renderer. TextMesh Pro also features an advanced text rendering system and a lot more. Beta releases of TextMesh Pro for Unity 4.6 & 5.0 are available to registered users on the TMP user forum.
     
    Last edited: May 13, 2015
    SweatyChair and jprocha101 like this.
  11. QI

    QI

    Joined:
    Oct 27, 2012
    Posts:
    229
    Thanks you so much, your free script works really nice.
     
  12. kineticabstract

    kineticabstract

    Joined:
    Nov 15, 2014
    Posts:
    10
    Thanks, Deeperbeige. This is extremely useful.
     
  13. Nsingh13

    Nsingh13

    Joined:
    Dec 17, 2014
    Posts:
    38
    Can someone please help me? Where do I put this script?
     
  14. QI

    QI

    Joined:
    Oct 27, 2012
    Posts:
    229
    To the GameObject where your Text component attached at.
     
  15. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    476
    This is SO F***ING GOOD. YOU ARE F***ING MASTER.

    I could not write better code myself. If you ever want a job contact us.

    F***ing amazing. The best thing I have ever seen on the forum. Than you so much. This should cost €200-

    Thank you. You are too good.
     
  16. kromenak

    kromenak

    Joined:
    Feb 9, 2011
    Posts:
    270
    One other thing: I noticed that this effect doesn't quite work as expected when Unity inserts automatic line breaks (for example, when your text area wraps). However, I think I found a workable solution using the Text component's "cachedTextGenerator" member. You can do something like this in ModifyVertices, after getting the Text component:

    Code (CSharp):
    1. string str = text.text;
    2.  
    3. // Artificially insert line breaks for automatic line breaks.
    4. IList<UILineInfo> lineInfos = text.cachedTextGenerator.lines;
    5. for(int i = lineInfos.Count - 1; i > 0; i--)
    6. {
    7.     // Insert a \n at the location Unity wants to automatically line break.
    8.     // Also, remove any space before the automatic line break location.
    9.     str = str.Insert(lineInfos[i].startCharIdx, "\n");
    10.     str = str.Remove(lineInfos[i].startCharIdx - 1, 1);
    11. }
    12.  
    13. string[] lines = str.Split('\n');
    14. // Rest of the code down here.
    You want to iterate over the array backwards because the Insert operation changes the index values of the string. You also want to NOT include the 0th element because that will always be 0 (the first line's starting character is always 0). You don't want to insert a line break for the first line.
     
    PaladinGames and Senshi like this.
  17. trooper

    trooper

    Joined:
    Aug 21, 2009
    Posts:
    748
  18. imandic

    imandic

    Joined:
    Mar 6, 2014
    Posts:
    31
    Working great. Thank you....
     
  19. mubasherikram

    mubasherikram

    Joined:
    Nov 24, 2014
    Posts:
    5
    v v thanks for your effort
    it solved the issue
    unity should include this feature builtin
     
  20. StructureMaster

    StructureMaster

    Joined:
    Sep 4, 2015
    Posts:
    1
  21. Sadware

    Sadware

    Joined:
    Jan 31, 2014
    Posts:
    3
    Excellent work! Kudos to the original post
    For my intentions, the free script works as if it were a native component~
     
  22. Masterio

    Masterio

    Joined:
    Sep 27, 2013
    Posts:
    8
    Not works in Unity 5.2 :(
     
  23. CrazyTegger

    CrazyTegger

    Joined:
    Feb 15, 2015
    Posts:
    57
    I noticed this as well. Would like to move to Unity 5.2, but there are some other bugs limiting me. If I get those bugs sorted out, I will try and update the code if no one else has already.
     
  24. ChoMPi

    ChoMPi

    Joined:
    Jul 11, 2013
    Posts:
    112
    Here's a cross-version compatible script i modified.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. /*
    7.  
    8. Produces an simple tracking/letter-spacing effect on UI Text components.
    9.  
    10. Set the spacing parameter to adjust letter spacing.
    11.   Negative values cuddle the text up tighter than normal. Go too far and it'll look odd.
    12.   Positive values spread the text out more than normal. This will NOT respect the text area you've defined.
    13.   Zero spacing will present the font with no changes.
    14.  
    15. Relies on counting off characters in your Text compoennt's text property and
    16. matching those against the quads passed in via the verts array. This is really
    17. rather primative, but I can't see any better way at the moment. It means that
    18. all sorts of things can break the effect...
    19.  
    20. This component should be placed higher in component list than any other vertex
    21. modifiers that alter the total number of verticies. Eg, place this above Shadow
    22. or Outline effects. If you don't, the outline/shadow won't match the position
    23. of the letters properly. If you place the outline/shadow effect second however,
    24. it will just work on the altered vertices from this component, and function
    25. as expected.
    26.  
    27. This component works best if you don't allow text to automatically wrap. It also
    28. blows up outside of the given text area. Basically, it's a cheap and dirty effect,
    29. not a clever text layout engine. It can't affect how Unity chooses to break up
    30. your lines. If you manually use line breaks however, it should detect those and
    31. function more or less as you'd expect.
    32.  
    33. The spacing parameter is measured in pixels multiplied by the font size. This was
    34. chosen such that when you adjust the font size, it does not change the visual spacing
    35. that you've dialed in. There's also a scale factor of 1/100 in this number to
    36. bring it into a comfortable adjustable range. There's no limit on this parameter,
    37. but obviously some values will look quite strange.
    38.  
    39. This component doesn't really work with Rich Text. You don't need to remember to
    40. turn off Rich Text via the checkbox, but because it can't see what makes a
    41. printable character and what doesn't, it will typically miscount characters when you
    42. use HTML-like tags in your text. Try it out, you'll see what I mean. It doesn't
    43. break down entirely, but it doesn't really do what you'd want either.
    44.  
    45. */
    46.  
    47. namespace UnityEngine.UI
    48. {
    49.     [AddComponentMenu("UI/Effects/Letter Spacing", 14)]
    50. #if UNITY_5_2
    51.     public class LetterSpacing : BaseMeshEffect
    52. #else
    53.     public class LetterSpacing : BaseVertexEffect
    54. #endif
    55.     {
    56.         [SerializeField]
    57.         private float m_spacing = 0f;
    58.        
    59.         protected LetterSpacing() { }
    60.        
    61. #if UNITY_EDITOR
    62.         protected override void OnValidate()
    63.         {
    64.             spacing = m_spacing;
    65.             base.OnValidate();
    66.         }
    67. #endif
    68.        
    69.         public float spacing
    70.         {
    71.             get { return m_spacing; }
    72.             set
    73.             {
    74.                 if (m_spacing == value) return;
    75.                 m_spacing = value;
    76.                 if (graphic != null) graphic.SetVerticesDirty();
    77.             }
    78.         }
    79.  
    80. #if UNITY_5_2
    81.         public override void ModifyMesh(Mesh mesh)
    82.         {
    83.             if (!this.IsActive())
    84.                 return;
    85.  
    86.             List<UIVertex> list = new List<UIVertex>();
    87.             using (VertexHelper vertexHelper = new VertexHelper(mesh))
    88.             {
    89.                 vertexHelper.GetUIVertexStream(list);
    90.             }
    91.  
    92.             ModifyVertices(list);  // calls the old ModifyVertices which was used on pre 5.2
    93.  
    94.             using (VertexHelper vertexHelper2 = new VertexHelper())
    95.             {
    96.                 vertexHelper2.AddUIVertexTriangleStream(list);
    97.                 vertexHelper2.FillMesh(mesh);
    98.             }
    99.         }
    100. #endif
    101.  
    102. #if UNITY_5_2
    103.         public void ModifyVertices(List<UIVertex> verts)
    104.         {
    105.             if (!IsActive()) return;
    106.  
    107.             Text text = GetComponent<Text>();
    108.             if (text == null)
    109.             {
    110.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    111.                 return;
    112.             }
    113.  
    114.             string[] lines = text.text.Split('\n');
    115.             Vector3 pos;
    116.             float letterOffset = spacing * (float)text.fontSize / 100f;
    117.             float alignmentFactor = 0;
    118.             int glyphIdx = 0;
    119.  
    120.             switch (text.alignment)
    121.             {
    122.                 case TextAnchor.LowerLeft:
    123.                 case TextAnchor.MiddleLeft:
    124.                 case TextAnchor.UpperLeft:
    125.                     alignmentFactor = 0f;
    126.                     break;
    127.  
    128.                 case TextAnchor.LowerCenter:
    129.                 case TextAnchor.MiddleCenter:
    130.                 case TextAnchor.UpperCenter:
    131.                     alignmentFactor = 0.5f;
    132.                     break;
    133.  
    134.                 case TextAnchor.LowerRight:
    135.                 case TextAnchor.MiddleRight:
    136.                 case TextAnchor.UpperRight:
    137.                     alignmentFactor = 1f;
    138.                     break;
    139.             }
    140.  
    141.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    142.             {
    143.                 string line = lines[lineIdx];
    144.                 float lineOffset = (line.Length - 1) * letterOffset * alignmentFactor;
    145.  
    146.                 for (int charIdx = 0; charIdx < line.Length; charIdx++)
    147.                 {
    148.                     int idx1 = glyphIdx * 6 + 0;
    149.                     int idx2 = glyphIdx * 6 + 1;
    150.                     int idx3 = glyphIdx * 6 + 2;
    151.                     int idx4 = glyphIdx * 6 + 3;
    152.                     int idx5 = glyphIdx * 6 + 4;
    153.                     int idx6 = glyphIdx * 6 + 5;
    154.  
    155.                     // Check for truncated text (doesn't generate verts for all characters)
    156.                     if (idx6 > verts.Count - 1) return;
    157.  
    158.                     UIVertex vert1 = verts[idx1];
    159.                     UIVertex vert2 = verts[idx2];
    160.                     UIVertex vert3 = verts[idx3];
    161.                     UIVertex vert4 = verts[idx4];
    162.                     UIVertex vert5 = verts[idx5];
    163.                     UIVertex vert6 = verts[idx6];
    164.  
    165.                     pos = Vector3.right * (letterOffset * charIdx - lineOffset);
    166.  
    167.                     vert1.position += pos;
    168.                     vert2.position += pos;
    169.                     vert3.position += pos;
    170.                     vert4.position += pos;
    171.                     vert5.position += pos;
    172.                     vert6.position += pos;
    173.  
    174.                     verts[idx1] = vert1;
    175.                     verts[idx2] = vert2;
    176.                     verts[idx3] = vert3;
    177.                     verts[idx4] = vert4;
    178.                     verts[idx5] = vert5;
    179.                     verts[idx6] = vert6;
    180.  
    181.                     glyphIdx++;
    182.                 }
    183.  
    184.                 // Offset for carriage return character that still generates verts
    185.                 glyphIdx++;
    186.             }
    187.         }
    188. #else
    189.         public override void ModifyVertices(List<UIVertex> verts)
    190.         {
    191.             if (! IsActive()) return;
    192.            
    193.             Text text = GetComponent<Text>();
    194.             if (text == null)
    195.             {
    196.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    197.                 return;
    198.             }
    199.            
    200.             string[] lines = text.text.Split('\n');
    201.             Vector3  pos;
    202.             float    letterOffset    = spacing * (float)text.fontSize / 100f;
    203.             float    alignmentFactor = 0;
    204.             int      glyphIdx        = 0;
    205.            
    206.             switch (text.alignment)
    207.             {
    208.             case TextAnchor.LowerLeft:
    209.             case TextAnchor.MiddleLeft:
    210.             case TextAnchor.UpperLeft:
    211.                 alignmentFactor = 0f;
    212.                 break;
    213.                
    214.             case TextAnchor.LowerCenter:
    215.             case TextAnchor.MiddleCenter:
    216.             case TextAnchor.UpperCenter:
    217.                 alignmentFactor = 0.5f;
    218.                 break;
    219.                
    220.             case TextAnchor.LowerRight:
    221.             case TextAnchor.MiddleRight:
    222.             case TextAnchor.UpperRight:
    223.                 alignmentFactor = 1f;
    224.                 break;
    225.             }
    226.            
    227.             for (int lineIdx=0; lineIdx < lines.Length; lineIdx++)
    228.             {
    229.                 string line = lines[lineIdx];
    230.                 float lineOffset = (line.Length -1) * letterOffset * alignmentFactor;
    231.                
    232.                 for (int charIdx = 0; charIdx < line.Length; charIdx++)
    233.                 {
    234.                     int idx1 = glyphIdx * 4 + 0;
    235.                     int idx2 = glyphIdx * 4 + 1;
    236.                     int idx3 = glyphIdx * 4 + 2;
    237.                     int idx4 = glyphIdx * 4 + 3;
    238.                    
    239.                     // Check for truncated text (doesn't generate verts for all characters)
    240.                     if (idx4 > verts.Count - 1) return;
    241.                    
    242.                     UIVertex vert1 = verts[idx1];
    243.                     UIVertex vert2 = verts[idx2];
    244.                     UIVertex vert3 = verts[idx3];
    245.                     UIVertex vert4 = verts[idx4];
    246.                    
    247.                     pos = Vector3.right * (letterOffset * charIdx - lineOffset);
    248.                    
    249.                     vert1.position += pos;
    250.                     vert2.position += pos;
    251.                     vert3.position += pos;
    252.                     vert4.position += pos;
    253.                    
    254.                     verts[idx1] = vert1;
    255.                     verts[idx2] = vert2;
    256.                     verts[idx3] = vert3;
    257.                     verts[idx4] = vert4;
    258.                    
    259.                     glyphIdx++;
    260.                 }
    261.                
    262.                 // Offset for carriage return character that still generates verts
    263.                 glyphIdx++;
    264.             }
    265.         }
    266. #endif
    267.     }
    268. }
    269.  
    And let's not forget to thank Deeperbeige for bringing us this great tool.
     
    Trisibo, PaladinGames and WillemKokke like this.
  25. Masterio

    Masterio

    Joined:
    Sep 27, 2013
    Posts:
    8
    Thanks ChoMPi it works. But Unity 5.2 is slower by 25% on android i don't know why. I will stay on 5.0.3 and wait for optimizations ;)
     
  26. kromenak

    kromenak

    Joined:
    Feb 9, 2011
    Posts:
    270
    Hey guys, I have a "Content Size Fitter" on some pieces of text, but the spacing adjustments from the LetterSpacing script aren't taken into account by the Content Size Fitter. I've tried to have LetterSpacing implement the ILayoutElement interface to affect the minimum/preferred sizes of the text, but thus far, I haven't gotten it to work. Has anyone else had any success with something like that?
     
  27. ChoMPi

    ChoMPi

    Joined:
    Jul 11, 2013
    Posts:
    112
    Here try this, also fixed the line wrapping problem.

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. /*
    6.  
    7. Produces an simple tracking/letter-spacing effect on UI Text components.
    8.  
    9. Set the spacing parameter to adjust letter spacing.
    10.   Negative values cuddle the text up tighter than normal. Go too far and it'll look odd.
    11.   Positive values spread the text out more than normal. This will NOT respect the text area you've defined.
    12.   Zero spacing will present the font with no changes.
    13.  
    14. Relies on counting off characters in your Text compoennt's text property and
    15. matching those against the quads passed in via the verts array. This is really
    16. rather primative, but I can't see any better way at the moment. It means that
    17. all sorts of things can break the effect...
    18.  
    19. This component should be placed higher in component list than any other vertex
    20. modifiers that alter the total number of verticies. Eg, place this above Shadow
    21. or Outline effects. If you don't, the outline/shadow won't match the position
    22. of the letters properly. If you place the outline/shadow effect second however,
    23. it will just work on the altered vertices from this component, and function
    24. as expected.
    25.  
    26. This component works best if you don't allow text to automatically wrap. It also
    27. blows up outside of the given text area. Basically, it's a cheap and dirty effect,
    28. not a clever text layout engine. It can't affect how Unity chooses to break up
    29. your lines. If you manually use line breaks however, it should detect those and
    30. function more or less as you'd expect.
    31.  
    32. The spacing parameter is measured in pixels multiplied by the font size. This was
    33. chosen such that when you adjust the font size, it does not change the visual spacing
    34. that you've dialed in. There's also a scale factor of 1/100 in this number to
    35. bring it into a comfortable adjustable range. There's no limit on this parameter,
    36. but obviously some values will look quite strange.
    37.  
    38. This component doesn't really work with Rich Text. You don't need to remember to
    39. turn off Rich Text via the checkbox, but because it can't see what makes a
    40. printable character and what doesn't, it will typically miscount characters when you
    41. use HTML-like tags in your text. Try it out, you'll see what I mean. It doesn't
    42. break down entirely, but it doesn't really do what you'd want either.
    43.  
    44. */
    45.  
    46. namespace UnityEngine.UI
    47. {
    48.     [AddComponentMenu("UI/Effects/Letter Spacing", 14), RequireComponent(typeof(Text))]
    49. #if UNITY_5_2
    50.     public class LetterSpacing : BaseMeshEffect, ILayoutElement
    51. #else
    52.     public class LetterSpacing : BaseVertexEffect, ILayoutElement
    53. #endif
    54.     {
    55.         [SerializeField] private float m_spacing = 0f;
    56.  
    57.         public float spacing
    58.         {
    59.             get { return this.m_spacing; }
    60.             set
    61.             {
    62.                 if (this.m_spacing == value) return;
    63.                 this.m_spacing = value;
    64.                 if (this.graphic != null) this.graphic.SetVerticesDirty();
    65.                 LayoutRebuilder.MarkLayoutForRebuild((RectTransform)this.transform);
    66.             }
    67.         }
    68.  
    69.         private Text text
    70.         {
    71.             get { return this.gameObject.GetComponent<Text>(); }
    72.         }
    73.  
    74.         public float minWidth
    75.         {
    76.             get { return this.text.minWidth; }
    77.         }
    78.  
    79.         public float preferredWidth
    80.         {
    81.             get { return this.text.preferredWidth + ((this.spacing * (float)this.text.fontSize / 100f) * (this.text.text.Length - 1)); }
    82.         }
    83.  
    84.         public float flexibleWidth
    85.         {
    86.             get { return this.text.flexibleWidth; }
    87.         }
    88.  
    89.         public float minHeight
    90.         {
    91.             get { return this.text.minHeight; }
    92.         }
    93.  
    94.         public float preferredHeight
    95.         {
    96.             get { return this.text.preferredHeight; }
    97.         }
    98.  
    99.         public float flexibleHeight
    100.         {
    101.             get { return this.text.flexibleHeight; }
    102.         }
    103.  
    104.         public int layoutPriority
    105.         {
    106.             get { return this.text.layoutPriority; }
    107.         }
    108.        
    109.         protected LetterSpacing() { }
    110.        
    111. #if UNITY_EDITOR
    112.         protected override void OnValidate()
    113.         {
    114.             base.OnValidate();
    115.             this.spacing = this.m_spacing;
    116.             LayoutRebuilder.MarkLayoutForRebuild((RectTransform)this.transform);
    117.         }
    118. #endif
    119.  
    120.         public void CalculateLayoutInputHorizontal() { }
    121.         public void CalculateLayoutInputVertical() { }
    122.  
    123.         private string[] GetLines()
    124.         {
    125.             IList<UILineInfo> lineInfos = text.cachedTextGenerator.lines;
    126.             string[] lines = new string[lineInfos.Count];
    127.  
    128.             for (int i = 0; i < lineInfos.Count; i++)
    129.             {
    130.                 if ((i + 1) < lineInfos.Count)
    131.                 {
    132.                     lines[i] = this.text.text.Substring(lineInfos[i].startCharIdx, lineInfos[i + 1].startCharIdx - 1);
    133.                 }
    134.                 else
    135.                 {
    136.                     lines[i] = this.text.text.Substring(lineInfos[i].startCharIdx);
    137.                 }
    138.             }
    139.  
    140.             return lines;
    141.         }
    142.  
    143. #if UNITY_5_2
    144.         public override void ModifyMesh(Mesh mesh)
    145.         {
    146.             if (!this.IsActive())
    147.                 return;
    148.  
    149.             List<UIVertex> list = new List<UIVertex>();
    150.             using (VertexHelper vertexHelper = new VertexHelper(mesh))
    151.             {
    152.                 vertexHelper.GetUIVertexStream(list);
    153.             }
    154.  
    155.             this.ModifyVertices(list);  // calls the old ModifyVertices which was used on pre 5.2
    156.  
    157.             using (VertexHelper vertexHelper2 = new VertexHelper())
    158.             {
    159.                 vertexHelper2.AddUIVertexTriangleStream(list);
    160.                 vertexHelper2.FillMesh(mesh);
    161.             }
    162.         }
    163. #endif
    164.  
    165. #if UNITY_5_2
    166.         public void ModifyVertices(List<UIVertex> verts)
    167.         {
    168.             if (!this.IsActive()) return;
    169.            
    170.             string[] lines = this.GetLines();
    171.            
    172.             Vector3 pos;
    173.             float letterOffset = this.spacing * (float)this.text.fontSize / 100f;
    174.             float alignmentFactor = 0;
    175.             int glyphIdx = 0;
    176.  
    177.             switch (this.text.alignment)
    178.             {
    179.                 case TextAnchor.LowerLeft:
    180.                 case TextAnchor.MiddleLeft:
    181.                 case TextAnchor.UpperLeft:
    182.                     alignmentFactor = 0f;
    183.                     break;
    184.  
    185.                 case TextAnchor.LowerCenter:
    186.                 case TextAnchor.MiddleCenter:
    187.                 case TextAnchor.UpperCenter:
    188.                     alignmentFactor = 0.5f;
    189.                     break;
    190.  
    191.                 case TextAnchor.LowerRight:
    192.                 case TextAnchor.MiddleRight:
    193.                 case TextAnchor.UpperRight:
    194.                     alignmentFactor = 1f;
    195.                     break;
    196.             }
    197.  
    198.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    199.             {
    200.                 string line = lines[lineIdx];
    201.                 float lineOffset = ((line.Length - 1) * letterOffset) * alignmentFactor;
    202.  
    203.                 for (int charIdx = 0; charIdx < line.Length; charIdx++)
    204.                 {
    205.                     int idx1 = glyphIdx * 6 + 0;
    206.                     int idx2 = glyphIdx * 6 + 1;
    207.                     int idx3 = glyphIdx * 6 + 2;
    208.                     int idx4 = glyphIdx * 6 + 3;
    209.                     int idx5 = glyphIdx * 6 + 4;
    210.                     int idx6 = glyphIdx * 6 + 5;
    211.  
    212.                     // Check for truncated text (doesn't generate verts for all characters)
    213.                     if (idx6 > verts.Count - 1) return;
    214.  
    215.                     UIVertex vert1 = verts[idx1];
    216.                     UIVertex vert2 = verts[idx2];
    217.                     UIVertex vert3 = verts[idx3];
    218.                     UIVertex vert4 = verts[idx4];
    219.                     UIVertex vert5 = verts[idx5];
    220.                     UIVertex vert6 = verts[idx6];
    221.  
    222.                     pos = Vector3.right * ((letterOffset * charIdx) - lineOffset);
    223.  
    224.                     vert1.position += pos;
    225.                     vert2.position += pos;
    226.                     vert3.position += pos;
    227.                     vert4.position += pos;
    228.                     vert5.position += pos;
    229.                     vert6.position += pos;
    230.  
    231.                     verts[idx1] = vert1;
    232.                     verts[idx2] = vert2;
    233.                     verts[idx3] = vert3;
    234.                     verts[idx4] = vert4;
    235.                     verts[idx5] = vert5;
    236.                     verts[idx6] = vert6;
    237.  
    238.                     glyphIdx++;
    239.                 }
    240.  
    241.                 // Offset for carriage return character that still generates verts
    242.                 glyphIdx++;
    243.             }
    244.         }
    245. #else
    246.         public override void ModifyVertices(List<UIVertex> verts)
    247.         {
    248.             if (!this.IsActive()) return;
    249.            
    250.             string[] lines = this.GetLines();
    251.  
    252.             Vector3  pos;
    253.             float    letterOffset    = this.spacing * (float)this.text.fontSize / 100f;
    254.             float    alignmentFactor = 0;
    255.             int      glyphIdx        = 0;
    256.            
    257.             switch (this.text.alignment)
    258.             {
    259.             case TextAnchor.LowerLeft:
    260.             case TextAnchor.MiddleLeft:
    261.             case TextAnchor.UpperLeft:
    262.                 alignmentFactor = 0f;
    263.                 break;
    264.                
    265.             case TextAnchor.LowerCenter:
    266.             case TextAnchor.MiddleCenter:
    267.             case TextAnchor.UpperCenter:
    268.                 alignmentFactor = 0.5f;
    269.                 break;
    270.                
    271.             case TextAnchor.LowerRight:
    272.             case TextAnchor.MiddleRight:
    273.             case TextAnchor.UpperRight:
    274.                 alignmentFactor = 1f;
    275.                 break;
    276.             }
    277.            
    278.             for (int lineIdx=0; lineIdx < lines.Length; lineIdx++)
    279.             {
    280.                 string line = lines[lineIdx];
    281.                 float lineOffset = (line.Length -1) * letterOffset * alignmentFactor;
    282.                
    283.                 for (int charIdx = 0; charIdx < line.Length; charIdx++)
    284.                 {
    285.                     int idx1 = glyphIdx * 4 + 0;
    286.                     int idx2 = glyphIdx * 4 + 1;
    287.                     int idx3 = glyphIdx * 4 + 2;
    288.                     int idx4 = glyphIdx * 4 + 3;
    289.                    
    290.                     // Check for truncated text (doesn't generate verts for all characters)
    291.                     if (idx4 > verts.Count - 1) return;
    292.                    
    293.                     UIVertex vert1 = verts[idx1];
    294.                     UIVertex vert2 = verts[idx2];
    295.                     UIVertex vert3 = verts[idx3];
    296.                     UIVertex vert4 = verts[idx4];
    297.                    
    298.                     pos = Vector3.right * (letterOffset * charIdx - lineOffset);
    299.                    
    300.                     vert1.position += pos;
    301.                     vert2.position += pos;
    302.                     vert3.position += pos;
    303.                     vert4.position += pos;
    304.                    
    305.                     verts[idx1] = vert1;
    306.                     verts[idx2] = vert2;
    307.                     verts[idx3] = vert3;
    308.                     verts[idx4] = vert4;
    309.                    
    310.                     glyphIdx++;
    311.                 }
    312.                
    313.                 // Offset for carriage return character that still generates verts
    314.                 glyphIdx++;
    315.             }
    316.         }
    317. #endif
    318.     }
    319. }
     
    Trisibo and kromenak like this.
  28. IndieForger

    IndieForger

    Joined:
    Dec 31, 2012
    Posts:
    92
    I created a file LetterSpacing.cs and pasted the code but getting an error:

    Code (CSharp):
    1. Assets/Imported/UI/LetterSpacing.cs(50,18): error CS0534: `UnityEngine.UI.LetterSpacing' does not implement inherited abstract member `UnityEngine.UI.BaseMeshEffect.ModifyMesh(UnityEngine.UI.VertexHelper)
    How do I use it? Sorry for asking silly (perhaps) question but I am still new to many aspects in Unity.
     
  29. IndieForger

    IndieForger

    Joined:
    Dec 31, 2012
    Posts:
    92
    Ahh I just realized I am using patch release version 5.2.1p2. Looks like API is changing a bit again.... rolled back to 5.2.1f1 and UI extensions seem to work again.
     
  30. ActionVillain

    ActionVillain

    Joined:
    Apr 23, 2014
    Posts:
    3
    Having the same problem in 5.2.1p2

    *edit* And, I forgot: Thanks for sharing this great script!! :D
     
    Last edited: Oct 6, 2015
  31. Pratap-Dafedar

    Pratap-Dafedar

    Joined:
    Aug 30, 2013
    Posts:
    22
  32. IndieForger

    IndieForger

    Joined:
    Dec 31, 2012
    Posts:
    92
  33. MaDDoX

    MaDDoX

    Joined:
    Nov 10, 2009
    Posts:
    764
    Just a minor fix, there's an #elif missing in line 152. It should be like this:

    Code (csharp):
    1.  
    2. #if UNITY_5_2
    3.  
    4. publicoverridevoidModifyMesh (Meshmesh)
    5.  
    6. {
    7.  
    8. if (!this.IsActive ())
    9.  
    10. return;
    11.  
    12.  
    13.  
    14. List<UIVertex>list=newList<UIVertex> ();
    15.  
    16. using (VertexHelpervertexHelper=newVertexHelper (mesh)) {
    17.  
    18. vertexHelper.GetUIVertexStream (list);
    19.  
    20. }
    21.  
    22.  
    23.  
    24. this.ModifyVertices (list);  // calls the old ModifyVertices which was used on pre 5.2
    25.  
    26.  
    27.  
    28. using (VertexHelpervertexHelper2=newVertexHelper ()) {
    29.  
    30. vertexHelper2.AddUIVertexTriangleStream (list);
    31.  
    32. vertexHelper2.FillMesh (mesh);
    33.  
    34. }
    35.  
    36. }
    37.  
    38. #elif
    39.  
    40. public override void ModifyMesh (VertexHelper vh)
    41.  
    42. {
    43.  
    44. if (!this.IsActive ())
    45.  
    46. return;
    47.  
    48.  
    49.  
    50. List<UIVertex> vertexList = new List<UIVertex> ();
    51.  
    52. vh.GetUIVertexStream (vertexList);
    53.  
    54.  
    55.  
    56. ModifyVertices (vertexList);
    57.  
    58.  
    59.  
    60. vh.Clear ();
    61.  
    62. vh.AddUIVertexTriangleStream (vertexList);
    63.  
    64. }
    65.  
    66. #endif
    67.  
     
    Trisibo likes this.
  34. SirHamsteyr

    SirHamsteyr

    Joined:
    Mar 4, 2014
    Posts:
    4
    @Pratap Dafedar @MaDDoX I'm on Unity 5.2.2f1, and I'm using the combination of the scripts you guys wrote (with some modifications) but it doesn't seem to be working. I keep getting this error:

    the code is as follows:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. /*
    6. Produces an simple tracking/letter-spacing effect on UI Text components.
    7. Set the spacing parameter to adjust letter spacing.
    8.   Negative values cuddle the text up tighter than normal. Go too far and it'll look odd.
    9.   Positive values spread the text out more than normal. This will NOT respect the text area you've defined.
    10.   Zero spacing will present the font with no changes.
    11. Relies on counting off characters in your Text compoennt's text property and
    12. matching those against the quads passed in via the verts array. This is really
    13. rather primative, but I can't see any better way at the moment. It means that
    14. all sorts of things can break the effect...
    15. This component should be placed higher in component list than any other vertex
    16. modifiers that alter the total number of verticies. Eg, place this above Shadow
    17. or Outline effects. If you don't, the outline/shadow won't match the position
    18. of the letters properly. If you place the outline/shadow effect second however,
    19. it will just work on the altered vertices from this component, and function
    20. as expected.
    21. This component works best if you don't allow text to automatically wrap. It also
    22. blows up outside of the given text area. Basically, it's a cheap and dirty effect,
    23. not a clever text layout engine. It can't affect how Unity chooses to break up
    24. your lines. If you manually use line breaks however, it should detect those and
    25. function more or less as you'd expect.
    26. The spacing parameter is measured in pixels multiplied by the font size. This was
    27. chosen such that when you adjust the font size, it does not change the visual spacing
    28. that you've dialed in. There's also a scale factor of 1/100 in this number to
    29. bring it into a comfortable adjustable range. There's no limit on this parameter,
    30. but obviously some values will look quite strange.
    31. This component doesn't really work with Rich Text. You don't need to remember to
    32. turn off Rich Text via the checkbox, but because it can't see what makes a
    33. printable character and what doesn't, it will typically miscount characters when you
    34. use HTML-like tags in your text. Try it out, you'll see what I mean. It doesn't
    35. break down entirely, but it doesn't really do what you'd want either.
    36. */
    37.  
    38. namespace UnityEngine.UI
    39. {
    40.     [AddComponentMenu("UI/Effects/Letter Spacing", 14), RequireComponent(typeof(Text))]
    41. #if UNITY_5_2
    42.     public class LetterSpacing : BaseMeshEffect, ILayoutElement
    43. #else
    44.     public class LetterSpacing : BaseVertexEffect, ILayoutElement
    45. #endif
    46.     {
    47.         [SerializeField]
    48.         private float m_spacing = 0f;
    49.  
    50.         public float spacing
    51.         {
    52.             get { return this.m_spacing; }
    53.             set
    54.             {
    55.                 if (this.m_spacing == value) return;
    56.                 this.m_spacing = value;
    57.                 if (this.graphic != null) this.graphic.SetVerticesDirty();
    58.                 LayoutRebuilder.MarkLayoutForRebuild((RectTransform)this.transform);
    59.             }
    60.         }
    61.  
    62.         private Text text
    63.         {
    64.             get { return this.gameObject.GetComponent<Text>(); }
    65.         }
    66.  
    67.         public float minWidth
    68.         {
    69.             get { return this.text.minWidth; }
    70.         }
    71.  
    72.         public float preferredWidth
    73.         {
    74.             get { return this.text.preferredWidth + ((this.spacing * (float)this.text.fontSize / 100f) * (this.text.text.Length - 1)); }
    75.         }
    76.  
    77.         public float flexibleWidth
    78.         {
    79.             get { return this.text.flexibleWidth; }
    80.         }
    81.  
    82.         public float minHeight
    83.         {
    84.             get { return this.text.minHeight; }
    85.         }
    86.  
    87.         public float preferredHeight
    88.         {
    89.             get { return this.text.preferredHeight; }
    90.         }
    91.  
    92.         public float flexibleHeight
    93.         {
    94.             get { return this.text.flexibleHeight; }
    95.         }
    96.  
    97.         public int layoutPriority
    98.         {
    99.             get { return this.text.layoutPriority; }
    100.         }
    101.  
    102.         protected LetterSpacing() { }
    103.  
    104. #if UNITY_EDITOR
    105.         protected override void OnValidate()
    106.         {
    107.             base.OnValidate();
    108.             this.spacing = this.m_spacing;
    109.             LayoutRebuilder.MarkLayoutForRebuild((RectTransform)this.transform);
    110.         }
    111. #endif
    112.  
    113.         public void CalculateLayoutInputHorizontal() { }
    114.         public void CalculateLayoutInputVertical() { }
    115.  
    116.         private string[] GetLines()
    117.         {
    118.             IList<UILineInfo> lineInfos = text.cachedTextGenerator.lines;
    119.             string[] lines = new string[lineInfos.Count];
    120.  
    121.             for (int i = 0; i < lineInfos.Count; i++)
    122.             {
    123.                 if ((i + 1) < lineInfos.Count)
    124.                 {
    125.                     lines[i] = this.text.text.Substring(lineInfos[i].startCharIdx, lineInfos[i + 1].startCharIdx - 1);
    126.                 }
    127.                 else
    128.                 {
    129.                     lines[i] = this.text.text.Substring(lineInfos[i].startCharIdx);
    130.                 }
    131.             }
    132.  
    133.             return lines;
    134.         }
    135.  
    136.  
    137. #if UNITY_5_2
    138.         public override void ModifyMesh(Mesh mesh)
    139.         {
    140.             if (!this.IsActive())
    141.                 return;
    142.  
    143.             List<UIVertex> list = new List<UIVertex>();
    144.             using (VertexHelper vertexHelper = new VertexHelper(mesh))
    145.             {
    146.                 vertexHelper.GetUIVertexStream(list);
    147.             }
    148.  
    149.             this.ModifyVertices(list);  // calls the old ModifyVertices which was used on pre 5.2
    150.  
    151.             using (VertexHelper vertexHelper2 = new VertexHelper())
    152.             {
    153.                 vertexHelper2.AddUIVertexTriangleStream(list);
    154.                 vertexHelper2.FillMesh(mesh);
    155.             }
    156.         }
    157.  
    158. #else
    159.         public override void ModifyMesh (VertexHelper vh)
    160.         {
    161.             if (!this.IsActive ())
    162.                 return;
    163.             List<UIVertex> vertexList = new List<UIVertex> ();
    164.             vh.GetUIVertexStream (vertexList);
    165.             ModifyVertices (vertexList);
    166.             vh.Clear ();
    167.             vh.AddUIVertexTriangleStream (vertexList);
    168.         }
    169. #endif
    170.  
    171.         public void ModifyVertices(List<UIVertex> verts)
    172.         {
    173.             if (!this.IsActive()) return;
    174.  
    175.             string[] lines = this.GetLines();
    176.  
    177.             Vector3 pos;
    178.             float letterOffset = this.spacing * (float)this.text.fontSize / 100f;
    179.             float alignmentFactor = 0;
    180.             int glyphIdx = 0;
    181.  
    182.             switch (this.text.alignment)
    183.             {
    184.                 case TextAnchor.LowerLeft:
    185.                 case TextAnchor.MiddleLeft:
    186.                 case TextAnchor.UpperLeft:
    187.                     alignmentFactor = 0f;
    188.                     break;
    189.  
    190.                 case TextAnchor.LowerCenter:
    191.                 case TextAnchor.MiddleCenter:
    192.                 case TextAnchor.UpperCenter:
    193.                     alignmentFactor = 0.5f;
    194.                     break;
    195.  
    196.                 case TextAnchor.LowerRight:
    197.                 case TextAnchor.MiddleRight:
    198.                 case TextAnchor.UpperRight:
    199.                     alignmentFactor = 1f;
    200.                     break;
    201.             }
    202.  
    203.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    204.             {
    205.                 string line = lines[lineIdx];
    206.                 float lineOffset = ((line.Length - 1) * letterOffset) * alignmentFactor;
    207.  
    208.                 for (int charIdx = 0; charIdx < line.Length; charIdx++)
    209.                 {
    210.                     int idx1 = glyphIdx * 6 + 0;
    211.                     int idx2 = glyphIdx * 6 + 1;
    212.                     int idx3 = glyphIdx * 6 + 2;
    213.                     int idx4 = glyphIdx * 6 + 3;
    214.                     int idx5 = glyphIdx * 6 + 4;
    215.                     int idx6 = glyphIdx * 6 + 5;
    216.  
    217.                     // Check for truncated text (doesn't generate verts for all characters)
    218.                     if (idx6 > verts.Count - 1) return;
    219.  
    220.                     UIVertex vert1 = verts[idx1];
    221.                     UIVertex vert2 = verts[idx2];
    222.                     UIVertex vert3 = verts[idx3];
    223.                     UIVertex vert4 = verts[idx4];
    224.                     UIVertex vert5 = verts[idx5];
    225.                     UIVertex vert6 = verts[idx6];
    226.  
    227.                     pos = Vector3.right * ((letterOffset * charIdx) - lineOffset);
    228.  
    229.                     vert1.position += pos;
    230.                     vert2.position += pos;
    231.                     vert3.position += pos;
    232.                     vert4.position += pos;
    233.                     vert5.position += pos;
    234.                     vert6.position += pos;
    235.  
    236.                     verts[idx1] = vert1;
    237.                     verts[idx2] = vert2;
    238.                     verts[idx3] = vert3;
    239.                     verts[idx4] = vert4;
    240.                     verts[idx5] = vert5;
    241.                     verts[idx6] = vert6;
    242.  
    243.                     glyphIdx++;
    244.                 }
    245.  
    246.                 // Offset for carriage return character that still generates verts
    247.                 glyphIdx++;
    248.             }
    249.         }
    250.     }
    251. }
     
  35. jo-seemoore

    jo-seemoore

    Joined:
    Apr 1, 2015
    Posts:
    3
    Here is a working version for Unity 5.2.2+

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. /*
    6. http://forum.unity3d.com/threads/adjustable-character-spacing-free-script.288277/
    7.  
    8. Unity 5.1 and 5.2.2+ compatible
    9.  
    10. Produces an simple tracking/letter-spacing effect on UI Text components.
    11. Set the spacing parameter to adjust letter spacing.
    12. Negative values cuddle the text up tighter than normal. Go too far and it'll look odd.
    13. Positive values spread the text out more than normal. This will NOT respect the text area you've defined.
    14. Zero spacing will present the font with no changes.
    15.  
    16. Relies on counting off characters in your Text compoennt's text property and
    17. matching those against the quads passed in via the verts array. This is really
    18. rather primative, but I can't see any better way at the moment. It means that
    19. all sorts of things can break the effect...
    20. This component should be placed higher in component list than any other vertex
    21. modifiers that alter the total number of verticies. Eg, place this ABOVE Shadow
    22. or Outline effects. If you don't, the outline/shadow won't match the position
    23. of the letters properly. If you place the outline/shadow effect second however,
    24. it will just work on the altered vertices from this component, and function
    25. as expected.
    26. This component works best if you don't allow text to automatically wrap. It also
    27. blows up outside of the given text area. Basically, it's a cheap and dirty effect,
    28. not a clever text layout engine. It can't affect how Unity chooses to break up
    29. your lines. If you manually use line breaks however, it should detect those and
    30. function more or less as you'd expect.
    31. The spacing parameter is measured in pixels multiplied by the font size. This was
    32. chosen such that when you adjust the font size, it does not change the visual spacing
    33. that you've dialed in. There's also a scale factor of 1/100 in this number to
    34. bring it into a comfortable adjustable range. There's no limit on this parameter,
    35. but obviously some values will look quite strange.
    36. This component doesn't really work with Rich Text. You don't need to remember to
    37. turn off Rich Text via the checkbox, but because it can't see what makes a
    38. printable character and what doesn't, it will typically miscount characters when you
    39. use HTML-like tags in your text. Try it out, you'll see what I mean. It doesn't
    40. break down entirely, but it doesn't really do what you'd want either.
    41. */
    42.  
    43. namespace UnityEngine.UI
    44. {
    45.     [AddComponentMenu("UI/Effects/Letter Spacing", 14)]
    46. #if UNITY_5_2
    47.     public class LetterSpacing : BaseMeshEffect
    48. #else
    49.     public class LetterSpacing : BaseVertexEffect
    50. #endif
    51.     {
    52.         [SerializeField]
    53.         private float m_spacing = 0f;
    54.  
    55.         protected LetterSpacing() { }
    56.  
    57. #if UNITY_EDITOR
    58.         protected override void OnValidate()
    59.         {
    60.             spacing = m_spacing;
    61.             base.OnValidate();
    62.         }
    63. #endif
    64.  
    65.         public float spacing
    66.         {
    67.             get { return m_spacing; }
    68.             set
    69.             {
    70.                 if (m_spacing == value) return;
    71.                 m_spacing = value;
    72.                 if (graphic != null) graphic.SetVerticesDirty();
    73.             }
    74.         }
    75.  
    76. #if UNITY_5_2
    77.  
    78.         /**
    79.         * Note: Unity 5.2.1 ModifyMesh(Mesh mesh) used VertexHelper.FillMesh(mesh);
    80.         * For performance reasons, ModifyMesh(VertexHelper vh) was introduced
    81.         * @see http://forum.unity3d.com/threads/unity-5-2-ui-performance-seems-much-worse.353650/
    82.         */
    83.         public override void ModifyMesh(VertexHelper vh)
    84.         {
    85.             if (!this.IsActive())
    86.                 return;
    87.  
    88.             List<UIVertex> list = new List<UIVertex>();
    89.             vh.GetUIVertexStream(list);
    90.  
    91.             ModifyVertices(list);
    92.  
    93.             vh.Clear();
    94.             vh.AddUIVertexTriangleStream(list);
    95.         }
    96.  
    97.         public void ModifyVertices(List<UIVertex> verts)
    98.         {
    99.             if (!IsActive()) return;
    100.  
    101.             Text text = GetComponent<Text>();
    102.             if (text == null)
    103.             {
    104.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    105.                 return;
    106.             }
    107.  
    108.             string[] lines = text.text.Split('\n');
    109.             Vector3 pos;
    110.             float letterOffset = spacing * (float)text.fontSize / 100f;
    111.             float alignmentFactor = 0;
    112.             int glyphIdx = 0;
    113.  
    114.             switch (text.alignment)
    115.             {
    116.                 case TextAnchor.LowerLeft:
    117.                 case TextAnchor.MiddleLeft:
    118.                 case TextAnchor.UpperLeft:
    119.                     alignmentFactor = 0f;
    120.                     break;
    121.  
    122.                 case TextAnchor.LowerCenter:
    123.                 case TextAnchor.MiddleCenter:
    124.                 case TextAnchor.UpperCenter:
    125.                     alignmentFactor = 0.5f;
    126.                     break;
    127.  
    128.                 case TextAnchor.LowerRight:
    129.                 case TextAnchor.MiddleRight:
    130.                 case TextAnchor.UpperRight:
    131.                     alignmentFactor = 1f;
    132.                     break;
    133.             }
    134.  
    135.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    136.             {
    137.                 string line = lines[lineIdx];
    138.                 float lineOffset = (line.Length - 1) * letterOffset * alignmentFactor;
    139.  
    140.                 for (int charIdx = 0; charIdx < line.Length; charIdx++)
    141.                 {
    142.                     int idx1 = glyphIdx * 6 + 0;
    143.                     int idx2 = glyphIdx * 6 + 1;
    144.                     int idx3 = glyphIdx * 6 + 2;
    145.                     int idx4 = glyphIdx * 6 + 3;
    146.                     int idx5 = glyphIdx * 6 + 4;
    147.                     int idx6 = glyphIdx * 6 + 5;
    148.  
    149.                     // Check for truncated text (doesn't generate verts for all characters)
    150.                     if (idx6 > verts.Count - 1) return;
    151.  
    152.                     UIVertex vert1 = verts[idx1];
    153.                     UIVertex vert2 = verts[idx2];
    154.                     UIVertex vert3 = verts[idx3];
    155.                     UIVertex vert4 = verts[idx4];
    156.                     UIVertex vert5 = verts[idx5];
    157.                     UIVertex vert6 = verts[idx6];
    158.  
    159.                     pos = Vector3.right * (letterOffset * charIdx - lineOffset);
    160.  
    161.                     vert1.position += pos;
    162.                     vert2.position += pos;
    163.                     vert3.position += pos;
    164.                     vert4.position += pos;
    165.                     vert5.position += pos;
    166.                     vert6.position += pos;
    167.  
    168.                     verts[idx1] = vert1;
    169.                     verts[idx2] = vert2;
    170.                     verts[idx3] = vert3;
    171.                     verts[idx4] = vert4;
    172.                     verts[idx5] = vert5;
    173.                     verts[idx6] = vert6;
    174.  
    175.                     glyphIdx++;
    176.                 }
    177.  
    178.                 // Offset for carriage return character that still generates verts
    179.                 glyphIdx++;
    180.             }
    181.         }
    182. #else
    183.         public override void ModifyVertices(List<UIVertex> verts)
    184.         {
    185.             if (! IsActive()) return;
    186.          
    187.             Text text = GetComponent<Text>();
    188.             if (text == null)
    189.             {
    190.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    191.                 return;
    192.             }
    193.          
    194.             string[] lines = text.text.Split('\n');
    195.             Vector3  pos;
    196.             float    letterOffset    = spacing * (float)text.fontSize / 100f;
    197.             float    alignmentFactor = 0;
    198.             int      glyphIdx        = 0;
    199.          
    200.             switch (text.alignment)
    201.             {
    202.             case TextAnchor.LowerLeft:
    203.             case TextAnchor.MiddleLeft:
    204.             case TextAnchor.UpperLeft:
    205.                 alignmentFactor = 0f;
    206.                 break;
    207.              
    208.             case TextAnchor.LowerCenter:
    209.             case TextAnchor.MiddleCenter:
    210.             case TextAnchor.UpperCenter:
    211.                 alignmentFactor = 0.5f;
    212.                 break;
    213.              
    214.             case TextAnchor.LowerRight:
    215.             case TextAnchor.MiddleRight:
    216.             case TextAnchor.UpperRight:
    217.                 alignmentFactor = 1f;
    218.                 break;
    219.             }
    220.          
    221.             for (int lineIdx=0; lineIdx < lines.Length; lineIdx++)
    222.             {
    223.                 string line = lines[lineIdx];
    224.                 float lineOffset = (line.Length -1) * letterOffset * alignmentFactor;
    225.              
    226.                 for (int charIdx = 0; charIdx < line.Length; charIdx++)
    227.                 {
    228.                     int idx1 = glyphIdx * 4 + 0;
    229.                     int idx2 = glyphIdx * 4 + 1;
    230.                     int idx3 = glyphIdx * 4 + 2;
    231.                     int idx4 = glyphIdx * 4 + 3;
    232.                  
    233.                     // Check for truncated text (doesn't generate verts for all characters)
    234.                     if (idx4 > verts.Count - 1) return;
    235.                  
    236.                     UIVertex vert1 = verts[idx1];
    237.                     UIVertex vert2 = verts[idx2];
    238.                     UIVertex vert3 = verts[idx3];
    239.                     UIVertex vert4 = verts[idx4];
    240.                  
    241.                     pos = Vector3.right * (letterOffset * charIdx - lineOffset);
    242.                  
    243.                     vert1.position += pos;
    244.                     vert2.position += pos;
    245.                     vert3.position += pos;
    246.                     vert4.position += pos;
    247.                  
    248.                     verts[idx1] = vert1;
    249.                     verts[idx2] = vert2;
    250.                     verts[idx3] = vert3;
    251.                     verts[idx4] = vert4;
    252.                  
    253.                     glyphIdx++;
    254.                 }
    255.              
    256.                 // Offset for carriage return character that still generates verts
    257.                 glyphIdx++;
    258.             }
    259.         }
    260. #endif
    261.     }
    262. }

    You can get more info about the changes here:
    http://forum.unity3d.com/threads/unity-5-2-ui-performance-seems-much-worse.353650/
     
    Trisibo and Tactical_Beard like this.
  36. Grosswood

    Grosswood

    Joined:
    May 18, 2015
    Posts:
    1
    Thank you so much for the script guys!
     
  37. QI

    QI

    Joined:
    Oct 27, 2012
    Posts:
    229

    Thanks you for the changes for 5.2.2, it works perfect !
     
  38. ArshakKroyan

    ArshakKroyan

    Joined:
    Mar 4, 2015
    Posts:
    32
    Hi. First of all thanks to Deeperbeige and all modifiers.
    This is very useful and great script.

    I think my modification also will help others.
    I have added RichText support, so you can use this script with <size>, <b>, <i>, <color>, <material> tags.



    But be careful using big texts because there are problems with wrapping
    (I tried to use ChoMPi's modified versin for wrapping, but It works with some bugs).

    I think there are some cases to make better.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Text.RegularExpressions;
    5.  
    6. /*
    7. http://forum.unity3d.com/threads/adjustable-character-spacing-free-script.288277/
    8. Unity 5.1 and 5.2.2+ compatible
    9. Produces an simple tracking/letter-spacing effect on UI Text components.
    10. Set the spacing parameter to adjust letter spacing.
    11.  
    12. Negative values cuddle the text up tighter than normal. Go too far and it'll look odd.
    13. Positive values spread the text out more than normal. This will NOT respect the text area you've defined.
    14. Zero spacing will present the font with no changes.
    15. Relies on counting off characters in your Text compoennt's text property and
    16. matching those against the quads passed in via the verts array. This is really
    17. rather primative, but I can't see any better way at the moment. It means that
    18. all sorts of things can break the effect...
    19.  
    20. This component should be placed higher in component list than any other vertex
    21. modifiers that alter the total number of verticies. Eg, place this ABOVE Shadow
    22. or Outline effects. If you don't, the outline/shadow won't match the position
    23. of the letters properly. If you place the outline/shadow effect second however,
    24. it will just work on the altered vertices from this component, and function
    25. as expected.
    26.  
    27. This component works best if you don't allow text to automatically wrap. It also
    28. blows up outside of the given text area. Basically, it's a cheap and dirty effect,
    29. not a clever text layout engine. It can't affect how Unity chooses to break up
    30. your lines. If you manually use line breaks however, it should detect those and
    31. function more or less as you'd expect.
    32.  
    33. The spacing parameter is measured in pixels multiplied by the font size. This was
    34. chosen such that when you adjust the font size, it does not change the visual spacing
    35. that you've dialed in. There's also a scale factor of 1/100 in this number to
    36. bring it into a comfortable adjustable range. There's no limit on this parameter,
    37. but obviously some values will look quite strange.
    38.  
    39. Now component works with RichText. You need to remember to turn on RichText via the checkbox (text.supportRichText)
    40. and turn on component's [useRichText] checkbox.
    41. */
    42.  
    43. namespace UnityEngine.UI
    44. {
    45.     [AddComponentMenu("UI/Effects/Letter Spacing", 15)]
    46. #if UNITY_5_2
    47.     public class LetterSpacing : BaseMeshEffect
    48. #else
    49.     public class LetterSpacing : BaseVertexEffect
    50. #endif
    51.     {
    52.         private const string SupportedTagRegexPattersn = @"<b>|</b>|<i>|</i>|<size=.*?>|</size>|<color=.*?>|</color>|<material=.*?>|</material>";
    53.         [SerializeField]
    54.         private bool useRichText;
    55.  
    56.         [SerializeField]
    57.         private float m_spacing = 0f;
    58.  
    59.         protected LetterSpacing() { }
    60.  
    61. #if UNITY_EDITOR
    62.         protected override void OnValidate()
    63.         {
    64.             spacing = m_spacing;
    65.             base.OnValidate();
    66.         }
    67. #endif
    68.  
    69.         public float spacing
    70.         {
    71.             get { return m_spacing; }
    72.             set
    73.             {
    74.                 if (m_spacing == value) return;
    75.                 m_spacing = value;
    76.                 if (graphic != null) graphic.SetVerticesDirty();
    77.             }
    78.         }
    79.  
    80. #if UNITY_5_2
    81.  
    82.         /**
    83.         * Note: Unity 5.2.1 ModifyMesh(Mesh mesh) used VertexHelper.FillMesh(mesh);
    84.         * For performance reasons, ModifyMesh(VertexHelper vh) was introduced
    85.         * @see http://forum.unity3d.com/threads/unity-5-2-ui-performance-seems-much-worse.353650/
    86.         */
    87.         public override void ModifyMesh(VertexHelper vh)
    88.         {
    89.             if (!this.IsActive())
    90.                 return;
    91.  
    92.             List<UIVertex> list = new List<UIVertex>();
    93.             vh.GetUIVertexStream(list);
    94.  
    95.             ModifyVertices(list);
    96.  
    97.             vh.Clear();
    98.             vh.AddUIVertexTriangleStream(list);
    99.         }
    100.  
    101.         public void ModifyVertices(List<UIVertex> verts)
    102.         {
    103.             if (!IsActive()) return;
    104.  
    105.             Text text = GetComponent<Text>();
    106.             if (text == null)
    107.             {
    108.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    109.                 return;
    110.             }
    111.  
    112.             string[] lines = text.text.Split('\n');
    113.             Vector3 pos;
    114.             float letterOffset = spacing * (float)text.fontSize / 100f;
    115.             float alignmentFactor = 0;
    116.             int glyphIdx = 0;
    117.  
    118.             bool isRichText = useRichText && text.supportRichText;
    119.             IEnumerator matchedTagCollection = null; //when using RichText this will collect all tags (index, length, value)
    120.             Match currentMatchedTag = null;
    121.  
    122.             switch (text.alignment)
    123.             {
    124.                 case TextAnchor.LowerLeft:
    125.                 case TextAnchor.MiddleLeft:
    126.                 case TextAnchor.UpperLeft:
    127.                     alignmentFactor = 0f;
    128.                     break;
    129.  
    130.                 case TextAnchor.LowerCenter:
    131.                 case TextAnchor.MiddleCenter:
    132.                 case TextAnchor.UpperCenter:
    133.                     alignmentFactor = 0.5f;
    134.                     break;
    135.  
    136.                 case TextAnchor.LowerRight:
    137.                 case TextAnchor.MiddleRight:
    138.                 case TextAnchor.UpperRight:
    139.                     alignmentFactor = 1f;
    140.                     break;
    141.             }
    142.  
    143.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    144.             {
    145.                 string line = lines[lineIdx];
    146.                 int lineLength = line.Length;
    147.  
    148.                 if (isRichText)
    149.                 {
    150.                     matchedTagCollection = GetRegexMatchedTagCollection(line, out lineLength);
    151.                     currentMatchedTag = null;
    152.                     if (matchedTagCollection.MoveNext())
    153.                     {
    154.                         currentMatchedTag = (Match)matchedTagCollection.Current;
    155.                     }
    156.                 }
    157.  
    158.                 float lineOffset = (lineLength - 1) * letterOffset * alignmentFactor;
    159.  
    160.                 for (int charIdx = 0, charPositionIndex = 0; charIdx < line.Length; charIdx++, charPositionIndex++)
    161.                 {
    162.                     if (isRichText)
    163.                     {
    164.                         if (currentMatchedTag != null && currentMatchedTag.Index == charIdx)
    165.                         {
    166.                             charIdx = currentMatchedTag.Length + currentMatchedTag.Index - 1;
    167.                             glyphIdx = currentMatchedTag.Length + currentMatchedTag.Index - 1;
    168.                             charPositionIndex--;
    169.                             currentMatchedTag = null;
    170.                             if (matchedTagCollection.MoveNext())
    171.                             {
    172.                                 currentMatchedTag = (Match)matchedTagCollection.Current;
    173.                             }
    174.                         }
    175.                     }
    176.  
    177.                     int idx1 = glyphIdx * 6 + 0;
    178.                     int idx2 = glyphIdx * 6 + 1;
    179.                     int idx3 = glyphIdx * 6 + 2;
    180.                     int idx4 = glyphIdx * 6 + 3;
    181.                     int idx5 = glyphIdx * 6 + 4;
    182.                     int idx6 = glyphIdx * 6 + 5;
    183.  
    184.                     // Check for truncated text (doesn't generate verts for all characters)
    185.                     if (idx6 > verts.Count - 1) return;
    186.  
    187.                     UIVertex vert1 = verts[idx1];
    188.                     UIVertex vert2 = verts[idx2];
    189.                     UIVertex vert3 = verts[idx3];
    190.                     UIVertex vert4 = verts[idx4];
    191.                     UIVertex vert5 = verts[idx5];
    192.                     UIVertex vert6 = verts[idx6];
    193.  
    194.                     pos = Vector3.right * (letterOffset * charPositionIndex - lineOffset);
    195.  
    196.                     vert1.position += pos;
    197.                     vert2.position += pos;
    198.                     vert3.position += pos;
    199.                     vert4.position += pos;
    200.                     vert5.position += pos;
    201.                     vert6.position += pos;
    202.  
    203.                     verts[idx1] = vert1;
    204.                     verts[idx2] = vert2;
    205.                     verts[idx3] = vert3;
    206.                     verts[idx4] = vert4;
    207.                     verts[idx5] = vert5;
    208.                     verts[idx6] = vert6;
    209.  
    210.                     glyphIdx++;
    211.                 }
    212.  
    213.                 // Offset for carriage return character that still generates verts
    214.                 glyphIdx++;
    215.             }
    216.         }
    217. #else
    218.         public override void ModifyVertices(List<UIVertex> verts)
    219.         {
    220.             if (!IsActive()) return;
    221.  
    222.             Text text = GetComponent<Text>();
    223.             if (text == null)
    224.             {
    225.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    226.                 return;
    227.             }
    228.  
    229.             string[] lines = text.text.Split('\n');
    230.             Vector3  pos;
    231.             float    letterOffset    = spacing * (float)text.fontSize / 100f;
    232.             float    alignmentFactor = 0;
    233.             int      glyphIdx        = 0;
    234.  
    235.             bool isRichText = useRichText && text.supportRichText;
    236.             IEnumerator matchedTagCollection = null; //when using RichText this will collect all tags (index, length, value)
    237.             Match currentMatchedTag = null;
    238.            
    239.             switch (text.alignment)
    240.             {
    241.                 case TextAnchor.LowerLeft:
    242.                 case TextAnchor.MiddleLeft:
    243.                 case TextAnchor.UpperLeft:
    244.                     alignmentFactor = 0f;
    245.                     break;
    246.  
    247.                 case TextAnchor.LowerCenter:
    248.                 case TextAnchor.MiddleCenter:
    249.                 case TextAnchor.UpperCenter:
    250.                     alignmentFactor = 0.5f;
    251.                     break;
    252.  
    253.                 case TextAnchor.LowerRight:
    254.                 case TextAnchor.MiddleRight:
    255.                 case TextAnchor.UpperRight:
    256.                     alignmentFactor = 1f;
    257.                     break;
    258.             }
    259.  
    260.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    261.             {
    262.                 string line = lines[lineIdx];
    263.  
    264.                 int lineLength = line.Length;
    265.  
    266.                 if (isRichText)
    267.                 {
    268.                     matchedTagCollection = GetRegexMatchedTagCollection(line, out lineLength);
    269.                     currentMatchedTag = null;
    270.                     if (matchedTagCollection.MoveNext())
    271.                     {
    272.                         currentMatchedTag = (Match)matchedTagCollection.Current;
    273.                     }
    274.                 }
    275.  
    276.                 float lineOffset = (lineLength -1) * letterOffset * alignmentFactor;
    277.  
    278.                 for (int charIdx = 0, charPositionIndex =0; charIdx < line.Length; charIdx++, charPositionIndex++)
    279.                 {
    280.                     if (isRichText)
    281.                     {
    282.                         if (currentMatchedTag != null && currentMatchedTag.Index == charIdx)
    283.                         {
    284.                             charIdx = currentMatchedTag.Length + currentMatchedTag.Index - 1;
    285.                             glyphIdx = currentMatchedTag.Length + currentMatchedTag.Index - 1;
    286.                             charPositionIndex--;
    287.                             currentMatchedTag = null;
    288.                             if (matchedTagCollection.MoveNext())
    289.                             {
    290.                                 currentMatchedTag = (Match)matchedTagCollection.Current;
    291.                             }
    292.                         }
    293.                     }
    294.  
    295.                     int idx1 = glyphIdx * 4 + 0;
    296.                     int idx2 = glyphIdx * 4 + 1;
    297.                     int idx3 = glyphIdx * 4 + 2;
    298.                     int idx4 = glyphIdx * 4 + 3;
    299.  
    300.                     // Check for truncated text (doesn't generate verts for all characters)
    301.                     if (idx4 > verts.Count - 1) return;
    302.  
    303.                     UIVertex vert1 = verts[idx1];
    304.                     UIVertex vert2 = verts[idx2];
    305.                     UIVertex vert3 = verts[idx3];
    306.                     UIVertex vert4 = verts[idx4];
    307.  
    308.                     pos = Vector3.right * (letterOffset * charPositionIndex - lineOffset);
    309.  
    310.                     vert1.position += pos;
    311.                     vert2.position += pos;
    312.                     vert3.position += pos;
    313.                     vert4.position += pos;
    314.  
    315.                     verts[idx1] = vert1;
    316.                     verts[idx2] = vert2;
    317.                     verts[idx3] = vert3;
    318.                     verts[idx4] = vert4;
    319.  
    320.                     glyphIdx++;
    321.                 }
    322.  
    323.                 // Offset for carriage return character that still generates verts
    324.                 glyphIdx++;
    325.             }
    326.         }
    327. #endif
    328.  
    329.         private IEnumerator GetRegexMatchedTagCollection(string line, out int lineLengthWithoutTags)
    330.         {
    331.             MatchCollection matchedTagCollection = Regex.Matches(line,SupportedTagRegexPattersn);
    332.             lineLengthWithoutTags = 0;
    333.             int tagsLength = 0;
    334.  
    335.             if (matchedTagCollection.Count > 0)
    336.             {
    337.                 foreach (Match matchedTag in matchedTagCollection)
    338.                 {
    339.                     tagsLength += matchedTag.Length;
    340.                 }
    341.             }
    342.             lineLengthWithoutTags = line.Length - tagsLength;
    343.             return matchedTagCollection.GetEnumerator();
    344.         }
    345.     }
    346. }
     
    Trisibo likes this.
  39. Rob-Reijnen

    Rob-Reijnen

    Joined:
    Oct 14, 2013
    Posts:
    60
    Exactly what I needed thanks!
     
  40. gecko

    gecko

    Joined:
    Aug 10, 2006
    Posts:
    2,240
    Trying this in Unity 5.3, and there is no Spacing field in the inspector, and I get two errors:

    Assets/02Scripts/LetterSpacing.cs(49,18): error CS0619: `UnityEngine.UI.BaseVertexEffect' is obsolete: `Use BaseMeshEffect instead'

    Assets/02Scripts/LetterSpacing.cs(62,33): error CS0115: `UnityEngine.UI.LetterSpacing.OnValidate()' is marked as an override but no suitable method found to override

    Are those just me, or a 5.3 issue?
     
  41. jasonoda

    jasonoda

    Joined:
    Sep 7, 2012
    Posts:
    67
    In 5.3 I get those two errors too. Can anybody figure out how to update?
     
  42. Megalon2D

    Megalon2D

    Joined:
    Nov 29, 2015
    Posts:
    8
    I'm using Unity 5.3 and I'm having the same problem as geko and jasonoda.
    Does anyone have a fixed version of the script for 5.3?
     
  43. The-Paper

    The-Paper

    Joined:
    Jul 11, 2015
    Posts:
    2
    I fixed it for 5.3 minutes ago.
    Good job.
    잘 쓰세요.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5.  
    6. /*
    7.  
    8. Produces an simple tracking/letter-spacing effect on UI Text components.
    9.  
    10. Set the spacing parameter to adjust letter spacing.
    11.   Negative values cuddle the text up tighter than normal. Go too far and it'll look odd.
    12.   Positive values spread the text out more than normal. This will NOT respect the text area you've defined.
    13.   Zero spacing will present the font with no changes.
    14.  
    15. Relies on counting off characters in your Text compoennt's text property and
    16. matching those against the quads passed in via the verts array. This is really
    17. rather primative, but I can't see any better way at the moment. It means that
    18. all sorts of things can break the effect...
    19.  
    20. This component should be placed higher in component list than any other vertex
    21. modifiers that alter the total number of verticies. Eg, place this above Shadow
    22. or Outline effects. If you don't, the outline/shadow won't match the position
    23. of the letters properly. If you place the outline/shadow effect second however,
    24. it will just work on the altered vertices from this component, and function
    25. as expected.
    26.  
    27. This component works best if you don't allow text to automatically wrap. It also
    28. blows up outside of the given text area. Basically, it's a cheap and dirty effect,
    29. not a clever text layout engine. It can't affect how Unity chooses to break up
    30. your lines. If you manually use line breaks however, it should detect those and
    31. function more or less as you'd expect.
    32.  
    33. The spacing parameter is measured in pixels multiplied by the font size. This was
    34. chosen such that when you adjust the font size, it does not change the visual spacing
    35. that you've dialed in. There's also a scale factor of 1/100 in this number to
    36. bring it into a comfortable adjustable range. There's no limit on this parameter,
    37. but obviously some values will look quite strange.
    38.  
    39. This component doesn't really work with Rich Text. You don't need to remember to
    40. turn off Rich Text via the checkbox, but because it can't see what makes a
    41. printable character and what doesn't, it will typically miscount characters when you
    42. use HTML-like tags in your text. Try it out, you'll see what I mean. It doesn't
    43. break down entirely, but it doesn't really do what you'd want either.
    44.  
    45. */
    46.  
    47. namespace UnityEngine.UI
    48. {
    49.     [AddComponentMenu("UI/Effects/Letter Spacing", 14)]
    50. #if UNITY_5_2 || UNITY_5_3
    51.     public class LetterSpacing : BaseMeshEffect
    52. #else
    53.     public class LetterSpacing : BaseVertexEffect
    54. #endif
    55.     {
    56.         [SerializeField]
    57.         private float m_spacing = 0f;
    58.        
    59.         protected LetterSpacing() { }
    60.        
    61. #if UNITY_EDITOR
    62.         protected override void OnValidate()
    63.         {
    64.             spacing = m_spacing;
    65.             base.OnValidate();
    66.         }
    67. #endif
    68.        
    69.         public float spacing
    70.         {
    71.             get { return m_spacing; }
    72.             set
    73.             {
    74.                 if (m_spacing == value) return;
    75.                 m_spacing = value;
    76.                 if (graphic != null) graphic.SetVerticesDirty();
    77.             }
    78.         }
    79. #if UNITY_5_2 || UNITY_5_3
    80.         public void ModifyVertices(List<UIVertex> verts)
    81. #else
    82.     public override void ModifyVertices(List<UIVertex> verts)
    83. #endif
    84.         {
    85.             if (! IsActive()) return;
    86.            
    87.             Text text = GetComponent<Text>();
    88.             if (text == null)
    89.             {
    90.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    91.                 return;
    92.             }
    93.            
    94.             string[] lines = text.text.Split('\n');
    95.             Vector3  pos;
    96.             float    letterOffset    = spacing * (float)text.fontSize / 100f;
    97.             float    alignmentFactor = 0;
    98.             int      glyphIdx        = 0;
    99.            
    100.             switch (text.alignment)
    101.             {
    102.             case TextAnchor.LowerLeft:
    103.             case TextAnchor.MiddleLeft:
    104.             case TextAnchor.UpperLeft:
    105.                 alignmentFactor = 0f;
    106.                 break;
    107.                
    108.             case TextAnchor.LowerCenter:
    109.             case TextAnchor.MiddleCenter:
    110.             case TextAnchor.UpperCenter:
    111.                 alignmentFactor = 0.5f;
    112.                 break;
    113.                
    114.             case TextAnchor.LowerRight:
    115.             case TextAnchor.MiddleRight:
    116.             case TextAnchor.UpperRight:
    117.                 alignmentFactor = 1f;
    118.                 break;
    119.             }
    120.             for (int lineIdx=0; lineIdx < lines.Length; lineIdx++)
    121.             {
    122.                 string line = lines[lineIdx];
    123.                 float lineOffset = (line.Length -1) * letterOffset * alignmentFactor;
    124.                
    125.                 for (int charIdx = 0; charIdx < line.Length; charIdx++)
    126.                 {
    127.                     int idx1 = glyphIdx * 6 + 0;
    128.                     int idx2 = glyphIdx * 6 + 1;
    129.                     int idx3 = glyphIdx * 6 + 2;
    130.                     int idx4 = glyphIdx * 6 + 3;
    131.                     int idx5 = glyphIdx * 6 + 4;
    132.                     int idx6 = glyphIdx * 6 + 5;
    133.  
    134.                     // Check for truncated text (doesn't generate verts for all characters)
    135.                     if (idx4 > verts.Count - 1) return;
    136.                    
    137.                     UIVertex vert1 = verts[idx1];
    138.                     UIVertex vert2 = verts[idx2];
    139.                     UIVertex vert3 = verts[idx3];
    140.                     UIVertex vert4 = verts[idx4];
    141.                     UIVertex vert5 = verts[idx5];
    142.                     UIVertex vert6 = verts[idx6];
    143.  
    144.                     pos = Vector3.right * (letterOffset * charIdx - lineOffset);
    145.                    
    146.                     vert1.position += pos;
    147.                     vert2.position += pos;
    148.                     vert3.position += pos;
    149.                     vert4.position += pos;
    150.                     vert5.position += pos;
    151.                     vert6.position += pos;
    152.  
    153.                     verts[idx1] = vert1;
    154.                     verts[idx2] = vert2;
    155.                     verts[idx3] = vert3;
    156.                     verts[idx4] = vert4;
    157.                     verts[idx5] = vert5;
    158.                     verts[idx6] = vert6;
    159.  
    160.                     glyphIdx++;
    161.                 }
    162.                
    163.                 // Offset for carriage return character that still generates verts
    164.                 glyphIdx++;
    165.             }
    166.         }
    167.  
    168. #if UNITY_5_2
    169.         public override void ModifyMesh(Mesh mesh)
    170.         {
    171.             if (!this.IsActive())
    172.                 return;
    173.  
    174.             List<UIVertex> list = new List<UIVertex>();
    175.             using (VertexHelper vertexHelper = new VertexHelper(mesh))
    176.             {
    177.                 vertexHelper.GetUIVertexStream(list);
    178.             }
    179.  
    180.             ModifyVertices(list);  // calls the old ModifyVertices which was used on pre 5.2
    181.  
    182.             using (VertexHelper vertexHelper2 = new VertexHelper())
    183.             {
    184.                 vertexHelper2.AddUIVertexTriangleStream(list);
    185.                 vertexHelper2.FillMesh(mesh);
    186.             }
    187.         }
    188. #elif UNITY_5_3
    189.         public override void ModifyMesh(VertexHelper vh)
    190.         {
    191.             if (!this.IsActive())
    192.                 return;
    193.  
    194.             List<UIVertex> vertexList = new List<UIVertex>();
    195.             vh.GetUIVertexStream(vertexList);
    196.  
    197.             ModifyVertices(vertexList);
    198.  
    199.             vh.Clear();
    200.             vh.AddUIVertexTriangleStream(vertexList);
    201.         }
    202.     }
    203. #endif
    204.  
    205. }
    206.  
     
    doggan likes this.
  44. djoshi

    djoshi

    Joined:
    Mar 28, 2014
    Posts:
    182
    Hi, Thanks but this script also still gives this error in Unity 5.2.3
    Assets/Scripts/LetterSpacing.
    cs(51,22): error CS0534: `UnityEngine.UI.LetterSpacing' does not implement inherited abstract member `UnityEngine.UI.BaseMeshEffect.ModifyMesh(UnityEngine.UI.VertexHelper)'
     
  45. BonyYousuf

    BonyYousuf

    Joined:
    Aug 22, 2013
    Posts:
    110
    I am using it on Unity 5.3 and it's working great. However I am using it with InputField and the cursor is not adjusting to the new spacing. Any idea, how I can fix that?
     
  46. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    224
    I have not checked InputField so the caret problem is most probably still there, but I merged ArshakKroyan's Rich Text support with The Paper's Unity 5.3+ support. Additionally I made the following modifications:

    • Support Rich Text on multiple lines
    Now you can use Rich Text tags on the 2nd, 3rd lines, etc. and also span a single tag over multiple lines.

    Just changed the


    Code (CSharp):
    1. charIdx = currentMatchedTag.Length + currentMatchedTag.Index - 1;
    2. glyphIdx = currentMatchedTag.Length + currentMatchedTag.Index - 1;
    3. charPositionIndex--;
    to the corresponding relative operations (I also renamed charPositionIndex to actualCharIndex to show it's the index of a non-tag character):


    Code (CSharp):
    1. // skip matched RichText tag
    2. charIdx += currentMatchedTag.Length - 1;
    3. actualCharIndex--;
    4. glyphIdx += currentMatchedTag.Length;
    5. continue;
    (decrements are just here to cancel natural for loop increments; indices may become -1 if the text starts with a tag, but continue ensures the loop body is not executed anyway)

    This way,
    glyphIdx will not be reset on each line iteration and new lines will be correctly offset instead of characters being offset from the 1st line repeatedly. Note that I also immediately continue if we are dealing with a RichText tag, since offsetting an invisible character is not useful.

    • Later versions of Unity 5 will use the Unity 5.2 and 5.3 branches of the script
    UPDATE: use UNITY_X_Y_OR_NEWER macro instead of "#if UNITY_5 && !UNITY_5_0 && !UNITY_5_1"

    I just
    used a complementary test

    Code (CSharp):
    1. #if UNITY_5_2_OR_NEWER
    instead of

    Code (CSharp):
    1. #if UNITY_5_2 || UNITY_5_3
    Just revert these parts if you prefer stating explicit versions.

    I'm not sure why The Paper uses
    Code (CSharp):
    1. if (idx4 > verts.Count - 1) return;
    for the truncated text test and others use idx6. Since vertices come as a bundle I guess it doesn't matter.

    Here is the full code again:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Text.RegularExpressions;
    5.  
    6. /*
    7. http://forum.unity3d.com/threads/adjustable-character-spacing-free-script.288277/
    8. Unity 5.1 and 5.2.2+ compatible
    9. Produces an simple tracking/letter-spacing effect on UI Text components.
    10. Set the spacing parameter to adjust letter spacing.
    11.  
    12. Negative values cuddle the text up tighter than normal. Go too far and it'll look odd.
    13. Positive values spread the text out more than normal. This will NOT respect the text area you've defined.
    14. Zero spacing will present the font with no changes.
    15. Relies on counting off characters in your Text component's text property and
    16. matching those against the quads passed in via the verts array. This is really
    17. rather primative, but I can't see any better way at the moment. It means that
    18. all sorts of things can break the effect...
    19.  
    20. This component should be placed higher in component list than any other vertex
    21. modifiers that alter the total number of verticies. Eg, place this ABOVE Shadow
    22. or Outline effects. If you don't, the outline/shadow won't match the position
    23. of the letters properly. If you place the outline/shadow effect second however,
    24. it will just work on the altered vertices from this component, and function
    25. as expected.
    26.  
    27. This component works best if you don't allow text to automatically wrap. It also
    28. blows up outside of the given text area. Basically, it's a cheap and dirty effect,
    29. not a clever text layout engine. It can't affect how Unity chooses to break up
    30. your lines. If you manually use line breaks however, it should detect those and
    31. function more or less as you'd expect.
    32.  
    33. The spacing parameter is measured in pixels multiplied by the font size. This was
    34. chosen such that when you adjust the font size, it does not change the visual spacing
    35. that you've dialed in. There's also a scale factor of 1/100 in this number to
    36. bring it into a comfortable adjustable range. There's no limit on this parameter,
    37. but obviously some values will look quite strange.
    38.  
    39. Now component works with RichText. You need to remember to turn on RichText via the checkbox (text.supportRichText)
    40. and turn on component's [useRichText] checkbox.
    41. */
    42.  
    43. namespace UnityEngine.UI
    44. {
    45.     [AddComponentMenu("UI/Effects/Letter Spacing", 15)]
    46. #if UNITY_5 && !UNITY_5_0 && !UNITY_5_1
    47.     public class LetterSpacing : BaseMeshEffect
    48. #else
    49.     public class LetterSpacing : BaseVertexEffect
    50. #endif
    51.     {
    52.         private const string SupportedTagRegexPattersn = @"<b>|</b>|<i>|</i>|<size=.*?>|</size>|<color=.*?>|</color>|<material=.*?>|</material>";
    53.         [SerializeField]
    54.         private bool useRichText;
    55.  
    56.         [SerializeField]
    57.         private float m_spacing = 0f;
    58.  
    59.         protected LetterSpacing() { }
    60.  
    61. #if UNITY_EDITOR
    62.         protected override void OnValidate()
    63.         {
    64.             spacing = m_spacing;
    65.             base.OnValidate();
    66.         }
    67. #endif
    68.  
    69.         public float spacing
    70.         {
    71.             get { return m_spacing; }
    72.             set
    73.             {
    74.                 if (m_spacing == value) return;
    75.                 m_spacing = value;
    76.                 if (graphic != null) graphic.SetVerticesDirty();
    77.             }
    78.         }
    79.  
    80. #if UNITY_5_2_OR_NEWER
    81.  
    82.         /**
    83.         * Note: Unity 5.2.1 ModifyMesh(Mesh mesh) used VertexHelper.FillMesh(mesh);
    84.         * For performance reasons, ModifyMesh(VertexHelper vh) was introduced
    85.         * @see http://forum.unity3d.com/threads/unity-5-2-ui-performance-seems-much-worse.353650/
    86.         */
    87.         public override void ModifyMesh(VertexHelper vh)
    88.         {
    89.             if (!this.IsActive())
    90.                 return;
    91.  
    92.             List<UIVertex> list = new List<UIVertex>();
    93.             vh.GetUIVertexStream(list);
    94.  
    95.             ModifyVertices(list);
    96.  
    97.             vh.Clear();
    98.             vh.AddUIVertexTriangleStream(list);
    99.         }
    100.  
    101.         public void ModifyVertices(List<UIVertex> verts)
    102.         {
    103.             if (!IsActive()) return;
    104.  
    105.             Text text = GetComponent<Text>();
    106.             if (text == null)
    107.             {
    108.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    109.                 return;
    110.             }
    111.  
    112.             string[] lines = text.text.Split('\n');
    113.             Vector3 pos;
    114.             float letterOffset = spacing * (float)text.fontSize / 100f;
    115.             float alignmentFactor = 0;
    116.             int glyphIdx = 0;  // character index from the beginning of the text, including RichText tags and line breaks
    117.  
    118.             bool isRichText = useRichText && text.supportRichText;
    119.             IEnumerator matchedTagCollection = null; // when using RichText this will collect all tags (index, length, value)
    120.             Match currentMatchedTag = null;
    121.  
    122.             switch (text.alignment)
    123.             {
    124.                 case TextAnchor.LowerLeft:
    125.                 case TextAnchor.MiddleLeft:
    126.                 case TextAnchor.UpperLeft:
    127.                     alignmentFactor = 0f;
    128.                     break;
    129.  
    130.                 case TextAnchor.LowerCenter:
    131.                 case TextAnchor.MiddleCenter:
    132.                 case TextAnchor.UpperCenter:
    133.                     alignmentFactor = 0.5f;
    134.                     break;
    135.  
    136.                 case TextAnchor.LowerRight:
    137.                 case TextAnchor.MiddleRight:
    138.                 case TextAnchor.UpperRight:
    139.                     alignmentFactor = 1f;
    140.                     break;
    141.             }
    142.  
    143.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    144.             {
    145.                 string line = lines[lineIdx];
    146.                 int lineLength = line.Length;
    147.  
    148.                 if (isRichText)
    149.                 {
    150.                     matchedTagCollection = GetRegexMatchedTagCollection(line, out lineLength);
    151.                     currentMatchedTag = null;
    152.                     if (matchedTagCollection.MoveNext())
    153.                     {
    154.                         currentMatchedTag = (Match)matchedTagCollection.Current;
    155.                     }
    156.                 }
    157.  
    158.                 float lineOffset = (lineLength - 1) * letterOffset * alignmentFactor;
    159.  
    160.                 for (int charIdx = 0, actualCharIndex = 0; charIdx < line.Length; charIdx++, actualCharIndex++)
    161.                 {
    162.                     if (isRichText)
    163.                     {
    164.                         if (currentMatchedTag != null && currentMatchedTag.Index == charIdx)
    165.                         {
    166.                             // skip matched RichText tag
    167.                             charIdx += currentMatchedTag.Length - 1;  // -1 because next iteration will increment charIdx
    168.                             actualCharIndex--;                          // tag is not an actual character, cancel counter increment on this iteration
    169.                             glyphIdx += currentMatchedTag.Length;      // glyph index is not incremented in for loop so skip entire length
    170.  
    171.                             // prepare next tag to detect
    172.                             currentMatchedTag = null;
    173.                             if (matchedTagCollection.MoveNext())
    174.                             {
    175.                                 currentMatchedTag = (Match)matchedTagCollection.Current;
    176.                             }
    177.  
    178.                             continue;
    179.                         }
    180.                     }
    181.  
    182.                     int idx1 = glyphIdx * 6 + 0;
    183.                     int idx2 = glyphIdx * 6 + 1;
    184.                     int idx3 = glyphIdx * 6 + 2;
    185.                     int idx4 = glyphIdx * 6 + 3;
    186.                     int idx5 = glyphIdx * 6 + 4;
    187.                     int idx6 = glyphIdx * 6 + 5;
    188.  
    189.                     // Check for truncated text (doesn't generate verts for all characters)
    190.                     if (idx6 > verts.Count - 1) return;
    191.  
    192.                     UIVertex vert1 = verts[idx1];
    193.                     UIVertex vert2 = verts[idx2];
    194.                     UIVertex vert3 = verts[idx3];
    195.                     UIVertex vert4 = verts[idx4];
    196.                     UIVertex vert5 = verts[idx5];
    197.                     UIVertex vert6 = verts[idx6];
    198.  
    199.                     pos = Vector3.right * (letterOffset * actualCharIndex - lineOffset);
    200.  
    201.                     vert1.position += pos;
    202.                     vert2.position += pos;
    203.                     vert3.position += pos;
    204.                     vert4.position += pos;
    205.                     vert5.position += pos;
    206.                     vert6.position += pos;
    207.  
    208.                     verts[idx1] = vert1;
    209.                     verts[idx2] = vert2;
    210.                     verts[idx3] = vert3;
    211.                     verts[idx4] = vert4;
    212.                     verts[idx5] = vert5;
    213.                     verts[idx6] = vert6;
    214.  
    215.                     glyphIdx++;
    216.                 }
    217.  
    218.                 // Offset for carriage return character that still generates verts
    219.                 glyphIdx++;
    220.             }
    221.         }
    222. #else
    223.         public override void ModifyVertices(List<UIVertex> verts)
    224.         {
    225.             if (!IsActive()) return;
    226.  
    227.             Text text = GetComponent<Text>();
    228.             if (text == null)
    229.             {
    230.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    231.                 return;
    232.             }
    233.  
    234.             string[] lines = text.text.Split('\n');
    235.             Vector3  pos;
    236.             float    letterOffset    = spacing * (float)text.fontSize / 100f;
    237.             float    alignmentFactor = 0;
    238.             int      glyphIdx        = 0;
    239.  
    240.             bool isRichText = useRichText && text.supportRichText;
    241.             IEnumerator matchedTagCollection = null; //when using RichText this will collect all tags (index, length, value)
    242.             Match currentMatchedTag = null;
    243.  
    244.             switch (text.alignment)
    245.             {
    246.                 case TextAnchor.LowerLeft:
    247.                 case TextAnchor.MiddleLeft:
    248.                 case TextAnchor.UpperLeft:
    249.                     alignmentFactor = 0f;
    250.                     break;
    251.  
    252.                 case TextAnchor.LowerCenter:
    253.                 case TextAnchor.MiddleCenter:
    254.                 case TextAnchor.UpperCenter:
    255.                     alignmentFactor = 0.5f;
    256.                     break;
    257.  
    258.                 case TextAnchor.LowerRight:
    259.                 case TextAnchor.MiddleRight:
    260.                 case TextAnchor.UpperRight:
    261.                     alignmentFactor = 1f;
    262.                     break;
    263.             }
    264.  
    265.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    266.             {
    267.                 string line = lines[lineIdx];
    268.  
    269.                 int lineLength = line.Length;
    270.  
    271.                 if (isRichText)
    272.                 {
    273.                     matchedTagCollection = GetRegexMatchedTagCollection(line, out lineLength);
    274.                     currentMatchedTag = null;
    275.                     if (matchedTagCollection.MoveNext())
    276.                     {
    277.                         currentMatchedTag = (Match)matchedTagCollection.Current;
    278.                     }
    279.                 }
    280.  
    281.                 float lineOffset = (lineLength -1) * letterOffset * alignmentFactor;
    282.  
    283.                 for (int charIdx = 0, charPositionIndex =0; charIdx < line.Length; charIdx++, charPositionIndex++)
    284.                 {
    285.                     if (isRichText)
    286.                     {
    287.                         if (currentMatchedTag != null && currentMatchedTag.Index == charIdx)
    288.                         {
    289.                             // skip rich text tag matched (-1 because each iteration already increments the indices)
    290.                             charIdx += currentMatchedTag.Length - 1;
    291.                             glyphIdx += currentMatchedTag.Length - 1;
    292.                             charPositionIndex--;
    293.                             currentMatchedTag = null;
    294.                             if (matchedTagCollection.MoveNext())
    295.                             {
    296.                                 currentMatchedTag = (Match)matchedTagCollection.Current;
    297.                             }
    298.                         }
    299.                     }
    300.  
    301.                     int idx1 = glyphIdx * 4 + 0;
    302.                     int idx2 = glyphIdx * 4 + 1;
    303.                     int idx3 = glyphIdx * 4 + 2;
    304.                     int idx4 = glyphIdx * 4 + 3;
    305.  
    306.                     // Check for truncated text (doesn't generate verts for all characters)
    307.                     if (idx4 > verts.Count - 1) return;
    308.  
    309.                     UIVertex vert1 = verts[idx1];
    310.                     UIVertex vert2 = verts[idx2];
    311.                     UIVertex vert3 = verts[idx3];
    312.                     UIVertex vert4 = verts[idx4];
    313.  
    314.                     pos = Vector3.right * (letterOffset * charPositionIndex - lineOffset);
    315.  
    316.                     vert1.position += pos;
    317.                     vert2.position += pos;
    318.                     vert3.position += pos;
    319.                     vert4.position += pos;
    320.  
    321.                     verts[idx1] = vert1;
    322.                     verts[idx2] = vert2;
    323.                     verts[idx3] = vert3;
    324.                     verts[idx4] = vert4;
    325.  
    326.                     glyphIdx++;
    327.                 }
    328.  
    329.                 // Offset for carriage return character that still generates verts
    330.                 glyphIdx++;
    331.             }
    332.         }
    333. #endif
    334.  
    335.         private IEnumerator GetRegexMatchedTagCollection(string line, out int lineLengthWithoutTags)
    336.         {
    337.             MatchCollection matchedTagCollection = Regex.Matches(line,SupportedTagRegexPattersn);
    338.             lineLengthWithoutTags = 0;
    339.             int tagsLength = 0;
    340.  
    341.             if (matchedTagCollection.Count > 0)
    342.             {
    343.                 foreach (Match matchedTag in matchedTagCollection)
    344.                 {
    345.                     tagsLength += matchedTag.Length;
    346.                 }
    347.             }
    348.             lineLengthWithoutTags = line.Length - tagsLength;
    349.             return matchedTagCollection.GetEnumerator();
    350.         }
    351.     }
    352. }
    353.  
    It starts getting neat I think (even if the original author considers this as a terrible hack), any plan to release it on some Wiki, public repository (such as UnityToolbag) or something? Just to avoid copy-pasting the new version of the code each time. By the way I also attached the script if you prefer downloading it.

    UPDATE: please replace "#if UNITY_5 && !UNITY_5_0 && !UNITY_5_1" with "if UNITY_5_2_OR_NEWER" in the script if you download it
     

    Attached Files:

    Last edited: Aug 19, 2016
    Trisibo, Razouille and Aseemy like this.
  47. SteveSmooth

    SteveSmooth

    Joined:
    Sep 4, 2014
    Posts:
    1
    Wooooow Thanks for everyone who made this work. Seriously Unity Community for the win!
     
  48. Spatanz

    Spatanz

    Joined:
    Mar 28, 2013
    Posts:
    4
    Work like charm, really thanks for everyone, especially @Deeperbeige.
     
  49. elmax

    elmax

    Joined:
    Oct 6, 2013
    Posts:
    19
    Didn't work for me in 5.4 - since BaseVertexEffect is deprecated now.

    I've removed about half of the script so it's now 5.4+ only, and also included kromenak's line wrap fix.
    HTH.


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Text.RegularExpressions;
    5.  
    6. /*
    7. http://forum.unity3d.com/threads/adjustable-character-spacing-free-script.288277/
    8. Unity 5.4 compatible version
    9. Produces an simple tracking/letter-spacing effect on UI Text components.
    10. Set the spacing parameter to adjust letter spacing.
    11.  
    12. Negative values cuddle the text up tighter than normal. Go too far and it'll look odd.
    13. Positive values spread the text out more than normal. This will NOT respect the text area you've defined.
    14. Zero spacing will present the font with no changes.
    15. Relies on counting off characters in your Text component's text property and
    16. matching those against the quads passed in via the verts array. This is really
    17. rather primative, but I can't see any better way at the moment. It means that
    18. all sorts of things can break the effect...
    19.  
    20. This component should be placed higher in component list than any other vertex
    21. modifiers that alter the total number of verticies. Eg, place this ABOVE Shadow
    22. or Outline effects. If you don't, the outline/shadow won't match the position
    23. of the letters properly. If you place the outline/shadow effect second however,
    24. it will just work on the altered vertices from this component, and function
    25. as expected.
    26.  
    27. This component works best if you don't allow text to automatically wrap. It also
    28. blows up outside of the given text area. Basically, it's a cheap and dirty effect,
    29. not a clever text layout engine. It can't affect how Unity chooses to break up
    30. your lines. If you manually use line breaks however, it should detect those and
    31. function more or less as you'd expect.
    32.  
    33. The spacing parameter is measured in pixels multiplied by the font size. This was
    34. chosen such that when you adjust the font size, it does not change the visual spacing
    35. that you've dialed in. There's also a scale factor of 1/100 in this number to
    36. bring it into a comfortable adjustable range. There's no limit on this parameter,
    37. but obviously some values will look quite strange.
    38.  
    39. Now component works with RichText. You need to remember to turn on RichText via the checkbox (text.supportRichText)
    40. and turn on component's [useRichText] checkbox.
    41. */
    42.  
    43. namespace UnityEngine.UI
    44. {
    45.     [AddComponentMenu("UI/Effects/Letter Spacing", 15)]
    46.  
    47.     public class LetterSpacing : BaseMeshEffect
    48.     {
    49.         private const string SupportedTagRegexPattersn = @"<b>|</b>|<i>|</i>|<size=.*?>|</size>|<color=.*?>|</color>|<material=.*?>|</material>";
    50.         [SerializeField]
    51.         private bool useRichText;
    52.  
    53.         [SerializeField]
    54.         private float m_spacing = 0f;
    55.  
    56.         protected LetterSpacing() { }
    57.  
    58. #if UNITY_EDITOR
    59.         protected override void OnValidate()
    60.         {
    61.             spacing = m_spacing;
    62.             base.OnValidate();
    63.         }
    64. #endif
    65.  
    66.         public float spacing
    67.         {
    68.             get { return m_spacing; }
    69.             set
    70.             {
    71.                 if (m_spacing == value) return;
    72.                 m_spacing = value;
    73.                 if (graphic != null) graphic.SetVerticesDirty();
    74.             }
    75.         }
    76.        
    77.         /**
    78.         * Note: Unity 5.2.1 ModifyMesh(Mesh mesh) used VertexHelper.FillMesh(mesh);
    79.         * For performance reasons, ModifyMesh(VertexHelper vh) was introduced
    80.         * @see http://forum.unity3d.com/threads/unity-5-2-ui-performance-seems-much-worse.353650/
    81.         */
    82.         public override void ModifyMesh(VertexHelper vh)
    83.         {
    84.             if (!this.IsActive())
    85.                 return;
    86.  
    87.             List<UIVertex> list = new List<UIVertex>();
    88.             vh.GetUIVertexStream(list);
    89.  
    90.             ModifyVertices(list);
    91.  
    92.             vh.Clear();
    93.             vh.AddUIVertexTriangleStream(list);
    94.         }
    95.  
    96.         public void ModifyVertices(List<UIVertex> verts)
    97.         {
    98.             if (!IsActive()) return;
    99.  
    100.             Text text = GetComponent<Text>();
    101.  
    102.  
    103.             string str = text.text;
    104.  
    105.             // Artificially insert line breaks for automatic line breaks.
    106.             IList<UILineInfo> lineInfos = text.cachedTextGenerator.lines;
    107.             for (int i = lineInfos.Count - 1; i > 0; i--)
    108.             {
    109.                 // Insert a \n at the location Unity wants to automatically line break.
    110.                 // Also, remove any space before the automatic line break location.
    111.                 str = str.Insert(lineInfos[i].startCharIdx, "\n");
    112.                 str = str.Remove(lineInfos[i].startCharIdx - 1, 1);
    113.             }
    114.  
    115.             string[] lines = str.Split('\n');
    116.  
    117.  
    118.             if (text == null)
    119.             {
    120.                 Debug.LogWarning("LetterSpacing: Missing Text component");
    121.                 return;
    122.             }
    123.            
    124.             Vector3 pos;
    125.             float letterOffset = spacing * (float)text.fontSize / 100f;
    126.             float alignmentFactor = 0;
    127.             int glyphIdx = 0;  // character index from the beginning of the text, including RichText tags and line breaks
    128.  
    129.             bool isRichText = useRichText && text.supportRichText;
    130.             IEnumerator matchedTagCollection = null; // when using RichText this will collect all tags (index, length, value)
    131.             Match currentMatchedTag = null;
    132.  
    133.             switch (text.alignment)
    134.             {
    135.                 case TextAnchor.LowerLeft:
    136.                 case TextAnchor.MiddleLeft:
    137.                 case TextAnchor.UpperLeft:
    138.                     alignmentFactor = 0f;
    139.                     break;
    140.  
    141.                 case TextAnchor.LowerCenter:
    142.                 case TextAnchor.MiddleCenter:
    143.                 case TextAnchor.UpperCenter:
    144.                     alignmentFactor = 0.5f;
    145.                     break;
    146.  
    147.                 case TextAnchor.LowerRight:
    148.                 case TextAnchor.MiddleRight:
    149.                 case TextAnchor.UpperRight:
    150.                     alignmentFactor = 1f;
    151.                     break;
    152.             }
    153.  
    154.             for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
    155.             {
    156.                 string line = lines[lineIdx];
    157.                 int lineLength = line.Length;
    158.  
    159.                 if (isRichText)
    160.                 {
    161.                     matchedTagCollection = GetRegexMatchedTagCollection(line, out lineLength);
    162.                     currentMatchedTag = null;
    163.                     if (matchedTagCollection.MoveNext())
    164.                     {
    165.                         currentMatchedTag = (Match)matchedTagCollection.Current;
    166.                     }
    167.                 }
    168.  
    169.                 float lineOffset = (lineLength - 1) * letterOffset * alignmentFactor;
    170.  
    171.                 for (int charIdx = 0, actualCharIndex = 0; charIdx < line.Length; charIdx++, actualCharIndex++)
    172.                 {
    173.                     if (isRichText)
    174.                     {
    175.                         if (currentMatchedTag != null && currentMatchedTag.Index == charIdx)
    176.                         {
    177.                             // skip matched RichText tag
    178.                             charIdx += currentMatchedTag.Length - 1;  // -1 because next iteration will increment charIdx
    179.                             actualCharIndex--;                          // tag is not an actual character, cancel counter increment on this iteration
    180.                             glyphIdx += currentMatchedTag.Length;      // glyph index is not incremented in for loop so skip entire length
    181.  
    182.                             // prepare next tag to detect
    183.                             currentMatchedTag = null;
    184.                             if (matchedTagCollection.MoveNext())
    185.                             {
    186.                                 currentMatchedTag = (Match)matchedTagCollection.Current;
    187.                             }
    188.  
    189.                             continue;
    190.                         }
    191.                     }
    192.  
    193.                     int idx1 = glyphIdx * 6 + 0;
    194.                     int idx2 = glyphIdx * 6 + 1;
    195.                     int idx3 = glyphIdx * 6 + 2;
    196.                     int idx4 = glyphIdx * 6 + 3;
    197.                     int idx5 = glyphIdx * 6 + 4;
    198.                     int idx6 = glyphIdx * 6 + 5;
    199.  
    200.                     // Check for truncated text (doesn't generate verts for all characters)
    201.                     if (idx6 > verts.Count - 1) return;
    202.  
    203.                     UIVertex vert1 = verts[idx1];
    204.                     UIVertex vert2 = verts[idx2];
    205.                     UIVertex vert3 = verts[idx3];
    206.                     UIVertex vert4 = verts[idx4];
    207.                     UIVertex vert5 = verts[idx5];
    208.                     UIVertex vert6 = verts[idx6];
    209.  
    210.                     pos = Vector3.right * (letterOffset * actualCharIndex - lineOffset);
    211.  
    212.                     vert1.position += pos;
    213.                     vert2.position += pos;
    214.                     vert3.position += pos;
    215.                     vert4.position += pos;
    216.                     vert5.position += pos;
    217.                     vert6.position += pos;
    218.  
    219.                     verts[idx1] = vert1;
    220.                     verts[idx2] = vert2;
    221.                     verts[idx3] = vert3;
    222.                     verts[idx4] = vert4;
    223.                     verts[idx5] = vert5;
    224.                     verts[idx6] = vert6;
    225.  
    226.                     glyphIdx++;
    227.                 }
    228.  
    229.                 // Offset for carriage return character that still generates verts
    230.                 glyphIdx++;
    231.             }
    232.         }
    233.  
    234.         private IEnumerator GetRegexMatchedTagCollection(string line, out int lineLengthWithoutTags)
    235.         {
    236.             MatchCollection matchedTagCollection = Regex.Matches(line,SupportedTagRegexPattersn);
    237.             lineLengthWithoutTags = 0;
    238.             int tagsLength = 0;
    239.  
    240.             if (matchedTagCollection.Count > 0)
    241.             {
    242.                 foreach (Match matchedTag in matchedTagCollection)
    243.                 {
    244.                     tagsLength += matchedTag.Length;
    245.                 }
    246.             }
    247.             lineLengthWithoutTags = line.Length - tagsLength;
    248.             return matchedTagCollection.GetEnumerator();
    249.         }
    250.     }
    251. }
    252.  
     
  50. StaffanEk

    StaffanEk

    Joined:
    Jul 13, 2012
    Posts:
    380
    A huge thank you, to all contributors in this thread. I was so amazed to find that kerning isn't included in the Unity 5 ui.
     
    Arashisan likes this.