自定义属性检查器面板

有自定义属性检查器需求的开发者建议先参考文档 通过修饰器定义属性,若该文档满足需求,建议优先使用该文档中的方法。

属性检查器 作为 Cocos Creator 里显示当前选中状态的模块,为大家提供了一些基础的扩展能力。

属性检查器 里,定义了两个层级的数据:

  1. 选中的物体主类型
  2. 渲染主类型内容时,内容里包含的子数据类型

当在 层级管理器/资源管理器 中选中一个 节点/资源 时,Cocos Creator 会将物体被选中的消息进行广播。当 属性检查器 接收到消息后,会检查被选中的物体的类型,例如选中的是节点,那么类型便是 node

针对两种类型,允许注册两种渲染器:

  1. 主类型渲染器
  2. 主类型渲染器接收数据开始渲染的时候,允许附带子类型渲染器

示例里选中的是 nodenode 上携带了多个 component,所以主类型就是 node,而子类型则是 component

属性检查器 收到选中物体的广播消息后,首先确定类型,而后 属性检查器 会将收到的数据(物体的 uuid),传递给 node 渲染器,将渲染权限完全移交。

而在 node 渲染器里,会根据自身的实现,渲染到每个 组件 的时候,将该区域的渲染权限交给 子类型渲染器。大部分情况下,我们不需要关注这些。我们先来看看几种常用的的自定义方式吧。

自定义 Component 渲染

默认提供的组件渲染器有时候并不能满足我们的需求,这时候我们就需要自定义一个组件的渲染方式。

首先在项目内新建一个脚本组件 CustomLabelComponent.ts,并添加一个字符串属性,内容如下:

  1. import { _decorator, Component, Node } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. @ccclass('CustomLabelComponent')
  4. export class CustomLabelComponent extends Component {
  5. @property
  6. label = '';
  7. start () {
  8. }
  9. }

将组件拖到节点上,这时候能看到组件上有一个输入框。

新建一个扩展,在扩展的 package.json 里注册如下的 contributions.inspector 信息:

  1. {
  2. "contributions": {
  3. "inspector": {
  4. "section": {
  5. "node": {
  6. "CustomLabelComponent": "./dist/contributions/inspector/comp-label.js"
  7. }
  8. }
  9. }
  10. }
  11. }

自动渲染

编写一个 src/contributions/inspector/comp-label.ts 文件,内容如下:

  1. 'use strict';
  2. type Selector<$> = { $: Record<keyof $, any | null> }
  3. export const template = `
  4. <ui-prop type="dump" class="test"></ui-prop>
  5. `;
  6. export const $ = {
  7. test: '.test',
  8. };
  9. export function update(this: Selector<typeof $> & typeof methods, dump: any) {
  10. // 使用 ui-porp 自动渲染,设置 prop 的 type 为 dump
  11. // render 传入一个 dump 数据,能够自动渲染出对应的界面
  12. // 自动渲染的界面修改后,能够自动提交数据
  13. this.$.test.render(dump.label.value);
  14. }
  15. export function ready(this: Selector<typeof $> & typeof methods) {}

编译并刷新插件后,我们可以发现 CustomLabelComponent 组件的渲染被接管了。

注意:每一个 ui-prop 对应一条属性,若要显示多条属性需要定义多个 ui-prop

手动渲染

上面的自动渲染示例中,我们使用了类型为 dump 的特殊的 ui-prop 进行渲染数据提交,它使我们可以快捷的接管组件的渲染,但若面临一些极端情况却很难处理一些细节问题,此时可以切换到手动渲染模式,代码如下所示:

  1. 'use strict';
  2. type Selector<$> = { $: Record<keyof $, any | null> }
  3. export const template = `
  4. <!-- 帮忙提交数据的元素 -->
  5. <ui-prop type="dump" class="test"></ui-prop>
  6. <!-- 实际渲染的元素 -->
  7. <ui-label class="label"></ui-label>
  8. <ui-input class="test-input"></ui-input>
  9. `;
  10. export const $ = {
  11. label:'.label',
  12. test: '.test',
  13. testInput: '.test-input',
  14. };
  15. type PanelThis = Selector<typeof $> & { dump: any };
  16. export function update(this: PanelThis, dump: any) {
  17. // 缓存 dump 数据,请挂在 this 上,否则多开的时候可能出现问题
  18. this.dump = dump;
  19. // 将 dump 数据传递给帮忙提交数据的 prop 元素
  20. this.$.test.dump = dump.value.label;
  21. // 更新负责输入和显示的 input 元素上的数据
  22. this.$.testInput.value = dump.value.label.value;
  23. this.$.label.value = dump.value.label.name;
  24. }
  25. export function ready(this: PanelThis) {
  26. // 监听 input 上的提交事件,当 input 提交数据的时候,更新 dump 数据,并使用 prop 发送 change-dump 事件
  27. this.$.testInput.addEventListener('confirm', () => {
  28. this.dump.value.label.value = this.$.testInput.value;
  29. this.$.test.dispatch('change-dump');
  30. });
  31. }

