How to fix the seams between tiles and sprites
Hairline gaps along tile edges. A dark fringe around a sprite. A row of pixels that appears and vanishes as the camera scrolls. These look like one bug and are three, with three different fixes — and trying the wrong one is why this takes an afternoon.
1. Ten seconds to tell them apart
Move the camera slowly and watch one seam.
It flickers — appears at some positions and not others, or crawls along as you scroll. That is subpixel positioning. The tile boundary is landing between two screen pixels and the renderer has to round; sometimes it rounds both tiles the same way and there is no gap, sometimes it does not.
It stays put — the same edge, always, whatever the camera does. That is texture bleeding. The sampler is reaching past the edge of the frame in the atlas and finding something that is not the frame.
Everything is soft, not just the seams — the artwork itself looks slightly blurred. That is the texture filter, and it is also making the other two worse.
This test is worth doing before changing anything, because the fixes do not overlap. Extruding an atlas does nothing for a flickering gap, and a pixel-perfect camera does nothing for a fringe that was baked in at atlas time.
2. Bleeding: the sampler reaches past the edge
A sprite in an atlas is a rectangle inside a bigger texture. When the renderer draws it at anything other than 1:1 — a zoom of 1.5, a rotation, a camera at a fractional position — the sample for a pixel at the very edge sits partway between the last pixel of the frame and the first pixel of whatever is next to it, and a filtered sample takes a bit of both.
What is next to it is usually transparent padding. Blending a colour with transparency gives you a darker, more transparent version of that colour — a fringe. If the neighbour is another frame, you get a smear of the wrong sprite instead, which is worse and easier to spot.
More padding does not fix this. That is the trap: the obvious response is to space the frames further apart, and a wider gap of transparent pixels is still transparent pixels. The sample only reaches a pixel or two — it will find the padding whatever its width.
The fix is extrusion: fill the padding with a copy of the frame's own outermost row and column, so a sample that reaches over the edge finds the colour it would have found inside. One or two pixels is enough, and it needs twice that in padding so two neighbours growing towards each other do not meet. Sprite Sheet Generator has it as a setting; so do most desktop packers, usually under the same name.
One related but different problem, worth knowing because the symptom looks the same: a PNG can hold colour information in pixels that are fully transparent. Most editors write black there. When that gets blended in, you get a dark halo around everything. Godot's import dock calls the fix Fix Alpha Border; other tools call it premultiplied alpha or alpha bleeding. It is not the same as atlas extrusion and fixing one does not fix the other.
3. Subpixel gaps: the boundary lands between pixels
Two tiles sit exactly adjacent in world space. On screen, the boundary between them works out to x = 100.5. The renderer draws the left tile up to 100 and the right one from 101, and there is a one-pixel line of background between them. Move the camera a fraction and the boundary lands on 101.0, both tiles round the same way, and the line disappears.
This has nothing to do with the atlas and everything to do with arithmetic. The fixes are all the same idea:
Keep the zoom a whole number. At 2× or 3×, whole-pixel positions stay whole-pixel positions. At 1.5× they do not, and no amount of rounding elsewhere saves it.
Round positions to whole pixels — the camera's especially, since it moves everything at once. Most engines have a setting or a component for this rather than needing it done by hand.
Do not scale individual sprites by fractions. A sprite at scale 0.999 to “close the gap” is the fix people reach for, and it trades a flickering seam for a permanently wrong sprite size. If tiles overlap by a fraction to hide the gap, the same problem will reappear somewhere else at a different camera position.
4. The filter, which makes both worse
Bilinear filtering is the default nearly everywhere and it is the wrong default for pixel art. It blurs the artwork, and it is also the mechanism by which bleeding happens at all: nearest-neighbour sampling picks one pixel and never blends, so it cannot pull in a neighbour's colour.
Setting the filter to nearest therefore fixes the blur and reduces the bleeding at the same time. It does not eliminate bleeding entirely — at a fractional zoom the sample can still land on the wrong side of an edge — which is why extrusion is still worth doing on an atlas that will be scaled.
Turn off block compression too. DXT and its relatives approximate 4×4 blocks of colour with an interpolated pair, which smears colour across frame boundaries inside the atlas before the renderer even gets there. A pixel art texture compresses well as PNG and does not need it.
5. The settings, per engine
Two or three each, and they are not all in the same place.
Godot
Import dock: Filter Nearest, and Fix Alpha Border on. Project Settings → Rendering → 2D: Snap 2D Transforms to Pixel. For a TileMap, the tile set's separation and margin have to match the sheet you actually exported.
Unity
Texture: Filter Mode Point, Compression None, Mip Maps off. Add the Pixel Perfect Camera component for the positioning half. If you use a Sprite Atlas, check its padding — and extrude before packing if the packer does not.
Phaser
pixelArt: true in the game config sets nearest filtering and rounds pixels. Keep the camera zoom whole. For a tilemap, pass the same margin and spacing to load.spritesheet that the sheet was built with.
Common problems
The gap comes and goes as the camera moves
Subpixel positioning. Whole-number zoom, and round the camera position.
The same edge is always dark, camera or not
Texture bleeding. Extrude the atlas — more padding will not do it.
I added padding and nothing changed
Expected. Padding gives the sample more transparency to find, not less. Fill the padding by extruding.
Everything has a dark halo, even standalone sprites
Colour information in fully transparent pixels. Godot calls the fix Fix Alpha Border; elsewhere look for alpha bleed or premultiplied alpha.
A sliver of the wrong sprite shows along one edge
Bleeding from the neighbouring frame in the atlas. The frames have no padding between them at all.
It is fine at 2x and broken at 1.5x
That is the whole-number rule. Fractional zoom puts boundaries between pixels by construction.