Search Unity

How to force pixelated text into the UI canvas?

Discussion in 'UGUI & TextMesh Pro' started by LeRan, Jun 25, 2016.

  1. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Hi forum,

    I've been looking for hours on the internet but couldn't find the solution to my problem. I'm trying to make a clean pixel-art game, and thus I need the text displayed by the UI to be pixelated just as much as the rest of the game.

    Thanks to this post, I found a way to make the text conveniently sharp when using big character fonts, so that's OK so far.

    My concern now is to be able to scale the UI to match the current orthographic scale of the game (like 2, or 3, or 4 actual screen pixels per drawn game pixel). The "scale factor" in the canvas scaler does the trick for the sprites of the UI, but not for the text : when scaled up, all texts gain finer definition. What I would need is to change the orthographic size of the camera displaying the UI: when I set the display of the canvas to "world space" and change the orthographic size of the main camera, it works and scales up with respect to their pixels both the sprites and the texts.

    But how to do this without having the UI in world space, which really is the opposite of what I need for a UI?
     
  2. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    The best workaround I could find so far is this. Supposing that I want to have my UI display X screen pixels per game pixel:
    - set the canvas scale factor to X,
    - manually divide the font size of each text by X,
    - manually multiply the scale of each text by X.

    It still seems very, very suboptimal...
     
  3. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Personally I would do this through a Shader. We have a few shaders in the UI Extensions project (link in sig) for applying to UI elements, so if you can try one of the standard pixillated shaders using the same techniques, it should work. (but might need a tweak for the way UI draws)

    Hope this helps
     
  4. Zaflis

    Zaflis

    Joined:
    May 26, 2014
    Posts:
    438
    What if you render the game into texture and then scale it by screen size without texture filtering? Retro games had resolutions like 320x240. This would practically allow huge flexibility (even 3D models) to rendering itself, because it will all become pixelated anyway.
     
    SimonDarksideJ likes this.
  5. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @LeRan - I simply can't understand, what are you actually trying to achieve?

    1. You set the text (point) filtered and hinted, then it's as good as it gets (...with standard tools). If you want to show 32pixel text on screen the make it 32 pixels and set it's size 32 in text. Also set aligned to geometry in text. I think it looks quite OK else use Pixel fonts. If either is different size, you can get some amount of filtering visible.

    2. Setting camera to World space example simple does not make sense, technically you are just moving in front of your flat UI image back and forth in 3D space, of course it will scale as one unit.

    3. By Setting "scale factor", do you mean you set "Reference Pixels per Unit" ? I think it works as expected, setting it to low values make UI elements appear high DPI and vice versa.

    4. If you set Canvas Scaler to "Scale with Screen Size", you get the expected anti-aliasing when sizing viewport smaller or larger than exact resolution you want. When you set Canvas Scaler to "Constant Pixel Size", you get static pixel sized UI, basicly your viewport pixel surface area just grows or shrinks around your UI.

    Anyway, pixel fonts won't stay crisp, no matter what, unless you: A. have them be constant size or exact multiple of their pixel resolution.

    Having some fancy shader won't change this pixel issue, what would it do, I don't know.

    Anyway, maybe you can create a script that compensates the size of Text UI component automatically?
     
    Last edited: Jun 29, 2016
  6. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @LeRan

    To continue scripting idea;

    Create script that takes into account actual current screen size, then resize your text elements by float value that is exact scaling multiplier, that way you can counter screen size changes, fonts stay the same size despite of scaling screen size.

    This is most likely not enough so, then when screen size is double, half, quarter or whatever size of reference resolution, you can divide/multiply font size by that amount --> screen size goes from 800px to 400px, then font snaps from 32px to 16px and so on.

    That's one way to do it.

    But anyway, I think it's just matter of deciding what resolutions you aim at, then decide you want to show more stuff at same pixel density, or if you have to step up in pixel size.

    I think I did read some article about iOS game, some pixel game makers went to extremes, and created multiple tile / sprite sets too for different pixel densities, to allow similar experience on different screens, but that sounds like a nightmarishly overkill task.
     
  7. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @LeRan - Hi again, did you manage to fix your problem?
     
  8. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Hello again, thanks very much for your answers, and sorry for the late answer, I've been on vacation...

    Yes, I've somewhat fixed the problem by using the workaround I told above : as far as I understand, it's more or less the same as what eses explains:
    The script goes like this (expurgated of try/catch and debug.log, but not of the French variable names I use... it should be rather straightforward though):
    Code (CSharp):
    1.  
    2. //note : "ratioPixels" is a static integer that's set elsewhere, and can be modified at runtime
    3. foreach (Text leTexte in tousLesTextes)
    4.         {
    5.                 //NOTE : I store initial font size and initial rect dimension of each text field in a script attached to the text object. That's not necessary if I only scale things once at start, but that way I can change resolution as many times as I want at runtime. The next line is this script that stores those 2 variables being called.
    6.                 ResolutionFonte scriptResolutionFonte= leTexte.gameObject.GetComponent<ResolutionFonte>();
    7.                 int laRésolutionInitiale = scriptResolutionFonte.résolutionInitiale;
    8.                 Vector2 dimensionsInitiales=scriptResolutionFonte.dimensionsInitiales;
    9.                 leTexte.fontSize=(int)Math.Round((decimal)laRésolutionInitiale/ratioPixels);
    10.                 Vector3 leScale = new Vector3(ratioPixels, ratioPixels, 1);
    11.                 Vector2 dimensionsFinales=new Vector2(dimensionsInitiales.x/ratioPixels, dimensionsInitiales.y/ratioPixels);
    12.                 leTexte.rectTransform.localScale=leScale;
    13.                 //Under there : I must change manually the RectTransform, or the text field could become larger than the sprite setting it's graphic boundaries.
    14.                 leTexte.rectTransform.sizeDelta=dimensionsFinales;
    15.             }
    Here is what I obtain when redimensionning at runtime. Note the pixelated aspect of the non-pixel fonts, which is what I was trying to do (please don't mind the terrain graphics, they are not even nearly finished yet).


    As for the question "why am I trying to obtain that effect, and moreover why do I want to be able to change that ratio at runtime", the answer is: because 1) I like old games and 2) I don't know yet how old I want my game to look, so I prefer to make that customizable to avoid redoing my whole UI if I change my mind someday...
     
    Last edited: Jul 15, 2016
  9. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    On the other hand, I'm still trying to make the sprites in the UI get a more "pixely" look when the custom pixel ratio goes up, but the Transfrom "scale" parameter as well as the Canvas "Scale Factor" tend to do my own good against my will, and do their best to combinate scales up and down and avoid thus to create grainy picture. Which is what I want :(

    (to make things clear, I was hoping that setting the sprite scale to 0.1 and the canvas scale factor to 10 would create big "virtual" pixels of 10 real pixels each but no, for Unity it just means that the sprite scale is 1, nothing to see here...)
     
  10. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @LeRan

    something like this? Here is same font rendered in 32,16,8 pixel size:

     
  11. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    @eses Hey, that's what I'm trying to achieve! But the fonts I'm using would never be so clean and readable in small sizes (like size 8) with just pixel perfect B&W (using "hinted raster" as the font render mode), so I was happy to let them have their "pixelated blur" (with the "smooth" render) like the text in my example (I'm not sure if that's clear ?). I'm impressed that the font you're using remains so beautiful at a small size. Was is designed for that purpose, or am I just unlucky with my fonts?

    Besides that, now I'm trying to obtain a similar result with UI sprites: game sprites can just become larger when the resolution goes down (like in my example), but UI sprites need to keep more or less the same screen size, thus they need to become more pixelated. To be clear, I would like to change at runtime fig.1 into fig.2, without having to draw multiple sprites for everything :


    Is there a way to achieve that?

    @SimonDarksideJ Thanks for your advice! I've downloaded your package and am experimenting with it now. I'm already glad you helped, but if that's not asking too much, I could use some direction: do you know if there is a shader or script that could achieve what's stated above?
     
  12. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @LeRan

    I don't think there is easy solutions for sprite/UI Image detail level AFAIK.

    Someone more informed can correct if I'm wrong.

    There are some scripts available in Unity wiki, then there are articles about pixel perfect graphics in Unity, that might have some info how to setup things. Haven't researched this issue that much... although very interesting topic.

    I know you mentioned UI elements, but UI Image component uses sprites so it's basically the same as Sprites themselves.

    But this is what you can do with out of the box tools / no coding:
    1. Set sprite import size, in some cases this might be enough to have pixelated image
    2. Set sprite filter mode to point to avoid blur.

    I think there isn't way to reduce size of sprites in effortless manner; it gets involved:

    1.You can't read sprites without first setting it's texture to readable mode in Advanced import settings.
    2. You can't just "scale" the sprite texture2D with .Resize, you get only empty resized texture (what?!?)
    3. You have to read pixels of original sprite to create new scaled down sprite (luckily there is GetPixelBilinear)
    4. Seems like it can be slow... or very slow.
    5. You will have to create new sprite to contain your new texture, otherwise you can't set PPU.
    6. So you have to calculate Pixels Per Unit too.

    Couldn't resist and gave it a go with Kenneys sprite. Note that this is a Sprite with SpriteRenderer, but I think doesn't make difference as it's sprite we are manipulating. By pushing all the pixels in array and setting them at once is OK, otherwise this is really slow:

    https://db.tt/ukGdxfup

    It might be best to use some tool or shader from Asset Store like @SimonDarksideJ already mentioned, if such exists.
     
    LeRan likes this.
  13. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    @eses Oh, nice! I may as well give it a go while I have some free time. What is that script that you are using? Something you coded yourself? If so, would you care enough to share? (I'm not used to coding that sort of thing, and I like very much to learn by imitation!)

    edit: nevermind, I could figure something by tweaking the "GetPixelBilinear" example in Unity's documentation; for the record and in case it can be useful to someone, here is the resulting code (that's just for testing purpose: the target texture is still provided manually to this script). I think I'll have to add an "original sprite" parameter to all my UI sprite elements, different from the "currently displayed sprite", that will be programmatically generated everytime the resolution changes. Hey, if it works, kudos to us :)

    Code (CSharp):
    1. public class TrafiquePixel : MonoBehaviour {
    2.  
    3.     public Texture2D sourceTex;
    4.     public int facteurRéduction = 1;
    5.     public float pixelsParUnité = 100f;
    6.     private Texture2D destTex;
    7.     private Color[] destPix;
    8.  
    9.     void Start() {
    10.         destTex = new Texture2D(Mathf.RoundToInt(sourceTex.width/facteurRéduction), Mathf.RoundToInt(sourceTex.height/facteurRéduction));
    11.         //Debug.Log ("dimensions : "+destTex.width+", "+destTex.height);
    12.         destPix = new Color[destTex.width * destTex.height];
    13.         int y = 0;
    14.         while (y < destTex.height) {
    15.             int x = 0;
    16.             while (x < destTex.width) {
    17.                 float xFrac = x * 1.0F / (destTex.width - 1);
    18.                 float yFrac = y * 1.0F / (destTex.height - 1);
    19.                 destPix[y * destTex.width + x] = sourceTex.GetPixelBilinear(xFrac, yFrac);
    20.                 x++;
    21.             }
    22.             y++;
    23.         }
    24.         destTex.SetPixels(destPix);
    25.         destTex.Apply();
    26.         destTex.filterMode = FilterMode.Point;
    27.         SpriteRenderer sr = GetComponent<SpriteRenderer>();
    28.         sr.sprite = Sprite.Create(destTex, new Rect(0, 0, destTex.width, destTex.height), new Vector2(0.5f, 0.5f), pixelsParUnité);
    29.         sr.transform.localScale = new Vector3(facteurRéduction, facteurRéduction, 1);
    30.     }
    31. }
    32.  
     
    Last edited: Jul 17, 2016
    eses likes this.
  14. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @LeRan - OK, you got it working already - Good for you - by looking at your code I don't think my few lines are that much different, and your code looks pretty much the same to me, except I'm not sure if my code allows for scaling of sprite larger than it was, now that I think about it.
     
  15. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    All right, everything was working smoothly, I thought about posting a couple screenshots of how it worked beautifully to acknowledge our fine cooperation, but... once the project is built, UI elements flicker 2 or 3 seconds after launch and then disappear. What happens? Everything works fine when launched directly from Unity.

    Maybe I'll post that on a separate thread just in case, that I'll close if I get an answer here.