Search code examples
c#xnamonogamescreen-resolution

Problem with RenderTarget and Transformation Matrix in MonoGame


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?


Solution

  • 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}"
                );
            }
        }
    }