手动渲染模式下依然要通过一个类型为 dumpui-prop 元素进行数据提交。但 template 中的 html 布局就是一个完全可以自由掌控的内容,可根据需求制定出十分复杂的显示方式。

自定义 Asset 渲染

当在资源管理窗口(Assets)中选中文件时,属性检查器窗口会显示当前选中文件的重要属性,如果默认显示的信息不满足要求,我们也可以自定义渲染内容。

package.json 中添加 contributions.section.asset 字段,并定义对应资源类型的自定义渲染脚本,如下所示:

  1. {
  2. "contributions": {
  3. "inspector": {
  4. "section": {
  5. "asset": {
  6. "effect": "./dist/contributions/inspector/asset-effect.js"
  7. }
  8. }
  9. }
  10. }
  11. }

effect 表示我们要对 Cocos Effect(*.effect) 文件类型的资源属性面板自定义渲染。常见的资源文件类型如下:

  • scene - 场景文件
  • typescript - TypeScript 脚本文件
  • prefab - 预制体文件
  • fbx - FBX 文件
  • material - 材质文件
  • directory - 文件夹
  • image - 图片文件

可通过查看文件对应的 *.meta 中的 importer 字段获取该文件的类型定义。

接下来,在插件目录下新建一个 src/contributions/inspector/asset-effect.ts 脚本文件,并编写如下代码:

  1. 'use strict';
  2. interface Asset {
  3. displayName: string;
  4. file: string;
  5. imported: boolean;
  6. importer: string;
  7. invalid: boolean;
  8. isDirectory: boolean;
  9. library: {
  10. [extname: string]: string;
  11. };
  12. name: string;
  13. url: string;
  14. uuid: string;
  15. visible: boolean;
  16. subAssets: {
  17. [id: string]: Asset;
  18. };
  19. }
  20. interface Meta {
  21. files: string[];
  22. imported: boolean;
  23. importer: string;
  24. subMetas: {
  25. [id: string]: Meta;
  26. };
  27. userData: {
  28. [key: string]: any;
  29. };
  30. uuid: string;
  31. ver: string;
  32. }
  33. type Selector<$> = { $: Record<keyof $, any | null> } & { dispatch(str: string): void, assetList: Asset[], metaList: Meta[] };
  34. export const $ = {
  35. 'test': '.test',
  36. };
  37. export const template = `
  38. <ui-prop>
  39. <ui-label slot="label">Test</ui-label>
  40. <ui-checkbox slot="content" class="test"></ui-checkbox>
  41. </ui-prop>
  42. `;
  43. type PanelThis = Selector<typeof $>;
  44. export function update(this: PanelThis, assetList: Asset[], metaList: Meta[]) {
  45. this.assetList = assetList;
  46. this.metaList = metaList;
  47. this.$.test.value = metaList[0].userData.test || false;
  48. };
  49. export function ready(this: PanelThis) {
  50. this.$.test.addEventListener('confirm', () => {
  51. this.metaList.forEach((meta: any) => {
  52. // 修改对应的 meta 里的数据
  53. meta.userData.test = !!this.$.test.value;
  54. });
  55. // 修改后手动发送事件通知,资源面板是修改资源的 meta 文件,不是修改 dump 数据,所以发送的事件和组件属性修改不一样
  56. this.dispatch('change');
  57. });
  58. };
  59. export function close(his: PanelThis, ) {
  60. // TODO something
  61. };

编译、刷新扩展后,回到 Cocos Creator 编辑器界面,选中某个 Cocos Effect 文件,可以在属性检查器底部发现,多了一个 Test 复选框

注意:多个扩展注册的数据是并存的。如果一个 组件/资源 已经有了自定义渲染器,那么再次注册的自定义渲染器都会附加在后面。如果一个 组件/资源 没有内置自定义渲染器,使用的是默认的渲染器,那么当扩展注册自定义渲染器的时候,会完全接管渲染内容。