Pure data-oriented Entity Component System (ECS) framework built with TypeScript, designed for high performance and simplicity. 纯数据导向的Entity Component System (ECS) 框架,使用TypeScript构建,专为高性能和简洁性设计。
npm install @esengine/nova-ecs
For complete API documentation, visit: https://esengine.github.io/NovaECS/ 完整的API文档请访问:https://esengine.github.io/NovaECS/
import { World } from '@esengine/nova-ecs';
import { system } from '@esengine/nova-ecs/core/System';
import { Scheduler } from '@esengine/nova-ecs/core/Scheduler';
// 组件定义 | Define components
class Position { x = 0; y = 0; }
class Velocity { x = 0; y = 0; }
class Disabled {}
// 系统定义 | Define systems
const MoveSystem = system('Move', (ctx) => {
ctx.world
.query(Position, Velocity)
.without(Disabled)
.forEach((e, p, v) => {
p.x += v.x * ctx.deltaTime;
p.y += v.y * ctx.deltaTime;
ctx.world.markChanged(e, Position);
});
})
.stage('update')
.inSet('Gameplay')
.build();
const SpawnSystem = system('Spawn', (ctx) => {
const cmd = ctx.commandBuffer;
const e = cmd.create(true);
cmd.add(e, Position, { x: 10, y: 10 });
cmd.add(e, Velocity, { x: 1, y: 0 });
})
.stage('preUpdate')
.before('set:Gameplay')
.flushPolicy('afterStage')
.build();
const KillSleepingSystem = system('KillSleeping', (ctx) => {
const cmd = ctx.commandBuffer;
ctx.world.query(Velocity).forEach((e, v) => {
if (v.x === 0 && v.y === 0) cmd.destroy(e);
});
})
.stage('postUpdate')
.after('Move')
.build();
// 调度器组装 | Scheduler setup
const scheduler = new Scheduler()
.add(SpawnSystem)
.add(MoveSystem)
.add(KillSleepingSystem);
// 创建世界 | Create world
const world = new World();
// 游戏循环 | Game loop
function mainLoop(deltaTime: number) {
// 自动执行:beginFrame() -> startup -> preUpdate -> update -> postUpdate
// startup阶段仅第一次tick时运行
scheduler.tick(world, deltaTime);
}
// 启动游戏循环 | Start game loop
setInterval(() => mainLoop(16), 16);
Entities are represented as numeric handles with generation-based safety: 实体表示为数值句柄,具有基于世代的安全性:
// Entity is just a number (28-bit index + 20-bit generation)
// Entity只是一个数字(28位索引 + 20位世代)
const entity: Entity = world.createEntity();
console.log(entity); // e.g., 268435457
// Check if entity is still alive
// 检查实体是否仍然存在
if (world.isAlive(entity)) {
// Entity is valid 实体有效
}
Components are stored using sparse-set data structure for O(1) operations: 组件使用稀疏集数据结构存储,实现O(1)操作:
// Add component to entity 向实体添加组件
world.addComponent(entity, Position, { x: 10, y: 20 });
// Get component from entity 从实体获取组件
const pos = world.getComponent(entity, Position);
if (pos) {
console.log(`Position: ${pos.x}, ${pos.y}`);
}
// Remove component from entity 从实体移除组件
world.removeComponent(entity, Position);
// Check if entity has component 检查实体是否有组件
if (world.hasComponent(entity, Position)) {
// Entity has Position component 实体有Position组件
}
Use command buffer for batched entity operations: 使用命令缓冲进行批量实体操作:
const cmd = new CommandBuffer(world);
// Create entity with components 创建带组件的实体
const entity = cmd.create(true);
cmd.add(entity, Position, { x: 0, y: 0 });
cmd.add(entity, Velocity, { x: 1, y: 1 });
// Modify existing entities 修改现有实体
cmd.remove(otherEntity, Health);
cmd.destroy(deadEntity);
// Apply all changes at once 一次性应用所有更改
cmd.flush();
Query entities with specific component combinations: 查询具有特定组件组合的实体:
// Basic query 基础查询
world.query(Position, Velocity).forEach((entity, pos, vel) => {
pos.x += vel.x * deltaTime;
pos.y += vel.y * deltaTime;
});
// Query with exclusions 带排除条件的查询
world.query(Position).without(Disabled).forEach((entity, pos) => {
// Process enabled entities only 只处理启用的实体
});
// Query with change detection 带变更检测的查询
world.query(Position).changed().forEach((entity, pos) => {
// Process only entities with changed Position components
// 只处理Position组件发生变更的实体
});
Define and schedule systems with dependencies: 定义和调度带依赖关系的系统:
// System with stage and dependencies 带阶段和依赖的系统
const PhysicsSystem = system('Physics', (ctx) => {
// Physics simulation logic 物理模拟逻辑
})
.stage('update')
.inSet('Core')
.before('Rendering')
.after('Input')
.runIf(world => world.hasComponent(world.getSingleton(), GameRunning))
.flushPolicy('afterStage')
.build();
// System execution stages 系统执行阶段
// startup: Run once on first tick 在第一次tick时运行一次
// preUpdate: Pre-processing 预处理
// update: Main game logic 主要游戏逻辑
// postUpdate: Post-processing 后处理
// cleanup: Resource cleanup 资源清理
Track component changes with frame-based timestamps: 使用基于帧的时间戳跟踪组件变更:
// Mark component as changed 标记组件为已变更
world.markChanged(entity, Position);
// Get current frame number 获取当前帧号
const currentFrame = world.frame;
// Check if component changed in specific frame 检查组件是否在特定帧变更
const changed = world.isChanged(entity, Position, currentFrame - 1);
For deterministic physics and gameplay, use FixedTimestepScheduler: 用于确定性物理和游戏玩法,使用FixedTimestepScheduler:
import { FixedTimestepScheduler } from '@esengine/nova-ecs/core/FixedTimestepScheduler';
const world = new World();
const scheduler = new Scheduler();
const fixedScheduler = new FixedTimestepScheduler(world, scheduler, {
fixedDt: 1/60, // 60 FPS固定时间步长
maxSubSteps: 6, // 最多6次子步数防止死亡螺旋
smoothFactor: 0.1, // 10%时间平滑
clampDt: 0.25, // 最大帧时间夹紧到0.25秒
});
// 游戏主循环(浏览器或原生环境) | Game main loop (browser or native)
let lastTime = performance.now();
function gameLoop(now: number) {
const frameDt = (now - lastTime) / 1000;
lastTime = now;
fixedScheduler.tick(frameDt, (alpha) => {
// 使用alpha进行渲染插值 | Use alpha for render interpolation
// 例如:渲染位置 = lerp(prevPos, currPos, alpha)
// e.g.: renderPos = lerp(prevPos, currPos, alpha)
renderWithInterpolation(alpha);
});
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
Complete system configuration with fluent API: 使用流式API进行完整的系统配置:
const MySystem = system('MySystem', (ctx) => {
// System logic here 系统逻辑
})
.stage('update') // Execution stage 执行阶段
.inSet('MyGroup') // System group 系统组
.before('OtherSystem') // Run before other systems 在其他系统前运行
.after('set:Prerequisites') // Run after system set 在系统集合后运行
.runIf(world => gameIsRunning) // Conditional execution 条件执行
.flushPolicy('afterEach') // Command flush policy 命令刷新策略
.build();
MIT License - See LICENSE file for details. MIT License - 详见 LICENSE 文件。
Issues and Pull Requests are welcome! 欢迎提交 Issue 和 Pull Request!
If you encounter problems during use, please: 如果您在使用过程中遇到问题,请: