Рабочее пространство AdaEditor

После долгого пути я рад представить 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.

Сравнение layout в AdaUI и SwiftUI для стека media cards
Сравнение layout в AdaUI и SwiftUI для стека карточек media review.
Сравнение layout в AdaUI и SwiftUI для chat composer shell
Сравнение layout в AdaUI и SwiftUI для оболочки chat composer.

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-системы, которую смогут использовать игры.

UI AdaEditor
Редактор планируется вокруг той же основы AdaUI, которая доступна играм и инструментам.

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: рисовать уровни в редакторе, загружать их как данные, прикреплять физику и быстро итерироваться.

Демо tilemap
Сцена tilemap, отрендеренная AdaEngine.

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 движка.

Демо Duck Hunt
Небольшое демо в стиле Duck Hunt, запущенное на AdaEngine.
Демо Space Invaders
Демо в стиле Space Invaders из примеров AdaEngine.

Зачем я построил 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.

Комментарии (0)