Properly switching between fullscreen and windowed modes while keeping aspect ratio

Hello,

I’ve been trying to make it so that, in my game, the player can switch between windowed and fullscreen modes. My game has a virtual resolution of 640 x 480, and when fullscreen is activated, the graphics are stretched.

I followed the MonoGame.Extended example code for Camera2D with viewport adapter, as shown here: http://craftworkgames.github.io/MonoGame.Extended/MonoGame.Extended/Camera2D/#_viewportadapter

In my Initialize method, I have this:

In my constructor method, I have this:

`public MyGame()
{
graphics = new GraphicsDeviceManager(this);

        // Start fullscreen
        graphics.PreferredBackBufferWidth = 640; // remove later
        graphics.PreferredBackBufferHeight = 480; // remove later
        graphics.IsFullScreen = true; // set true to default later

        graphics.ApplyChanges();

        Content.RootDirectory = "Content";
    }`

And in my Initialize method, this:

var viewportAdapter = new BoxingViewportAdapter(Window, GraphicsDevice, 640, 480); camera = new Camera2D(viewportAdapter);

Then, in my Draw method:

var transformMatrix = camera.GetViewMatrix(); spriteBatch.Begin(transformMatrix: transformMatrix);

But in fullscreen mode, the graphics cover the entire screen and they stretch out.

What am I missing, or what is wrong with my implementation?

It’s not really clear from your question what you actually want?

The way to think about this stuff is in terms of “actual screen” size vs “virtual screen” size. In the code you’ve posted both screen sizes are the same, which explains why it’s behaving the way it is.

What you probably want is to have the “actual screen” size to be different when you go full screen. For example, if you set the PreferredBackBufferWidth and PreferredBackBufferHeight to 1024 x 768 respectively you’ll get black bars on either side of the image.

Play around with the values and try the other viewport adapters.

I don’t think you want to launch your game with a fixed resolution (640x480) in fullscreen mode. When your fullscreen resolution has a different aspect ratio than your monitor, you get nasty stretching. 640x480 is a 4:3 aspect ratio, most screens today are 16:9. To avoid stretching in fullscreen mode, you need to select a resolution that matches the screen. The easiest way to do this is to just keep the resolution Windows is already using.

graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
graphics.IsFullScreen = true;

You can still use a fixed resolution like 640x480 for your game, but you have to render to a RenderTarget2D first, instead of rendering directly to the backbuffer. When all rendering is done, you can then draw that render target to the backbuffer using SpriteBatch.Draw(). To avoid stretching, you have to make sure to keep the 4:3 aspect ratio of your render target. This is usually done by adding black bars on the borders of the screen (if needed). I’m not familiar with MonoGame.Extended, but it looks like that’s what BoxingViewportAdapter is taking care of.

1 Like

EDIT: Please see my last response. I almost got it. Thanks!

Thanks for responding!

What I want is to keep my aspect ratio of 4:3 (virtual screen size) regardless of the actual screen aspect ratio, or whether the game is on fullscreen or windowed mode, and cover the rest with black bars, as you said. I have been trying to do this on my own, but documentation is lacking and, when I find examples, most are too old and incompatible with my version of either MonoGame or Visual Studio itself.

I’ve tried changing the values but either I get the stretched image as I described, or it’s cut off at some point.

@markus Recommends render targers for this. I have read this before but haven’t found examples of how to use it. Would this conflict with MonoGame.Extended or is it already implemented in the library?

EDIT: Please see my last response. I almost got it. Thanks!

It looks like it, but honestly, the lack of documented examples is driving me crazy, and I wish I were savvy enough that I could just go into the source code and understand everything that’s going on, but I’m not. Still, I’ll give it a try.

Thank you!

Thank you. I’ve almost done it by starting the game in windowed mode and adjusting resolutions in fullscreen mode accordingly. However, the game screen only appears in the center the first time I go fullscreen. If I switch back to windowed mode and then back again to fullscreen, it will appear in the top left corner.

How can I make sure it always stays centered in the actual screen? Please let me know if my explanation is not clear and I’ll do my best to rephrase.

(Sorry for multi-posting.)

Is it only important to you to have a 4:3 aspect ratio, or do you really want a fixed 640x480 resolution?
If you only care about aspect ratio, you could use a viewport to restrict the drawable area to a 4:3 region.
If you want a fixed resolution, I really think a 640x480 render target is the way to go.

The most important thing for me is the aspect ratio, really. I am already using the MonoGame.Extended BoxingViewportAdapter and it does the pillarboxing/letterboxing part pretty well, but the viewport is only centered on the actual screen the first time in fullscreen. After that, the viewport is always placed in the top left corner.

I’m going to try doing it without MonoGame.Extended again if I can’t get that to work.

It’s just a shot in the dark, but does it maybe help to recreate the viewportAdapter and camera when you switch screen modes?

I just did something more or less like that, which worked!: every time the screen size changes, I check whether the game is fullscreen or not and change the preferred back buffer width accordingly.

This is my current constructor:

public MyGame()
        {
            graphics = new GraphicsDeviceManager(this);

            // Start windowed
            graphics.PreferredBackBufferWidth = 640;
            graphics.PreferredBackBufferHeight = 480;
            graphics.IsFullScreen = false;

            this.Window.ClientSizeChanged += new EventHandler<EventArgs>(Window_ClientSizeChanged);

            graphics.ApplyChanges();

            Content.RootDirectory = "Content";
        }

My current Initialize method:

protected override void Initialize()
        {
            base.Initialize();

            var viewportAdapter = new BoxingViewportAdapter(Window, GraphicsDevice, 640, 480);
            camera = new Camera2D(viewportAdapter);

            Window.Title = gameTitle;

            this.gameTimer = maxGameTime;
            this.matchBeginning = true;
        }

And my client size changed event:

void Window_ClientSizeChanged(object sender, EventArgs e)
        {
            if (graphics.IsFullScreen)
            {
                graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            }
            else
            {
                graphics.PreferredBackBufferWidth = 640;
                graphics.PreferredBackBufferHeight = 480;
            }

            graphics.ApplyChanges();
        }

There’s probably a more proper way to do it, and I’ll find out sooner or later. XD Also, I would ideally have the game start in fullscreen mode, but right now I can’t figure it out. I’ll make sure I use a more standard aspect ratio next time.

I’m just glad I made some progress. There’s so much to learn. Thanks a lot, and if you have any suggestions, they’re always welcome. :slight_smile:

1 Like