Author Topic: PixelPerfect and Atlas switching  (Read 7638 times)

electrodruid

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 1
  • Posts: 48
    • View Profile
PixelPerfect and Atlas switching
« on: March 20, 2013, 10:35:19 AM »
Hi,

Apologies for all my atlas switching questions recently - I've been having trouble getting to grips with it all  :-[

Here's my setup: UIRoot is set to PixelPerfect, and although I'm using UIStretch (and several custom variants of it) to scale panels made from UISlicedSprites, anything involving a UISprite is set to the same size as the resolution of the texture it uses in the editor (i.e. by pressing the SetPixelPerfect button). Likewise with UILabels - we don't scale text. The artist I'm working with is very particular about not wanting any scaling to muddy up any of his textures. Having the UIRoot set to PixelPerfect is a difficult path to take, since a lot of the stuff that "just works" in NGUI only works if you have a fixed size set.

The problem is that there doesn't seem to be a way to automatically rescale UISprites and UILabels after switching over to a HD atlas. So, a button image that's 150*50 pixels in the SD atlas is 300*100 in the corresponding HD atlas - but when the game decides to use the HD atlas and changes the reference, the widgets don't resize. If I call NGUITools.Broadcast("MakePixelPerfect"); after setting the atlas the active widgets resize properly, but the inactive widgets in the scene don't and will still look small when they get activated.

Is broadcasting the MakePixelPerfect the only (or the recommended) way of doing this? And if I do it that way, will I have to broadcast the message every time I activate something, just in case it has been inactive since the atlases switched over? Or do I need every active object that holds a reference to an inactive one to use that reference to try to walk through the children trying to rescale everything?

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #1 on: March 20, 2013, 11:55:54 AM »
Pixel Size setting on the Atlas. 1.0 for SD atlas, 0.5 for HD, for example. You shouldnt need to MakePixelPerfect after an atlas change as your UIRoot should have a fixed size.

electrodruid

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 1
  • Posts: 48
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #2 on: March 20, 2013, 12:54:47 PM »
I must have missed something about how fixed size UIRoots can be made to work...

I can see how setting the Pixel Size on the atlases will work if the UIRoot has a fixed size - what I can't see is how you can maintain pixel perfect widgets across different resolutions if the UIRoot has a fixed size. It always scales stuff according to how the device resolution compares to the Manual Height setting, which I don't want. I want a 50*50 pixel sprite to be 50*50 pixels at any resolution except when the device is high-resolution enough to switch over to using an HD atlas. Then I want it to be 100*100, same size as on the HD atlas. I don't want the sprite to ever be 42*42 pixels, or 87*87, or any other number.

franktinsley

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 4
  • Posts: 17
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #3 on: January 03, 2014, 04:50:19 AM »
Did you ever find a satisfactory solution to this? ArenMook's suggestion to use a fixed size uiroot has me baffled. How is an iOS game with any screen rotation supposed to work then? After all, their screen height will change between portrait and landscape.
It's actually proving to be really difficult to find complete support for swapping different resolution atlases. All the examples I find leave the actual runtime implementation a total guessing game assuming you don't know NGUI as well as the developer that wrote it.
A real example that actually works on different devices would be very helpful assuming it doesn't leave out support for autorotation.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #4 on: January 03, 2014, 05:45:15 AM »
With reference atlases it's up to the developer to add code that loads the appropriate atlas at run-time based on whatever conditions he wishes to use -- whether it's screen height, device name, or anything else. If you are worried about landscape vs portrait modes, simply use Mathf.Min(width, height), and use that in your logic.

What I suggest doing is this:

1. Create an HD and an SD atlases. One has pixel size of twice that of the other one. They must all use the same sprite names. Sprite called "NGUI" in one atlas must also be called "NGUI" in the other atlas. Don't use suffixes like "NGUI HD" and "NGUI SD". Place both atlas prefabs into the Resources folder.

2. Create a third atlas -- your reference atlas, and set it to point to your default atlas (SD for example).

3. Set UIRoot to fixed size and create your entire UI using the reference atlas. You don't need to keep "fixed size" if you don't want, but it's useful for design. PixelPerfect works as well as long as you are aware of the differences in size between your game window and your target device's screen resolution.

4. Create a script that references the Reference atlas and has some logic in its Awake() function that will load the appropriate atlas -- HD or SD using Resources.Load. This is where your "which atlas to load?" logic would be located. Set the reference atlas to point to the newly loaded atlas.

5. When ready to publish, start a new scene and clear the reference on the reference atlas (so that it points to nothing). Assuming your script is present in the very first scene that will load (such as your splash screen), your logic will run and load the appropriate atlas.

franktinsley

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 4
  • Posts: 17
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #5 on: January 03, 2014, 11:48:20 PM »
I appreciate the instructions but they just don't work. I triple checked everything I have setup and I did it exactly how you describe. I assume we're supposed to be swapping atlases in Awake() via myAtlas.replacement? Because when I do that, it loads the atlas, but as electrodruid mentioned, the sprites end up staying the size they happen to be setup with so extra pixel density is lost as the size of the image does not increase in order to not appear smaller on a higher resolution screen. Follow? I tried both fixed size and pixel perfect and neither one helps because the sprites stay whatever size they were no matter what size atlas or what pixel size the atlas has set to it. It just feels like it's broken or bugged.

