2

I've been trying to reach a good solution on different resolutions, but none have been working very well, either the sprites get distorted, everythings gets offset, or a variety of different shenanigans.

The best solution I got was this, where it uses a RenderTarget and Transformation Matrix to scale everything down according to the resolution, however when the aspect ration is not the same as the virtual resolution, things get offset on the Y axis gif of it happening, here's the Draw code:

GraphicsDevice.SetRenderTarget(RenderTarget);

var scaleX = (float)ScreenWidths[CurrentResolution] / 1920;
var scaleY = (float)ScreenHeights[CurrentResolution] / 1080;
var matrix = Matrix.CreateScale(scaleX, scaleX, 1.0f);

spriteBatch.Begin(transformMatrix: matrix);

GraphicsDevice.Clear(BackgroundColor);            

foreach (var uiElement in UIElements)
{
    uiElement.Draw(gameTime, spriteBatch);
}

spriteBatch.End();

GraphicsDevice.SetRenderTarget(null);

spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend,
            SamplerState.LinearClamp, DepthStencilState.Default,
            RasterizerState.CullNone);

var offsetX = ScreenWidths[CurrentResolution] / 2 - 1920 / 2 * scaleX;
var offsetY = ScreenHeights[CurrentResolution] / 2 - 1080 / 2 * scaleY;
spriteBatch.Draw(RenderTarget, new Rectangle((int)offsetX, (int)offsetY, (int)(1920), (int)(1080)), Color.White);

spriteBatch.End();

var mouseState = Mouse.GetState();
MousePosition = Vector2.Transform(new Vector2(mouseState.X, mouseState.Y), Matrix.Invert(matrix));

base.Draw(gameTime);

This is on the Initialise:

ScreenWidths = new int\[\] { 1920, 960, 1366, 1280, 1280, 1366 };
ScreenHeights = new int\[\] { 1080, 540, 768, 1024, 720, 680 };

RenderTarget = new RenderTarget2D(
    GraphicsDevice,
    GraphicsDevice.PresentationParameters.BackBufferWidth,
    GraphicsDevice.PresentationParameters.BackBufferHeight,
    false,
    GraphicsDevice.PresentationParameters.BackBufferFormat,
    DepthFormat.Depth24);

And this is the code for the button:

if (Main.CurrentResolution >= 0 && Main.CurrentResolution < Main.ScreenWidths.Length - 1)              { 
    Main.CurrentResolution++;
    Main.graphics.PreferredBackBufferWidth = Main.ScreenWidths[Main.CurrentResolution];
    Main.graphics.PreferredBackBufferHeight = Main.ScreenHeights[Main.CurrentResolution];
    Main.graphics.ApplyChanges();             
}

How would I fix this offset on the Y axis? Or even what would be a better way to go about different resolutions?

burnsi
  • 6,194
  • 13
  • 17
  • 27
ExitThe
  • 23
  • 2

2 Answers2

0

In this example, you can see how to:

  • put a point in relative coordinates, placed at the same relative position on screen
  • put a point in absolute coordinates, placed at the same relative position on screen

Things are then correctly positioned in a relative manner no matter the aspect ratio difference.

Next thing you want is an uniform scale, here it's min of X/Y but you can also force it to be on a specific axis.

Also, the matrix you want will likely be: scale, rotate, translate.

You may want to adjust all of this to what you're really looking for.

Result:

