跳转到内容
flamo
开始使用

脚本

Flamo 使用 node-scoped script:普通节点、组件边界和实例都可以拥有行为,区别在于它们可访问的能力与是否形成公开契约,而不是维护三套互不兼容的脚本 API。

脚本在每个 FlamoInstance 内建立独立作用域。节点选择器和 authoring id 会映射到当前实例自己的 runtime identity,因此同一份脚本用于多个实例时,状态、事件和节点操作不会串到其他实例。

脚本常见职责包括:

  • 响应 pointer、keyboard、timer 或自定义事件;
  • 管理局部 state、computed 与 effect;
  • 调用节点动画、属性 patch 和组件 method;
  • 组合可复用 Behavior、Binding 或 motion preset;
  • 在 mount/update/unmount/dispose 中登记和释放资源。

普通 Component、Component Set 和普通图形都使用同一个入口:setup(props, ctx)。区别不在 API 形式,而在 props 表示什么:

挂载位置 props 节点自身的视觉属性
普通 Component 当前组件实例的公开参数与 overlay 通过 ctx.node() 或节点查询 API 访问
Component Set definition 当前 concrete INSTANCE 的有效参数、Variant selection、component properties 与 overlay;不同实例互相隔离 同样通过当前实例 scope 内的节点 API 访问
普通图形节点 所属 FlamoInstance 的输入与脚本 overlay;不使用时写成 _props 不会自动放进 props;通过 ctx.node() 读取和修改

组件或 Component Set 脚本可以直接使用公开参数:

type ButtonProps = {
disabled: 'False' | 'True';
};
export default defineNodeScript<ButtonProps>({
setup(props, ctx) {
ctx.on('click', event => {
if (props.disabled === 'True') return;
ctx.emit('onClick', { targetNodeId: event.target });
});
},
});

普通图形如果只需要操作自身,保留同样的两个参数,把未使用的第一个参数命名为 _props:

export default defineNodeScript({
setup(_props, ctx) {
const shape = ctx.node();
if (!shape) return;
const initialOpacity = shape.design().compositing.opacity;
ctx.on('pointerenter', () => shape.setOpacity(0.7));
ctx.on('pointerleave', () => shape.setOpacity(initialOpacity));
},
});

design() 返回稳定的设计态语义快照;需要确认队列写入后的运行态时使用 await shape.current()。子节点可通过 ctx.$()、ctx.$$() 或 ctx.scene.findByName() 查询稳定语义树。需要读取 Slot/Variant 当前实际绘制的 direct children 或 live probe 时,改用 ctx.mounted.resolve/children/probe();不要把物理 entity id 混入 Scene 查询或持久化配置。位置、尺寸、填充、透明度和文本等视觉字段不会自动铺进 props,写入使用 setPosition()、setSize()、setFillColor()、setOpacity()、setText() 或动画 API;连续位置变化用一次 ctx.setTransformsBatch() 合并同帧写入。

defineNodeScript<MyProps> 只提供 TypeScript 类型检查,不会创建 public surface。若一个值需要由组件外部配置、校验并持久化,应把节点提升为 component boundary 并在 Artifact surface 声明参数;纯运行时交互继续使用节点 API。

用户脚本可通过 ctx.createShaderLayer() 创建屏幕空间背景或前景。层覆盖当前 canvas,独立于相机平移、缩放和节点裁切;Runtime 管理管线、uniform 和资源,脚本退出时自动释放。

export default defineNodeScript({
setup(_props, ctx) {
const layer = ctx.createShaderLayer({
label: 'color-cloud',
phase: 'background',
wgsl: `fn flamoFragment(uv: vec2<f32>) -> vec4<f32> {
return vec4<f32>(uv.x, uv.y, .5 + .5*sin(flamo.time*.2), 1.);
}`,
glsl: `vec4 flamoFragment(vec2 uv) {
return vec4(uv.x, uv.y, .5 + .5*sin(u_time*.2), 1.);
}`,
});
void layer.ready.catch(error => ctx.trace('shader.error', String(error)));
},
});

两份函数分别用于 WebGPU/WGSL 与 WebGL2/GLSL。uv 左上角为 (0, 0),右下角为 (1, 1);返回直通 alpha 的 RGBA。源代码只提供函数,不声明 shader stage、资源绑定或 main 入口。

参数 WGSL GLSL
实际画布像素尺寸 flamo.resolution u_resolution
创建后的独立时间,秒 flamo.time u_time
最多 16 个自定义浮点参数 flamo.parameters[0..3],四个 vec4 u_parameters[0..3],四个 vec4

通过 uniforms 初始化参数,随后用 layer.setUniforms(values) 更新。setTime(seconds) 固定时间,setTime(null) 恢复独立时钟;setVisible() 控制显示,dispose() 提前释放。ready 在编译完成后兑现,失败时拒绝并提供 status: 'error' 和 error 诊断,原生场景继续运行。每幅 canvas 最多 8 层,每份源码不超过 64 KiB。

background 在原生节点前绘制;不透明的原生背景填充会遮住它,需要在用户脚本中调整相应节点的填充透明度。foreground 在原生节点后绘制,可用于透明覆盖效果。shader 层是运行时效果,其源码与其他用户脚本一样保存在工程内。

Artifact 保存脚本声明、编译产物引用、bindings、公开 surface 和 provenance;运行时脚本本身不能绕过 authoring API 永久改写 Artifact。set() 或场景 mutation 改变的是当前 Instance,要沉淀为设计事实必须产生 typed patch 与 durable receipt。

任何节点都能加行为,但只有显式 component boundary 才会把脚本能力投影为 props、events、methods 和 slots。内部节点默认保持私有;只有 publicChildRefs 或 surface 中明确声明的能力才允许宿主访问。

脚本创建的 listener、timer、RAF、motion、pointer capture、service subscription 和 DOM materialization 都必须登记到实例 owner ledger,并在 dispose/remount 时清理。Hot update 或 child swap 需要取消被取代的异步操作,迟到结果不得修改新一代实例。

语义树与当前物理挂载的分层见语义 Scene Tree 与 Mounted Entity Tree;事件目标、按节点名查询、动态代理与 Interact 开关见事件、节点查询与动态代理;简单交互可以从 Playground验证;组件级 TS/JS 作者方式见纯 TS/JS 开发。