重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
npx skills add https://github.com/pmndrs/koota --skill kootaKoota 使用具有可组合特性的实体来管理状态。
行为与数据分离。数据被定义为特性,实体组合特性,系统通过查询来变更特性上的数据。完整示例请参见基本用法。
将系统设计为小型、单一用途的单元,而不是按顺序执行所有操作的庞大函数。每个系统应处理一个关注点,以便可以独立地启用/禁用行为。
// 良好:可组合的系统 - 每个都可以独立启用/禁用
function applyVelocity(world: World) {}
function applyGravity(world: World) {}
function applyFriction(world: World) {}
function syncToDOM(world: World) {}
// 不佳:庞大的系统 - 无法在不禁用所有功能的情况下禁用重力
function updatePhysicsAndRender(world: World) {
// 速度、重力、摩擦力、DOM 同步全部在一个函数中
}
这使得功能标志、调试(禁用一个系统以隔离问题)和灵活的运行时配置成为可能。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
将核心状态和逻辑("核心")与视图("应用")分离:
优先不使用类来封装数据和行为。使用特性表示数据,使用动作表示行为。仅在外部库(例如 THREE.js 对象)要求或用户偏好时才使用类。
如果用户有偏好的结构,请遵循它。否则,使用此指导原则:目录结构应反映应用数据模型的组织方式。将核心状态/逻辑与视图层分离:
src/
├── core/ # 纯 TypeScript,无视图导入
│ ├── traits/
│ ├── systems/
│ ├── actions/
│ └── world.ts
└── features/ # 视图层,按领域组织
文件按角色组织,而不是按功能切片组织。特性和系统是可组合的,并不直接映射到功能。
有关详细模式和 monorepo 结构,请参阅 references/architecture.md。
| 类型 | 语法 | 适用场景 | 示例 |
|---|---|---|---|
| SoA(模式) | trait({ x: 0 }) | 简单的原始数据 | Position, Velocity, Health |
| AoS(回调) | trait(() => new Thing()) | 复杂的对象/实例 | Ref (DOM), Keyboard (Set) |
| 标签 | trait() | 无数据,仅作为标志 | IsPlayer, IsEnemy, IsDead |
| 类型 | 模式 | 示例 |
|---|---|---|
| 标签 | 以 Is 开头 | IsPlayer, IsEnemy, IsDead |
| 关系 | 介词性 | ChildOf, HeldBy, Contains |
| 特性 | 名词 | Position, Velocity, Health |
关系在实体之间构建图,例如层次结构、库存、目标。
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))
// 查询父实体的子实体
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
有关详细模式、遍历、有序关系和反模式,请参阅 references/relations.md。
import { trait, createWorld } from 'koota'
// 1. 定义特性
const Position = trait({ x: 0, y: 0 })
const Velocity = trait({ x: 0, y: 0 })
const IsPlayer = trait()
// 2. 创建世界并生成实体
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
})
实体是组合了特性的唯一标识符。从世界中生成。
// 生成
const entity = world.spawn(Position, Velocity)
// 读取/写入特性
entity.get(Position) // 读取特性数据
entity.set(Position, { x: 10 }) // 写入(触发变更事件)
entity.add(IsPlayer) // 添加特性
entity.remove(Velocity) // 移除特性
entity.has(Position) // 检查是否具有特性
// 销毁
entity.destroy()
实体 ID
实体内部是一个打包了实体 ID、生成 ID(用于回收)和世界 ID 的数字。可以直接存储以用于持久化或网络传输。
entity.id() // 仅实体 ID(销毁后可复用)
entity // 完整的打包数字(永远唯一)
类型提示
使用 TraitRecord 来获取 entity.get() 返回的类型
type PositionRecord = TraitRecord<typeof Position>
查询获取匹配原型的实体,是批量更新状态的主要方式。
// 查询和更新
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)) // 具有任一特性
对于承载数据的查询,优先使用 updateEach/readEach 而非 for...of + entity.get()。readEach 仍然将实体作为第二个参数提供。
注意: updateEach/readEach 仅返回承载数据的特性(SoA/AoS)。标签、Not() 和关系过滤器被排除在外:
world.query(IsPlayer, Position, Velocity).updateEach(([pos, vel]) => {
// 数组有 2 个元素 - IsPlayer(标签)被排除
})
有关跟踪变更、缓存查询和高级模式,请参阅 references/queries.md。
导入: 核心类型(World, Entity)来自 'koota'。React 钩子来自 'koota/react'。
变更检测: entity.set() 和 world.set() 会触发变更事件,导致像 useTrait 这样的钩子重新渲染。对于直接修改对象的 AoS 特性,需使用 entity.changed(Trait) 手动发出信号。
有关 React 钩子和动作,请参阅 references/react-hooks.md。
有关组件模式(App、Startup、Renderer、视图同步、输入),请参阅 references/react-patterns.md。
系统查询世界并更新实体。通过帧循环(连续)或事件处理程序(离散)运行它们。
有关系统、帧循环、事件驱动模式和时间管理,请参阅 references/runtime.md。
每周安装量
56
代码仓库
GitHub 星标数
660
首次出现
2026 年 2 月 6 日
安全审计
安装于
codex55
opencode54
gemini-cli54
github-copilot54
amp53
kimi-cli53
Koota manages state using entities with composable traits.
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.
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.
// 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.
Separate core state and logic (the "core") from the view ("app"):
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.
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 domain
Files 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.
| Type | Syntax | Use when | Examples |
|---|---|---|---|
| SoA (Schema) | trait({ x: 0 }) | Simple primitive data | Position, Velocity, Health |
| AoS (Callback) | trait(() => new Thing()) | Complex objects/instances | Ref (DOM), Keyboard (Set) |
| Type | Pattern | Examples |
|---|---|---|
| Tags | Start with Is | IsPlayer, IsEnemy, IsDead |
| Relations | Prepositional | ChildOf, HeldBy, Contains |
| Trait |
Relations build graphs between entities such as hierarchies, inventories, targeting.
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 | undefined
For detailed patterns, traversal, ordered relations, and anti-patterns, see references/relations.md.
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
})
Entities are unique identifiers that compose traits. Spawned from a world.
// 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.
entity.id() // Just the entity ID (reused after destroy)
entity // Full packed number (unique forever)
Typing
Use TraitRecord to get the type that entity.get() returns
type PositionRecord = TraitRecord<typeof Position>
Queries fetch entities matching an archetype and are the primary way to batch update state.
// 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 trait
Prefer updateEach/readEach over for...of + entity.get() for data-bearing queries. readEach still gives you the entity as the second argument.
Note: updateEach/readEach only return data-bearing traits (SoA/AoS). Tags, Not(), and relation filters are excluded :
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.
Imports: Core types (World, Entity) from 'koota'. React hooks from 'koota/react'.
Change detection: entity.set() and world.set() trigger change events that cause hooks like useTrait to rerender. For AoS traits where you mutate objects directly, manually signal with entity.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.
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.
Weekly Installs
56
Repository
GitHub Stars
660
First Seen
Feb 6, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex55
opencode54
gemini-cli54
github-copilot54
amp53
kimi-cli53
Phaser 3 游戏开发指南:场景架构、物理系统与最佳实践
692 周安装
| Tag | trait() | No data, just a flag | IsPlayer, IsEnemy, IsDead |
| Noun |
Position, Velocity, Health |