Игра на SpriteKit без единого файла изображений или звука: как мы сделали Street Legends

В Street Legends нет ни одного файла изображения или звука. Каждая форма, мяч, поле и свисток созданы кодом. Рассказываем, как это сделано и почему вам может захотеться того же.

Mustafa GüderОснователь GuderTech4 мин чтения
spritekit-game-without-asset-files.md

Street Legends — наша быстрая игра в уличный футбол пять на пять для iPhone с лигой, по которой нужно подниматься, составом из девяти игроков и формами для открытия. Откройте пакет приложения, и вы не найдёте ни одного PNG или аудиофайла. Каждое изображение и каждый звук создаются на Swift во время работы.

Зачем делать игру так?

  • Крошечная загрузка: никаких атласов текстур и звуковых пакетов, приложение устанавливается за секунды
  • Чёткость на любом размере: фигуры рисуются под конкретный экран, от самого маленького iPhone до iPad
  • Бесконечные вариации: новая форма — это несколько цветов, а не новый набор картинок
  • Быстрые итерации: поменяйте число, и вся игра обновится, без шага экспорта

Цена в том, что иллюстратору нельзя передать файл. Подход подходит для стилизованной геометричной графики — именно такой вид мы и хотели.

Рисуем спрайты с Core Graphics

Рисовать сложные фигуры через SKShapeNode в каждом кадре дорого. Вместо этого мы один раз рисуем в изображение с помощью UIGraphicsImageRenderer и превращаем его в текстуру:

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))

Текстуры кэшируются по параметрам, поэтому каждая рисуется один раз и переиспользуется всеми узлами, которым она нужна. Формы устроены так же: силуэт футболки в цветах команды и номер поверх.

Синтезируем звук с AVAudioEngine

Звуковые эффекты — это короткие волны с огибающей. AVAudioSourceNode позволяет записывать сэмплы напрямую:

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
    }
}

Удар по мячу — низкий тон, который быстро затухает. Свисток — высокий тон с лёгким вибрато. Шум трибун — отфильтрованный случайный шум, который нарастает после гола. Горсти таких генераторов хватает на все звуки игры.

Сохраняем скорость

  • Рисуйте текстуры один раз при загрузке, никогда внутри update(_:)
  • Переиспользуйте узлы для повторяющихся эффектов вместо создания новых
  • Используйте физические тела SpriteKit простой формы: круги для игроков и мяча
  • Профилируйте на самом старом поддерживаемом устройстве, а не на самом новом

Когда процедурная графика — правильный выбор

Она блистает в аркадах, головоломках и стилизованных мирах, где геометрия и есть эстетика. Для живописной графики или игр с множеством персонажей художник и атлас текстур по-прежнему лучший инструмент; наша игра «три в ряд» Paw & Home — хороший пример этой стороны.

Хотите заказать игру? Расскажите нам о ней. Мы работаем со SpriteKit, SceneKit и Unity.

Частые вопросы

SpriteKit всё ещё хороший выбор для iOS-игр в 2026 году?

Да, для 2D-игр на платформах Apple. Он лёгкий, нативный и хорошо интегрирован с Game Center и StoreKit. Для кроссплатформенных игр или игр с упором на 3D обычно лучше подходит Unity.

Процедурный звук звучит хуже записанного?

Для коротких эффектов вроде ударов, щелчков и свистков он может звучать отлично и занимает мизерный объём. Для музыки и голоса записи по-прежнему лучше.

Замедляет ли генерация текстур запуск приложения?

Только если рисовать их в каждом кадре. Нарисованные один раз при запуске и закэшированные простые текстуры занимают миллисекунды.

Насколько маленькой может быть игра на SpriteKit?

Без изображений и звуков полноценная игра может весить всего несколько мегабайт, большая часть которых — скомпилированный код.

ПоделитьсяXLinkedInWhatsApp
Mustafa Güder

Об авторе

Mustafa Güder

Mustafa Güder — основатель GuderTech и iOS-разработчик. После двух лет учёбы в Австралии он вернулся в Адану и создал Scevia, Paw & Home и Street Legends.

Учёба в Австралии, 2 года Адана, Турция

Все статьи