0
\$\begingroup\$

I am working on my first MonoGame project. I love the framework so far!

I have implemented my own letterbox/pillarboxing to scale my native resolution by the maximum integer scale allowable on my display. Basically, I determine the maximum integer scale, set my PreferredBackBuffer to the screen resolution, create a Viewport that is my native resolution * maximum scale, and then set my SpriteBatch to draw everything at Matrix.CreateScale(max_scale).

This works much better than rendering to a texture that has my native resolution and then scaling it up. (Scaling using a Matrix in SpriteBatch, as opposed to just rendering to a texture and then scaling it up, allows you to fake "subpixel rendering" to some extent).

That said, I am facing two issues.

  1. When my sprite moves, there is very subtle "ghosting" happening. The sprite is subtly blurry and there's a faint ghostly trail behind it.

  2. When I mirror to an external monitor, there is less ghosting, but there is ugly artifacting on the outside of sprite when stationary. See below.

enter image description here

Rounding my player's position to integers in the Draw() command doesn't help for either problem.

Does anyone have any thoughts about how to fix these issues?

Here is my game class:

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace MyMonoGame
{
    public class MyGame : Game
    {
        // declare variables
        GraphicsDeviceManager graphics;
        SpriteBatch sprite_batch;

        // resolution management
        int native_width;
        int native_height;
        int screen_width;
        int screen_height;
        int max_scale;
        int horizontal_margin;
        int vertical_margin;

        // objects
        Player player;

        public MyGame()
        {
            // create GraphicsDeviceManager instance
            graphics = new GraphicsDeviceManager(this);
            // specify root directory
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            // set window title
            this.Window.Title = "My Game";

            // create SpriteBatch instance, which can be used to draw textures.
            sprite_batch = new SpriteBatch(GraphicsDevice);

            // initialize some variables
            native_width = 160;
            native_height = 144;

            // resolution management
            // get screen size
            screen_width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
            screen_height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            // get max_scale, the maximum integer scale that will fit on the screen
            // note: must be integer to prevent pixel distortion
            int width_divisor = (int) Math.Floor((float)screen_width/(float)native_width);
            int height_divisor = (int) Math.Floor((float)screen_height/(float)native_height);
            max_scale = Math.Min(width_divisor, height_divisor);
            // get margins for letterboxing and pillarboxing
            int max_width = native_width * max_scale;
            int max_height = native_height * max_scale;
            horizontal_margin = (int)((screen_width - max_width)/2f);
            vertical_margin = (int)((screen_height - max_height)/2f);

            // toggle fullscreen
            graphics.PreferredBackBufferWidth = screen_width;
            graphics.PreferredBackBufferHeight = screen_height;
            graphics.ToggleFullScreen();
            GraphicsDevice.Viewport = new Viewport(horizontal_margin, vertical_margin, native_width * max_scale, native_height * max_scale);
            graphics.ApplyChanges();

            // objects
            player = new Player(this);
        }

        protected override void LoadContent()
        {
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            // update objects
            player.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            // clear window & fill with solid color
            GraphicsDevice.Clear(Color.DarkRed);

            // draw objects
            var transform_matrix = Matrix.CreateScale(max_scale);
            sprite_batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise, transformMatrix: transform_matrix);
            player.Draw(sprite_batch);
            sprite_batch.End();
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ Can you capture these artifacts in a screenshot or video captured on your device itself, not through a camera lens? This can help isolate the problem by taking your monitor and camera out of the equation. \$\endgroup\$ – DMGregory Feb 14 at 12:15
  • \$\begingroup\$ The motion blur is not captured in any single frame when I record a video, suggesting maybe it's just an optical illusion from the moving sprite. But I don't experience this in other engines on my machine, like Godot, GameMaker, Unity, or Pygame. The stationary artifacting can only be captured by camera. It looks distorted mirrored on my television while simultaneously looking fine on my laptop screen. \$\endgroup\$ – GrimGrom Feb 15 at 2:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.