Edge pixels of adjecent sprite on sprite sheet are interfering with player sprite when animating it via Sprite Factory

I’m trying to animate through MonoGame Extended using Sprite Factory and I’m running into a problem.

When I animate the player this is what happens
Screenshot 2021-05-04 190955

Screenshot 2021-05-04 202129

Screenshot 2021-05-04 203043

To explain why this may be happening here’s the sprite sheet on Sprite Factory

and zoom in to the circled part:

What’s happening is that when I animate the player it ends up taking the bottom pixel of the sprite above it on the sprite sheet and puts it on top of the player sprite while removing bottom pixel of said player sprite. It’s as if the frame keeps moving up and down one pixel every other frame during animation.

I’m not sure if this is a sprite sheet or Sprite Factory problem but one thing to note about the sprite sheet is that the sprites are 16x16 in close proximity to each other and I don’t know if that’s a problem or not but let me know if you can think of any solutions to remove this problem.

Here’s the png of the sprite sheet if it’s of any help

thanks!

Is the coordinate for the rect in your spritesheet one pixel too high?

Sprites getting stretched and cut off take a look at this thread

1 Like

The file info says the sprite sheet is 720x464 which is divisible by 16, there being 16x16 sprites in each tile. Not sure if that answered your question. Here’s a zoom in on where its interfering from Aesprite

Right, but the bottom of the purple sprite looks a lot like what’s drawing in your image, so maybe check the math in your rectangles?

Or post some code :slight_smile:

I did some playing about and yea, I think your calculations for your sprite rectangle might be off. I’m using this code…

public class Game1 : Game
{
	private const double SPRITE_FRAMERATE_PLAYER = 0.5;

	private GraphicsDeviceManager _graphics;
	private SpriteBatch _spriteBatch;

	private float _scale = 4f;

	private Texture2D _pixel;
	private Texture2D _spriteSheet;
	private Point _spriteSize = new Point(16);
	private Point _playerSpriteIndex = new Point(0, 10);
	private int _frameOffset = 0;

	private double _frameAccum = SPRITE_FRAMERATE_PLAYER;

	private Vector2 _playerPos = new Vector2(100);

	public Game1()
	{
		_graphics = new GraphicsDeviceManager(this);
		Content.RootDirectory = "Content";
	}

	protected override void Initialize()
	{
		_graphics.PreferredBackBufferWidth = 1280;
		_graphics.PreferredBackBufferHeight = 720;
		_graphics.ApplyChanges();

		base.Initialize();
	}

	protected override void LoadContent()
	{
		_spriteBatch = new SpriteBatch(GraphicsDevice);

		_pixel = new Texture2D(_graphics.GraphicsDevice, 1, 1);
		_pixel.SetData<Color>(new Color[] { Color.White });
		_spriteSheet = this.Content.Load<Texture2D>("Sprites");
	}

	protected override void Update(GameTime gameTime)
	{
		_frameAccum -= gameTime.ElapsedGameTime.TotalSeconds;
		if (_frameAccum <= 0)
		{
			_frameOffset = 1 - _frameOffset;
			Console.WriteLine(_frameOffset);
			_frameAccum += SPRITE_FRAMERATE_PLAYER;
		}

		base.Update(gameTime);
	}

	protected override void Draw(GameTime gameTime)
	{
		GraphicsDevice.Clear(Color.CornflowerBlue);

		_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);

		_spriteBatch.Draw(
			_spriteSheet,
			_playerPos,
			new Rectangle((_playerSpriteIndex + new Point(_frameOffset, 0)) * _spriteSize , _spriteSize),
			Color.White,
			0f,
			Vector2.Zero,
			4f,
			SpriteEffects.None,
			0f
		);

		_spriteBatch.Draw(
			_spriteSheet,
			_playerPos + new Vector2(32 * _scale, 0),
			new Rectangle((_playerSpriteIndex + new Point(_frameOffset, 0)) * _spriteSize - new Point(0, 1) , _spriteSize),
			Color.White,
			0f,
			Vector2.Zero,
			_scale,
			SpriteEffects.None,
			0f
		);

		_spriteBatch.End();

		base.Draw(gameTime);
	}
}

So I’m rendering the sprite twice. Once at where it should be, and again at one pixel up from where it should be (at 4x the size to make it easier to see).

image

The one on the right looks a lot like yours. I also checked the pixel size in the sprites you’re rendering and they are 16 high, so it’s not like you have them sized wrong somehow.

You want to calculate your rectangle as…

Rectangle sourceRectangle = new Rectangle(
  spriteIndex * spriteSize,
  spriteSize
);

spriteIndex is the column/row the sprite is at, 0 based. So for the sprite I’m showing, it’s starting at (0, 10), animating to (1, 10), and then back.

1 Like

I’m animating through the way that is shown in the MonoGame Extended docs.

In this way the Draw method doesn’t have an overload that uses Rectangle. The problem may be that I’m trying to move the player’s position at a velocity that’s not a whole number (because of the float for deltaTime) but there’s no overload that uses Point either.

I may have to dive in the MonoGame Extended code to see what’s up.

Will post code momentarily

Oh I see, I didn’t realize MonoGame.Extended might do something else. I’m not sure if I would expect float movement to make a difference since the rectangle of the texture is what should be mapped to whatever MonoGame.Extended renders (likely a quad, given our prior discussion).

If you followed that tutorial, you’re presumably using PointClamp for your sampler, so that wouldn’t be the culprit. You could try just making your position an int right before you render and seeing if that has any impact. As a test, you can just do something like myPos.ToPoint().ToVector2() in your draw call, assuming that myPos is a Vector2 already.

