Mobile game dev: How to fix Pixel Art for iOS games

A common problem when you’re using pixel art on your games is scaling your graphics. Scaling down may look alright as long as we’re using a high-quality image, scaling up is where generally everything goes south, however, it could be alright for pixel art graphics as long as we increase both width and height in equal increments (x2, x4, x8, etc, … ) and we keep certain awareness of the general style of our game: It’s entirely different an 8-bit style than a 64-bit style when we’re playing on an iPhone 5s or an iPad Pro 12.9-inch

So, back to scaling:

A common way to resolve scaling problems when working pixel art (besides increasing the image size on equal ratio increments) is by disabling the engine’s anti-aliasing process.

What is anti-aliasing?

The anti-aliasing process tries to use color when an image’s size is altered in order to fill in-between pixels and create the illusion of a softer transition, effectively smoothing jagged edges, which is exactly the opposite of what you want to accomplish when using pixel art.

Disabling the engine’s anti-aliasing will stop these processes from trying to smooth the borders when images scale up. The game engine is doing what is supposed to do for 99.9% of cases until pixel nerds come by and suddenly this becomes a problem.

Creating sprites in iOS using SKSpriteNode

In order to create a sprite for our game, we’ll use iOS SpriteKit framework, and its SKSpriteNode data type. We’ve created a 64x64px sprite representing our player in some image editing software (Photoshop, Aseprite, Pixaki, whatever, …) and everything looks good:

func makePlayer(at position: CGPoint, sprite: String){

        let player = SKSpriteNode(imageNamed: sprite)
        player.position = position
        player.name = "Player"

        addChild(player)
    }

makePlayer(at: CGPoint(x: 0, y: 0), sprite: "playerSprite64px")

However, now we realize that the sprite is way too small for our game, and we want to make it bigger, we actually need to make it 6 times bigger! We can use setScale(:) for this:

func makePlayer(at position: CGPoint, sprite: String){

        let player = SKSpriteNode(imageNamed: sprite)
        player.position = position
        player.name = "Player"

        // Scale the Sprite x6 times:
        player.setScale(6.0)

        addChild(player)
    }

makePlayer(at: CGPoint(x: 0, y: 0), sprite: "playerSprite64px")

We scale the sprite x6, and suddenly we have a problem: Our graphics look bad…, pretty bad. SpriteKit’s anti-aliasing is trying to smooth the pixel borders now that we scaled up our graphics, and is something we really don’t want to do with pixel art, or will look blurry on screen.

Blurred pixel art. Un-textured and antialiasing is enabled

How to fix Pixel Art on iOS?

As commented previously we’ll need to disable how the engine’s antialiasing work with the sprite, for this, we will need to 1) Set a texture using the same sprite, and 2) Change the filtering mode

Although SpriteKit can create textures automatically when a sprite is created, for more complex apps or operations where we need finer control over graphics, we’ll use textures instead. By using SKTexture on top of our sprite ( rather than using only SKSpriteNode ) we have access to different built-in methods that will allow us to overcome this problem.

For step 1, setting a texture, there are different ways to change a sprite’s texture, Paul Hudson explains here different methods depending on your specific case. I’ll use the most basic approach. For step 2, changing the filtering mode, different filtering modes are used when the size of a sprite drawn with the texture is not drawn at the texture’s native size. In this case we’ll use SKTextureFilteringMode.nearest, where each pixel is drawn using the nearest point in the texture:

func makePlayer(at position: CGPoint, sprite: String){

        let player = SKSpriteNode(imageNamed: sprite)
        player.position = position
        player.name = "Player"

        // Scaling & Antialiasing:
        player.setScale(6.0)
        let texture = SKTexture(imageNamed: "playerSprite64px")
        texture.filteringMode = .nearest
        player.texture = texture

        addChild(player)
    }

makePlayer(at: CGPoint(x: 0, y: 0), sprite: "playerSprite64px")

And this fixes the problem!

Scaled pixel art. Textured and without antialiasing

For comparison (move the arrow to see the difference between images):

Leave a Reply

%d bloggers like this: