Fixing Blurry and Jagged Sprites in Phaser CE When Scaling Composite Cards
The Real Problem: Compositing Happens at Draw Time
When you scale a parent sprite that holds several children as visual layers, Phaser CE composites all of them at draw time — every single frame. Downscaling by a factor like 0.11 means WebGL has to sample a 700×1050 texture and squash it into roughly 77×115 pixels in one step, with no pre-computed intermediate versions to help bridge that gap. That’s the source of the jaggedness and blur — not a bug in Phaser, just what happens when you skip mipmapping.
Small scales hurt more than large ones for exactly this reason.
Mipmapping — and What Phaser CE Actually Does with It
Mipmapping pre-computes a chain of progressively halved textures (e.g. 512px → 256px → 128px → 64px) so the GPU picks the closest-sized version before sampling. Standard WebGL mipmapping requires power-of-two texture dimensions — 512×1024, 1024×2048, and so on. Your 700×1050 source images are not power-of-two, which means the GPU can’t generate a mip chain for them in most WebGL implementations without resizing first.
Phaser CE runs on PIXI.js v2 internally. PIXI exposes a mipmap flag on BaseTexture, but it won’t produce usable results on non-POT textures. Resizing your card layers to 512×1024 or 1024×2048 is usually the cleaner path if GPU mipmapping is a priority.
What the Third Argument to load.image() Actually Does
Worth clearing up: in Phaser CE, the third parameter in this.load.image('key', path, true) is an overwrite flag. It lets you reload a texture that’s already in the cache under the same key. It has no effect on filtering, mipmapping, or texture quality of any kind.
The Fastest Fix: Bake to a RenderTexture
Rather than scaling a group of child sprites, render the assembled card into a Phaser.RenderTexture at full resolution, then scale that one flat image. You composite once; after that the GPU deals with a single clean source.
// Assemble your card group first, then:
var rt = game.add.renderTexture(700, 1050, 'cardBaked');
rt.render(cardGroup);
var cardSprite = game.add.sprite(x, y, rt);
cardSprite.scale.setTo(0.17);
For hover tweens that expand and shrink the card, you tween cardSprite.scale as normal — the RenderTexture was already composited at full resolution. Re-baking is only necessary if the card’s visual state changes (a status effect alters one layer, for instance).
This won’t eliminate all sampling artifacts at extreme downscale, but it removes the multi-layer compositing overhead and gives the GPU a single clean image to work from. The difference is usually significant.
Antialiasing and the smoothed Property
Verify your game was created with antialiasing enabled. In Phaser CE the constructor signature is:
var game = new Phaser.Game(1280, 800, Phaser.AUTO, '', null, false, true);
// The last argument enables antialiasing — it defaults to true in WebGL mode
Each sprite also carries a smoothed property. When it’s false, Phaser switches to nearest-neighbor sampling — sharp pixels for pixel art, but harsh for photographic card art scaled down.
cardSprite.smoothed = true; // bilinear filtering on this sprite
You can also set a global default before any textures load:
PIXI.scaleModes.DEFAULT = PIXI.scaleModes.LINEAR; // 0 = linear, 1 = nearest
LINEAR is already the default, but it’s worth confirming nothing in your setup has overridden it.
Multi-Resolution Assets for Wide Scale Ranges
Your cards swing between scales of 0.11 and 0.35 — roughly a 3× range. For that spread, maintaining a half-resolution set (350×525) and swapping it in at small sizes is the cleanest long-term solution. More upfront work, but the GPU has far less sampling distance to cover.
Phaser CE doesn’t have built-in resolution switching, but a simple runtime swap works well:
function setCardScale(cardSprite, scale) {
cardSprite.scale.setTo(scale);
var key = cardSprite.baseKey;
if (scale < 0.2 && cardSprite.texture.key !== key + '_half') {
cardSprite.loadTexture(key + '_half');
} else if (scale >= 0.2 && cardSprite.texture.key !== key) {
cardSprite.loadTexture(key);
}
}
It’s not as automatic as GPU mipmapping, but it works on non-POT textures and gives you full control over which resolution loads at which scale threshold.
Approaches by Effort Level
- Low effort: Verify
smoothed = trueon your sprites and confirm antialiasing is on in the game config. - Medium effort: Bake the composite card group to a RenderTexture before applying any scaling.
- Higher effort: Resize source assets to power-of-two dimensions to unlock proper GPU mipmapping, or maintain half-res asset variants and swap them at a runtime scale threshold.
