mcpField
K
koota

koota

github.com/pmndrs开发763149 次安装9 周前更新
代码仓库

关于 koota

Koota 是一种面向 TypeScript 开发的 ECS 状态管理工具,以实体和可组合特征组织数据,并通过查询和系统更新状态。它强调逻辑与视图分离,适合构建可组合的运行时逻辑。

主要功能

  1. 01实体与特征组合
  2. 02关系图建模
  3. 03按原型查询实体
  4. 04动作执行状态变更
  5. 05系统响应状态变化
  6. 06React 集成

适用场景

  1. 01组织游戏或应用的实体状态
  2. 02批量查询并更新实体数据
  3. 03将核心逻辑与视图层分离
  4. 04在 React 中订阅状态变化

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pmndrs/koota --skill koota

安装后在 Claude Code 中输入/koota确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
architecture.md
queries.md
react-hooks.md
react-patterns.md
relations.md
runtime.md

详细介绍

Koota ECS

Koota 使用具有可组合特征的实体来管理状态。

术语表

  • Entity - 指向由特征定义的数据的唯一标识符。从世界中生成。
  • Trait - 可复用的数据定义。可以基于架构(SoA)、基于回调(AoS)或作为标签。
  • Relation - 实体之间的有向连接,用于构建图。
  • World - 所有实体及其数据(特征)的上下文。
  • Archetype - 实体共享的特征唯一组合。
  • Query - 获取符合某个原型的实体。批量更新状态的主要方式。
  • Action - 离散、同步的数据变更(创建、更新、销毁)。可在任何调用位置复用。
  • System - 响应式协调器,用于观察状态变化并协调工作,包括异步工作流。在帧循环或事件回调中运行。

设计原则

面向数据

行为与数据分离。数据定义为特征,实体由特征组合而成,系统通过查询来变更特征上的数据。完整示例请参阅 基本用法。

可组合的系统

将系统设计为小型、单一用途的单元,而不是按顺序完成所有工作的单体函数。每个系统都应处理一个方面,以便能够独立启用或停用行为。

// 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
}

这样可以支持功能标志、调试(停用某个系统以隔离问题)以及灵活的运行时配置。

将视图与逻辑解耦

将核心状态和逻辑(“core”)与视图(“app”)分离:

  • 独立于渲染运行逻辑
  • 在保留状态的同时切换视图(2D ↔ 3D)
  • 在工作线程或服务端运行逻辑
优先使用特征和动作,而非类

尽量不要使用类来封装数据和行为。使用特征表示数据,使用动作表示行为。只有在外部库要求时(例如 THREE.js 对象),或用户更偏好使用类时,才使用类。

目录结构

如果用户有偏好的结构,请遵循该结构。否则,请参考以下建议:目录结构应与应用的数据模型组织方式相对应。将核心状态/逻辑与视图层分开:

  • 核心 - 纯 TypeScript。包括特征、系统、动作和世界。不导入视图。
  • 视图 - 从世界读取数据,通过动作进行变更。按领域/功能组织。
src/
├── core/              # Pure TypeScript, no view imports
│   ├── traits/
│   ├── systems/
│   ├── actions/
│   └── world.ts
└── features/          # View layer, organized by domain

文件按角色而非功能切片组织。特征和系统具有可组合性,无法清晰地映射到各个功能。

有关详细模式和 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, trait } 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))
const gold = world.spawn()
const silver = world.spawn()
const inventory = world.spawn(Contains(gold), Contains(silver))

// Query children of parent
const children = world.query(ChildOf(parent))

// Query all entities with any ChildOf relation
const allChildren = world.query(ChildOf('*'))

// Query relation targets
const targets = inventory.targetsFor(Contains)

// Filter by traits on the target entity
const IsRare = trait()
silver.add(IsRare)
// Target filters can use any legal query, not just a single trait
const rareInventories = world.query(Contains(IsRare))

// Get targets from entity
const items = inventory.targetsFor(Contains) // Entity[]
const target = child.targetFor(ChildOf) // Entity | undefined

有关详细模式、遍历、有序关系和反模式,请参阅 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
})

实体

实体是由特征组成的唯一标识符。它们从世界中生成。

// 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()

实体 ID

实体在内部是一个数字,其中打包了实体 ID、代际 ID(用于回收)和世界 ID。可以安全地直接存储,以用于持久化或网络传输。

entity.id() // Just the entity ID (reused after destroy)
entity // Full packed number (unique forever)

类型标注

使用 TraitRecord 获取 entity.get() 返回值的类型

type PositionRecord = TraitRecord<typeof Position>

查询

查询会获取符合某种原型的实体,是批量更新状态的主要方式。

// 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

对于包含数据的查询,优先使用 updateEach/readEach,而不是 for...of + entity.get()。readEach 仍会将实体作为第二个参数提供。

注意: updateEach/readEach 仅返回包含数据的特征(SoA/AoS)。标签、Not() 和关系筛选条件不包含在内:

world.query(IsPlayer, Position, Velocity).updateEach(([pos, vel]) => {
  // Array has 2 elements - IsPlayer (tag) excluded
})

有关变更跟踪、查询缓存和高级模式,请参阅 references/queries.md。

React 集成

导入: 从 'koota' 导入核心类型(World、Entity)。从 'koota/react' 导入 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。

评论

支持 Markdown