Stretch game to fit android screen

I know that in windows desktop monogame automatically stretches your game to fit the window size.

How can i have the same behavior in my monogame android projects as well ? Right now the default behavior is to have black bars (to hide the extra pixels from the aspect ratio difference between the game and the screen)

No. You have to stick to the device resolution on modern platforms.

Use a Resolution renderer to deal with different screen resolutions:

public class ResolutionRenderer : IDisposable
{
    private readonly Game _game;
    public Viewport Viewport { get; protected set; }

    private static Matrix _scaleMatrix;
    /// <summary>
    /// Indicates that matrix update is needed
    /// </summary>
    private bool _dirtyMatrix = true;
    /// <summary>
    /// Game BG color
    /// </summary>
    public Color BackgroundColor = Color.Black;

    /// <summary>
    /// Gets virtual screen center
    /// </summary>
    public Vector2 VirtualScreenCenter { get; private set; }
    /// <summary>
    /// Gets virtual scree size
    /// </summary>
    public Vector2 VirtualScreenSize { get; private set; }

    /// <summary>
    /// Gets or sets virtual screen height
    /// </summary>
    public int VirtualHeight { get; private set; }

    /// <summary>
    /// Gets or set virtual screen width
    /// </summary>
    public int VirtualWidth { get; private set; }

    /// <summary>
    /// Gets or sets real screen width
    /// </summary>
    public int ScreenWidth;
    /// <summary>
    /// Gets or sets real screen height
    /// </summary>
    public int ScreenHeight;

    public ResolutionRenderer(Game game, int virtualWidth, int virtualHeight, int realWidth, int realHeight)
    {
        _game = game;
        VirtualWidth = virtualWidth;
        VirtualHeight = virtualHeight;
        VirtualScreenCenter = new Vector2(VirtualWidth * .5f, VirtualHeight * .5f);
        VirtualScreenSize = new Vector2(VirtualWidth, VirtualHeight);

        ScreenWidth = realWidth;
        ScreenHeight = realHeight;
        Initialize();
    }

    /// <summary>
    /// Initializes resolution renderer and marks it for refresh
    /// </summary>
    private void Initialize()
    {
        SetupVirtualScreenViewport();
        //calculate new ratio
        //mark for refresh
        _dirtyMatrix = true;
    }

    /// <summary>
    /// Setup viewport to real screen size
    /// </summary>
    public void SetupFullViewport()
    {
        var vp = new Viewport();
        vp.X = vp.Y = 0;
        vp.Width = ScreenWidth;
        vp.Height = ScreenHeight;
        _game.GraphicsDevice.Viewport = vp;
        _dirtyMatrix = true;
    }

    /// <summary>
    /// Draw call
    /// </summary>
    public void Draw()
    {
        //set full viewport
        SetupFullViewport();
        //clear screen with BG color
        _game.GraphicsDevice.Clear(BackgroundColor);
        //set virtual viewport
        SetupVirtualScreenViewport();
    }

    /// <summary>
    /// Get modified matrix for sprite rendering
    /// </summary>
    public Matrix GetTransformationMatrix()
    {
        if (_dirtyMatrix)
            RecreateScaleMatrix();

        return _scaleMatrix;
    }

    private void RecreateScaleMatrix()
    {
        if (!_invertScale)
            Matrix.CreateScale((float)ScreenWidth / VirtualWidth, (float)ScreenWidth / VirtualWidth, 1f, out _scaleMatrix);
        else Matrix.CreateScale((float)ScreenHeight / VirtualHeight, (float)ScreenHeight / VirtualHeight, 1f, out _scaleMatrix);
        _dirtyMatrix = false;
    }

