GUI aspect ratio independence

Started by
19 comments, last by rpiller 10 years, 5 months ago

If you want positions, spacing and sizes look the same regardless of display aspect ratio, then there is definitely just one way: letterbox / pillarbox (perhaps covered with some background, but not with UI elements). As soon as you want the recognized density of UI elements over the entire screen being independent on the display aspect ratio, you need to free one or more of position, spacing, and/or size from above restriction.

There are 2 situations to look at: (1) a HUD during the gameplay time, and (2) a general GUI ("menu screen").

For (1) it is usually so that the "logical" locations center of screen, edges, and corners give a good anchor for placement.

IMHO, the most satisfying solution for (2) is to define a mean aspect ratio from the targeted platforms, and use that at the design choice. When running on a target platform, fit the designed size into the available display and scale both the positions and sizes accordingly to the factor calculated from fitting (this is one factor for both dimensions). To avoid the nasty black margins of letter-/pillarbox, just use an oversized background so that all pixels are covered. Doing so, on a 16:9 display there are small GUI-less areas left and right, and on a 4:3 display they are at the top and bottom.

Advertisement

OK, I'll give this a shot. I have to see it in action and the same settings for different resolutions as I can't picture how it would exactly look in terms of size (I get position) in my head. I get that you'd get more clean space but for some reason I feel like that's not ideal. I feel like trying to closely match the screen space volume the UI takes up on all screen resolutions is ideal but maybe it's just not possible.

What is res.x & res.y? Resolution x & y would be 0's wouldn't they or are you assuming the positioning was done before this code and res.x and res.y represent the top-center point?


so if you place a 0.5 units wide and 0.1 unit high healthbar 0.05 units down and 0.25 units to the left (to compensate for its width) from the top-center you just draw it at:

x = res.x / 2 + (-0.25 * res.y) //not a typo, we always multiply with the vertical resolution, the horizontal resolution only affects the origin position
y = 0 + (0.05 * res.y) 

So the size formula in the above situation would be: width = .5 * res.width & .1 * res.width?

So basically all controls have to be given a starting point of either the edges, corners, or center and then these units are offsets from that?

res.x = resolution on the x axis (or resolution width).

as for the size formula: multiply by height rather than width (otherwise things get bigger on a widescreen)

you could also multiply by whichever of width and height is smallest to handle vertical displays as well. (otherwise objects would get very big on vertical displays which you probably don't want)

[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!

So the way you get top-center in the equation is:

(res.x / 2) center in the x formula and the 0 in the y formula for top. So in my designer I'll have to make like a dropdown to select which the control should be anchored to.

Good call on the size formula. I guess at startup (or orientation change event for mobile) I'll calculate which one to use.

Very well said haegarr. +1

rpiller, you can't have it both ways, Stretch to fit, but with proper aspect ratio. If you could, then DVD's wouldn't have the display options Letter Box, Stretch to Fit, Zoom and Pan. They's just have "do it right without stretching, letterboxes, or cropping the display". As soon as you start dealing with multiple aspect ratios, you have to decide which is most important to you.

Let's take an extreme example with numbers our brains can easily process. Let's say we have two screen resolutions.

Resolution 1:

100 width x 50 height

And there is one gui element, a red X in the 50 x 50 square, filling up the left half of the screen.

Resolution 2:

400 width x 50 height

And there is one gui element, a red X that "looks the same" as Resolution 1

How do you want that Red X drawn in Resolution 2?

* Stretch to fit, it should cover the left half. X will look more like ><

* Letter Box, so that the X is drawn in a size 50 x 50. Stays looking like an X, but we have left over screen real estate

* Fill in leftovers with black (DVD players do this)

* Fill in leftovers with a bigger background image.

* Fill in leftovers with a wider camera of your game world (this is what World of Warcraft does)

* Zoom and Pan - probably not a good option for most games

EckTech Games - Games and Unity Assets I'm working on
Still Flying - My GameDev journal
The Shilwulf Dynasty - Campaign notes for my Rogue Trader RPG

Have you considered positioning items relative to fixed anchors (bottom left, left center, top left, top center, etc.) and either

a) using distances/lengths defined relative to the minimum of the width and height of the screen

b) using area percentages instead of length percentages

You could also use the width/height and anchors defined by the appropriate safe areas for these

I'm using SimonForsman's method but I'm converting from pixels to units and units to pixels in my GUI editor. When I use TopLeft for the anchor the formulas he has to convert units to pixels works. The functionality I want is when the user changes the anchor I want the control to stay where it is, but recalc the units using the new anchor values in the formula. Below is what I have in my calc for anchor values but when I change the anchor the control doesn't get the right new units. What am I messing up with this?