resolution:   <960, 540>, pointRelative:   <480.000, 270.000>, pointAbsolute:     <50.000, 50.000>, scaleAbsolute: <0.500, 0.500>, scaleUniform: 0.500
resolution:   <960, 680>, pointRelative:   <480.000, 340.000>, pointAbsolute:     <50.000, 62.963>, scaleAbsolute: <0.500, 0.630>, scaleUniform: 0.500
resolution:   <960, 720>, pointRelative:   <480.000, 360.000>, pointAbsolute:     <50.000, 66.667>, scaleAbsolute: <0.500, 0.667>, scaleUniform: 0.500
resolution:   <960, 768>, pointRelative:   <480.000, 384.000>, pointAbsolute:     <50.000, 71.111>, scaleAbsolute: <0.500, 0.711>, scaleUniform: 0.500
resolution:  <960, 1024>, pointRelative:   <480.000, 512.000>, pointAbsolute:     <50.000, 94.815>, scaleAbsolute: <0.500, 0.948>, scaleUniform: 0.500
resolution:  <960, 1080>, pointRelative:   <480.000, 540.000>, pointAbsolute:    <50.000, 100.000>, scaleAbsolute: <0.500, 1.000>, scaleUniform: 0.500
resolution:  <1280, 540>, pointRelative:   <640.000, 270.000>, pointAbsolute:     <66.667, 50.000>, scaleAbsolute: <0.667, 0.500>, scaleUniform: 0.500
resolution:  <1280, 680>, pointRelative:   <640.000, 340.000>, pointAbsolute:     <66.667, 62.963>, scaleAbsolute: <0.667, 0.630>, scaleUniform: 0.630
resolution:  <1280, 720>, pointRelative:   <640.000, 360.000>, pointAbsolute:     <66.667, 66.667>, scaleAbsolute: <0.667, 0.667>, scaleUniform: 0.667
resolution:  <1280, 768>, pointRelative:   <640.000, 384.000>, pointAbsolute:     <66.667, 71.111>, scaleAbsolute: <0.667, 0.711>, scaleUniform: 0.667
resolution: <1280, 1024>, pointRelative:   <640.000, 512.000>, pointAbsolute:     <66.667, 94.815>, scaleAbsolute: <0.667, 0.948>, scaleUniform: 0.667
resolution: <1280, 1080>, pointRelative:   <640.000, 540.000>, pointAbsolute:    <66.667, 100.000>, scaleAbsolute: <0.667, 1.000>, scaleUniform: 0.667
resolution:  <1366, 540>, pointRelative:   <683.000, 270.000>, pointAbsolute:     <71.146, 50.000>, scaleAbsolute: <0.711, 0.500>, scaleUniform: 0.500
resolution:  <1366, 680>, pointRelative:   <683.000, 340.000>, pointAbsolute:     <71.146, 62.963>, scaleAbsolute: <0.711, 0.630>, scaleUniform: 0.630
resolution:  <1366, 720>, pointRelative:   <683.000, 360.000>, pointAbsolute:     <71.146, 66.667>, scaleAbsolute: <0.711, 0.667>, scaleUniform: 0.667
resolution:  <1366, 768>, pointRelative:   <683.000, 384.000>, pointAbsolute:     <71.146, 71.111>, scaleAbsolute: <0.711, 0.711>, scaleUniform: 0.711
resolution: <1366, 1024>, pointRelative:   <683.000, 512.000>, pointAbsolute:     <71.146, 94.815>, scaleAbsolute: <0.711, 0.948>, scaleUniform: 0.711
resolution: <1366, 1080>, pointRelative:   <683.000, 540.000>, pointAbsolute:    <71.146, 100.000>, scaleAbsolute: <0.711, 1.000>, scaleUniform: 0.711
resolution:  <1920, 540>, pointRelative:   <960.000, 270.000>, pointAbsolute:    <100.000, 50.000>, scaleAbsolute: <1.000, 0.500>, scaleUniform: 0.500
resolution:  <1920, 680>, pointRelative:   <960.000, 340.000>, pointAbsolute:    <100.000, 62.963>, scaleAbsolute: <1.000, 0.630>, scaleUniform: 0.630
resolution:  <1920, 720>, pointRelative:   <960.000, 360.000>, pointAbsolute:    <100.000, 66.667>, scaleAbsolute: <1.000, 0.667>, scaleUniform: 0.667
resolution:  <1920, 768>, pointRelative:   <960.000, 384.000>, pointAbsolute:    <100.000, 71.111>, scaleAbsolute: <1.000, 0.711>, scaleUniform: 0.711
resolution: <1920, 1024>, pointRelative:   <960.000, 512.000>, pointAbsolute:    <100.000, 94.815>, scaleAbsolute: <1.000, 0.948>, scaleUniform: 0.948
resolution: <1920, 1080>, pointRelative:   <960.000, 540.000>, pointAbsolute:   <100.000, 100.000>, scaleAbsolute: <1.000, 1.000>, scaleUniform: 1.000

Code:

public void Test()
{
    var sx = new[] { 1920, 1366, 1280, 960 };
    var sy = new[] { 1080, 1024, 768, 720, 680, 540 };

    var pt1 = new Vector2(0.5f, 0.5f);
    var pt2 = new Vector2(100, 100);

    foreach (var w in sx.Reverse())
    {
        foreach (var h in sy.Reverse())
        {
            var scaleX = w / 1920.0f;
            var scaleY = h / 1080.0f;

            var resolution = new Vector2(w, h);
            var scaleUniform = Math.Min(scaleX, scaleY);
            var scaleAbsolute = new Vector2(scaleX, scaleY);
            var pointRelative = pt1 * resolution;
            var pointAbsolute = pt2 * scaleAbsolute;
            Console.WriteLine(
                $"{nameof(resolution)}: {resolution,12}, " +
                $"{nameof(pointRelative)}: {pointRelative,20:F3}, " +
                $"{nameof(pointAbsolute)}: {pointAbsolute,20:F3}, " +
                $"{nameof(scaleAbsolute)}: {scaleAbsolute:F3}, " +
                $"{nameof(scaleUniform)}: {scaleUniform:F3}"
            );
        }
    }
}
aybe
  • 15,516
  • 9
  • 57
  • 105
  • Thanks! That takes care of the positioning, but how would I make it so the Transform Matrix doesn't distort the game when it is on a different aspect ratio (with then using black borders to fill in the rest of the space)? – ExitThe Jan 28 '23 at 15:23
  • As explained, your matrix should probably be `Matrix.CreateScale(scaleUniform, scaleUniform, 1.0f)` which just makes sense as you want to scale things uniformly. – aybe Jan 28 '23 at 23:02
  • What happens with a 32:10 screen? Every game I have run on it requires a windowed mode to avoid stretching, I would like for developers to start taking these screens into account. –  Feb 01 '23 at 04:06
0

Without clipping or stretching, a single image will be distorted when drawn.

You need to define your virtual screen sources in terms of aspect ratio groups and size groups, instead of resolutions.

Make source assets for the major aspect ratios, 5:4, 16:9(10), 32:9(10). If designing for mobile(or possible PC screens) for portrait modes, the inverses as well. The reason for the (10) is screens vary from 9 to 11(so a 10 will mean less distortion for the majority of screens).

A single set of resources will alias if the source too large, and pixelize if too small. (Mipmaps solve this problem in 3D, but would be wasteful for discrete resolusions)

So size groups need to be two sizes, small and medium, large would be applicable only for native 4k and 8k resolutions(both are greater than human perception at a reasonable distance). The eye has a limited ~5K focus zone across a small region.

Since half and doubling of resolutions produce minimal artefacts, I purpose creating the smallest dimension size of 800 and medium at 1600.

Not all size/aspect ratio sources need to fit this recommendation.


I would suggest calculating and storing in a class level variable, the aspect ratio and size selection in Initialize and in the Resize event.

These variables make the choice for the first render pass' source assets. The final draw scales, with minimal distortion to fit and fill the screen(10 vs 9 or 11).