    bool _invertScale;
    public void SetupVirtualScreenViewport()
    {
        var targetAspectRatio = VirtualWidth / (float)VirtualHeight;
        // figure out the largest area that fits in this resolution at the desired aspect ratio
        var width = ScreenWidth;
        var height = (int)(width / targetAspectRatio + .5f);

        if (height > ScreenHeight)
        {
            _invertScale = true;
            height = ScreenHeight;
            // PillarBox
            width = (int)(height * targetAspectRatio + .5f);
        }
        else _invertScale = false;

        // set up the new viewport centered in the backbuffer
        Viewport = new Viewport
        {
            X = (ScreenWidth / 2) - (width / 2),
            Y = (ScreenHeight / 2) - (height / 2),
            Width = width,
            Height = height
        };

        _game.GraphicsDevice.Viewport = Viewport;
    }

    /// <summary>
    /// Converts screen coordinates to virtual coordinates
    /// </summary>
    /// <param name="screenPosition">Screen coordinates</param>
    public Vector2 ToVirtual(Vector2 screenPosition)
    {
        return Vector2.Transform(screenPosition - new Vector2(Viewport.X, Viewport.Y), Matrix.Invert(GetTransformationMatrix()));
    }

    /// <summary>
    /// Converts screen coordinates to virtual coordinates
    /// </summary>
    /// <param name="screenPosition">Screen coordinates</param>
    public Point ToVirtual(Point screenPosition)
    {
        var v = Vector2.Transform(new Vector2(screenPosition.X, screenPosition.Y) - new Vector2(Viewport.X, Viewport.Y), Matrix.Invert(GetTransformationMatrix()));
        return new Point((int)v.X, (int)v.Y);
    }

    /// <summary>
    /// Converts screen coordinates to virtual coordinates
    /// </summary>
    /// <param name="virtualPosition">Screen coordinates</param>
    public Point ToDisplay(Point virtualPosition)
    {
        var v = Vector2.Transform(new Vector2(virtualPosition.X, virtualPosition.Y) + new Vector2(Viewport.X, Viewport.Y), GetTransformationMatrix());
        return new Point((int)v.X, (int)v.Y);
    }

    /// <summary>
    /// Optional dispose routine
    /// </summary>
    public void Dispose()
    {
    }

    ~ResolutionRenderer()
    {
        Dispose();
    }

}

To use the correct aspect ratio, this method can be used:

public Tuple<int, int> GetVirtualResolution(int width, int height)
    {
        double aspect = (double)width / (double)height;
        double a = numberOfPixels / forhold;
        int virtualHeight = (int)Math.Sqrt(a);
        int virtualWidth = (int)(virtualHeight * aspect);

        return Tuple.Create(virtualWidth, virtualHeight);
    }

The numberOfPixels is how many pixels in total. The higher amount of resolution your images etc are made for, the better(better to scale down, than up). Etc will numberOfPixels be if your “main” resolution is 1920x1080 be: 2073600
So when you initialize the renderer:

Tuple<int, int> resolution = GetVirtualResolution(game.GraphicsDevice.Viewport.Width, game.GraphicsDevice.Viewport.Height);

        _irr = new ResolutionRenderer(game, resolution.Item1, resolution.Item2, game.GraphicsDevice.Viewport.Width, game.GraphicsDevice.Viewport.Height);

This will work on all platforms on all devices, and you dont need to think about different screen resolutions more.

1 Like

thanks a lot :slight_smile: this is very helpful, exactly what i wanted

No problem:) Remember to do this in your draw method:

_irr.Draw();

//The things you want drawn on screen here,

 _irr.SetupFullViewport();

do i have to use gettransformationmatrix as a parameter on each draw call for this to work ? how would i use this if i need to draw with primitives ?

Alternatively (this is the approach I’m using at the moment):

When you make a new Monogame (or XNA) game from the template, you get all the basic methods - LoadContent, Update, Draw etc. What you don’t get by default in the template is a seemingly little-known method override called “BeginDraw”.

So my approach is to define a RenderTarget2D of a fixed maximum size (typically 1600x900), and scale all my graphics to fit. In BeginDraw, I then point my graphics device at this RenderTarget2D and draw out all my visible game content. My Draw method is then simply a matter of rendering this RenderTarget2D to the screen, suitably scaled and/or cropped to fit.

