
После долгого пути я рад представить AdaEngine 0.1.0: бесплатный игровой движок и фреймворк для приложений с открытым исходным кодом, написанный на Swift.
AdaEngine строится вокруг простой идеи: Swift должен быть отличным языком для создания игр, интерактивных приложений, инструментов и творческого софта — не только приложений для платформ Apple. Swift выразителен, безопасен, быстр и удобен в написании. AdaEngine пытается перенести эти сильные стороны в разработку игр через модульный движок, data-driven архитектуру и API, которые ощущаются естественно для Swift-разработчиков.
AdaEngine доступен на GitHub под лицензией MIT. Этот первый релиз все еще ранний, но для проекта это уже настоящий рубеж: движок умеет открывать окна, запускать игровой цикл на ECS, рендерить спрайты и UI, загружать ассеты и сцены, воспроизводить звук, обрабатывать ввод, запускать физику и собирать примеры из разных модулей движка.
⚠️ Ранний релиз AdaEngine 0.1.0 — ранний релиз. API будут меняться, часть возможностей пока не завершена, документация еще растет, а шероховатости ожидаемы. Я пока не рекомендую использовать его для серьезных production-проектов, если только вы не готовы к нестабильности и не хотите помочь сформировать движок.
Если это звучит интересно, можно сразу перейти к туториалам или посмотреть репозиторий на GitHub.
? В этой статье я по возможности даю ссылки на документацию AdaEngine и исходный код. Документация генерируется из кодовой базы, поэтому она будет развиваться вместе с движком.
Что такое AdaEngine?
AdaEngine — data-driven игровой движок и фреймворк для приложений на Swift. Его основные цели:
Простота: легко начать новичкам, но при этом достаточно гибко для опытных пользователей.
Модульность: большинство возможностей движка поставляется в виде плагинов, так что можно выбрать только то, что нужно вашему приложению.
Data-driven подход: в основе AdaEngine лежит Entity Component System.
Быстрая итерация: движок проектируется под быстрые сборки и быструю обратную связь.
Практичность: первый фокус — полноценный 2D workflow, при этом поддержка 3D уже есть и будет развиваться.
Кроссплатформенность по дизайну: сейчас AdaEngine ориентирован на платформы Apple и активно движется к более широкой поддержке, включая Windows, Linux, Android и WebAssembly/WebGPU.
Текущий набор возможностей включает:
Спрайты: рендер множества текстур с batching; отдельные текстуры, sprite sheets и анимированные текстуры.
Сцены: сохранение и загрузка ECS-миров из человекочитаемых файлов сцен.
Tilemaps: создание уровней через LDtk или интеграция другого редактора через предоставленные API.
2D-физика: встроенная поддержка на базе Box2D v3.
Ассеты: загрузка и сохранение игровых ассетов, async-загрузка и handles для ассетов.
Hot asset reloading: перезагрузка измененных ассетов во время выполнения, чтобы оставаться в потоке.
Аудио: загрузка и воспроизведение звуковых ресурсов, включая пространственное воспроизведение, привязанное к сущностям.
Плагины: рендеринг, аудио, ввод, UI, события, физика, сцены, спрайты и другие системы собираются через плагины.
События и observation: коммуникация внутри игры через глобальные события или ECS-style frame events.
Parent/child-связи: иерархии сущностей и распространение transform через них.
Несколько render backends: Metal на платформах Apple и WebGPU/Dawn там, где это включено.
Render graphs: управление тем, как планируется и компонуется работа рендеринга.
AdaUI: UI для игр и приложений с API, вдохновленным SwiftUI.
Геймпады: доступ к подключенным геймпадам на поддерживаемых платформах.
Примеры: растущий набор демо для спрайтов, UI, ввода, событий, сцен, tilemaps и 3D.
Swift-native точка входа приложения
Приложения AdaEngine стартуют с API, который должен быть знаком тем, кто пользовался SwiftUI:
import AdaEngine @main struct AdaApp: App { var body: some AppScene { DefaultAppWindow() .windowMode(.windowed) .windowTitle("Ada App") } }
Этого достаточно, чтобы создать окно и установить стандартные плагины движка.
Ключевая философия — кастомизация через плагины. Рендеринг, аудио, ввод, события, UI, физика, сцены, спрайты и другие возможности добавляются в приложение через композицию плагинов. Можно начать с разумных defaults или собрать более легкий runtime, выбрав только нужные части.
Для большего контроля используйте EmptyWindow и добавляйте плагины вручную:
import AdaEngine @main struct AdaApp: App { var body: some AppScene { EmptyWindow() .addPlugins(DefaultPlugins()) .windowMode(.windowed) .windowTitle("Ada App") } }
DefaultPlugins — набор, с которого стоит начинать большинству пользователей. Когда нужен более легкий runtime, части этого набора можно отключать через disable(_:).
Entity Component System
Сердце AdaEngine — ECS-фреймворк. Он вдохновлен движками и фреймворками вроде Bevy и RealityKit, но спроектирован так, чтобы ощущаться естественно в Swift.
В Entity Component System:
Entities — уникальные идентификаторы.
Components — данные, прикрепленные к сущностям.
Systems — логика, которая читает и изменяет компоненты.
Resources — уникальные значения уровня мира.
Такой подход отделяет игровые данные от игровой логики. Еще он помогает масштабировать игру от нескольких объектов до множества систем и сущностей.
AdaECS использует обычные Swift-типы и добавляет макросы, чтобы уменьшить boilerplate:
import AdaEngine @Component struct Position { var value: Float } @Component struct Velocity { var value: Float } @System func Movement( _ query: Query< Ref<Position>, // read-write access Velocity // read-only access > ) { query.forEach { position, velocity in position.value += velocity.value } } struct ExamplePlugin: Plugin { func setup(in app: AppWorlds) { app.spawn { Position(value: 0) Velocity(value: 1) } app.spawn { Position(value: 1) Velocity(value: 2) } app.addSystem(MovementSystem.self, on: .update) } } @main struct AdaApp: App { var body: some AppScene { DefaultAppWindow() .addPlugins(ExamplePlugin()) } }
Макрос @System генерирует конкретный тип системы за вас. Вы пишете логику как Swift-функцию, а AdaEngine превращает ее в зарегистрированную ECS-систему.
Queries
Queries извлекают компоненты из мира:
@System func Movement(_ query: Query<Entity, Transform>) { query.forEach { entity, transform in // Iterate over every entity with a Transform. } }
Filter queries
Фильтры ограничивают набор подходящих сущностей:
@System func PlayerMovement( _ query: FilterQuery<Entity, Transform, With<Player>> ) { query.forEach { entity, transform in // Iterate only over entities that also have Player. } }
Change detection
Change detection позволяет системе реагировать только тогда, когда меняются релевантные данные:
@System func EnemyHealthBar( _ query: FilterQuery<Enemy, Changed<Health>> ) { query.forEach { enemy in // Run when Health has been added or changed. } }
Resources
Resources хранят уникальные данные уровня мира:
struct GameScore: Resource { var score: Int var bulletFireCount: Int } world.insertResource(GameScore(score: 0, bulletFireCount: 0)) @System func UpdateScore(score: ResMut<GameScore>) { score.score += 1 }
Delta time тоже доступен как resource:
@System func Movement( time: Res<DeltaTime>, query: Query<Ref<Position>> ) { query.forEach { $0.value += 20 * time.deltaTime } }
Commands
Когда системе нужно создать или удалить сущности либо вставить компоненты, она может использовать Commands. Команды собираются и применяются после завершения системы, что сохраняет выполнение систем безопасным.
@System func GameStartup(_ commands: Commands) { commands.spawn("Player") { Player() Transform() } }
Local values
Системы могут хранить локальное состояние через Local:
@System func UpdateData(isUpdated: Local<Bool> = false) { if !isUpdated.wrappedValue { // Perform one-time work. isUpdated.wrappedValue = true } }
Struct systems
Для большего контроля AdaECS также поддерживает struct-based системы через @PlainSystem:
@PlainSystem(dependencies: [ .after(EnemyMovement.self), .before(PhysicsSystem.self) ]) struct MovementSystem { @Query<Player, Transform> private var playerQuery init(world: World) {} func update(context: UpdateContext) { playerQuery.forEach { // Update player movement here. } } }
Schedulers
Системы выполняются в schedulers. В AdaEngine есть типовые стадии: startup, pre-update, update, fixed update и другие:
world .addSystem(StartupSystem.self, on: .startup) .addSystem(MovementSystem.self, on: .fixedUpdate) .addSystem(UpdateEnemySystem.self, on: .preUpdate) .addSystem(UpdateScoreSystem.self, on: .update)
.startup выполняется один раз при запуске приложения. Можно создавать и собственные schedulers, если игре нужна своя модель выполнения.
⚠️ Ранний релиз Будьте осторожны с зависимостями систем. Если система зависит от другой системы, которая не зарегистрирована в том же scheduler, приложение может упасть во время выполнения.
Bundles
Bundles объединяют несколько компонентов в одну переиспользуемую единицу. Макрос @Bundle генерирует код, нужный для распаковки bundle в компоненты:
@Bundle struct EnemyBundle { let enemy = Enemy() let transform: Transform let health: Health } world.spawn( "Enemy", bundle: EnemyBundle( transform: Transform(), health: Health(30) ) )
Scriptable objects
Если для части gameplay-кода вам ближе Unity-подобный workflow, AdaEngine предоставляет ScriptableObject и ScriptableComponents:
final class Player: ScriptableObject { func update(_ deltaTime: TimeInterval) { if input.isKeyPressed(.w) { // Move player. } } } world.spawn("Player") { ScriptableComponents( components: [ Player() ] ) }
Это дает знакомый object-style escape hatch, при этом движок остается ECS-first.
AdaUI
AdaEngine включает UI-фреймворк AdaUI. Он вдохновлен SwiftUI и рассчитан как на игры, так и на инструменты в духе редакторов.
SwiftUI показал, насколько продуктивным может быть декларативный UI. AdaUI приносит похожий стиль внутрь движка: UI-код можно писать прямо на Swift и рендерить внутри сцены AdaEngine.


Views
View реализует протокол View:
struct GameOverView: View { var body: some View { Text("Game Over") } }
Layout
В AdaUI есть привычные stack layout primitives:
struct GameOverView: View { var body: some View { VStack(spacing: 20) { Text("Game Over") Text("Try again") } } }
Interactive elements
Кнопки и другие интерактивные controls можно компоновать в том же стиле:
struct MenuView: View { var body: some View { Button("Start Game") { // Start game. } Button(action: { // Open settings. }, label: { Text("Settings") .foregroundColor(.red) }) } }
Modifiers
Modifiers применяют стиль и поведение:
struct GameOverView: View { var body: some View { VStack(spacing: 20) { Text("Game Over") .font(.system(size: 50)) .foregroundColor(.red) } } }
State and bindings
Views могут хранить состояние и обновляться при его изменении:
struct GameOverView: View { @State private var isDead = false var body: some View { VStack(spacing: 20) { if isDead { Text("Game Over") .font(.system(size: 50)) .foregroundColor(.red) } } .onEvent(YourGameEvent.UserDied) { self.isDead = true } } }
Bindings передают состояние между views:
struct ParentView: View { @State private var isDead = false var body: some View { SubView(isDead: $isDead) } } struct SubView: View { @Binding var isDead: Bool var body: some View { if isDead { Text("Game Over") } } }
Attaching UI to an entity
Чтобы показать view в мире, прикрепите его через UIComponent:
let gameOverView = GameOverView() world.spawn("GameOverView") { UIComponent(view: gameOverView) }
Environment access
AdaUI views могут читать значения из environment. Например, view, прикрепленный к сущности, может получить доступ к ECS-миру:
struct DebugView: View { @Environment(\.world) private var world var body: some View { Button("Spawn Enemy") { world.spawn("Enemy", bundle: EnemyBundle()) } } }
Images
Images можно использовать прямо в UI:
struct UserAvatarView: View { var body: some View { Image("@res://avatar.png") } }
AdaUI особенно важен для будущего AdaEngine, потому что редактор планируется строить поверх той же UI-системы, которую смогут использовать игры.

2D-возможности
AdaEngine 0.1.0 сфокусирован на создании крепкой 2D-основы.
Sprites
Спрайты — базовый строительный блок для многих 2D-игр. AdaEngine умеет рендерить спрайты из Texture2D и других texture resources:
let texture = try await AssetsManager.load(Texture2D.self, at: "@res://sprite.png") world.spawn { Sprite(texture: texture) Transform() }
Texture atlases and sprite sheets
Texture atlases можно использовать для анимации, tile sets и оптимизированного рендеринга:
let image = try await AssetsManager.load(Image.self, at: "@res://characters.png") let textureAtlas = TextureAtlas(from: image, size: Vector2(16, 16)) world.spawn { Sprite( texture: textureAtlas[0, 1], size: Size(width: 16, height: 16) ) Transform() }
Если размер спрайта не указан, AdaEngine может вывести его из текстуры.
Tilemaps
В AdaEngine есть отдельный модуль AdaTilemap. Встроенные демо включают и собственные примеры tilemap, и загрузку tilemap на базе LDtk. Это позволяет визуально собирать уровни, а затем загружать их в ECS-мир.
Цель — поддержать практичные 2D workflows: рисовать уровни в редакторе, загружать их как данные, прикреплять физику и быстро итерироваться.

2D physics
AdaEngine включает AdaPhysics на базе Box2D. Можно прикреплять collision components к сущностям и получать collision events через систему событий.
Физика интегрирована в ECS-мир, поэтому gameplay-код может объединять transforms, sprites, collision components и systems в одной data-driven модели.
Scenes
Сцена — это набор сущностей, компонентов и ресурсов, который можно сохранить, загрузить и заспавнить в мир.
О сцене можно думать как о prefab или файле уровня: она описывает часть игры, которую можно загрузить при необходимости.
Scene files
Сцены сохраняются как человекочитаемый YAML. Файл сцены может включать сущности, данные компонентов, transforms, sprites, physics components и resources:
version: 1.0.0 scene: Scene world: entities: - name: Ground id: 122210699653662020 components: AdaSprite.Sprite: tintColor: red: 1.0 green: 1.0 blue: 1.0 alpha: 1.0 flipX: false flipY: false AdaTransform.Transform: rotation: x: 0.0 y: 0.0 z: 0.0 w: 1.0 scale: x: 3.0 y: 0.19 z: 0.19 position: x: 0.0 y: -1.0 z: 0.0 AdaPhysics.Collision2DComponent: shapes: - fixture: box: _0: halfWidth: 0.5 halfHeight: 0.5 offset: x: 0.0 y: 0.0 mode: default: {} resources: {}
Loading scenes
Сцены являются ассетами, поэтому их можно загружать через asset system:
let scene = try await AssetsManager.load(Scene.self, at: "@res://game_scene.ascn") world.spawn("Spawned scene") { DynamicScene(scene: scene) }
Заспавненная сцена может прикрепить свои сущности и ресурсы под parent entity.
Hot reloading scenes
Hot reloading сцен — одна из самых важных возможностей для итерации. Когда файл сцены меняется, AdaEngine может применить эти изменения к уже запущенной сцене без перезапуска и полной пересборки. Это заметно ускоряет редактирование уровней и настройку gameplay.
? Hot reload — ранняя возможность, но направление понятное: редактировать данные, сразу видеть результат и оставаться сфокусированным на игре, а не на цикле сборки.
Events
Играм и приложениям постоянно нужно общаться внутри себя: начинаются столкновения, нажимаются кнопки, открывается UI, появляются враги, подключаются игроки, а системы должны на это реагировать.
AdaEngine поддерживает и глобальный event-style messaging, и ECS frame events.
EventManager
Можно подписаться на событие и сохранить cancellable token:
let cancellable = world.subscribe( on: CollisionEvents.Began.self ) { payload in // Handle collision. } world.eventManager.sendEvent(SomeEvent()) // Or send globally: EventManager.default.sendEvent(SomeEvent())
ECS events
Для ECS-native workflows AdaEngine предоставляет Events и EventSender:
@System func HostConnection(_ events: Events<OnConnect>) { for event in events { print("User connected", event.userId) } } @System func ConnectionUpdate(_ sender: EventSender<OnConnect>) { sender(OnConnect(userId: "player#123")) }
? ECS events — это frame events: они хранятся только в течение текущего кадра.
Assets
Asset system позволяет загружать и сохранять игровые данные. Ассеты ссылаются через handles, что делает возможным hot reloading.
Например, загрузка текстуры выглядит так:
let texture: AssetHandle<Texture2D> = try await AssetsManager.load( Texture2D.self, at: "@res://my_texture.png" )
Префикс @res:// указывает на директорию ресурсов приложения. По умолчанию AdaEngine ищет папку Assets или Resources в вашем target. Также директорию ресурсов можно задать вручную.
Чтобы загрузить из конкретного bundle:
let texture: AssetHandle<Texture2D> = try await AssetsManager.load( Texture2D.self, at: "my_texture.png", from: Foundation.Bundle(path: "") )
Чтобы включить hot reloading для ассета, передайте handleChanges: true:
let texture: AssetHandle<Texture2D> = try await AssetsManager.load( Texture2D.self, at: "@res://my_texture.png", handleChanges: true )
Adding a new asset type
Можно добавить поддержку собственных ассетов, реализовав протокол Asset:
struct MyAsset: Asset { init(asset decoder: AssetDecoder) async throws { // Decode asset contents. } func encodeContents(with encoder: AssetEncoder) async throws { // Encode asset contents. } static func extensions() -> [String] { ["txt"] } }
После этого ассет становится доступен через тот же loading pipeline, что и встроенные текстуры, звуки, сцены и другие ресурсы.
Audio
AdaEngine включает модуль AdaAudio на базе miniaudio. Можно загрузить audio resource и воспроизвести его от сущности:
let backgroundSound = try await AssetsManager.load( AudioResource.self, at: "@res://background.wav" ) let player = world.spawn { Player() } player.prepareAudio(backgroundSound) .setLoop(true) .play()
Аудио можно привязывать к сущностям, что открывает путь к spatial sound и воспроизведению, управляемому gameplay.
Rendering
Рендеринг в AdaEngine разделен на модули и плагины. В текущей кодовой базе есть:
AdaRenderдля render abstractions, камер, материалов, meshes, текстур, render pipelines и render graphs.AdaSpriteдля 2D sprite rendering.AdaCorePipelinesдля встроенных rendering pipelines и shaders.Поддержка Metal на платформах Apple.
Поддержка WebGPU через Dawn/Swan там, где она включена.
Инфраструктура компиляции и транспиляции shaders вокруг SPIR-V tooling.
В этом релизе уже есть основа и для 2D-, и для 3D-рендеринга. Сейчас 2D-путь наиболее зрелый. 3D существует — включая meshes, камеры, материалы и демо с кубом, — но ему нужно больше работы, прежде чем он будет ощущаться завершенным.
Render graphs — важная часть будущего направления. Они делают работу рендеринга явной и компонуемой, что должно помочь движку вырасти от простых sprite scenes до более продвинутых pipelines.
Platforms and tooling
AdaEngine — это Swift Package на Swift 6.2. Сейчас package объявляет targets для платформ Apple, таких как macOS 15, iOS 18, tvOS 18 и visionOS 2. Также в нем есть conditional compilation и platform backends для Linux, Windows, Android, WASI/WebAssembly, Metal, WebGPU, X11 и browser runtimes.
Не все платформы пока одинаково зрелые. Платформы Apple сегодня готовы лучше всего, а Windows, Linux, Android и Web входят в активное кроссплатформенное направление.
В репозитории также есть SwiftPM-плагины и инструменты, включая:
Ada web export plugin,
build tooling, связанный с WebGPU/Tint,
инструмент и плагины для сборки texture atlas,
tooling для транспиляции shaders,
поддержку генерации документации через DocC.
Examples
В репозитории есть примеры в Demos, включая:
рендеринг спрайтов,
many sprites / stress examples,
custom materials,
2D lighting,
transparency,
text rendering,
gamepad input,
загрузку сцен,
LDtk tilemaps,
scriptable components,
collision events,
UI-примеры: buttons, text fields, scene views, animated text и Kanban board,
простой 3D-пример с кубом,
небольшие игровые демо вроде Snowman Attacks.
Примеры важны, потому что показывают, что движок уже умеет, а еще служат практическими тестами workflows движка.


Зачем я построил AdaEngine
Создавать игры было моей детской мечтой. Я начал учить Java, потому что хотел делать моды для Minecraft. Позже я стал iOS-инженером, но мечта делать игры никуда не исчезла.
Я много свободного времени изучал Godot, погружался в сообщество game development и пытался понять, как движки устроены изнутри. Я начал с небольшого Metal-проекта, продолжал экспериментировать и после нескольких лет работы дошел до этого рубежа: первого релиза AdaEngine.
Я люблю open source. Я люблю Swift. Я сделал много open source Swift-проектов, и мне было интересно посмотреть, что получится, если использовать Swift не только для приложений, но и для полноценного игрового движка.
Swift может предложить многое: value types, protocol-oriented design, macros, structured concurrency, memory safety, сильный tooling и приятный для написания синтаксис. Самая большая проблема не в языке, а в представлении, что Swift принадлежит только разработке под macOS и iOS.
Я не думаю, что это правда. Swift может быть чем-то большим. AdaEngine — моя попытка помочь это доказать.
Что дальше?
AdaEngine 0.1.0 — это начало, а не финишная черта. Следующая фаза — расширять движок, полировать опыт использования и растить сообщество.
Больше платформ
Долгосрочная цель — поддерживать как можно больше платформ. Swift — безопасный и мощный язык, и я верю, что он может отлично подойти для кроссплатформенной разработки игр.
Следующая важная работа по платформам включает WebAssembly/WebGPU, Linux, Android и дальнейшую поддержку Windows.
Редактор
Разработчики игр хотят быстрее прототипировать и писать меньше boilerplate. AdaUI дает основу для редактора на той же UI-системе, которую смогут использовать игры.
Собрать AdaEditor на AdaUI — важная цель: это улучшит UI-фреймворк, проверит tooling движка и сделает AdaEngine доступнее для пользователей, которым ближе визуальные workflows.
3D rendering and polish
2D feature set — главный фокус этого релиза, но поддержка 3D уже есть и продолжит улучшаться. Работы много: лучшие материалы, более полные возможности рендеринга, MSAA, более богатый scene tooling, workflows для моделей и многое другое.
Движку также нужна полировка во многих системах: asset workflows, hot reloading, интеграция с редактором, diagnostics, examples и дизайн API.
Документация и туториалы
API все еще нестабилен, а документация местами sparse. В ближайшем будущем AdaEngine нужны новые туториалы, более качественные guides и больше примеров, которые показывают полные workflows: от настройки проекта до готовых игровых механик.
Хорошая документация — не опция. Это часть движка.
Присоединяйтесь к AdaEngine
Если вам это интересно, загляните в AdaEngine на GitHub, прочитайте серию туториалов, изучите примеры и присоединяйтесь к обсуждению.
AdaEngine сейчас создается волонтерами. Если вы хотите помочь строить игровой движок на Swift — кодом, документацией, примерами, тестированием, feedback по дизайну или идеями, — вам очень рады.
Это всего лишь версия 0.1.0, но это начало того, что я давно хотел построить.
Давайте делать игры на Swift.