首页 > 解决方案 > 如何将 ECS 与 SwiftUI 结合使用?(实体组件系统)

问题描述

也许你可以照亮我黑暗无知的道路?我的目标是一个 SwiftUI 应用程序,我可以在其中将组件注入视图并让系统在这些组件上运行一些逻辑。每次我觉得我已经到了;基础的弱点让我重回起点。让我告诉你我试过什么!

protocol DataComponent: Component {
    associatedtype DataType
    var data: DataType { get set }
    init()
    init(data: DataType)
}

struct NameComponent: DataComponent { var data = "default" }

这是基本的数据组件。我将EntitasKit用于我的实体组件系统。

enum Entities {
    case source
    ...
}

protocol EntityServicing {
    var context: Context { get }
    func entity(_ entity: Entities) -> Entity
}

final class EntityService: EntityServicing {
    let context = Context()
    func entity(_ entity: Entities) -> Entity {
        switch entity {
        case .source:
            guard let first = context.entities.first else {
                return context.createEntity()
            }
            return first
        ...
        }
    }
}

这是应用程序范围的 EntityService,它包含实体及其组件的上下文。它是使用Resolver注入的。

现在,这是 SwiftUI 中的视图。

struct ContentView: View {
    @EntityState(.source, "Dale Cooper") var name: NameComponent
    var body: some View {
        Button(action: { name = NameComponent(data: "Dougie Jones") }) {
            Text(name.data).padding()
        }
    }
}

EntityState 就是它听起来的样子;一个属性包装器,它捕获实体组件的状态并使其对 SwiftUI 可见。

@propertyWrapper
struct EntityState<C>: DynamicProperty where C: DataComponent {

    @State private var state = C()
    @Injected private var entityService: EntityServicing
    private let entityID: Entities
    private var entity: Entity { entityService.entity(entityID) }
    
    init(_ id: Entities, _ data: C.DataType? = nil) {
        self.entityID = id
        if let data = data {
            entity.set(C(data: data))
        } else {
            guard entity.has(C.cid) else {
                entity.set(state)
                return
            }
        }
        _state = State(wrappedValue: entity.get()!)
    }

    public var wrappedValue: C {
        get { entity.get()! }
        nonmutating set { state = newValue }
    }
        
    mutating func update() {
        entity.set(state)
    }
}

SwiftUI 会注意到动态属性中的状态变化。单击该按钮将重新呈现视图并更新实体上的组件。

当我添加一个更改组件的系统时,这个基础开始崩溃。

class NameSystem: ReactiveSystem {
    
    @Injected var entityService: EntityServicing
    lazy var collector: Collector = Collector(
        group: entityService.context.group(
            Matcher(
                all: [NameComponent.cid]
            )
        ),
        type: .added
    )
    let name = "Name System Added Here!"

    func execute(entities: Set<Entity>) {
        for e in entities {
            if let data = e.get(NameComponent.self)?.data {
                e.set(NameComponent(data: "Special Agent " + data))
            }
        }
    }
}

问题是 SwiftUI 状态永远不会被通知,但我希望 SwiftUI 知道组件在系统更新时发生的变化。

我尝试了许多不同的方法:

如何从系统将组件分配给状态,以便 SwiftUI 重新渲染?

我希望你发现这是一个有趣的问题来解决,它让你和我一样对解决方案感到好奇。至少让我很兴奋。

祝大家拥有美好的一天!

标签: swiftswiftuientity-component-system

解决方案


推荐阅读