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?
Без изображений и звуков полноценная игра может весить всего несколько мегабайт, большая часть которых — скомпилированный код.