A SpriteKit game with zero image or audio files: how we built Street Legends

Street Legends ships without a single image or audio file. Every kit, ball, pitch and whistle is generated in code. Here is how, and why you might want to do the same.

Mustafa GüderFounder, GuderTech4 min read
spritekit-game-without-asset-files.md

Street Legends is our fast five-a-side street football game for iPhone, with a league to climb, a nine-player squad and kits to unlock. Open the app bundle and you will not find a single PNG or audio file. Every visual and every sound is generated in Swift at runtime.

Why build a game this way?

  • Tiny download: no texture atlases or audio packs, so the app installs in seconds
  • Sharp at every size: shapes are drawn for the exact screen, from the smallest iPhone to an iPad
  • Infinite variations: a new kit is a few colours, not a new set of images
  • Fast iteration: change a number and the whole game updates, no export step

The trade-off is that you cannot hand a file to an illustrator. It suits stylized, geometric art, which is exactly the look we wanted.

Drawing sprites with Core Graphics

Drawing complex shapes with SKShapeNode every frame is expensive. Instead, we draw once into an image with UIGraphicsImageRenderer and turn it into a texture:

import SpriteKit
import UIKit

func ballTexture(diameter: CGFloat) -> SKTexture {
    let size = CGSize(width: diameter, height: diameter)
    let image = UIGraphicsImageRenderer(size: size).image { context in
        let rect = CGRect(origin: .zero, size: size)
        UIColor.white.setFill()
        context.cgContext.fillEllipse(in: rect)

        UIColor.black.setFill()
        let patch = rect.insetBy(dx: diameter * 0.34, dy: diameter * 0.34)
        context.cgContext.fillEllipse(in: patch)
    }
    return SKTexture(image: image)
}

let ball = SKSpriteNode(texture: ballTexture(diameter: 28))

Textures are cached by their parameters, so each one is drawn once and reused by every node that needs it. Kits work the same way: a jersey shape filled with the team's colours and a number drawn on top.

Synthesizing sound with AVAudioEngine

Sound effects are short waveforms with an envelope. AVAudioSourceNode lets you write samples directly:

import AVFoundation

final class Synth {
    private let engine = AVAudioEngine()
    private var phase = 0.0
    private var frequency = 0.0
    private var amplitude = 0.0

    init() {
        let sampleRate = engine.outputNode.inputFormat(forBus: 0).sampleRate
        let source = AVAudioSourceNode { [unowned self] _, _, frameCount, bufferList -> OSStatus in
            let buffers = UnsafeMutableAudioBufferListPointer(bufferList)
            for frame in 0..<Int(frameCount) {
                let sample = Float(sin(phase) * amplitude)
                phase += 2 * .pi * frequency / sampleRate
                if phase > 2 * .pi { phase -= 2 * .pi }
                amplitude *= 0.9993 // quick decay, like a plucked sound
                for buffer in buffers {
                    buffer.mData?.assumingMemoryBound(to: Float.self)[frame] = sample
                }
            }
            return noErr
        }
        let format = AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)
        engine.attach(source)
        engine.connect(source, to: engine.mainMixerNode, format: format)
        try? engine.start()
    }

    func kick() {
        frequency = 90
        amplitude = 0.7
    }
}

A kick is a low tone that drops quickly. A whistle is a high tone with a little vibrato. Crowd noise is filtered random noise that swells after a goal. Combining a handful of these generators covers every sound in the game.

Keeping it fast

  • Draw textures once, at load time, never inside update(_:)
  • Reuse nodes for repeated effects instead of creating new ones
  • Use SpriteKit physics bodies with simple shapes, circles for players and the ball
  • Profile on the oldest device you support, not on the newest

When procedural art is the right call

It shines for arcade games, puzzle games and stylized worlds where geometry is the aesthetic. For painterly or character-heavy art, an artist and a texture atlas are still the better tool; our match-3 game Paw & Home is a good example of that side.

Want a game built? Tell us about it. We build in SpriteKit, SceneKit and Unity.

Frequently asked questions

Is SpriteKit still a good choice for iOS games in 2026?

Yes, for 2D games on Apple platforms. It is light, native and well integrated with Game Center and StoreKit. For cross-platform or 3D-heavy games, Unity is usually the better fit.

Does procedural audio sound worse than recorded audio?

For short effects like kicks, clicks and whistles it can sound great and stays tiny. For music and voices, recordings are still the better choice.

Does generating textures slow down app launch?

Only if you draw them every frame. Drawn once at launch and cached, simple textures take milliseconds.

How small can a SpriteKit game be?

Without image and audio assets, a complete game can be just a few megabytes, most of it compiled code.

ShareXLinkedInWhatsApp
Mustafa Güder

About the author

Mustafa Güder

Mustafa Güder is the founder of GuderTech and an iOS developer. After two years of study in Australia he came home to Adana and built Scevia, Paw & Home and Street Legends.

Studied in Australia, 2 years Adana, Türkiye

All posts