Monday, October 10, 2016

xna - How to get map tiles to line up nicely?


When I first created my code, there were quite a few cases where I would get horizontal and vertical gaps between tiles when displayed. I put this down to rounding issues with floats and just moved on to other things. Now that I've revisited the issue, I can't seem to get it working properly, even after trying my hardest to eliminate places where this sort of issue might happen. It seems to only happen when I place the camera at certain positions, which is why I think it's likely to be a float issue. If anyone could help me with the correct way to draw the tile layout, it'd be much appreciated.


This is my drawing method for the map:


public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{

var topLeft = Camera.ScreenCoordinateToWorld(new Vector2(0, 0));
// Readjust the position of the topLeft element so we get the actual
// drawing position for the tile here
topLeft = new WorldCoordinate(topLeft.X - 1, topLeft.Y - 1,
-TileSize / 2, -TileSize / 2);
var topLeftDrawPosition = topLeft.DrawPosition;
var screenWidthInTiles = Game.GraphicsDevice.Viewport.Width / TileSize + 1;
var screenHeightInTiles = Game.GraphicsDevice.Viewport.Height / TileSize + 1;

for (var y = topLeft.Y; y <= topLeft.Y + screenHeightInTiles; y++)

{
for (var x = topLeft.X; x <= topLeft.X + screenWidthInTiles; x++)
{
if (x < 0 || y < 0 || y >= MapData.Count || x >= MapData[y].Count)
{
// Ignore out of range tiles to avoid exceptions
continue;
}

var tile = MapData[y][x];

var drawPosition = topLeftDrawPosition +
new Vector2((x - topLeft.X)*TileSize,
(y - topLeft.Y)*TileSize);
Tileset[tile].Draw(spriteBatch, drawPosition);
}
}

base.Draw(gameTime, spriteBatch);
}


Broken:


Screenshot of broken rendering


Semi-working:


enter image description here



Answer



One method I've discovered to fix this is to use a two-pass approach; first render the tiles onto a texture, then render the texture in the correct place. This seems to be mildly more expensive, as you'd expect, but does the job perfectly.


public override void LoadContent()
{
MapSprite = new RenderTarget2D(Game.GraphicsDevice, ScreenWidthInTiles * TileSize, ScreenHeightInTiles * TileSize, true, Game.GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24);
MapSpriteBatch = new SpriteBatch(Game.GraphicsDevice);


base.LoadContent();
}

public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
var topLeft = Camera.ScreenCoordinateToWorld(Vector2.Zero);
// Readjust the position of the topLeft element so we get the actual drawing position for the tile here
var topLeftTile = new WorldCoordinate(topLeft.X - 1, topLeft.Y - 1, -TileSize / 2, -TileSize / 2);
Vector2 drawPosition;


Game.GraphicsDevice.SetRenderTarget(MapSprite);
Game.GraphicsDevice.Clear(Color.Black);

MapSpriteBatch.Begin();

for (var y = topLeftTile.Y; y <= topLeftTile.Y + ScreenHeightInTiles; y++)
{
for (var x = topLeftTile.X; x <= topLeftTile.X + ScreenWidthInTiles; x++)
{

if (x < 0 || y < 0 || y >= MapData.Count || x >= MapData[y].Count)
{
// Ignore out of range tiles to avoid exceptions
continue;
}

var tile = MapData[y][x];
drawPosition = new Vector2((x - topLeftTile.X)*TileSize,
(y - topLeftTile.Y)*TileSize);
Tileset[tile].Draw(MapSpriteBatch, drawPosition);

}
}

MapSpriteBatch.End();
Game.GraphicsDevice.SetRenderTarget(null);

// Render the result into the right place. First push the sprite up into the top left corner
drawPosition = Camera.Position.DrawPosition - Game1.ScreenCentre -
// The account for the offset of the camera
topLeft.Offset -

// And account for centering issues
new Vector2(TileSize*1.5f);
spriteBatch.Draw(MapSprite, drawPosition, null, Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 1.0f);

base.Draw(gameTime, spriteBatch);
}

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...