
游戏开发【免费下载链接】flecsA fast entity component system (ECS) for C C项目地址https://gitcode.com/gh_mirrors/fl/flecs点击查看免费下载Flecs Script 是 Flecs一个面向 C 与 C 的实体组件系统内置的运行时解释型 DSL专门用于以声明方式创建实体、层级与组件适合搭建场景、制作资产与编写配置文件。本教程将带你从 Explorer 的空白画布开始逐步用纯脚本构建一堵可复用的参数化围栏资产先掌握实体、组件、变量与with作用域等基础语法再依次封装为 Prefab、模板Template并用Grid组件与嵌套模板将资产组合成可调尺寸的封闭围场。读完本文你将能独立用 Flecs Script 编写、运行、参数化并复用你自己的 ECS 场景资产并能在 C/C 代码中加载与实例化它们。说明本教程的教学环境是 Flecs Explorer 与 Playground渲染画布 预置模块与运行时使用同一套 Flecs Script 语法。关于脚本语言在当前版本中的完整手册可参考 docs/FlecsScript.md。一、Flecs Script 是什么Flecs Script 是一种声明式语言让你无需编写并编译 C/C 代码即可创建实体。它可以用于不同目的例如构建场景scenes、资产assets或作为配置文件configuration files的语言。在 Flecs 生态中脚本对 ECS 的意义类似于 HTML/JSX 对浏览器它以文本形式描述实体、层级、组件与关系并在运行时被解释执行原生集成了 Flecs 的反射meta、预制体prefab、层级hierarchy与关系relationships等特性。从 docs/FlecsScript.md 可以看到它支持的能力清单命名实体、层级与继承的原生支持为组件赋值表达式与变量如var 10条件与循环如if var 10、for i in [0..10]与模板procedural assets的原生集成。使用前提应用需要使用FLECS_SCRIPTaddon 构建才能使用 Flecs Script。脚本在源码中的入口 API 定义在 include/flecs/addons/script.h包括ecs_script_run()、ecs_script_run_file()、ecs_script_parse()/ecs_script_eval()以及托管脚本ecs_script_init()等。二、环境准备在 Explorer 与 Playground 中运行脚本Flecs Explorer 是一个基于 Web 的应用让我们编写脚本并直接看到结果。教程将 Explorer 与 Flecs playground 组合使用——playground 自带渲染画布并预加载了多个模块与资产。打开 Explorer 与 playgroundhttps://www.flecs.dev/explorer/?wasmhttps://www.flecs.dev/explorer/playground.js页面打开后类似这样页面布局由三部分组成左侧实体树视图entity treeview显示场景中所有实体中间画布canvas显示场景中可渲染的实体右侧编辑器editor用于编写 Flecs 脚本。任何时候都可以点击面板右上角的x关闭面板被关闭的面板可以通过左侧菜单栏中的按钮重新打开。另一个重要的控件是屏幕右上角的链接按钮link button用它可以把当前内容生成一个链接是保存进度或与他人分享成果的好方式。三、基础语法实体、组件与层级3.1 创建实体与标签当前 Explorer 显示的是默认场景先把编辑器中的代码全部删掉你会看到一个空画布空画布截图。接着在编辑器中输入实体名my_entity输入时实体就会出现在左侧树视图中。Flecs 脚本的规则是实体如果不存在会自动创建。试试输入两次同名实体my_entity my_entity树视图中只会出现一个实体——第二次解析my_entity时它已经存在因此无需任何操作。这与 examples/script/hello_world.flecs 中的行为一致脚本声明实体的语法本身就是创建或引用。3.2 添加标签与作用域为实体添加一个名为SpaceShip的标签my_entity :- SpaceShip注意我们不必预先显式声明SpaceShip它会自动作为实体出现在树视图中。可以这样给实体添加多个标签my_entity :- SpaceShip my_entity :- FasterThanLight为了避免反复写实体名可以用{}为实体开启一个作用域。在作用域内用-前缀列出组件的写法my_entity { - SpaceShip - FasterThanLight }点击树视图中的实体可以打开实体检查器entity inspector查看其内容。可以看到SpaceShip和FasterThanLight标签都显示出来此外还有一个Script: main标签——这是 Flecs 用来跟踪哪些实体由我们的脚本创建的标记。3.3 添加组件带值的标签添加组件与添加标签类似只是多了一个值。playground 预加载了flecs.components.transform模块下面添加其中的Position3组件my_entity { - SpaceShip - FasterThanLight - flecs.components.transform.Position3{1, 2, 3} }每次都要输入完整模块名很麻烦可以在脚本顶部添加using flecs.components.*之后就可以直接写Position3{1, 2, 3}。添加Position3后检查器中还会出现Transform和WorldCell组件——这是因为 playground 导入的模块实现了世界分区与变换使用Position3组件会连带获得这些能力。组件也可以是标量直接赋值详见 docs/FlecsScript.md例如Mass: 100、Mass: 50 50、Mass: $weight。3.4 关系对与子实体层级除了组件与标签还可以给实体添加关系对- (OwnedBy, Player)实体还可以创建为层级。子实体在父实体作用域内创建写法与组件/标签的区别是不带-前缀cockpit { pilot :- (Faction, Earth) }展开树视图中的my_entity即可看到这个层级。至此你已经掌握了创建实体、层级、添加组件与标签的全部基础接下来让实体在画布上可见。四、绘制形状渲染所需的组件组合渲染器使用常规的 ECS 查询query来寻找要渲染的实体。要让实体进入这些查询它至少需要以下三个组件Position3Rgb颜色Box或Rectangle先从画地面开始。删掉编辑器里除using flecs.components.*之外的所有代码然后添加plane { - Position3{} Rectangle: {100, 100} - Rgb{0.9, 0.9, 0.9} }画面上出现了东西但看起来不太对错误朝向的地面——矩形旋转方向不对。修复方式是把它在 x 轴上旋转 90 度即π/2弧度。先在脚本中把π定义为常量const PI 3.1415926再在plane作用域中添加- Rotation3{$PI / 2}现在地面方向正确了正确的地面。把平面边长加大到10000雾效会让它和背景融合形成地平线的错觉plane { - Position3{} - Rotation3{$PI / 2} - Rectangle{10000, 10000} - Rgb{0.9, 0.9, 0.9} }注意PI变量不会出现在树视图中——变量不会创建实体只存在于脚本上下文中。这与 docs/FlecsScript.md 中变量用const关键字创建可用于重复出现的值的描述一致。接着添加一个立方体box { - Position3{} Box: {10, 10, 10} Rgb: {1, 0, 0} }盒子与地面相交了与地面相交的立方体。通过设置Position3的y成员为它高度的一半来抬升它box { - Position3{y: 5} Box: {10, 10, 10} Rgb: {1, 0, 0} }整个立方体现在完全可见。相机操作提示先点击画布使其获得焦点即可用 WASD 键移动相机再次点击画布释放焦点绿色边框消失焦点交还给 Explorer。五、变量与with语句消除魔法数字绘制围栏只需要把不同尺寸的盒子组合起来。先删掉立方体的代码。5.1 用with统一添加组件如果每个实体都添加相同值的颜色组件改颜色时会非常繁琐。使用with语句可以避免重复with Rgb{0.15, 0.1, 0.05} { // Boxes go here }在with作用域内创建围栏左右两根柱子with Rgb{0.15, 0.1, 0.05} { left_pillar { - Position3{x: -10, y: 5} Box: {2, 10, 2} } right_pillar { - Position3{x: 10, y: 5} Box: {2, 10, 2} } }效果见两根柱子。with语句是 docs/FlecsScript.md 中正式支持的特性它可以把相同组件/标签应用到其作用域内的所有实体等价于为每个实体单独添加。with还支持多个标签with SpaceShip, HasWeapons、带括号的组件值with Color(38, 25, 13)以及带$前缀的变量with $color。5.2 用变量重构上面的代码有大量魔法数字难以维护。先定义颜色与柱子形状两个变量——复合值需要显式声明类型const color: Rgb {0.15, 0.1, 0.05} const pillar_box: Box {2, 10, 2}进一步让柱子形状也参数化const height 10 const color: Rgb {0.15, 0.1, 0.05} const pillar_width 2 const pillar_box: Box { $pillar_width, $height, $pillar_width }再加一个表示围栏宽度两根柱子间的距离的width变量const width 20现在柱子的代码可以这样写with $color, $pillar_box { left_pillar { - Position3{x: -$width/2, y: $height/2} } right_pillar { - Position3{x: $width/2, y: $height/2} } }这会重建出完全相同的场景但现在可以通过调整变量来改变围栏形状试试不同取值的效果见修改变量后的柱子。在 Flecs Script 中$前缀用于引用变量避免变量名与实体名冲突详见 docs/FlecsScript.md 的 Variables 一节。5.3 添加横杆只有柱子还不够。先加一根横杆看看效果bar { - Position3{y: $height / 2} Box: {$width, 2, 1} - $color }效果见两根柱子和一根横杆。再加第二根横杆并注意两个实体名字要不同否则会覆盖之前的实体值top_bar { - Position3{y: $height/2 2} Box: {$width, 2, 1} - $color } bottom_bar { - Position3{y: $height/2 - 2} Box: {$width, 2, 1} - $color }现在更像围栏了简易围栏。继续消除冗余定义横杆间距、横杆高度与深度让横杆厚度随柱子宽度缩放const bar_sep 4 const bar_height 2 const bar_depth $pillar_width/2 const bar_box: Box {$width, $bar_height, $bar_depth}清理横杆实体代码with $color, $bar_box { top_bar { - Position3{y: $height/2 $bar_sep/2} } bottom_bar { - Position3{y: $height/2 - $bar_sep/2} } }围栏基本成型且完全由变量驱动。代码变长后可以用注释保持条理// Create two bar entities with $color, $bar_box { top_bar { - Position3{y: $height/2 $bar_sep/2} } bottom_bar { - Position3{y: $height/2 - $bar_sep/2} } }当前代码仍有不足代码没有打包成易于复用的形式围栏拉宽或拉高后会显得不自然理想情况下柱子与横杆数量应随尺寸增加。六、Prefab把资产打包复用想要两堵围栏复制全部代码不现实。解决方案是把现有代码打包进一个prefab预制体。只需在围栏代码外围加上Prefab Fence { // fence code goes here }这会创建一个带Prefab标签的实体Fence并把此前创建的所有实体存为Fence的子实体。它等价于下面这段代码只是更便捷Fence { - Prefab // fence code goes here }把围栏实体放进 prefab 作用域后你会注意到围栏从画布上消失了围栏实体在树视图中出现在Fence之下见围栏 prefab 层级。围栏消失的原因是Prefab是一个内建标签默认会被查询忽略渲染器因此也不渲染它prefab 的子实体也会成为 prefab同样被忽略。6.1 继承并实例化 Prefab现在可以创建一个继承自Fenceprefab 的实体my_fence : Fence围栏回来了而且可以实例化两次只需给两个实例不同的位置fence_a : Fence { - Position3{-10} } fence_b : Fence { - Position3{10} }这里的:记法本质是添加IsA关系my_spaceship : SpaceShip等价于my_spaceship { (IsA, SpaceShip) }见 docs/FlecsScript.md 的 Inheritance 一节。examples/script/prefabs.flecs 也展示了同族写法prefab SpaceShip {...}、prefab Freighter : SpaceShip {...}、my_spaceship : Freighter {...}。6.2 在 C/C 中加载脚本并实例化可以把脚本保存为fence.flecs加载进游戏后从常规 C/C 代码实例化 prefab// In C ecs_script_run_file(world, fence.flecs); ecs_entity_t fence ecs_lookup(world, Fence); ecs_entity_t fence_a ecs_new_w_pair(world, EcsIsA, fence); ecs_entity_t fence_b ecs_new_w_pair(world, EcsIsA, fence); ecs_set(world, fence_a, EcsPosition3, {-10}); ecs_set(world, fence_b, EcsPosition3, {10});// In C using namespace flecs::components::transform; ecs_script_run_file(world, fence.flecs); auto fence world.lookup(Fence); auto fence_a world.entity().is_a(fence); auto fence_b world.entity().is_a(fence); fence_a.setPosition3({-10}); fence_b.setPosition3({10});ecs_script_run_file()等脚本运行 API 的完整签名见 include/flecs/addons/script.h除了从文件运行还可以用ecs_script_run()直接运行代码字符串或用ecs_script_parse()ecs_script_eval()多次运行同一脚本以及用ecs_script_init()创建可追踪实体归属的托管脚本。不过这里仍有遗留问题将在下一节解决prefab 是静态的无法改变围栏参数围栏尺寸增大时柱子和横杆数量不会增加。七、模板可参数化的资产Prefab 是可以多次实例化的静态实体与组件集合模板template则是可以参数化的 prefab 组合。换句话说模板让我们在创建围栏的同时指定宽度和高度。把 prefab 改成模板只需一行template Fence { // fence code }编辑器会报错template Fence has no properties含义是与 prefab 不同模板需要 properties属性它们是模板的输入。对我们而言输入可以是width和height值。把部分const变量改为prop即可对外暴露为属性并且属性必须显式声明类型。习惯上把 props 放在模板代码顶部便于一眼看出它的输入template Fence { prop width: f32 20 prop height: f32 10 prop color: Rgb {0.15, 0.1, 0.05} // fence code }改为模板后围栏又从画布消失了——因为对 prefab 是继承而对模板是赋值。让围栏重新可见把实例化代码改成fence_a { - Fence{} - Position3{-10} } fence_b { - Fence{} - Position3{10} }围栏以 props 提供的默认值回来了。现在有了新能力——为两个实例传不同的参数fence_a { Fence: {width: 10, height: 20} - Position3{-10} } fence_b { Fence: {width: 25, height: 10} - Position3{10} }注意给模板赋值与给组件赋值看起来多么相似——这不是巧合模板会被翻译成一个组件每个属性成为该组件的一个成员。这一点从源码级测试可以得到印证test/script/src/Template.c 中的Template_template_prop测试用prop height: flecs.meta.f32 0定义一个模板后断言Tree实体上出现EcsStruct组件且其成员数量为 1、成员名为height、类型为f32——模板即组件、属性即成员的机制被测试直接验证。7.1 在 C/C 中像赋值组件一样使用模板利用模板即组件的特性可以在 C/C 中让模板的赋值像普通组件赋值一样简单// In C typedef struct { float width; float height; EcsRgb color; } Fence; // Register a regular component ECS_COMPONENT(world, Fence); // Because the Fence template has the same name as the // component it will bind to it. ecs_script_run_file(world, fence.flecs); // Set the component as usual ecs_entity_t fence_a ecs_insert(world, ecs_value(Fence, {10, 20})); ecs_entity_t fence_b ecs_insert(world, ecs_value(Fence, {25, 10})); ecs_set(world, fence_a, EcsPosition3, {-10}); ecs_set(world, fence_b, EcsPosition3, {10});// In C using namespace flecs::components::transform; struct Fence { float width; float height; flecs::components::graphics::Rgb color; } // Because the Fence template has the same name as the // component it will bind to it. ecs_script_run_file(world, fence.flecs); auto fence_a world.entity().setFence({10, 20}); auto fence_b world.entity().setFence({25, 10}); fence_a.setPosition3({-10}); fence_b.setPosition3({10});要使同名绑定生效必须保证脚本中属性的类型与顺序和组件类型中成员的类型与顺序完全一致。关于模板的更多细节prop默认值推导、mut可变属性、嵌套模板组合等参见 docs/FlecsScript.md 的 Templates 一节。八、Grid让柱子与横杆数量随尺寸变化现在解决数量随尺寸增长的问题。乍看这需要循环而 Flecs Script 不支持循环但有一个绕行方案使用Grid组件替我们完成实例化。Grid组件由flecs.game模块提供使用前先在脚本顶部添加using flecs.gameGrid 组件可以按 1、2 或 3 维网格创建实体的多个实例。把它应用到柱子数量上让柱子数随围栏宽度缩放。我们需要两样东西围栏宽度已有和柱子最小间距const pillar_spacing 10据此计算柱子数量const pillar_count $width / $pillar_spacingGrid组件不能直接消费柱子代码它接受一个 prefab。把现有柱子代码改成 prefabPrefab Pillar { - $color - $pillar_box }注意这里去掉了Position3因为位置将由Grid组件设置。然后创建一排柱子pillars { - Position3{y: $height/2} - Grid{ x.count: $pillar_count x.spacing: $pillar_spacing prefab: Pillar } }这段代码的含义创建一个pillars实体把它放在y: $height/2位置柱子才不会陷进地面x 轴上排布$pillar_count根柱子间距为$pillar_spacing用Pillarprefab 实例化实体。把实例化代码改成一个围栏并把width默认值加大到60fence :- Fence{}prop width: f32 60现在柱子数量与围栏长度匹配了多柱围栏。但还有一点不对柱子没有与围栏两端精确对齐。用一个简单计算修正根据柱子数量反推网格间距。const grid_spacing $width / ($pillar_count - 1)这个公式考虑了柱子数量比柱子间空隙数量多一的事实。把网格中的pillar_spacing换成grid_spacing后柱子对齐正确对齐后的围栏。横杆可以用完全相同的方法处理只是方向换成 y 轴这里直接给出结果多柱多杆围栏// Bar parameters const bar_spacing 3 const bar_height 2 const bar_depth $pillar_width / 2 const bar_count $height / $bar_spacing const b_grid_spacing $height / $bar_count // Bars bars { - Position3{y: $height/2} - Grid{ y.count: $bar_count, y.spacing: $b_grid_spacing prefab: Bar } }其中Bar同样用with Prefab, $color { Bar :- Box {...} }的形式定义。为了在放大时更美观各参数取值被微调过你可以自由调整变量得到自己喜欢的效果。参考 docs/FlecsScript.md 的 Component values 一节脚本还能直接读取其他实体上的组件值如Grid: { Game[Level].width, Game[Level].depth }进一步把布局数据外置到配置实体上。九、嵌套模板组合出封闭围场围栏模板已经就绪接下来构建一个更高层级的模板来复用Fence——一个典型的做法是实例化 4 次Fence围出一块封闭空间。这部分不涉及任何新语法。在脚本底部新建模板提供width和depth属性定义矩形区域并加一个color和height属性透传给Fence模板template Enclosing { prop width: f32 40 prop height: f32 10 prop depth: f32 40 prop color: Rgb {0.15, 0.1, 0.05} // enclosing code goes here }实例化 4 次Fence分别对应矩形的四个边。加几个便捷变量避免重复写同样的除法const width_half $width / 2 const depth_half $depth / 2 const PI 3.1415926 left { - Position3{x: -$width_half} - Rotation3{y: $PI/2} Fence: {width: $depth, height:$, color:$} } right { - Position3{x: $width_half} - Rotation3{y: $PI/2} Fence: {width: $depth, height:$, color:$} } back { - Position3{z: -$depth_half} Fence: {width: $width, height:$, color:$} } front { - Position3{z: $depth_half} Fence: {width: $width, height:$, color:$} }注意透传参数写成了height:$——这是height: $height的简写记法在处理嵌套模板时可以省去大量输入。这种简写同样适用于一般复合初始化的成员赋值Tree: {color: $, height: $}在 docs/FlecsScript.md 的 Initializers 一节有正式说明。把实例化代码从fence :- Fence{}改成enclosing :- Enclosing{}效果如下。可能需要用相机拉远一点才能看到完整的围栏点画布获得焦点后用 WASD 移动相机再点一次画布把焦点还给 Explorer现在可以非常方便地通过参数修改围场enclosing { Enclosing: {width: 100, height: 30} }十、总结与后续探索至此你已经掌握了用 Flecs Script 创建资产的完整路径从基础实体与组件语法到变量与with作用域再到 Prefab 打包、模板参数化、Grid程序化排布以及嵌套模板组合。围栏最终可以被width、height、depth、color等参数完整驱动这正是声明式 参数化资产管线的核心工作方式。想继续深入可以从以下方向入手查阅当前仓库中的脚本语言手册 docs/FlecsScript.md掌握表达式运算符、if/for控制流、字符串插值、match表达式、函数与方法、struct/enum/bitmask类型定义、module/include/using语句等完整语法阅读脚本 API 头文件 include/flecs/addons/script.h了解ecs_script_run、ecs_script_run_file、ecs_script_parse/eval、托管脚本ecs_script_init、外部函数ecs_function与向量函数注册等编程接口运行仓库自带的脚本示例如 examples/script/hello_world.flecs实体/层级/关系、examples/script/prefabs.flecsprefab 继承链、examples/script/expressions.flecs变量与表达式以及 examples/script/with.flecswith语句阅读脚本模板的测试用例 test/script/src/Template.c通过断言理解模板到组件的转换细节属性即成员、同名绑定等。在 Explorer 中还可以尝试实例化 playground 预载的资产之一把编辑器内容替换为下面的脚本生成一座小镇using flecs.components.* using templates const PI 3.1415926 plane { - Position3{} - Rotation3{$PI / 2} Rectangle: {10000, 10000} - Rgb{0.9, 0.9, 0.9} } town :- Town{}现在去创建属于你自己的可参数化资产吧。赞分享游戏开发【免费下载链接】flecsA fast entity component system (ECS) for C C项目地址https://gitcode.com/gh_mirrors/fl/flecs点击查看免费下载相关推荐Raycast Script Commands实战从零构建你的第一个脚本命令Raycast Script Commands实战从零构建你的第一个脚本命令 本文详细介绍了Raycast Script Commands的开发实践从基础模开发工具桌面应用Flecs 开源项目教程Flecs 开源项目教程 项目介绍 Flecs 是一个快速、灵活的实体组件系统ECS专为 C 和 C 设计。ECS 是一种设计模式广泛应用于游戏开发游戏开发终极Script Kit教程从零开始构建你的第一个自动化脚本终极Script Kit教程从零开始构建你的第一个自动化脚本 Script Kit是一款强大的自动化工具能够帮助你轻松实现各种任务的自动化处理。无论你是编程上一篇LiteGraph.js与TypeDocAPI文档生成与优化下一篇portracker完全指南如何一站式监控多服务器端口与服务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考