跳转到内容

组件

组件是挂在实体上的一小块纯数据结构。用 defineComponent 声明一个组件,给它一个 名字和默认值:

import { defineComponent } from 'esengine';
const Speed = defineComponent('Speed', { value: 200 });
const Health = defineComponent('Health', { current: 100, max: 100 });

默认值同时定义了组件的形状(字段与类型)和新实例的初始值。组件是纯数据——没有方法、 没有行为。行为存在于系统里。

字段的类型由默认值推断。数字、布尔、字符串如你所想,结构化形状会得到对应的编辑器 控件:

const Missile = defineComponent('Missile', {
speed: 400, // number
homing: true, // boolean
targetTag: '', // string
velocity: { x: 0, y: 0 }, // 向量——以 X/Y 一对编辑
tint: { r: 1, g: 1, b: 1, a: 1 }, // {r,g,b,a} 字段渲染成拾色器
});

**标签(tag)**是零字段的标记组件,纯粹用于过滤——PlayerEnemyFrozen。 用 defineTag 声明,在查询里配合 .with() / .without() 使用:

import { defineTag, defineSystem, Query, Mut, Transform } from 'esengine';
const Player = defineTag('Player');
const Frozen = defineTag('Frozen');
defineSystem([Query(Mut(Transform)).with(Player).without(Frozen)], (q) => { /* … */ });

第三个参数控制字段在编辑器 Details 面板里的呈现——范围、滑杆、单位、枚举:

const Turret = defineComponent('Turret', {
range: 300,
mode: 0,
}, {
fields: {
range: { min: 0, max: 1000, slider: true, unit: 'px' },
mode: { enum: [{ label: 'Idle', value: 0 }, { label: 'Attack', value: 1 }] },
},
});

常用的 fields 选项:min / max / stepsliderunitenum(下拉, 存储选项的整数值)、flags(位掩码多选)、tooltip。其他组件级元数据: assetFields(把字段标记为资产引用,Details 渲染选择器、cook 会带上依赖)、 animatableFieldsSequencer 可打关键帧的字段 ——默认为全部数字字段)、transient(永不存进场景——用于逐帧运行时状态),以及 replicatedFields(由联网的状态复制同步)。

SDK 自带约 30 个引擎组件,与你自己的组件一起查询——TransformSpriteShapeRendererCameraLight2DParticleEmitterSpineAnimationTilemapLayer、UI 组件族、物理碰撞体等等。各自在对应的子系统指南里有完整文档: 精灵与渲染相机2D 光照物理瓦片地图UI

系统通过查询拿到组件数据。当系统要写入某个组件时,把它包进 Mut(...),这样变更检测 和渲染器就知道这块数据被改动过:

Query(Mut(Transform), Speed)
// Transform 会被写入;Speed 只读

因为写入被跟踪,系统也能响应生命周期:Added(Comp) / Changed(Comp) 把查询收窄到 组件刚出现 / 刚变化的实体——完整的查询词汇见 系统


在底层,一个组件是带 ES_COMPONENT / ES_PROPERTY 注解的 C++ 结构体;跨边界的内存 布局是生成的,因此 TypeScript 组件与 C++ 结构体始终保持一致。