I think the issue may be here in UIAtlas.cs. Notice the comments above public float pixelSize:
   /// <summary>
   /// Pixel size is a multiplier applied to widgets dimensions when performing MakePixelPerfect() pixel correction.
   /// Most obvious use would be on retina screen displays. The resolution doubles, but with UIRoot staying the same
   /// for layout purposes, you can still get extra sharpness by switching to an HD atlas that has pixel size set to 0.5.
   /// </summary>
Pixel size is apparently performed during MakePixelPerfect(). So if that function isn't being called on every widget using a replaced atlas, how is pixel size coming into play?

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #6 on: January 04, 2014, 09:48:47 AM »
The size of the sprites isn't supposed to increase. That's the whole idea. Pixel density changes, but widget sizes remain constant. That's why you should set the pixel size on the atlas to the inverse of the actual dimension changes.

For example if the SD atlas uses 128x128 sprites, and HD atlas uses 256x256 sprites, SD atlas should have pixel size of 1, and HD atlas should have pixel size of 0.5.

128 * 1 = 128
256 * 0.5 = 128

Widget size remains constant. Only the pixel density increases.

electrodruid

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 1
  • Posts: 48
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #7 on: January 04, 2014, 08:48:40 PM »
To answer your question Frank, we ended up keeping the pixel perfect root, and dealing with the atlas switch by calling MakePixelPerfect, doing something like this to catch the widgets which are inactive at the time (I don't have the code in front of me, so this might not be 100% right):

  1. // This code is called from with a custom version of UIRoot - if you want to put it somewhere else, you'll need to find the UIRoot in the scene.
  2. Component[] widgets = GetComponentsInChildren(typeof(UIWidget), true);    // search for inactive children as well
  3.  
  4. foreach(UIWidget widget in widgets)
  5. {
  6.     if(widget != null)
  7.         widget.MakePixelPerfect();
  8. }
  9.  

It's slow, but you should only need to do it once. We also ended up basically replacing UIStretch with a number of custom versions, in order to stretch Colliders, UIGrid spacing, the areas in which UILabels can draw, etc. All of these could scale using "pixels" as well as/instead of the standard relative scaling. "Pixels" in this case could be adapted by some settings in our custom UIRoot, so that if you stretch something to be (say) 100 pixels wide, then that gets combined with a multiplier. When using standard def atlases, the multiplier is 1. When using HD atlases, it's 2, so the widget ends up 200 pixels wide, which is what you want.

The MakePixelPerfect call should (in theory) deal with most of this, but in practice we found that we had to attach custom anchors and stretches to pretty much everything in the scene, which then meant we had to add performance optimisations like the ability to turn all those anchors and stretches off after a set number of frames, once the screen had settled down. It was, to put it politely, a bit of a pain to get working, and something we definitely won't be doing again. As a result of all the custom code, we're a few versions behind on NGUI now, but I sincerely hope that more recent versions (or ones in the near future) support the PixelPerfect option in UIRoot properly, or remove it entirely.

If you're interested in the results, this is what I built: https://itunes.apple.com/gb/app/smash-bandits/id602403667?mt=8 - we had some praise for having a nice crisp UI, but in the end I'm not convinced that the extra effort was worth it. Better to ignore the PixelPerfect option, go with a relatively-scaled UIRoot, and if needs be write a custom component to specifically call MakePixelPerfect on any widgets that really need it. If you need to support both portrait and landscape mode, I'd suggest building a seperate UI for each orientation.

franktinsley

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 4
  • Posts: 17
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #8 on: January 04, 2014, 09:29:18 PM »
Wow that's really interesting and honestly really disappointing. I love NGUI but atlas swapping is such a do or die feature that it really badly needs to be addressed ASAP.

To help demonstrate the issue at hand I've created a project that breaks the problem down. At the center of the problem, I believe, is the PixelSize property of UIAtlas and the fact that it simply doesn't cause any effect when loading an atlas at start up. (The problem that I believe electrodruid had to create an elaborate work around for.)

To illustrate PixelSize not causing any effect I've created 4 web player builds of a simple project.

The project does NOT have different resolution atlases. It simply has one real atlas and one reference atlas to show the effect of loading an atlas at start up. The widgets of course use the reference atlas and not the real atlas.

FixedSize UIRoot with UIAtlas PixelSize set to 0.5

FixedSize UIRoot with UIAtlas PixelSize set to 1

PixelPerfect UIRoot with UIAtlas PixelSize set to 0.5

PixelPerfect UIRoot with UIAtlas PixelSize set to 1

The project. (Add your own NGUI)

The different builds all have different pixel sizes and different UIRoot scaling styles.

Notice that none of the changes have any effect!

I don't see how there can be any correct way to use atlases if the PixelSize property doesn't cause ANY effect when loading an atlas at runtime.

Hope this breakdown helps isolate the issue here. Thanks!

franktinsley

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 4
  • Posts: 17
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #9 on: January 04, 2014, 11:34:04 PM »
Okay I think I finally found a work-around. Essentially all I do is set the fixed size to the screen height but then scale it exactly how the current UIAtlas pixel size would be if that property had an effect. Seems to work so far. Here's my code. It keeps the fixed size to the screen height even if it auto rotates and changes.

Oh and the logic here for checking device would need to be expanded on to either take into account more devices or be based on screen size.

  1. using UnityEngine;
  2. using System.Collections;
  3.  
  4. public class CorrectForDevice : MonoBehaviour
  5. {
  6.         public UIAtlas referenceAtlas;
  7.  
  8.         private UIRoot uIRoot;
  9.         private ScreenOrientation currentOrientation;
  10.         private bool initialized = false;
  11.         private float resolutionScale = 1f;
  12.  
  13.         void Awake()
  14.         {
  15.                 uIRoot = GetComponent<UIRoot>();
  16.  
  17.                 if( iPhone.generation == iPhoneGeneration.Unknown )
  18.                 {
  19.                         referenceAtlas.replacement = Resources.Load<GameObject>( "iPadRetina" ).GetComponent<UIAtlas>();
  20.                         resolutionScale = 0.5f;
  21.                 }
  22.                 if( iPhone.generation == iPhoneGeneration.iPhone5 )
  23.                 {
  24.                         referenceAtlas.replacement = Resources.Load<GameObject>( "iPhone" ).GetComponent<UIAtlas>();
  25.                 }
  26.         }
  27.  
  28.         void Start()
  29.         {
  30.                 currentOrientation = Screen.orientation;
  31.         }
  32.  
  33.         void Update()
  34.         {
  35.                 if( !initialized )
  36.                 {
  37.                         SetHeight();
  38.                         initialized = true;
  39.                 }
  40.  
  41.                 if( Screen.orientation != currentOrientation )
  42.                 {
  43.                         SetHeight();
  44.                 }
  45.         }
  46.  
  47.         private void SetHeight()
  48.         {
  49.                 uIRoot.manualHeight = (int)((float)Screen.currentResolution.height * resolutionScale);
  50.                 currentOrientation = Screen.orientation;
  51.         }
  52. }
  53.  

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #10 on: January 05, 2014, 07:48:21 AM »
I don't see how there can be any correct way to use atlases if the PixelSize property doesn't cause ANY effect when loading an atlas at runtime.
It won't. Not unless you call MakePixelPerfect() or click the Snap button. Swapping an atlas won't resize your widgets for you. Again, the whole idea behind reference atlases is that you have fixed dimension widgets, and only the pixel density changes. Not the widget size.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #11 on: January 05, 2014, 07:52:22 AM »
Made a video explaining reference atlases (when it finishes processing):

http://www.youtube.com/watch?v=dbwgP6PC4go

franktinsley

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 4
  • Posts: 17
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #12 on: January 05, 2014, 07:05:43 PM »
I really appreciate the effort to further explain atlas swapping.

I think the confusion here comes from the design of atlas swapping in NGUI being centered around keeping the widgets the same size relative to the UIRoot. I understand why that would seem to be the desired behavior. After all, the button on an SD iPhone and an HD iPhone should be the same size visually, right?

But if the UIRoot is scaling all the widgets so that they appear the pixel size dimensions from sprites in an SD atlas, then when the screen itself is HD, that widget will now be a quarter the size it should be. The widget can only stay the same size visually on a higher density display if either the widget size increases to match the pixels dimensions of the HD sprite, or the UIRoot scales everything relative to a lower resolution than the actual display.

The solution I found was to indeed set the UIRoot fixed size height to a lower resolution than the display actually is when using a higher resolution atlas.

From what I can gather, it appears most NGUI users don't try to use a pixel perfect design across different devices. Maybe for most developers it's not important but I do wonder how many of them don't use pixel perfect designs because they can't get a handle on the atlas swapping and widget size designs in NGUI.

Just some food for thought.

franktinsley

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 4
  • Posts: 17
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #13 on: January 06, 2014, 07:55:56 PM »
Oh and the thing I think would totally put this to rest once and for all is if you added an example scene to NGUI that could be built to different devices and had working atlas swapping. Being able to see that all working end to end would be massively helpful, even if it was super basic, like you had to tell it in the editor if you're building for SD, HD, or SHD, etc instead of it detecting the device PPI or anything.

Might be a good idea to try this on real devices for yourself as well. You might be surprised by what you discover.

SphereBaby

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 4
  • Posts: 3
    • View Profile
Re: PixelPerfect and Atlas switching
« Reply #14 on: March 13, 2014, 01:09:35 PM »
The solution I found was to indeed set the UIRoot fixed size height to a lower resolution than the display actually is when using a higher resolution atlas.

Thanks so much for this solution. I had been hacking around in UIRoot.cs, but this approach is cleaner. I wrote up a script that scales the UI, in a pixel-perfect way, based on the DPI of the device. It also handles atlas switching. I posted the script over here.