Search Unity

New UI text is useless for scaled pixel art games

Discussion in 'UGUI & TextMesh Pro' started by st33d, Aug 27, 2014.

  1. st33d

    st33d

    Joined:
    Jan 22, 2014
    Posts:
    28
    So the new Beta has left me unequivocally screwed:

    My game is in 2D. It scales the artwork from a small size to a large size - this is pretty standard business for anything with pixel art. You either have a 1:1 ratio for the art to the screen or you double or treble up the pixels and so on. I've made over 40 games in Flash in this manner.

    My camera is looking at a small area and magnifying it. This means that I am forced to put the Canvas into World Space to get it to render graphics scaled properly. The Canvas will not render anything intelligible otherwise. It also means that text is useless. I'm using a pixel-font that should fit in nicely from Fonts For Flash and it renders massive or not at all. Worse is that when I apply scale to anything it completely screws with the rect transform, making it impossible to position it.

    I have looked at extensions on the asset store and one referred to using 3D Text - which it seems has been deprecated for the completely useless text option in the new UI.

    So I'm that's it. I can't buy a solution, I can't use the built in solution. I'm F***ed.

    What do I do?
     
  2. Triggles

    Triggles

    Joined:
    Aug 23, 2014
    Posts:
    30
    Have you added a Reference Resolution component to your canvas, and set it to whatever resolution you're designing your game in?
     
  3. JAKJ

    JAKJ

    Joined:
    Aug 17, 2014
    Posts:
    185
    Also make sure your font is imported as "Hinted Raster" to maintain the "pixelly" look.
     
  4. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    It's not clear to me from the text description what the problem is. Could you record a screencast (with Jing or similar) that shows the problem you're having? Make sure to include entire Editor with Scene View, Hierarchy and Inspector visible while showing the issues.
     
  5. ForceX

    ForceX

    Joined:
    Jun 22, 2010
    Posts:
    1,102
    You can read this thread where we discuss text blurriness and ways to restore pixel perfect text.
    http://forum.unity3d.com/threads/sharpenning-text.263810/#post-1749310

    As for your graphics why rely on unity scaling? Instead finalize your art as desired in your image program and import it into unity at a 1:1 scale. This way the final image produced in Unity appears exactly as it does in your authoring program.
     
    Last edited: Aug 27, 2014
  6. st33d

    st33d

    Joined:
    Jan 22, 2014
    Posts:
    28
    Here is a screenshot of my editor:

    http://imgur.com/efYxVIu

    Here's a look at just the panel and text in the editor with my camera selected:

    http://imgur.com/fy50cII

    Note the native resolution on the camera - this is important: 214x160. The UI has to scale so that the art on the UI is consistent with the rest of the game - allowing me to use gameplay graphics without having to rescale them in Photoshop.

    Looking into alternative fonts it looks like the font I have may refuse to go any smaller. But a smaller font I tried is very blurry.

    I can't use the "pixel perfect" setting because I have to use world space to position the Canvas over the game play. I tried using a Reference Resolution component but it appears to have no effect whatsoever on the Canvas in any mode. (I tried Hinted Raster as well.)

    This is increasingly looking like I'm going to have to construct text out of a S***load of Image components. I'm really hoping they've been programmed efficiently.
     
  7. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    I guess the fundamental problem is that there's no easy way to force the canvas so that (a) it's smaller than the screen resolution by a factor of 2, 3, 4, etc, and (b) it'll scale up in a point-sampled way rather than bilinearly sampled. Right?

    I would guess that maybe there's a solution that involves rendering to a texture and then scaling that, but I don't know enough about that to be sure (plus, that would be Pro only, I guess).
     
  8. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    If you need a text solution for Unity and the new UI, check out TextMesh Pro. I was demoing it working with the new UI last week at Unite in Seattle.

    The beta of this new TextMesh Pro UI component will be available this week for all Registered Users of TextMesh Pro.

    Here is an image showing (top) TextMesh Pro GUI object and (bottom) new UI Text both using the new RectTransform and rendering to the Canvas.


    The above image shows how TextMesh Pro's plain text compares to the new UI Text. The following image shows how this same text can be modified dynamically by tweaking the material parameters to add an outline and soft shadow and a lot more.



    Since TextMesh Pro uses a different text rendering technique, the text remains crisp at all font sizes. We are still using quads just like Unity's new UI Text so this remains super lightweight and efficient. These two images use the same exact font asset (atlas) with different material parameters.

    Here is a closeup on some letters which are using the Impact.ttf font with a few more material properties tweaked.


    Should you have any questions on how to use TextMesh Pro with the new UI, just head over to the TextMesh Pro Thread and it will be my pleasure to provide you with the answers you seek.

    BTW: TextMesh Pro is more than just pretty text is also provide better control over your text with expanded markup tags, improved word wrapping and text justification, kerning, etc...
     
  9. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    I think you missed the point of this thread...
     
    sgt3v, MaxEden and shaderop like this.
  10. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Sorry, but the problem is still not exactly clear to me. Again, I would advise to record a screencast, not just images, where you show and explain what your UI looks like when it looks right, and show how it goes wrong at a different resolution, and so on.

    Maybe the ReferenceResolution component can help, as was already suggested by Triggles, or maybe not. Again, the problem, is not quite clear to me yet.
     
  11. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Who missed the point?

    Isn't the OP looking for a way to have his text always look great at any point size / screen resolution / zoom factor?
     
    Last edited: Aug 28, 2014
  12. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    No, it looks like he or she is trying to render pixel art, which means scaling up small text to a larger size without smooth filtering or blending.
     
  13. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Just out of curiosity, I downloaded a pixel art font to see how well the shape would be preserved at various point size / resolution / zoom





    The corners do get rounded slightly at larger point size / zoom so I don't know if that is within an acceptable range or not.
     
    Last edited: Aug 28, 2014
    bgs likes this.
  14. ForceX

    ForceX

    Joined:
    Jun 22, 2010
    Posts:
    1,102
    This is what i was able to come up with. I just downloaded the first pixel font I came across.

    You can see how the Direct X 9 version looks blurry compared to the Direct X 11 version. But this is something Unity is addressing.

     
    Last edited: Aug 28, 2014
  15. st33d

    st33d

    Joined:
    Jan 22, 2014
    Posts:
    28
    Sorry, but this is possibly the most infuriating reply I could ever receive. I have had unending grief trying to find coherent tutorials for Unity - all of them prefer to use video. Not only video, but one without any summary so I have to spend my entire evening sitting through video after video, patiently listening to see if ANY of it is relevant to my problem. Even this site has numerous videos with no explanation of what is in the video or any attempt at a transcript.

    I F***ing despise the Unity community's reliance on video to the exclusion of everything else. I can read far faster than watch a video. I repeated your response to a number of developers and ALL of them expressed shock. One suggested I set up a Twitch channel instead of bothering with a forum if that was the sort of response I was going to receive.

    In the end, to get pixel perfect text that can be scaled I chose the hard way:
    Code (csharp):
    1.  
    2. // Save http://i.imgur.com/O1Yz8zS.png to Resources/Textures/font-small-chunk-8x8.png
    3. // Set the Texture Type to Sprite, the Sprite Mode to Multiple, the filter mode to Point and the pixel-units to 1 and
    4. // format to True Colour or 16-bits.
    5. // Slice the texture as a grid 8x8, pivot at center.
    6.  
    7. // now you can use the script below :)
    8.  
    9. using UnityEngine;
    10. using UnityEngine.UI;
    11. using System.Collections;
    12. using System.Collections.Generic;
    13. using System.Linq;
    14.  
    15. /* A solution to all of the concrete ball-ache I've had trying to do bitmap text in Unity3D
    16. *
    17. * Can insert images into the text as well.
    18. *
    19. * Character Sprites are assumed to be all pivotted at their center.
    20. * Images inserted are assumed to have abstract pivots, so a little extra work is done there.
    21. */
    22.  
    23. [ExecuteInEditMode]
    24. public class SpriteText : MonoBehaviour {
    25.  
    26.     [HideInInspector]
    27.     public List<List<SpriteRenderer>> lines;    // a 2D list of all the SpriteRenderers used, in lines
    28.     [HideInInspector]
    29.     public List<float> lineWidths;                // the width of each line of text (used for alignment)
    30.     [HideInInspector]
    31.     public List<float> lineHeights;                // the height of each line of text (used for alignment)
    32.     [HideInInspector]
    33.     public List<string> textLines;                // a 2D list of the characters used (used for fetching offset and kerning data - not implemented in this version yet, sorry.)
    34.  
    35.     [Multiline]
    36.     public string text = "text";                // use SetText to alter text
    37.     public float tracking = 0;                    // the spacing between letters
    38.     public AlignHoriz alignHoriz;                // whether the text is centered, left or right aligned
    39.     public AlignVert alignVert;                    // vertical alignment of the text
    40.     public float lineSpacing = 8;                // distance between each line of copy
    41.     public bool wordWrap;                        // turns word wrap calculation on and off
    42.     public float whitespaceLength = 4;            // the distance a whitespace takes up
    43.     public float width = 48;                    // editable when there is no RectTransform
    44.     public float height = 16;                    // editable when there is no RectTransform
    45.     public float border = 0;                    // gap from edge
    46.     public Color color = Color.white;            // a color that is applied to the Sprites
    47.     public bool visible = true;                    // current state of renderers - use SetVisible to alter
    48.     public string sortingLayerName = "Default";    // sorting layer to deploy the objects to
    49.     public Sprite [] spriteInserts;                // images that can be inserted into the text with ~0, ~3 etc.
    50.  
    51.     protected Stack<SpriteRenderer> spritePool;    // local object pool
    52.     protected GameObject spritePoolAnchor;        // keeps hierarchy clean
    53.  
    54.     protected static Vector3 defaultScale = new Vector3(1, 1, 1);
    55.  
    56.     // TODO: multiple sets of characters (putting characters into List<>characterSets)
    57.     public static Dictionary<char, Sprite> characters; // lookup table of sprites
    58.     public static Sprite [] sprites;            // raw character sprite list
    59.     public static bool initialised = false;
    60.  
    61.     public enum AlignVert{
    62.         TOP, CENTER, BOTTOM
    63.     }
    64.     public enum AlignHoriz{
    65.         LEFT, CENTER, RIGHT
    66.     }
    67.  
    68.     void InitPool(){
    69.         spritePool = new Stack<SpriteRenderer>();
    70.         spritePoolAnchor = new GameObject("pool");
    71.         spritePoolAnchor.transform.parent = transform;
    72.     }
    73.  
    74.     void InitCharacters(){
    75.         sprites = Resources.LoadAll<Sprite>("Textures/font-small-chunk-8x8");
    76.         // TODO: automate this ugliness
    77.         characters = new Dictionary<char, Sprite>{
    78.             {'a', sprites[0]},
    79.             {'b', sprites[1]},
    80.             {'c', sprites[2]},
    81.             {'d', sprites[3]},
    82.             {'e', sprites[4]},
    83.             {'f', sprites[5]},
    84.             {'g', sprites[6]},
    85.             {'h', sprites[7]},
    86.             {'i', sprites[8]},
    87.             {'j', sprites[9]},
    88.             {'k', sprites[10]},
    89.             {'l', sprites[11]},
    90.             {'m', sprites[12]},
    91.             {'n', sprites[13]},
    92.             {'o', sprites[14]},
    93.             {'p', sprites[15]},
    94.             {'q', sprites[16]},
    95.             {'r', sprites[17]},
    96.             {'s', sprites[18]},
    97.             {'t', sprites[19]},
    98.             {'u', sprites[20]},
    99.             {'v', sprites[21]},
    100.             {'w', sprites[22]},
    101.             {'x', sprites[23]},
    102.             {'y', sprites[24]},
    103.             {'z', sprites[25]},
    104.             //                {'', sprites[26]},
    105.             //                {'', sprites[27]},
    106.             {'0', sprites[28]},
    107.             {'1', sprites[29]},
    108.             {'2', sprites[30]},
    109.             {'3', sprites[31]},
    110.             {'4', sprites[32]},
    111.             {'5', sprites[33]},
    112.             {'6', sprites[34]},
    113.             {'7', sprites[35]},
    114.             {'8', sprites[36]},
    115.             {'9', sprites[37]},
    116.             {'%', sprites[38]},
    117.             {'?', sprites[39]},
    118.             {'!', sprites[40]},
    119.             {'.', sprites[41]},
    120.             {',', sprites[42]},
    121.             {'@', sprites[43]},
    122.             {'\'', sprites[44]},
    123.             {'"', sprites[45]},
    124.             {'\\', sprites[46]},
    125.             {'-', sprites[47]},
    126.             {'+', sprites[48]},
    127.             {'(', sprites[49]},
    128.             {')', sprites[50]},
    129.             {'=', sprites[51]},
    130.             {'/', sprites[52]}
    131.         };
    132.         initialised = true;
    133.  
    134.     }
    135.  
    136.     // Update is called once per frame
    137.     void Update () {
    138.         if(UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) {
    139.          
    140.          
    141.             // exectutes only in editor: - is a bit overzealous, need to look up ligher solution
    142.         } else {
    143.             if(!initialised) {
    144.                 InitCharacters();
    145.             }
    146.             DestroySprites();
    147.             UpdateText();
    148.         }
    149.     }
    150.  
    151.     void OnDrawGizmosSelected(){
    152.         Gizmos.color = Color.yellow;
    153.         Gizmos.DrawWireCube(transform.position, new Vector3(width, height, 0));
    154.         if(border > 0){
    155.             Gizmos.color = Color.green;
    156.             Gizmos.DrawWireCube(transform.position, new Vector3(width - border * 2, height - border * 2, 0));
    157.         }
    158.     }
    159.  
    160.     public void SetText(string str){
    161.         text = str;
    162.         UpdateText();
    163.     }
    164.  
    165.     public void SetVisible(bool value){
    166.         visible = value;
    167.         if(!value){
    168.             // not initialised - kill sprites
    169.             if(spritePool == null){
    170.                 DestroySprites();
    171.                 return;
    172.             }
    173.         }
    174.         if(lines != null){
    175.             foreach(List<SpriteRenderer> list in lines){
    176.                 foreach(SpriteRenderer item in list){
    177.                     if(item != null){
    178.                         item.enabled = value;
    179.                     }
    180.                 }
    181.             }
    182.         }
    183.     }
    184.  
    185.     public void DestroySprites(){
    186.         lines = null;
    187.         spritePool = null;
    188.         spritePoolAnchor = null;
    189.      
    190.         // take no prisoners
    191.         var children = new List<GameObject>();
    192.         foreach (Transform child in transform) children.Add(child.gameObject);
    193.         children.ForEach(child => DestroyImmediate(child));
    194.     }
    195.  
    196.     /* Calculates a list of SpriteRenderers needed to render the text,
    197.     * then positions them all.
    198.     *
    199.     * It's a long one.
    200.     */
    201.     public void UpdateText(){
    202.  
    203.         // check initialised
    204.         if(!initialised){
    205.             InitCharacters();
    206.         }
    207.         if(spritePool == null){
    208.             DestroySprites();
    209.             InitPool();
    210.  
    211.             // detect rect transform
    212.             var rectTransform = gameObject.GetComponent<RectTransform>();
    213.             if(rectTransform != null){
    214.                 width = rectTransform.sizeDelta.x;
    215.                 height = rectTransform.sizeDelta.y;
    216.             }
    217.         }
    218.  
    219.         // destruct previous session
    220.         if(lines != null){
    221.             foreach(List<SpriteRenderer> list in lines){
    222.                 foreach(SpriteRenderer item in list){
    223.                     if(item != null){
    224.                         item.enabled = false;
    225.                         item.transform.parent = spritePoolAnchor.transform;
    226.                         spritePool.Push(item);
    227.                     }
    228.                 }
    229.             }
    230.         }
    231.      
    232.         // we create an array called lines that holds references to all of the
    233.         // SpriteRemderers needed and structure it like the text
    234.      
    235.         // the lines property is public so it can be used to ticker text
    236.         lines = new List<List<SpriteRenderer>>();
    237.         lineWidths = new List<float>();
    238.         lineHeights = new List<float>();
    239.         textLines = new List<string>();
    240.      
    241.         List<SpriteRenderer> currentLine = new List<SpriteRenderer>();
    242.         List<SpriteRenderer> newLine = new List<SpriteRenderer>();
    243.         List<SpriteRenderer> offsets = new List<SpriteRenderer>();
    244.         string currentTextLine = "";
    245.         int wordBeginning = 0;
    246.         float currentLineWidth = 0;
    247.         float currentLineHeight = lineSpacing;
    248.         float lineHeightsTotal = 0;
    249.         float completeWordsWidth = 0;
    250.         float wordWidth = 0;
    251.         string newTextLine = "";
    252.         char c = ' ';
    253.         Sprite sprite;
    254.         Sprite imageInsert = null;
    255.         GameObject obj;
    256.         SpriteRenderer renderer;
    257.      
    258.         string formattedText = text.ToLower();
    259.  
    260.         for(int i = 0; i < formattedText.Length; i++){
    261.          
    262.             c = formattedText[i];
    263.  
    264.             // image insertion
    265.             if(c == '~' && i < formattedText.Length - 1 && spriteInserts != null){
    266.                 // get entire number
    267.                 string num = "";
    268.                 int n = i + 1;
    269.                 while(n < formattedText.Length){
    270.                     c = formattedText[n];
    271.  
    272.                     // do we want to put a number next to the image?
    273.                     // use a closing ~ to safely end an image index, eg: "~5~20 times"
    274.                     if(c == '~'){
    275.                         n++;
    276.                         break;
    277.                     }
    278.                     // c# is a pedant and wants properly formatted parsing input
    279.                     // 48 is (int)'0', 57 is (int)'9'
    280.                     if((int)c < 48 || (int)c > 57) break;
    281.                     num += c;
    282.                     n++;
    283.                 }
    284.                 i = n - 1;
    285.  
    286.                 // throw away this session if parsing doesn't go smoothly
    287.                 if(num != ""){
    288.                     int index = int.Parse(num);
    289.                     if(index < spriteInserts.Length) imageInsert = spriteInserts[index];
    290.                     else continue;
    291.                 } else {
    292.                     continue;
    293.                 }
    294.          
    295.             // new line characters
    296.             } else if(c == '\n' || c == '\r' || c == '|'){
    297.                 lines.Add(currentLine);
    298.                 textLines.Add(currentTextLine);
    299.                 lineWidths.Add(currentLineWidth);
    300.                 lineHeights.Add(currentLineHeight);
    301.                 lineHeightsTotal += currentLineHeight;
    302.                 currentLineWidth = 0;
    303.                 completeWordsWidth = 0;
    304.                 wordBeginning = 0;
    305.                 wordWidth = 0;
    306.                 currentLineHeight = lineSpacing;
    307.                 currentLine = new List<SpriteRenderer>();
    308.                 currentTextLine = "";
    309.                 continue;
    310.             }
    311.          
    312.             // push a character into the array
    313.             if(characters.ContainsKey(c) || imageInsert != null){
    314.  
    315.                 // generate/fetch an Sprite
    316.                 if(imageInsert == null){
    317.                     sprite = characters[c];
    318.                 } else {
    319.                     sprite = imageInsert;
    320.                 }
    321.                 if(spritePool.Count > 0){
    322.                     renderer = spritePool.Pop();
    323.                     obj = renderer.gameObject;
    324.                     renderer.enabled = visible;
    325.                 } else {
    326.                     obj = new GameObject("char-"+c);
    327.                     renderer = obj.AddComponent<SpriteRenderer>();
    328.                 }
    329.                 obj.transform.parent = transform;
    330.                 obj.transform.localScale = defaultScale;
    331.                 renderer.sprite = sprite;
    332.                 if(sprite == imageInsert){
    333.                     renderer.color = Color.white;
    334.                     // images will generally have abstract pivot points, so we correct afterwards
    335.                     offsets.Add(renderer);
    336.                 } else renderer.color = color;
    337.                 renderer.sortingLayerName = sortingLayerName;
    338.                 if(sprite.rect.height > currentLineHeight){
    339.                     currentLineHeight = sprite.rect.height;
    340.                 }
    341.                 imageInsert = null;
    342.              
    343.                 // check we're in the middle of a word - spaces are null
    344.                 if(currentLine.Count > 0 && currentLine[currentLine.Count - 1]){
    345.                     currentLineWidth += tracking;
    346.                     wordWidth += tracking;
    347.                 }
    348.                 float charWidth = sprite.rect.width;
    349.                 wordWidth += charWidth;
    350.                 currentLineWidth += charWidth;
    351.                 currentLine.Add(renderer);
    352.                 currentTextLine += c;
    353.              
    354.                 // the character is a ' ' or unrecognised and will be treated as a ' '
    355.             } else {
    356.                 if(currentLine.Count > 0 && currentLine[currentLine.Count - 1]){
    357.                     completeWordsWidth = currentLineWidth;
    358.                 }
    359.                 currentLineWidth += whitespaceLength;
    360.                 currentLine.Add(null);
    361.                 currentTextLine += " ";
    362.                 wordBeginning = currentLine.Count;
    363.                 wordWidth = 0;
    364.             }
    365.          
    366.             // if the length of the current line exceeds the width, we splice it into the next line
    367.             // effecting word wrap
    368.             // WARNING: have not implemented image insert - involves tedious backtracking
    369.  
    370.             if(currentLineWidth > width - border * 2 && wordWrap){
    371.                 // in the case where the word is larger than the text field we take back the last character
    372.                 // and jump to a new line with it
    373.                 if(wordBeginning == 0 && currentLine[currentLine.Count - 1] != null){
    374.                     SpriteRenderer lastItem = currentLine[currentLine.Count - 1];
    375.                     float spriteWidth = lastItem.sprite.rect.width;
    376.                     currentLineWidth -= tracking + spriteWidth;
    377.                     // now we take back the offending last character
    378.                     currentLine.Remove(lastItem);
    379.                     char lastChar = currentTextLine[currentTextLine.Length - 1];
    380.                     currentTextLine = currentTextLine.Substring(0, currentTextLine.Length - 1);
    381.                  
    382.                     lines.Add(currentLine);
    383.                     textLines.Add(currentTextLine);
    384.                     lineWidths.Add(currentLineWidth);
    385.                     lineHeights.Add(currentLineHeight);
    386.                     lineHeightsTotal += currentLineHeight;
    387.                  
    388.                     currentLineWidth = spriteWidth;
    389.                     currentLineHeight = lineSpacing;
    390.                     completeWordsWidth = 0;
    391.                     wordBeginning = 0;
    392.                     wordWidth = spriteWidth;
    393.                     currentLine = new List<SpriteRenderer>{lastItem};
    394.                     currentTextLine = "" + lastChar;
    395.                     continue;
    396.                 }
    397.              
    398.                 newLine = currentLine.Splice(wordBeginning, currentLine.Count - wordBeginning);
    399.                 newTextLine = currentTextLine.Substring(wordBeginning, currentTextLine.Length - wordBeginning);
    400.                 lines.Add(currentLine);
    401.                 textLines.Add(currentTextLine);
    402.                 lineWidths.Add(completeWordsWidth);
    403.                 lineHeights.Add(currentLineHeight);
    404.                 lineHeightsTotal += currentLineHeight;
    405.                 completeWordsWidth = 0;
    406.                 wordBeginning = 0;
    407.                 currentLine = newLine;
    408.                 currentTextLine = newTextLine;
    409.                 currentLineWidth = wordWidth;
    410.             }
    411.         }
    412.         // save the last line
    413.         lines.Add(currentLine);
    414.         textLines.Add(currentTextLine);
    415.         lineWidths.Add(currentLineWidth);
    416.         lineHeights.Add(currentLineHeight);
    417.         lineHeightsTotal += currentLineHeight;
    418.      
    419.         // position sprites
    420.         Vector3 point = new Vector3(0, 0, -0.01f); // nudge towards the camera so we can attach to objects
    421.         float x, y = 0;
    422.         float alignX = 0, alignY = 0;
    423.      
    424.         if(alignVert == AlignVert.TOP){
    425.             alignY = Mathf.Round(height * 0.5f - border);
    426.         } else if(alignVert == AlignVert.CENTER){
    427.             alignY = Mathf.Round(lineHeightsTotal * 0.5f);
    428.         } else if(alignVert == AlignVert.BOTTOM){
    429.             alignY = Mathf.Round(-height * 0.5f + (lineHeightsTotal + border));
    430.         }
    431.      
    432.         for(int i = 0; i < lines.Count; i++){
    433.          
    434.             x = 0;
    435.             wordBeginning = 0;
    436.          
    437.             if(lines[i].Count > 0){
    438.                 if(alignHoriz == AlignHoriz.LEFT){
    439.                     alignX = Mathf.Round(-width * 0.5f + (border));
    440.                 } else if(alignHoriz == AlignHoriz.CENTER){
    441.                     alignX = Mathf.Round(-lineWidths[i] * 0.5f);
    442.                 } else if(alignHoriz == AlignHoriz.RIGHT){
    443.                     alignX = Mathf.Round((width * 0.5f - (lineWidths[i] + border)));
    444.                 }
    445.             }
    446.  
    447.             y -= lineHeights[i] * 0.5f;
    448.             for(int j = 0; j < lines[i].Count; j++){
    449.                 renderer = lines[i][j];
    450.  
    451.                 if(renderer != null){
    452.                     if(j > wordBeginning){
    453.                         x += tracking;
    454.                     }
    455.                     x += renderer.sprite.rect.width * 0.5f;
    456.                     point.x = alignX + x;
    457.                     point.y = alignY + y;
    458.                     renderer.gameObject.transform.localPosition = point;
    459.                     x += renderer.sprite.rect.width * 0.5f;
    460.                  
    461.                 } else {
    462.                     x += whitespaceLength;
    463.                     wordBeginning = j + 1;
    464.                 }
    465.             }
    466.             y -= lineHeights[i] * 0.5f;
    467.         }
    468.  
    469.         // apply image offsets
    470.         if(offsets.Count > 0){
    471.             foreach(SpriteRenderer s in offsets){
    472.                 var temp = new Vector3(
    473.                     s.sprite.bounds.min.x + s.sprite.bounds.size.x * 0.5f,
    474.                     s.sprite.bounds.min.y + s.sprite.bounds.size.y * 0.5f
    475.                 );
    476.                 s.gameObject.transform.localPosition -= temp;
    477.             }
    478.         }
    479.      
    480.     }
    481.  
    482. }
    483.  
    484. // C# doesn't come with array.splice so we have to cobble this to make word-wrap work
    485. public static class SpliceExtension{
    486.     public static List<T> Splice<T>(this List<T> list, int offset, int count){
    487.         return list.Skip(offset).Take(count).ToList();
    488.     }
    489. }
    490.  
    The Text object supplied by Unity uses Bilinear filtering as opposed to Point. It will always end up blurry and unusable for 2D games makers like myself.

    If I can't turn off anti-aliasing, I can't use it. End of story.
     
  16. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    Huh? Rune was asking you to record a video, not to watch one. It's the over-the-internet equivalent of him saying 'I don't quite understand what you're saying - can I come watch over your shoulder while you walk me through it?'
     
    Nanako likes this.
  17. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    I don't know if there was a misunderstanding or something here. I didn't ask you to watch any videos. I asked you to record a screencast yourself demonstrating the problem you're having and show it to us here so we have a better change of seeing what the problem is.

    You see, despite all the text and images you posted explaining the problem, I didn't understand exactly what problem you were having. There is too much missing context. In our experience, screencasts let us understand issues far better than text plus images, so we often ask people to record screencasts showing their problems when we have problems understanding what it is.

    Ok, glad you found a solution in the end that you can use for now.

    Oh, the bilinear filtering is the problem? I didn't get that from your posts at all, where you didn't once mention anything about filtering. Instead you talked about "The Canvas will not render anything intelligible otherwise. It also means that text is useless. I'm using a pixel-font that should fit in nicely from Fonts For Flash and it renders massive or not at all. Worse is that when I apply scale to anything it completely screws with the rect transform, making it impossible to position it."

    References to the text being scaled wrong, useless, and things messing with the RectTransform didn't really give any hints about this.

    Allowing other filtering modes is probably something we can look into for the future. In the mean time I'm glad you found a workaround that works for you. I'd also suggest you spend more energy trying to help us understand what issues you're having so we can better help you, than on going on about how you F***ing despise people pointing you to video tutorials when nobody actually did this (in this thread anyway, as far as I can see).
     
    Ultroman, bgs, Jaimi and 3 others like this.
  18. JAKJ

    JAKJ

    Joined:
    Aug 17, 2014
    Posts:
    185
    It would be nice to have transcripts for the videos, though, since it is kind of annoying to go through 3-10 minutes for one little thing. Only a "would be nice", though, something to give to an intern if one wanders by, not for actual developers/staff to spend time on.
     
  19. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    my bitmap fonts look fine, and have a filter mode setting
     
  20. bgs

    bgs

    Joined:
    Jan 14, 2014
    Posts:
    12
    Hey, there's actually a trick to get this to work that I figured out after stumbling upon the same problem.

    You can set the filtering mode to point if you make a copy of the auto generated font asset and then use that in the Text component.

    Attached some screenshots.

    One downside is that you have to use Scale to set a different size than what you imported at or import multiple times with different sizes.

    IMPORTANT EDIT:
    Also if it's blurry for certain "non-pixel" fonts then use Rendering Mode -> Hinted Raster before creating editable copy.
     

    Attached Files:

    Last edited: Sep 16, 2014
  21. Veryname

    Veryname

    Joined:
    Jan 18, 2015
    Posts:
    1
  22. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Thanks to Veryname for the missing bit to what I was looking to achieve (and OP's goal too).
    Complete solution: set all your font rendering mode to Hinted Raster.

    Then on one text per font - not per Text component - you add this:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4.  
    5. [ExecuteInEditMode]
    6. public class SetFontFilteringToPoint : MonoBehaviour
    7. {
    8.     void Start ()
    9.     {
    10.         GetComponent<Text> ().font.material.mainTexture.filterMode = FilterMode.Point;
    11.  
    12.     }
    13. }
    And you get this:
     
    Last edited: Apr 25, 2015
  23. enhawk

    enhawk

    Joined:
    Aug 22, 2013
    Posts:
    833
  24. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    It works perfectly ! You're officially my new hero !
     
  25. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Wow @hawken those look much better...
    @LeRan, haha, but as Hawken demonstrated, you still need purposed pixel art fonts.
     
  26. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Pixel fonts are nice, but not mandatory for me: I'm not trying to have crisp white-and-black pixel letters. Actually, I'm happy with nice gray scales rounded letters, as long as the gray pixels are just as pixelated as the rest of the game. Or if you prefer, the render I'm looking for is the result of an Atari ST's best try to make you believe it has a fine definition when it actually does not.

    But maybe that's just me being a hipster :D

    example:
     
  27. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    The screencap looks like leisure suit larry!
     
  28. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Wow, I don't know if I should be glad that you compare my game-in-progress to such a legendary franchise, or horrified because I was hoping for a "Life is Strange"-esque atmosphere, and with Leisure Suit Larry we are quite far from the original goal ! Maybe that's because I have more of Larry Laffer than Maxine Caulfield in me ? :D

    Technically speaking, the only way I found to obtain pixelated gray scale characters is the one detailed in this thread, that is, your line of code plus combining smaller font size and bigger scale (if anyone has a better idea, please let me know).
     
  29. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Looking at your screenshot, I am convinced you are pulling my leg: there is no way you are aiming for Life is Strange. I mean that yoga girl posturing is a classic LSL moment and behind that UI is Larry himself wearing a very 70s yoga pant about to throw a very dense pick up line.
    Plus the recent LSL reboot was god awful so there is plenty of space for a toilet humor game.
     
  30. PhoenixRising1

    PhoenixRising1

    Joined:
    Sep 12, 2015
    Posts:
    488
    Thanks a lot for this very simple and elegant solution!

    edit: there's a downside with this though, making the text bold is pretty much impossible.
     
    Last edited: Aug 8, 2016
  31. kg

    kg

    Joined:
    Jan 6, 2013
    Posts:
    3
    TL;DR The default "Character = Dynamic" setting is making your fonts blurry!!!

    Thanks to everyone who provided tips in this thread. I found an easy solution I wanted to share. For some fonts, this may be enough. Pixel fonts will benefit even more if you also set the filter mode to Point.
    • Set the "Character" field to "Custom set"
    • Set your Font Size to something large (this increases the size of the texture used for your font)
    • Make sure to fill out the custom set of characters you need to be able to use.
    Demo attached. text-blurry.png text-smooth.png
     
    Last edited: Aug 28, 2016
    Ultroman, DanikV, toink and 1 other person like this.
  32. Peter-Bailey

    Peter-Bailey

    Joined:
    Oct 12, 2012
    Posts:
    36
    I think I found another work around for setting the font texture to point. So, instead of trying to create an editable copy of the font, simply expand the font and duplicate its font material and font texture files using "Ctrl d". Then set the new font texture to point filtering and assign it to the new font material. Go to the gear icon of the new font material and click on "Copy Material Properties". Then go to the gear icon of the old font material and click "Paste Material Properties". This should paste the new font texture into the old font material. Also, before duplicating the font files, make sure Character is set to Unicode. Didn't work with Dynamic. If you make any changes to Font Size or Rendering Mode in the import settings, you'll have to repeat the process. This seems to work, but Unity really needs to fix this. Point filtering fonts is essential.