koota
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseKoota ECS
Koota ECS
Koota manages state using entities with composable traits.
Koota 采用具备可组合Trait的实体来管理状态。
Glossary
术语表
- Entity - A unique identifier pointing to data defined by traits. Spawned from a world.
- Trait - A reusable data definition. Can be schema-based (SoA), callback-based (AoS), or a tag.
- Relation - A directional connection between entities to build graphs.
- World - The context for all entities and their data (traits).
- Archetype - A unique combination of traits that entities share.
- Query - Fetches entities matching an archetype. The primary way to batch update state.
- Action - A discrete, synchronous data mutation (create, update, destroy). Reusable from any call site.
- System - A reactive orchestrator that observes state changes and coordinates work, including async workflows. Runs in the frame loop or event callbacks.
- Entity - 指向由Trait定义的数据的唯一标识符,从World中生成。
- Trait - 可复用的数据定义,支持基于Schema(SoA)、基于回调(AoS)或标签类型。
- Relation - 实体间的定向连接,用于构建图结构。
- World - 所有实体及其数据(Trait)的上下文环境。
- Archetype - 实体共享的Trait的唯一组合。
- Query - 提取匹配特定Archetype的实体,是批量更新状态的主要方式。
- Action - 离散的同步数据变更操作(创建、更新、销毁),可在任意调用点复用。
- System - 响应式编排器,监听状态变更并协调任务(包括异步工作流),在帧循环或事件回调中运行。
Design Principles
设计原则
Data-oriented
面向数据
Behavior is separated from data. Data is defined as traits, entities compose traits, and systems mutate data on traits via queries. See Basic usage for a complete example.
行为与数据分离。数据通过Trait定义,实体组合Trait,System通过Query修改Trait上的数据。完整示例请查看基础用法。
Composable systems
可组合的System
Design systems as small, single-purpose units rather than monolithic functions that do everything in sequence. Each system should handle one concern so that behaviors can be toggled on/off independently.
typescript
// Good: Composable systems - each can be enabled/disabled independently
function applyVelocity(world: World) {}
function applyGravity(world: World) {}
function applyFriction(world: World) {}
function syncToDOM(world: World) {}
// Bad: Monolithic system - can't disable gravity without disabling everything
function updatePhysicsAndRender(world: World) {
// velocity, gravity, friction, DOM sync all in one function
}This enables feature flags, debugging (disable one system to isolate issues), and flexible runtime configurations.
将System设计为小型、单一职责的单元,而非包揽所有任务的单体函数。每个System应只处理一个关注点,以便独立开启/关闭行为。
typescript
// 推荐:可组合System - 每个System可独立启用/禁用
function applyVelocity(world: World) {}
function applyGravity(world: World) {}
function applyFriction(world: World) {}
function syncToDOM(world: World) {}
// 不推荐:单体System - 无法在不关闭所有功能的情况下禁用重力
function updatePhysicsAndRender(world: World) {
// 速度、重力、摩擦力、DOM同步全部在一个函数中
}这种设计支持功能开关、调试(禁用单个System以隔离问题)以及灵活的运行时配置。
Decouple view from logic
解耦视图与逻辑
Separate core state and logic (the "core") from the view ("app"):
- Run logic independent of rendering
- Swap views while keeping state (2D ↔ 3D)
- Run logic in a worker or on a server
将核心状态和逻辑(“核心层”)与视图(“应用层”)分离:
- 逻辑运行独立于渲染流程
- 可在保留状态的情况下切换视图(2D ↔ 3D)
- 逻辑可在Worker或服务器中运行
Prefer traits + actions over classes
优先使用Trait + Action而非类
Prefer not to use classes to encapsulate data and behavior. Use traits for data and actions for behavior. Only use classes when required by external libraries (e.g., THREE.js objects) or the user prefers it.
避免使用类来封装数据和行为,应使用Trait定义数据、Action处理行为。仅在外部库要求(如THREE.js对象)或用户偏好时使用类。
Directory structure
目录结构
If the user has a preferred structure, follow it. Otherwise, use this guidance: the directory structure should mirror how the app's data model is organized. Separate core state/logic from the view layer:
- Core - Pure TypeScript. Traits, systems, actions, world. No view imports.
- View - Reads from world, mutates via actions. Organized by domain/feature.
src/
├── core/ # Pure TypeScript, no view imports
│ ├── traits/
│ ├── systems/
│ ├── actions/
│ └── world.ts
└── features/ # View layer, organized by domainFiles are organized by role, not by feature slice. Traits and systems are composable and don't map cleanly to features.
For detailed patterns and monorepo structures, see references/architecture.md.
若用户已有偏好结构,可遵循其要求;否则可参考以下指导:目录结构应与应用的数据模型组织方式一致,分离核心状态/逻辑与视图层:
- Core - 纯TypeScript代码,包含Trait、System、Action、World,无视图相关导入。
- View - 从World读取数据,通过Action修改数据,按领域/功能组织。
src/
├── core/ # 纯TypeScript代码,无视图导入
│ ├── traits/
│ ├── systems/
│ ├── actions/
│ └── world.ts
└── features/ # 视图层,按领域组织文件按职责而非功能切片组织,Trait和System具备可组合性,无法与功能一一对应。
如需详细的模式和单体仓库结构,请查看references/architecture.md。
Trait types
Trait类型
| Type | Syntax | Use when | Examples |
|---|---|---|---|
| SoA (Schema) | | Simple primitive data | |
| AoS (Callback) | | Complex objects/instances | |
| Tag | | No data, just a flag | |
| 类型 | 语法 | 适用场景 | 示例 |
|---|---|---|---|
| SoA(Schema) | | 简单原始数据 | |
| AoS(回调) | | 复杂对象/实例 | |
| 标签 | | 无数据,仅作为标记 | |
Trait naming conventions
Trait命名规范
| Type | Pattern | Examples |
|---|---|---|
| Tags | Start with | |
| Relations | Prepositional | |
| Trait | Noun | |
| 类型 | 命名模式 | 示例 |
|---|---|---|
| 标签 | 以 | |
| Relation | 介词形式 | |
| Trait | 名词形式 | |
Relations
Relation
Relations build graphs between entities such as hierarchies, inventories, targeting.
typescript
import { relation } from 'koota'
const ChildOf = relation({ autoDestroy: 'orphan' }) // Hierarchy
const Contains = relation({ store: { amount: 0 } }) // With data
const Targeting = relation({ exclusive: true }) // One target only
// Build graph
const parent = world.spawn()
const child = world.spawn(ChildOf(parent))
// Query children of parent
const children = world.query(ChildOf(parent))
// Query all entities with any ChildOf relation
const allChildren = world.query(ChildOf('*'))
// Get targets from entity
const items = entity.targetsFor(Contains) // Entity[]
const target = entity.targetFor(Targeting) // Entity | undefinedFor detailed patterns, traversal, ordered relations, and anti-patterns, see references/relations.md.
Relation用于构建实体间的图结构,如层级、 inventory、目标定位等。
typescript
import { relation } from 'koota'
const ChildOf = relation({ autoDestroy: 'orphan' }) // 层级结构
const Contains = relation({ store: { amount: 0 } }) // 携带数据
const Targeting = relation({ exclusive: true }) // 仅允许一个目标
// 构建图
const parent = world.spawn()
const child = world.spawn(ChildOf(parent))
// 查询parent的子实体
const children = world.query(ChildOf(parent))
// 查询所有带有ChildOf关联的实体
const allChildren = world.query(ChildOf('*'))
// 从实体获取目标
const items = entity.targetsFor(Contains) // Entity[]
const target = entity.targetFor(Targeting) // Entity | undefined如需详细模式、遍历、有序Relation及反模式,请查看references/relations.md。
Basic usage
基础用法
typescript
import { trait, createWorld } from 'koota'
// 1. Define traits
const Position = trait({ x: 0, y: 0 })
const Velocity = trait({ x: 0, y: 0 })
const IsPlayer = trait()
// 2. Create world and spawn entities
const world = createWorld()
const player = world.spawn(Position({ x: 100, y: 50 }), Velocity, IsPlayer)
// 3. Query and update
world.query(Position, Velocity).updateEach(([pos, vel]) => {
pos.x += vel.x
pos.y += vel.y
})typescript
import { trait, createWorld } from 'koota'
// 1. 定义Trait
const Position = trait({ x: 0, y: 0 })
const Velocity = trait({ x: 0, y: 0 })
const IsPlayer = trait()
// 2. 创建World并生成实体
const world = createWorld()
const player = world.spawn(Position({ x: 100, y: 50 }), Velocity, IsPlayer)
// 3. 查询并更新
world.query(Position, Velocity).updateEach(([pos, vel]) => {
pos.x += vel.x
pos.y += vel.y
})Entities
实体
Entities are unique identifiers that compose traits. Spawned from a world.
typescript
// Spawn
const entity = world.spawn(Position, Velocity)
// Read/write traits
entity.get(Position) // Read trait data
entity.set(Position, { x: 10 }) // Write (triggers change events)
entity.add(IsPlayer) // Add trait
entity.remove(Velocity) // Remove trait
entity.has(Position) // Check if has trait
// Destroy
entity.destroy()Entity IDs
An entity is internally a number packed with entity ID, generation ID (for recycling), and world ID. Safe to store directly for persistence or networking.
typescript
entity.id() // Just the entity ID (reused after destroy)
entity // Full packed number (unique forever)Typing
Use to get the type that returns
TraitRecordentity.get()typescript
type PositionRecord = TraitRecord<typeof Position>实体是组合了Trait的唯一标识符,从World中生成。
typescript
// 生成实体
const entity = world.spawn(Position, Velocity)
// 读写Trait
entity.get(Position) // 读取Trait数据
entity.set(Position, { x: 10 }) // 写入(触发变更事件)
entity.add(IsPlayer) // 添加Trait
entity.remove(Velocity) // 移除Trait
entity.has(Position) // 检查是否拥有Trait
// 销毁实体
entity.destroy()实体ID
实体内部是一个包含实体ID、生成ID(用于复用)和World ID的数字,可直接存储用于持久化或网络传输。
typescript
entity.id() // 仅返回实体ID(销毁后可复用)
entity // 完整的打包数字(永久唯一)类型定义
使用获取返回的类型
TraitRecordentity.get()typescript
type PositionRecord = TraitRecord<typeof Position>Queries
查询
Queries fetch entities matching an archetype and are the primary way to batch update state.
typescript
// Query and update
world.query(Position, Velocity).updateEach(([pos, vel]) => {
pos.x += vel.x
pos.y += vel.y
})
// Read-only iteration (no write-back)
const data: Array<{ x: number; y: number }> = []
world.query(Position, Velocity).readEach(([pos, vel]) => {
data.push({ x: pos.x, y: pos.y })
})
// Get first match
const player = world.queryFirst(IsPlayer, Position)
// Filter with modifiers
world.query(Position, Not(Velocity)) // Has Position but not Velocity
world.query(Or(IsPlayer, IsEnemy)) // Has either traitNote: / only return data-bearing traits (SoA/AoS). Tags, , and relation filters are excluded:
updateEachreadEachNot()typescript
world.query(IsPlayer, Position, Velocity).updateEach(([pos, vel]) => {
// Array has 2 elements - IsPlayer (tag) excluded
})For tracking changes, caching queries, and advanced patterns, see references/queries.md.
查询用于提取匹配特定Archetype的实体,是批量更新状态的主要方式。
typescript
// 查询并更新
world.query(Position, Velocity).updateEach(([pos, vel]) => {
pos.x += vel.x
pos.y += vel.y
})
// 只读迭代(无写回操作)
const data: Array<{ x: number; y: number }> = []
world.query(Position, Velocity).readEach(([pos, vel]) => {
data.push({ x: pos.x, y: pos.y })
})
// 获取第一个匹配项
const player = world.queryFirst(IsPlayer, Position)
// 使用修饰符过滤
world.query(Position, Not(Velocity)) // 拥有Position但无Velocity
world.query(Or(IsPlayer, IsEnemy)) // 拥有任意一个Trait注意: /仅返回携带数据的Trait(SoA/AoS),标签、和Relation过滤器会被排除:
updateEachreadEachNot()typescript
world.query(IsPlayer, Position, Velocity).updateEach(([pos, vel]) => {
// 数组包含2个元素 - IsPlayer(标签)被排除
})如需变更追踪、查询缓存及高级模式,请查看references/queries.md。
React integration
React集成
Imports: Core types (, ) from . React hooks from .
WorldEntity'koota''koota/react'Change detection: and trigger change events that cause hooks like to rerender. For AoS traits where you mutate objects directly, manually signal with .
entity.set()world.set()useTraitentity.changed(Trait)For React hooks and actions, see references/react-hooks.md.
For component patterns (App, Startup, Renderer, view sync, input), see references/react-patterns.md.
导入: 核心类型(, )从导入,React钩子从导入。
WorldEntity'koota''koota/react'变更检测: 和会触发变更事件,导致等钩子重新渲染。对于直接修改对象的AoS Trait,需手动调用触发变更通知。
entity.set()world.set()useTraitentity.changed(Trait)如需React钩子和Action的详细内容,请查看references/react-hooks.md。
如需组件模式(应用、启动、渲染器、视图同步、输入),请查看references/react-patterns.md。
Runtime
运行时
Systems query the world and update entities. Run them via frameloop (continuous) or event handlers (discrete).
For systems, frameloop, event-driven patterns, and time management, see references/runtime.md.
System查询World并更新实体,可通过帧循环(持续运行)或事件处理器(离散运行)触发。
如需System、帧循环、事件驱动模式及时间管理的详细内容,请查看references/runtime.md。