我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

GDevelop 属性系统深度解析:PropertyDescriptor 与 PropertiesEditor 的声明、映射与渲染原理

GDevelop 属性系统深度解析:PropertyDescriptor 与 PropertiesEditor 的声明、映射与渲染原理 GDevelop 属性系统深度解析PropertyDescriptor 与 PropertiesEditor 的声明、映射与渲染原理【免费下载链接】GDevelop Open-source, cross-platform 2D/3D/multiplayer game engine designed for everyone.项目地址: https://gitcode.com/GitHub_Trending/gd/GDevelop导读GDevelop 场景中的对象Object、行为Behavior与实例Instance拥有大量可供用户编辑的字段例如文本对象的显示文字、粒子系统的粒子数量、场景实例的坐标与角度。与其为每一种对象/行为手写 React 编辑器GDevelop 采用了属性Property声明 → Schema 映射 → 自动生成属性编辑器的通用机制扩展作者只需声明一组带类型的属性描述IDE 便会自动为其生成对应的输入控件。本篇将以 newIDE/docs/Properties-schema-and-PropertiesEditor-explanations.md 为骨架结合 GDevelop 源码深入讲解属性声明方式、支持的类型、updateProperty回写机制以及PropertiesEditor的渲染链路帮助扩展开发者快速上手并理解其底层原理。为什么需要属性这一抽象概念GDevelop 中大量元素都带有可编辑字段而这些字段的形态各不相同行为Behavior的属性大多是字符串、数字和布尔值对象Object的属性在编辑器中展示常见为字符串如文本对象显示的文本、数字如粒子数量、布尔值如文本是否加粗的复选框有时也可以是资源引用如瓦片精灵对象使用的图片、文本对象使用的字体场景实例Instance同样拥有坐标 X/Y、角度等一批属性。虽然部分对象和行为拥有用 React 手写的专属编辑器对象编辑器位于 newIDE/app/src/ObjectEditor/Editors行为编辑器位于 newIDE/app/src/BehaviorsEditor/Editors但绝大多数对象/行为编辑器都可以通过读取对象的属性列表、为每个属性生成一个输入字段的方式自动生成。这正是引入属性概念的原因属性是对象/行为/实例暴露给 IDE 的统一元数据描述其定义落在 GDCore 中的PropertyDescriptor类上见 Core/GDCore/Project/PropertyDescriptor.h。PropertyDescriptor属性描述的核心数据结构类结构总览PropertyDescriptor位于gd命名空间是属性网格property grid中展示属性的标准描述结构。从 PropertyDescriptor.h 可以看到其完整的字段与链式方法所有 setter 均返回PropertyDescriptor支持链式调用字段说明对应 settercurrentValue属性的当前值统一以gd::String存储SetValuetype属性类型任意字符串由渲染方解释SetTypelabel在属性网格中显示的用户友好名称SetLabeldescription显示给用户的属性描述SetDescriptiongroup属性在编辑器中的分组名SetGroupchoices可选项列表PropertyDescriptorChoice含 value/labelAddChoice/ClearChoicesextraInformation附加信息例如资源类型、对象类型AddExtraInfo/SetExtraInfomeasurementUnit测量单位如像素、秒、角度SetMeasurementUnithidden是否在编辑器中隐藏SetHiddendeprecated是否标记为已弃用SetDeprecatedadvanced是否标记为高级默认折叠SetAdvancedhasImpactOnOtherProperties修改后是否需要重新渲染其他属性SetHasImpactOnOtherPropertiesquickCustomizationVisibility快速自定义面板中的可见性SetQuickCustomizationVisibility默认构造的PropertyDescriptor类型为string、标签为空、其余标志位均为 false详见构造函数PropertyDescriptor.h。此外该类还实现了SerializeTo/UnserializeFrom以及仅序列化值与附加信息的SerializeValuesTo/UnserializeValuesFrom保证属性描述可以随项目文件持久化。测量单位MeasurementUnitPropertyDescriptor通过SetMeasurementUnit关联gd::MeasurementUnit源码位于 Core/GDCore/Project/MeasurementUnit.h。内置单位包括GetUndefined()、GetDimensionless()、GetDegreeAngle()、GetSecond()、GetPixel()、GetPixelSpeed()、GetPixelAcceleration()、GetAngularSpeed()等MeasurementUnit.h。IDE 侧会在输入框的尾缀end adornment中显示单位缩写并提供带说明的 tooltip见 PropertiesEditor/MeasurementUnitDocumentation.js。如何声明属性JS 与 C 双路径在 JsExtension.js 中声明对象属性在 JavaScript 中声明对象或行为属性只需在ObjectJsImplementation或BehaviorJsImplementation上实现getProperties。以 Video 扩展的真实代码为例Extensions/Video/JsExtension.jsvideoObject.getProperties function () { var objectProperties new gd.MapStringPropertyDescriptor(); objectProperties .getOrCreate(Looped) .setValue(this.content.loop ? true : false) .setType(boolean) .setLabel(_(Loop the video)) .setGroup(_(Playback settings)); objectProperties .getOrCreate(Volume) .setValue(this.content.volume.toString()) .setType(number) .setLabel(_(Video volume (0-100))) .setGroup(_(Playback settings)); objectProperties .getOrCreate(videoResource) .setValue(this.content.videoResource) .setType(resource) .addExtraInfo(video) .setLabel(_(Video resource)); return objectProperties; };要点属性以gd.MapStringPropertyDescriptor键为属性名、值为PropertyDescriptor的容器返回布尔值以字符串true/false存储数字以toString()存储getOrCreate返回属性描述对象因此可以连续链式调用setValue、setType、setLabel、setGroup等原文中的Opacity属性在最新仓库代码中已由OpacityCapability::OpacityBehavior默认行为接管videoResource与Looped、Volume仍是手写声明属性的代表。在 C 中声明属性C 侧的写法与 JS 几乎一一对应对象需实现GetProperties()返回std::mapgd::String, gd::PropertyDescriptor。以 TopDownMovement 行为为例Extensions/TopDownMovementBehavior/TopDownMovementBehavior.cppstd::mapgd::String, gd::PropertyDescriptor TopDownMovementBehavior::GetProperties( const gd::SerializerElement behaviorContent) const { std::mapgd::String, gd::PropertyDescriptor properties; properties[Acceleration] .SetLabel(_(Acceleration)) .SetGroup(_(Movement)) .SetType(Number) .SetMeasurementUnit(gd::MeasurementUnit::GetPixelAcceleration()) .SetValue(gd::String::From( behaviorContent.GetDoubleAttribute(acceleration))); properties[UseLegacyTurnBack] .SetLabel(_(Only use acceleration to turn back (deprecated — best left unchecked))) .SetGroup(_(Deprecated options)) .SetDeprecated() .SetValue(...) .SetType(Boolean); return properties; }从中可以看出 C 属性的完整用法SetGroup做分组如Movement、Rotation、Deprecated options、SetMeasurementUnit指定单位、SetDeprecated()标记弃用项在编辑器中会单独归类或折叠显示。行为与实例的属性行为Behavior同样通过GetProperties/UpdatePropertyC或getProperties/updatePropertyJS声明与回写可参考各行为的JsExtension.js或 C 行为实现实例Instance对象实例也支持自定义属性JS 侧实现getInitialInstanceProperties与updateInitialInstancePropertyVideo 对象即声明了这两个空实现见 Extensions/Video/JsExtension.js。重要仅仅声明属性还不够。对于行为、对象和实例还必须实现updatePropertyJS或UpdatePropertyC方法编辑器修改属性时才会调用它。可以在该方法中加入必要的校验逻辑如数值范围、合法性判断返回false表示拒绝该次修改。updateProperty 回写示例Video 扩展的updateProperty展示了回写与校验的典型写法Extensions/Video/JsExtension.jsvideoObject.updateProperty function (propertyName, newValue) { if (propertyName Opacity) { this.content.opacity parseFloat(newValue); return true; } if (propertyName Looped) { this.content.loop newValue 1; return true; } if (propertyName videoResource) { this.content.videoResource newValue; return true; } return false; // 未识别的属性名拒绝修改 };注意Looped的回写将字符串1/0转换为布尔值——这与 PropertiesMapToSchema 中setBooleanValue写入1/0的约定相呼应见下文。支持哪些属性类型PropertyDescriptor::SetTypeC 为SetType接受以下取值类型编辑器控件说明string字符串输入框默认类型不设置类型即为字符串number数字输入框支持附带测量单位与可无限-1能力boolean复选框resource资源选择器值以字符串存储但编辑器展示资源选择器并可将资源加入项目必须通过addExtraInfo/AddExtraInfo指定资源类型image、audio、font或jsonVideo 扩展使用video资源类型除上述文档明确列出的 4 种基础类型外从 PropertiesMapToSchema.js 的映射逻辑可见当前 IDE 还支持以下扩展类型choice/numberwithchoices下拉选择框优先读取AddChoice声明的选项同时兼容历史版本通过addExtraInfo传入选项的用法behavior行为选择器extraInfo指定行为类型自动列出对象上已添加的该类型行为leaderboardid排行榜 ID 选择器color颜色选择器存储 RGB 字符串bitmask位掩码控件extraInfo支持bitCountN、firstBitN参数默认firstBit0、bitCount8测试用例见 PropertiesEditor/PropertiesMapToSchema.spec.jsmultilinestring多行文本输入objectanimationname动画名选择器自动枚举对象动画layer图层选择器keyboardkey按键选择器。若类型无法识别映射代码会输出错误日志并返回nullPropertiesMapToSchema.js。这些额外类型并非原文档要点但从源码结构看它们由同一套createField逻辑派生可作为扩展开发的参考。从 PropertyDescriptor 到 SchemaPropertiesMapToSchema原文档指出PropertyDescriptor会被映射为Schema再由PropertiesEditor渲染。这一转换发生在 newIDE/app/src/PropertiesEditor/PropertiesMapToSchema.js主函数propertiesMapToSchema在该文件第 642 行起Schema 的类型定义在 newIDE/app/src/PropertiesEditor/PropertiesEditorSchema.js。Schema 是什么Schema 是一棵字段树Schema ArrayField每个 Field 描述一个控件应如何渲染与读写值字段ValueFieldnumber、string、boolean、color、bitmask、multilinestring等原始类型字段以及resource、leaderboardId特殊字段均携带getValue/setValue回调、标签获取函数getLabel、描述getDescription、可见性visibility、默认值defaultValue等元数据PropertiesEditorSchema.js布局字段row同行排列与column分组列用于排版sectionTitle、button、toggleButtons等非字段元素用于特殊 UIPropertiesEditorSchema.js。映射过程中的关键逻辑可见性过滤isPropertyVisible依据hidden、deprecated、advanced标志及可见性模式All/Basic/Advanced/Deprecated/Basic-Quick决定属性是否进入 SchemaBasic-Quick模式还会参考属性级与容器级的 QuickCustomization 可见性设置PropertiesMapToSchema.js自动同行排列propertyKeywordCouples定义了一批属性名关键词配对如Top/Bottom、Width/Height、X/Y/Z、Min/Max、Color/Opacity等映射时会自动把成对的属性放进同一行type: row提升面板的紧凑性PropertiesMapToSchema.js分组按property.getGroup()聚合字段存在多个分组时生成带标题的column分组仅一个分组时直接平铺PropertiesMapToSchema.js默认值与高亮传入defaultValueProperties后非默认值会被高亮显示并支持一键恢复默认图标Restore多实例混合值当多个实例被同时选中且某属性值不一致时字段可被禁用disabled: onValuesDifferent并显示(Multiple values)。底层值类型转换映射时值读写做了统一抽象PropertiesMapToSchema.js数字缺失视为0避免传播 NaN布尔值读取时比较 true写入时转为1/0字符串数字写入时转为字符串。这也解释了为何声明属性时所有值都以字符串承载——PropertyDescriptor.currentValue本身就是gd::String。PropertiesEditorSchema 的渲染器PropertiesEditornewIDE/app/src/PropertiesEditor/index.js接收instances待编辑对象列表与schema字段树按字段类型分发到不同的 UI 组件布尔字段→InlineCheckboxindex.js数字字段→SemiControlledTextFieldtypenumber非数字输入如输入中的小数点不会立即写回并支持单位尾缀 adornmentindex.js颜色字段→ColorField值转为 RGB 字符串存储位掩码字段→CompactBitmaskField按firstBit/bitCount渲染为比特开关多行文本→multiline的SemiControlledTextField带选择项的字段→SelectField下拉或SemiControlledAutoComplete自动补全含非法值校验提示资源字段→ResourceSelectorWithThumbnail需要project、resourceManagementProps、projectScopedContainersAccessor等上下文缺省会报错并返回nullindex.js排行榜字段→LeaderboardIdPropertyFieldrow/column容器→ 递归渲染子字段分别采用横排/竖排布局index.js。所有字段修改最终都会调用_onInstancesModified触发未保存更改标记与重渲染index.js。PropertiesEditor的组件树是声明式递归的Schema 中每个 Field 都是一个独立可读的渲染指令这也是schema 与 PropertyDescriptor 概念相似、未来可合并原文档中的 TODO 展望的原因所在。从声明到渲染的完整链路综合以上源码一次属性编辑的完整调用链为扩展JS 或 C实现getProperties/GetProperties返回MapStringPropertyDescriptor/std::mapgd::String, gd::PropertyDescriptorIDE 调用propertiesMapToSchema或其适配变体effectPropertiesMapToSchema依据类型、可见性、分组、关键词配对生成 Schema 字段树PropertiesEditor以及PropertiesEditorByVisibility、CompactPropertiesEditor等变体接收 Schema 与实例列表递归渲染控件用户修改控件 → 调用字段的setValue→ 最终调用扩展的updateProperty/UpdatePropertyJS 侧布尔与数字会转换为1/0或字符串→ 扩展内部校验并更新自身内容触发onInstancesModified与未保存更改标记界面刷新。从哪里继续深入属性描述核心类Core/GDCore/Project/PropertyDescriptor.h、Core/GDCore/Project/MeasurementUnit.hJS 侧声明示例Extensions/Video/JsExtension.js、Extensions/ExampleJsExtension/JsExtension.jsC 侧声明示例Extensions/TopDownMovementBehavior/TopDownMovementBehavior.cpp、Extensions/ParticleSystem/ParticleEmitterObject.cppSchema 映射与类型定义newIDE/app/src/PropertiesEditor/PropertiesMapToSchema.js、newIDE/app/src/PropertiesEditor/PropertiesEditorSchema.jsSchema 映射的单元测试newIDE/app/src/PropertiesEditor/PropertiesMapToSchema.spec.js渲染器newIDE/app/src/PropertiesEditor/index.js、newIDE/app/src/PropertiesEditor/PropertiesEditorByVisibility.js手写专属编辑器的位置newIDE/app/src/ObjectEditor/Editors、newIDE/app/src/BehaviorsEditor/Editors【免费下载链接】GDevelop Open-source, cross-platform 2D/3D/multiplayer game engine designed for everyone.项目地址: https://gitcode.com/GitHub_Trending/gd/GDevelop创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表