Of course, you have to consider overspill: scaling to fit an 800x480 screen means that the RenderTarget2D is rendered at 853x480, so you have to actually render this at -23,0 in order to center the display correctly. But these overspills can be calculated at start-up and passed into any UI components, allowing you to anchor them correctly relative to screen edges

Input is simply a matter of reversing this translation and scaling to change a touch location on 800x480 to 1600x900

Could you give me an example of this method ? I think i understood how you do it, somewhat, but what about the actual code of it ? Thats what i want to know

Following article helps you to handle different screen sizes and explains how to start with 2D camera.
http://blog.roboblob.com/2013/07/27/solving-resolution-independent-rendering-and-2d-camera-using-monogame/

As, i said before in my post, what i want to do is stretch the game to fit the screen. Doing that with spritebatch is easy (just put in the transformation matrix on each Begin() method), what i haven’t exactly figured out is how to do effectively rescale 3d models.

@Shiro - below is my BackBuffer class that I use to sort out scaling. This version is set for full screen width in portrait orientation.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace [MyGame]
{
public class BackBuffer : RenderTarget2D
{
private Rectangle _onscreenDestinationArea;

    public Rectangle Viewport { get; private set; }
    public Vector2 ViewportTopLeft { get { return new Vector2(Viewport.X, Viewport.Y); } }

    public BackBuffer(GraphicsDevice graphicsDevice, int width, int height)
        : base(graphicsDevice, width, height)
    {
        CalculateRenderingMetrics(graphicsDevice.Viewport.Bounds);
    }

    private void CalculateRenderingMetrics(Rectangle screenArea)
    {
        float horizontalRatio = (float)screenArea.Width / (float)this.Width;
        float scaledHeight = (float)this.Height * horizontalRatio;
        float scaledVerticalOverspill = (scaledHeight - screenArea.Height) / 2.0f;
        float visibleVerticalArea = this.Height - ((scaledVerticalOverspill / horizontalRatio) * 2.0f);

        \_onscreenDestinationArea = new Rectangle(0, -(int)scaledVerticalOverspill, screenArea.Width, (int)scaledHeight);

        Viewport = new Rectangle(0, (int)(scaledVerticalOverspill / horizontalRatio), this.Width, (int)visibleVerticalArea);
    }

    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Begin();
        spriteBatch.Draw(this, \_onscreenDestinationArea, Color.White);
        spriteBatch.End();
    }
}

}

So this will scale to the width of the device in portrait mode, cropping the top and bottom overspill if the aspect ratio of the device does not fit that of the back buffer. The Viewport property provides the section of the back buffer that will actually be displayed, allowing you to anchor things like UI elements relative to the edge of the screen.

Typically, I call the constructor for this in Game.LoadContent, right after the SpriteBatch constructor call, in order to ensure that the GraphicsDevice has sorted itself out fully, ie:

protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
_backBuffer = new BackBuffer(_graphics.GraphicsDevice, Definitions.Back_Buffer_Width, Definitions.Back_Buffer_Height);

// Load any other content

}

… where Definitions.Back_Buffer_Width and Definitions.Back_Buffer_Height are constants defining the dimensions of the back buffer - in this case

Then, to draw:

protected override bool BeginDraw()
{
GraphicsDevice.SetRenderTarget(_backBuffer);
GraphicsDevice.Clear(Color.Black);

// Draw game objects here

return base.BeginDraw();

}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(Color.Black);

\_backBuffer.Draw(_spriteBatch);

base.Draw(gameTime);

}

Hope this helps!

This works well but how do you handle the x,y returned by mouse/touch input not scaling to the backbuffer please?

To convert mouse & touch coordinates into a virtual resolution, you need to create a second matrix that is the inverse of _scaleMatrix, and also takes into account any letterboxing.

Here’s a bit of code from the post up above, updated to provide the correct matrix…

This package also include the ScreenToGameCoord(mousPos) method to do all the math for you:

Hi dmanning23,

Do you have any example, how to use CameraBuddy with ResolutionBuddy?

Thanks