Code Monkey home page Code Monkey logo

ecs-swift's Introduction

rrbox

主要な開発言語

  • Swift

ジャンル

  • 2Dゲーム開発

主なプラットフォーム

  • macOS
  • iOS

ecs-swift's People

Contributors

rrbox avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar

ecs-swift's Issues

Sparse set を利用した構造

辞書よりもイテレーションに特化しているかもです。パフォーマンス比較してみたいところです。
また entity の実装を UUID ではなく、加算される数値を利用できるようにもし、衝突を完全に回避します。

下のは試しに考えた見たやつです。

import Foundation

class World {
    let buffer = WorldBuffer()
}

struct SparseSet<T: Component> {
    var indices: [Entity] = []
    var data: [T] = []
}

class SparseSetRef<T: Component>: BufferElement {
    var sparseSet: SparseSet<T>
    init(sparseSet: SparseSet<T>) {
        self.sparseSet = sparseSet
        super.init()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class SparseSetBuffer {
    let buffer: Buffer
    init(buffer: Buffer) {
        self.buffer = buffer
    }
    
    func addComponentStrage<T: Component>(typeOf type: T.Type) {
        self.buffer.addComponent(SparseSetRef(sparseSet: SparseSet<T>()))
    }
    
    func sparseSetRef<T: Component>(typeOf type: T.Type) -> SparseSetRef<T>? {
        self.buffer.component(ofType: SparseSetRef<T>.self)
    }
}

extension WorldBuffer {
    var sparseSetBuffer: SparseSetBuffer { SparseSetBuffer(buffer: self) }
}

final class Query<C: Component>: SystemParameter {
    let ref: SparseSetRef<C>
    init(ref: SparseSetRef<C>) {
        self.ref = ref
    }
    
    static func register(to worldBuffer: WorldBuffer) {
        guard worldBuffer.sparseSetBuffer.sparseSetRef(typeOf: C.self) == nil else { return }
        worldBuffer.sparseSetBuffer.addComponentStrage(typeOf: C.self)
    }
    
    static func getParameter(from worldBuffer: WorldBuffer) -> Query<C>? {
        Query<C>(ref: worldBuffer.sparseSetBuffer.sparseSetRef(typeOf: C.self)!)
    }
}

final class Query2<C0: Component, C1: Component>: SystemParameter {
    let ref: (SparseSetRef<C0>, SparseSetRef<C1>)
    init(ref: (SparseSetRef<C0>, SparseSetRef<C1>)) {
        self.ref = ref
    }
    
    static func register(to worldBuffer: WorldBuffer) {
        guard worldBuffer.sparseSetBuffer.sparseSetRef(typeOf: C0.self) == nil else { return }
        guard worldBuffer.sparseSetBuffer.sparseSetRef(typeOf: C1.self) == nil else { return }
        worldBuffer.sparseSetBuffer.addComponentStrage(typeOf: C0.self)
        worldBuffer.sparseSetBuffer.addComponentStrage(typeOf: C1.self)
    }
    
    static func getParameter(from worldBuffer: WorldBuffer) -> Query2<C0, C1>? {
        Query2<C0, C1>(ref: (worldBuffer.sparseSetBuffer.sparseSetRef(typeOf: C0.self)!,
                         worldBuffer.sparseSetBuffer.sparseSetRef(typeOf: C1.self)!))
    }
}

Commands.AddComponent, Commands.RemoveComponent が Chunk に反映されない

現在のこれら二つのコマンドは、world にある archetype に変更を与えるのみで、既存の Chunk に対して影響を与えないようになっています。Spawn, Despawn と同様に、World にメソッドを用意し、その中で Chunk への反映を実装し、World のメソッドを Commands から実行するような作りに書き換えてください。

`Buffer` の命名

  • AnyMap<Buffer> -> AnyMap<WorldStorage>
  • BufferElement -> WorldStorageElement
  • BufferRef -> WorldStorageRef

Wikis

リポジトリを public にしてコードを公開するのであれば、wiki も用意しておきたいところです。

なにを書こうかしら


  • 制作者の考えていること

Entity を Query のジェネリクスで指定して受け取る

to do:

  • Query で protocol Component 意外の型を指定できるようにする
  • 構造体 Entity を Query で受け取れるようにする
  • entity スポーン時にデータとして entity をそれ自身に紐づける
  • #63

この提案を実装すると、Query が Entity を受け取るかどうかをオプションにすることができます。

v0.1.0 での Query でコンポーネントを受け取る方法はこのようになっています:

func system(query: Query<SomeComponent>) {
    query.update { entity, someComponent in
        
    }
}

この場合は、Entity を常に受け取るようになっており、受け取りたくない場合は _ に置き換える処置となっています。

Entity を Component に準拠させた場合は以下のように変更することができます。

func system(query: Query2<Entity, SomeComponent>) {
    query.update { entity, someComponent in
        
    }
}

この場合は、Entity を受け取りたくない時は query の型を Query<SomeComponent> にすることで解決できます。

func system(query: Query<SomeComponent>) {
    query.update { someComponent in
        
    }
}

v0.1.0

新しい機能ほか

  • AnyMap<Mode>
  • Schedule
  • States
  • Buffer

変更

  • Buffer (旧) -> AnyMap<WorldStorage>
  • EntityRecord -> AnyMap<EntityRecord>
  • WorldBuffer -> WorldStorageRef
  • BufferElement -> WorldStorageElement

Schedule + Event

Event を WorldStorage (旧 WorldBuffer) から取り出す必要があります。Event 発信の方法を見直してください。

entity commands を2種類に分類する

entity spawn 時の component 操作は entity record を直接操作した方が効率がいいと思われます。

  1. entity spawn: Entity と EntityRecordRef をインスタンス化
  2. 生成した entity と record を transaction として commands に push
  3. component 操作: EntityRecordRef にコンポーネントを追加(あまり行わないが、削除も)
  4. フレーム終了後に world に push

entity を検索した際の component 操作はフレーム終了後に操作した方がいいと思われます。

  1. entity 検索操作: 検索は行わず、パラメータで受け取った entity を保持
  2. component 操作: record は取得されないため、操作コマンドを transaction として push
  3. フレーム終了後に entity を検索し、record に component を追加/削除
  4. world に同期

Graphic : データ指向にする

position, zRotation については、SpriteKit 側の物理演算の結果を component に反映させる必要があるので、双方向バインディングにします(didSimulatePhisics)。

その他のデータは ECS → SpriteKit の単方向のデータバインディングのみで十分となるはずです。

各プロパティを別々に管理するためには、各プロパティのデータを別のコンポーネントで管理することになると思われます。そこで #50 を利用して Graphic 用バンドルを予め用意しておくと便利かと思います。

States

RPG のようにゲーム全体として状態が切り替わるような場合に States の実装が不可欠です。まずは仕様から考えましょう。

World のライフサイクル

World は update の中でどの処理をどのような順番で実行するべきなのかを考えます
現在実装されている処理は以下の通りです

  • update systems update: すべての update system を実行する処理
  • apply command queue: queue に保存された command を実行する処理
  • apply event queue: queue に保存された event を event system に配信する処理

Filtered query: entity の更新が反映されない可能性

Chunk には、entity が追加/削除されたり component が追加/削除された時に、そのデータを反映する機能があります。

Filtered query も chunk の一種ですが、どうやら上記の関数が実装されていないようです。

まずは不具合が出ていないか確認してください。
不具合があったら、修正してくださいださい。

Entity commands の仕組みの見直し

#70 , #55#76 に関連のある変更です。

#70 で entity 生成直後か entity 検索によるかで entity commands の種類を変える方法を採用しました。

しかし、#55 で entity への graphic の割り当ての際に entity commands によって実装方法が異なることや、それぞれに同じメソッドを作成しても、entity commands 共通のメソッドとして動的に実行することが難しくなるなど、問題が生じているようです。

最良は、同じ entity command (entity record の変更)を別々の entity commands に push するだけで、実行方法が分けられることです。

親を持つ SKNode に entity を割り当てる

Entity commands に用意されている set graphic メソッドは、親のない SKNode を SKScene に配置するようになっています。
すでに配置されている、ECS 管理外の SKNode を entity に紐づける方法を用意したいです。

以下のような仕様にします。

  • SKNode を引数に受け取る
  • user info にアクセスして、ECS 管理外であることをチェックする
  • entity にすでに SKNode が紐づけられていないことをチェックする
  • entity と紐づける

Systems

新しい System の実装です。

#18 Schedule の登場により、これまでの Set up systems, Update systems, Event systems が 1 つに統合され、Systems というモジュールで実装されます。

Entity hierarchy

以下の Component を追加します。

  • Parent
  • Child

Entity 間で親子関係が作られた際に、Child と Parent がそれぞれ子と親に追加されます。Child は親の Entity が含まれています。Parent はこの Entity の配列が含まれます。

States + Event

特定の State が activated なときだけ Event に反応するシステムを作りたい。

Sparse set 専用 entity のための commands API

sparse set の導入により、entity 生成方法が変わります。それまでは引数を取らないイニシャライザで簡単に生成できましたが、今後は world の entities から空きスロットに対応する entity を生成しなければなりません。

この変化に伴い、これまでの commands の entity spawn 処理および、entity commands の作成がうまく機能しなくなります。とくに、entity spawn 処理は world へのアクセスを要求するため、遅延実行されることになります。従って、続く entity commands は entity のデータをプロパティに格納することができないことになります。

そこで entity commands の再定義と spawn, despawn の処理内容の調整を行うべきとしました。

README の作成と更新

何書こうかしら


  • バッジの追加 : https://shields.io/
  • What is ecs-swift?
  • 注意書き Warning
  • Getting Started
  • Examples
  • ライセンス契約
  • 英語化
  • 日本語版へのリンク
  • リンクの割り当て: What is ecs-swift?
  • リンクの割り当て: Examples

Commands.entity メソッドの戻り値は非オプショナル型にするべき

  • 元々は world から直接検索していたが、v0.2 からは SearchedEntityCommands をその場で生成する仕様になったので、オプショナル型である必要がありません。
  • 非オプショナルに変更しましょう。
  • 検索に失敗するケース(despawn した entity を検索するなど)の場合の挙動をチェックする必要があります。
    • おそらくフレーム終了時に何かしらの障害になる可能性があります。

EventWriter と EventReader

  • Rust 製 ECS ゲームエンジンである、Bevy に実装されている機能です。
  • EventWriter.send でイベントを発信します。
  • EventReader を受け取るシステムが実行されることでイベントを受け取ります。

Swift 自体の更新に備える

Swift 5.9 では以下の機能が通過されたようです。

  • Type Parameter Packs (関数のみ)
  • Ownership
  • C++ Interoperability

今後 swift の更新が ECS に大きな影響を与える可能性が大きいので、今のうちに妄想を膨らませておきます。

Commands 実行の順序

  • spawn/despawn: entity transaction -> chunk storage push
  • chunk storage apply
  • component transaction
  • command queue run: resource, graphic, etc.

Entity commands の開放性の制御

entity record や entity (id) は定数なので、public にしてもいいかもしれません。
commands は公開しなくてもいいです。

Schedule

システムが実行されるタイミングを Schedule という構造体で制御する提案です。

この機能により、それまで UpdateSystem, EventSystem, SetUpSystem として別々に実装していたシステム群を1種類にまとめることができます。

デメリットは、start up で使用できないパラメータ(Query)があること、event reader が他のスケジュールでは利用できないことが挙げられます(不正なパラメータの使用)。以前はコンパイル時に「不正なパラメータの使用」が検出できましたが、schedule を利用すると実行時のみでしか検出できないようです。

iOS 対応モジュール: Touch

画面タップイベントを ECS に送る plug in です。
MacOS 向けではマウスのクリックイベントを取り扱う予定です。

Filtered: 直接 `update` を呼び出す

Filtered query の更新処理は、プロパティ query を1度介して実行する必要があります。

func system(query: Filtered<Query<Use>, With<NotUse>>) {
    query.query.update { _, use in
        // update `use`
    }
}

しかし Filtered という名称ではあるものの、実際は Query の一種として扱うことが多いかと思います(例えば、パラメータ名をquery, -Query にするなど)。

そこで filtered query についても query と同様に直接 update でコンポーネントを更新するようにした方が扱いやすいのでは、と提案します。

func system(query: Filtered<Query<Use>, With<NotUse>>) {
    query.update { _, use in
        // update `use`
    }
}

Life cycle

World には以下の処理がデフォルトで実装されます。

  • build (builder pattern による定義)
  • set up (set up system の実行)
  • update (60 fps で関数が繰り返される)
  • pass through event
  • apply commands (commands を world に反映)

Object binding, Node binding は追加機能にする

クラスで実装された実体は、Binding を使わなくても Component ととして使用することが可能です。ただし、クラスは参照型なので、Component の実体以外の参照からコントロールすることが可能となってしまう欠点があります(複数の参照から操作可能な実体は不具合の原因になりやすいですよねー)。複数の参照を防ぐ方法として、 binding を使用して構造体とクラスを相互反映させる方法が考えられます。ただしこの方法は実装が複雑になりやすく(特にデータバインディング部分)、実装が過剰になりがちです。

上記のような問題を解決するために、以下のように方針を決めました。

  • ECS モジュールには Binding 機能を含めない
  • 別のモジュールで Binding 機能を実装し、オプショナルにする

Bundle の実装

作れそうだったら作ってみたいです。以下のようなイメージですが、はたして実現可能なのか..?

例: グラフィック用データの定義

struct Transform: Bundle {
    @component var position: Graphic.Position
    @component var zPosition: Graphic.ZPosition
}

struct Sprite: Bundle {
    @bundle var transform: Transform
    @component var size: Graphic.Size
    @component var texture: Graphic.Texture
}

最新の検討案の欄

struct Transform: Bundle {
    var body: some BuilderElement {
        bundleBuilder {
            component(Pos())
            component(ZPos())
        }
    }
}
struct Sprite: Bundle {
    var body: some BuilderElement {
        bundleBuilder {
            component(Size())
            component(Texture())
            bundle(Transform())
        }
    }
}

エラーハンドリングに変わる仕組み

v0.2 現在、システム実行中におこる何かしらの不具合やエラーは、握りつぶす or クラッシュのいずれかとなっています。

これはモデルの変更操作の根幹を成す Commands の実行方式に起因しています。

Commands のように遅延して実行される処理についても、どのシステムで実行されたタスクが失敗したのかを分かりやすくプログラマに伝える方法を考えたいです。

to do list

  • chunk
  • Commands
  • Commons
  • EntityCommands
  • PlugIn
  • Query
  • Resource
  • System
  • SystemParameter
  • World
  • Event : new!
  • #17

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.