1
votes

I want to create a low resolution game on a larger window. (96x54 res on 960x540 size window for example).

How would I go about this? Is there a way to resize a window independently of the preferred back buffer width and height? Or should I just keep a low resolution render target I draw on, and just draw it as a full screen quad on my window when I'm done adjusting for nearest texture sampling?

Thanks in advance,

xoorath

2

2 Answers

6
votes

I tend to opt for the "render to texture" solution so that I can allow for things like full screen without distortions.

The class I use to achieve this usually looks something like:

class VirtualScreen
{
    public readonly int VirtualWidth;
    public readonly int VirtualHeight;
    public readonly float VirtualAspectRatio;

    private GraphicsDevice graphicsDevice;
    private RenderTarget2D screen;

    public VirtualScreen(int virtualWidth, int virtualHeight, GraphicsDevice graphicsDevice)
    {
        VirtualWidth = virtualWidth;
        VirtualHeight = virtualHeight;
        VirtualAspectRatio = (float)(virtualWidth) / (float)(virtualHeight);

        this.graphicsDevice = graphicsDevice;
        screen = new RenderTarget2D(graphicsDevice, virtualWidth, virtualHeight, false, graphicsDevice.PresentationParameters.BackBufferFormat, graphicsDevice.PresentationParameters.DepthStencilFormat, graphicsDevice.PresentationParameters.MultiSampleCount, RenderTargetUsage.DiscardContents);
    }

    private bool areaIsDirty = true;

    public void PhysicalResolutionChanged()
    {
        areaIsDirty = true;
    }

    private Rectangle area;

    public void Update()
    {
        if (!areaIsDirty)
        {
            return;
        }

        areaIsDirty = false;
        var physicalWidth = graphicsDevice.Viewport.Width;
        var physicalHeight = graphicsDevice.Viewport.Height;
        var physicalAspectRatio = graphicsDevice.Viewport.AspectRatio;

        if ((int)(physicalAspectRatio * 10) == (int)(VirtualAspectRatio * 10))
        {
            area = new Rectangle(0, 0, physicalWidth, physicalHeight);
            return;
        }


        if (VirtualAspectRatio > physicalAspectRatio)
        {
            var scaling = (float)physicalWidth / (float)VirtualWidth;
            var width = (float)(VirtualWidth) * scaling;
            var height = (float)(VirtualHeight) * scaling;
            var borderSize = (int)((physicalHeight - height) / 2);
            area = new Rectangle(0, borderSize, (int)width, (int)height);
        }
        else
        {
            var scaling = (float)physicalHeight / (float)VirtualHeight;
            var width = (float)(VirtualWidth) * scaling;
            var height = (float)(VirtualHeight) * scaling;
            var borderSize = (int)((physicalWidth - width) / 2);
            area = new Rectangle(borderSize, 0, (int)width, (int)height);
        }
    }

    public void BeginCapture()
    {
        graphicsDevice.SetRenderTarget(screen);
    }

    public void EndCapture()
    {
        graphicsDevice.SetRenderTarget(null);
    }

    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(screen, area, Color.White);
    }


}

And then in my game, the initialisation tends look something like:

  VirtualScreen virtualScreen;

    protected override void Initialize()
    {
        virtualScreen = new VirtualScreen(96, 54, GraphicsDevice);
        Window.ClientSizeChanged += new EventHandler<EventArgs>(Window_ClientSizeChanged);
        Window.AllowUserResizing = true;
        base.Initialize();
    }

    void Window_ClientSizeChanged(object sender, EventArgs e)
    {
        virtualScreen.PhysicalResolutionChanged();
    }

With the all important call to Update:

protected override void Update(GameTime gameTime)
    {
        virtualScreen.Update();

        base.Update(gameTime);
    }

And then the act of drawing itself:

 protected override void Draw(GameTime gameTime)
    {
        virtualScreen.BeginCapture();


        GraphicsDevice.Clear(Color.CornflowerBlue);
        // game rendering happens here...


        virtualScreen.EndCapture();

        GraphicsDevice.Clear(Color.Black);
        spriteBatch.Begin();
        virtualScreen.Draw(spriteBatch);
        spriteBatch.End();

        base.Draw(gameTime);
    }

With this in place, I can basically stop caring about resolution at all and just focus on the game.

0
votes

Using the RenderToTexture method you're talking about might be a good idea (plus it will be easier for you if you want to do post-process shaders). Alternatively, you can set the Window's size, but your code will only work on a desktop.

You must add those 2 references in your project:

using System.Drawing;
using System.Windows.Forms;

And then in your Game class (i.e. in the Initialize method)

GraphicsDeviceManager.PreferredBackBufferWidth = 96;
GraphicsDeviceManager.PreferredBackBufferHeight = 54;
IntPtr ptr = this.Window.Handle;
Form form = (Form) Control.FromHandle(ptr);
form.Size = new Size(960, 540);