switch (Anchor)
            {
                case Anchors.TopLeft:
                    p.X = 0;
                    p.Y = 0;
                    break;
                case Anchors.TopCenter:
                    p.X = (res.Width / 2);
                    p.Y = 0;
                    break;
                case Anchors.TopRight:
                    p.X = res.Width;
                    p.Y = 0;
                    break;
                case Anchors.RightCenter:
                    p.X = res.Width;
                    p.Y = (res.Height / 2);
                    break;
                case Anchors.BottomRight:
                    p.X = res.Width;
                    p.Y = res.Height;
                    break;
                case Anchors.BottomCenter:
                    p.X = (res.Width / 2);
                    p.Y = res.Height;
                    break;
                case Anchors.BottomLeft:
                    p.X = 0;
                    p.Y = res.Height;
                    break;
                case Anchors.LeftCenter:
                    p.X = 0;
                    p.Y = (res.Height / 2);
                    break;
                case Anchors.Center:
                    p.X = (res.Width / 2);
                    p.Y = (res.Height / 2);
                    break;
            }


When I use TopLeft for the anchor the formulas he has to convert units to pixels works. The functionality I want is when the user changes the anchor I want the control to stay where it is, but recalc the units using the new anchor values in the formula.

Using the editors screen as reference, an absolute pixel position is

pp = ( x, y )

When computing the absolute position from the relative position pr, the relative position is scaled by the vertical resolution rv in both dimensions, and the absolute position of the anchor pa is added:

pp := pa + pr * rv

Because you're interested in the relative position for a given absolute position, you need to solve for that variable. Hence:

pr = ( pp - pa ) / rv

Notice that the anchor position pa herein is those of the newly picked anchor, because you want to calculate the result relative to those.

So pr = ( pp - pa ) / rv is the equation I'm using to get the (I call x,y unit):


         // this is called from the editor when a control is placed. we pass in the actual pixel x and convert to "units"
        private float _SetPixelX(int x, Size res)
        {
            return (float)(x - GetAnchorPoint(res).X) / (float)res.Height;
        }

To convert from units back to pixels for drawing I do the following (which works because it draws fine with default TopLeft anchor):


        protected int XUnitsToPixels(Size res)
        {
            return GetAnchorPoint(res).X + (int)(X * res.Height);
        }

When the user changes the anchor, I convert units (which is what I store) back to pixels using the above function, then using the new anchor values I convert the pixels back to the new units, but it doesn't work using a different anchor. The units end up all over the place.

[EDIT]

Actually initially it works for any anchor. It's when I change anchors that it doesn't end up being the same position after recalcing the units with the new anchor.

inside my Control class


        private Anchors anchor;
        public Anchors Anchor 
        {
            get { return anchor; }
            set
            {
                Size res = new Size();
 
                anchor = value;
 
                // get the canvas resolution so we can recalc position
                if (OnAnchorChanged != null)
                    res = OnAnchorChanged(this);
 
                // recalc the pos units to keep the widget in the same location given this new anchor selection
                int _x = XUnitsToPixels(res);
                int _y = YUnitsToPixels(res);
 
               // x & y are member variables that are in units
                x = _SetPixelX(_x, res);
                y = _SetPixelY(_y, res);
 
                // need to call this to show the changes by making the canvas redraw the controls
                if (OnInvalidated != null)
                    OnInvalidated(this);
            }
        }

If I understand your code snippet correctly, then this part


                anchor = value;

                // get the canvas resolution so we can recalc position
                if (OnAnchorChanged != null)
                    res = OnAnchorChanged(this);

                // recalc the pos units to keep the widget in the same location given this new anchor selection
                int _x = XUnitsToPixels(res);
                int _y = YUnitsToPixels(res);

               // x & y are member variables that are in units
                x = _SetPixelX(_x, res);
                y = _SetPixelY(_y, res);

should instead look like this


                // get the canvas resolution so we can recalc position
                if (OnAnchorChanged != null)
                    res = OnAnchorChanged(this);

                // recalc the pos units to keep the widget in the same location given this new anchor selection
                int _x = XUnitsToPixels(res);
                int _y = YUnitsToPixels(res);

                anchor = value;

               // x & y are member variables that are in units
                x = _SetPixelX(_x, res);
                y = _SetPixelY(_y, res);

in order to ensure that at the moment where the pixel position is calculated the now obsolete anchor is used. Just after that the anchor is allowed to be switched to the new one, and that one has to be used to re-calculate the new relative position.

However, make sure that an initial anchor is correctly set-up, or else the first invocation of the setter will fail.

Oh man how did I miss that :) You are correct! Thank you for helping me with that. It was driving me crazy as I was pretty sure my formulas were right.

This topic is closed to new replies.

Advertisement