Other than that, yea definitely post some code. I don’t have experience with MonoGame.Extended, but having some code there will help folks who do have a better understanding of what might be causing this :slight_smile:

It’s all it the main file since I’m only tinkering with MonoGame Extended:

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    public BoxingViewportAdapter BoxingViewportAdapter { get; set; }
    public DefaultViewportAdapter DefaultViewportAdapter { get; set; }

    TiledMap tiledMap;
    TiledMapRenderer tiledMapRenderer;

    AnimatedSprite playerSprite;
    private Vector2 playerPosition;

    Point worldSize;
    Point windowSize;

    State state;

    enum State
    {
        Front,
        Back,
        Left,
        Right
    }

    RenderTarget2D renderTarget;

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;

        windowSize = new Point(160 * 4, 144 * 4);
        worldSize = new Point(160, 144);
    }

    protected override void Initialize()
    {
        // TODO: Add your initialization logic here

        base.Initialize();
    }

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);

        _graphics.PreferredBackBufferWidth = windowSize.X;
        _graphics.PreferredBackBufferHeight = windowSize.Y;
        _graphics.ApplyChanges();
        // TODO: use this.Content to load your game content here
        BoxingViewportAdapter = new BoxingViewportAdapter(Window, GraphicsDevice, worldSize.X, worldSize.Y);
        DefaultViewportAdapter = new DefaultViewportAdapter(GraphicsDevice);

        tiledMap = Content.Load<TiledMap>("OverworldMap");
        tiledMapRenderer = new TiledMapRenderer(GraphicsDevice, tiledMap);

        state = State.Front;

        var spriteSheet = Content.Load<SpriteSheet>("Sprites.sf", new JsonContentLoader());
        var sprite = new AnimatedSprite(spriteSheet);
       
        playerPosition = new Vector2(120, 120);
        playerSprite = sprite;

        renderTarget = new RenderTarget2D(GraphicsDevice, worldSize.X, worldSize.Y);
        GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
       
    }

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();

        // TODO: Add your update logic here
        var dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
        var walkSpeed = dt * 35;
        var keyboardState = Keyboard.GetState();
        var animation = "idle_front";

        switch (state)
        {
            case State.Front:
                animation = "idle_front";
                break;
            case State.Back:
                animation = "idle_back";
                break;
            case State.Left:
                animation = "idle_left";
                break;
            case State.Right:
                animation = "idle_right";
                break;
            default:
                animation = "idle_front";
                break;
        }

        if (keyboardState.IsKeyDown(Keys.W) || keyboardState.IsKeyDown(Keys.Up))
        {
            state = State.Back;
            playerPosition.Y -= walkSpeed;
            animation = "walking_back";
        } 
            

        if (keyboardState.IsKeyDown(Keys.S) || keyboardState.IsKeyDown(Keys.Down))
        {
            state = State.Front;
            playerPosition.Y += walkSpeed;
            animation = "walking_front";
            
        }

        if (keyboardState.IsKeyDown(Keys.A) || keyboardState.IsKeyDown(Keys.Left))
        {
            state = State.Left;
            playerPosition.X -= walkSpeed;
            animation = "walking_left";
            
        } 

        if (keyboardState.IsKeyDown(Keys.D) || keyboardState.IsKeyDown(Keys.Right))
        {
            state = State.Right;
            playerPosition.X += walkSpeed;
            animation = "walking_right";
        }


        tiledMapRenderer.Update(gameTime);

        playerSprite.Play(animation);

        playerSprite.Update(dt);
        
        base.Update(gameTime);
    }

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

        var scaleMatrix = DefaultViewportAdapter.GetScaleMatrix();
        _spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, scaleMatrix);

        tiledMapRenderer.Draw(scaleMatrix);
        
        _spriteBatch.Draw(playerSprite, playerPosition);

        _spriteBatch.End();
        GraphicsDevice.SetRenderTarget(null);

        //render target to back buffer
        _spriteBatch.Begin(samplerState: SamplerState.PointClamp);
        _spriteBatch.Draw(renderTarget, new Rectangle(0, 0, worldSize.X * 4, worldSize.Y * 4), Color.White);
        _spriteBatch.End();



        base.Draw(gameTime);
    }
}

It may be sloppy but I found a fix.

I switched this:
_spriteBatch.Draw(playerSprite, playerPosition);

to this:

_spriteBatch.Draw(playerSprite, new Vector2((int)playerPosition.X, (int)playerPosition.Y));

Now I’m happy to get the end result but by looking at the code I provided can you think of a cleaner way of accomplishing this?

Or is it possible that the movement won’t be as smooth when they move by units of integers instead of floats? Is that a concern when doing it this way?

What you’re vone here is effectively the same as what I said above, where you can use playerPosition.ToPoint().ToVector2() :slight_smile:

Your hunch was correct! It did have to do with storing sub-pixel positions for your characters. Which I do find odd, but I don’t know how MG.E handles mapping a sprite to a quad.

What you’re doing is effectively rounding your rendering to the nearest pixel position. Doing this right before you render is certainly the most appropriate option. How you do it is up to you… if you just wanna hide the implementation, you could always use an extension method on Vector2. You might have to google, but I think it’s something like…

class Extensions
{
  public Vector2 Round(this Vector2 input) // Or whatever you want to call this.
  {
    return new Vector2((int)input.X, (int)input.Y);
  }
}

Then you can do…

_spriteBatch.Draw(playerSprite, playerPosition.Round());

Problem solved! Thank you.