
После долгого пути я рад представить 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@mainstruct AdaApp: App { var body: some AppScene { DefaultAppWindow() .windowMode(.windowed) .windowTitle("Ada App") }}
Этого достаточно, чтобы создать окно и установить стандартные плагины движка.
Ключевая философия — кастомизация через плагины. Рендеринг, аудио, ввод, события, UI, физика, сцены, спрайты и другие возможности добавляются в приложение через композицию плагинов. Можно начать с разумных defaults или собрать более легкий runtime, выбрав только нужные части.
Для большего контроля используйте EmptyWindow и добавляйте плагины вручную:
import AdaEngine@mainstruct 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@Componentstruct Position { var value: Float}@Componentstruct Velocity { var value: Float}@Systemfunc 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) }}@mainstruct AdaApp: App { var body: some AppScene { DefaultAppWindow() .addPlugins(ExamplePlugin()) }}
Макрос @System генерирует конкретный тип системы за вас. Вы пишете логику как Swift-функцию, а AdaEngine превращает ее в зарегистрированную ECS-систему.
Queries
Queries извлекают компоненты из мира:
@Systemfunc Movement(_ query: Query<Entity, Transform>) { query.forEach { entity, transform in // Iterate over every entity with a Transform. }}
Filter queries
Фильтры ограничивают набор подходящих сущностей:
@Systemfunc PlayerMovement( _ query: FilterQuery<Entity, Transform, With<Player>>) { query.forEach { entity, transform in // Iterate only over entities that also have Player. }}
Change detection
Change detection позволяет системе реагировать только тогда, когда меняются релевантные данные:
@Systemfunc 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))@Systemfunc UpdateScore(score: ResMut<GameScore>) { score.score += 1}
Delta time тоже доступен как resource:
@Systemfunc Movement( time: Res<DeltaTime>, query: Query<Ref<Position>>) { query.forEach { $0.value += 20 * time.deltaTime }}
Commands
Когда системе нужно создать или удалить сущности либо вставить компоненты, она может использовать Commands. Команды собираются и применяются после завершения системы, что сохраняет выполнение систем безопасным.
@Systemfunc GameStartup(_ commands: Commands) { commands.spawn("Player") { Player() Transform() }}
Local values
Системы могут хранить локальное состояние через Local:
@Systemfunc 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 в компоненты:
@Bundlestruct 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.0scene: Sceneworld: 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:
@Systemfunc HostConnection(_ events: Events<OnConnect>) { for event in events { print("User connected", event.userId) }}@Systemfunc 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.
ссылка на оригинал статьи https://habr.com/ru/articles/1044142/