1.编辑器:自定义属性检查器(自定义组件渲染)

编辑器是由一个个面板组成的,除了场景编辑器,其他的面板都可以选择关闭或显示,如果关闭了其他面板:
image
所有的面板包括插件面板都可以通过菜单面板来管理。

属性检查器 作为 Cocos Creator 里显示当前选中状态的模块,为开发者提供了一些基础的扩展能力。
以一个空节点为例:属性检查器面板可以显示节点的一系列属性,除了基础的Node属性和UITransform属性,也可以自定义来扩展节点的能力。
下面将一一介绍扩展属性检查器的方法:

修饰器扩展

ccclass

ccclass是封装在_decorator中的一个装饰器,_decorator是引擎cc模块中的装饰器集合,比较常用的如ccclass、property等。

ccclass用于装饰类,使用ccclass修饰的类又称为cc类,ccclass封装了额外的信息用于控制 Cocos Creator 对该类对象的序列化、编辑器对该类对象的展示等。所以未声明 ccclass 的组件类,是不能作为组件添加到节点上的。
ccclass 装饰器的参数 name 指定 cc 类的名称,需要注意该参数是唯一的,如果修饰了同类名会冲突并警告,即使不同目录下的同名类也不行。

ccclass的应用语法如下:

import { _decorator } from 'cc';//引用引擎的cc模块
const { ccclass } = _decorator;//引用_decorator中的cclass对象并赋值给同名变量
@ccclass('NewClass')//修饰/传入类名
export class NewClass {
    
}

装饰器都是通过@装饰器变量(参数)来声明的。

概念:CocosCreator中的序列化和反序列化
简单的理解,CocosCreator中的序列化就是把对象状态保存(比如存到JSON、二进制、ProtocolBuffer等),或者叫存档;反序列化就是读取数据恢复对象状态,或者叫加载。

在CocosCreator中,序列化和反序列化是处理数据存储、传输和资源管理的核心机制,其本质是对象状态与可存储格式之间的双向转换,贯穿了整了使用过程。

  1. 序列化
    将内存中的对象状态(如节点属性、组件数据)转换为可存储或传输的格式(如 JSON、二进制、Protocol Buffers 等),以便持久化到文件、数据库或网络传输。
    比如:
    游戏存档(角色位置、装备状态)
    预制体资源
    动画关键帧数据等等
  2. 反序列化
    将存储的格式(如 JSON 字符串、二进制数据)恢复为内存中的对象,重现其状态
    比如:
    加载存档还原游戏进度
    解析服务器传来的协议数据
    等等
    在CocosCreator的属性系统中,序列化通过装饰器来声明,这样在保存场景或预制时会自动序列化装饰器修饰的属性。
    如上述cc类会在序列化时记录该对象的 cc 类名,反序列化时根据此名称找到相应的 cc 类。
    而对于一些其他属性,如:
  • 值类型属性(@property(args)来声明):数字、字符串、Bool值、颜色等会直接序列化为文本或二进制
  • 引用类型属性(@property(args)来声明):节点、组件、纹理等会通过UUID(唯一标识符)关联引用,反序列化时通过该标识符确保资源正确加载
  • 场景(.fire)和预制体(.prefab):本质是 JSON 格式的序列化数据,描述节点层级结构和组件属性
  • 动画剪辑(.anim):序列化时间轴和关键帧数据
  • 网络传输:结合 Protocol Buffers(protobuf),通过 protobuf.js 将对象序列化为二进制,传输后反序列化还原
  • 用户数据存储:通过 cc.sys.localStorage 接口存储 JSON 序列化数据,反序列化通过JSON.parse来解析数据

组件类装饰器

CocosCreator工作流是以组件式开发为核心的架构,也称作 实体-组件架构。
在CocosCreator中,节点(Node) 是承载组件的实体,通过将具有各种功能的 组件(Component) 挂载到节点上,来让节点具有各式各样的表现和功能。组件是引擎创建的可以附加到节点的基类,继承自CCObject。所有继承自 Component 的类都称为组件类,其对象称为组件,也就是CocosCreator中的组件概念。
在 资源管理器 中创建的脚本,默认是一个 NewComponent 组件,也叫脚本组件。
上面有提到:未声明 ccclass 的组件类无法作为组件添加到节点上,反过来说,声明了ccclass的非组件类,也是无法附加到节点上的。

组件类装饰器,顾名思义,只能用来修饰组件类。

1. executeInEditMode

executeInEditMode修饰的组件类可以在编辑器环境下运行,默认参数是false。
在默认情况下,所有组件都只会在运行时执行,也就是说它们的生命周期回调在编辑器模式下并不会触发。
而如果通过@executeInEditMode(true)来修饰组件类,该组件就可以在编辑器模式下运行。

import { _decorator } from 'cc';//引用引擎的cc模块
const { ccclass, executeInEditMode } = _decorator;//引用_decorator中的cclass对象并赋值给同名变量
@ccclass('NewClass')//修饰/传入类名
@executeInEditMode(true)
export class NewClass extends Component {//组件类
    update (dt: number) {
        // 会在编辑器下每帧执行
    }
}

2. requireComponent

装饰器requireComponent 参数用来指定当前组件的依赖组件,默认值为 null。
举个例子,在场景中创建一个节点,然后创建一个默认的脚本组件附加到该节点上。
image
假如说想要该节点有Sprite 组件功能,并在默认的组件脚本中操作。有两种方式,一种是手动在该节点上添加Sprite组件。还有一种就是在默认脚本中声明依赖组件。
如:

import { _decorator, Component, Node, Sprite } from 'cc';
const { ccclass, property, requireComponent } = _decorator;

@ccclass('NewComponent')
@requireComponent(Sprite)
export class NewComponent extends Component {
    start() {

    }

    update(deltaTime: number) {
        
    }
}


保存脚本后首先要把原来的脚本组件移除掉然后重新添加
image
会发现在添加脚本组件时自动添加了一个Sprite组件。
在运行时同样有效,在移除该脚本组件之前是不允许移除依赖组件的,这在一定程度上可以防止出错。

3. executionOrder

一个节点上可以添加多个脚本组件,默认情况下是脚本的生命周期回调执行顺序是自上而下的。
executionOrder可以自定义声明脚本生命周期回调的执行优先级。
对于同一节点上的不同组件,数值小的先执行,数值相同的按组件添加先后顺序执行。
对于不同节点上的同一组件,按节点树排列决定执行的先后顺序。
需要注意的是,该优先级设定只对 onLoad、onEnable、start、update 和 lateUpdate 有效,对 onDisable 和 onDestroy 无效。

4. disallowMultiple

同一节点上只允许添加一个同类型(含子类)的组件,防止逻辑发生冲突,默认值为 false。

5. menu

在节点的属性检查器面板中有一个添加组件的按钮,点击该按钮可以通过一系列的菜单选项来选择添加组件。
image

@menu(path) 就可以用来将当前组件添加到这个组件菜单中,方便查找添加。
如下:

import { _decorator, Component, Node, Sprite } from 'cc';
const { ccclass, property, requireComponent, menu } = _decorator;

@ccclass('NewComponent')
@menu('1/specialSprite')
@requireComponent(Sprite)
export class NewComponent extends Component {
    start() {

    }

    update(deltaTime: number) {
        
    }
}

image

6. help

image
如图,在节点的基础属性和内置的组件右上角都有一个“帮助”图标,点击后会跳转到对应组件的CocosCreator文档。
@help可以指定当前组件的帮助文档的 URL。设置完成后,在 属性检查器 中就会出现一个帮助图标,点击即可打开指定的网页。
这里随便指定一个url比如百度一下。

import { _decorator, Component, Node, Sprite } from 'cc';
const { ccclass, property, requireComponent, menu, help } = _decorator;

@ccclass('NewComponent')
@menu('1/specialSprite')
@requireComponent(Sprite)
@help('https://www.baidu.com')
export class NewComponent extends Component {
    start() {

    }

    update(deltaTime: number) {
        
    }
}


image
点击NewComponent脚本组件右上角的问号图标,就可以跳转到百度的首页。
以上就是Creator中所有的组件类装饰器。

属性装饰器

属性装饰器 property 可以被应用在 cc 类的属性或访问器上。用于控制 Cocos Creator 编辑器中对该属性的序列化、属性检查器 中对该属性的展示等。
具体的功能特性可以通过 @property() 的参数来指定。
语法参考如下:

@property({type:Node})
    temp_node: Node = null;

1. type 参数

type 参数用于限定属性的数据类型

  • 基础cc类型:
    CCInteger(整数)、CCFloat(浮点数)、CCBoolean(布尔值)、CCString(字符串),基础类型一般仅用于数组属性的内部类型声明。非数组类型可以不用显式声明这些类型。
    @property()
    num:number = 0;
    @property({type:[CCInteger]})
    nums:number[] = [];
  • 其他 cc 类型
    其他的 cc 类型 都需要显式指定,否则编辑器无法正确识别类型,序列化也无法写入正确类型
    比如:
import { _decorator, CCInteger, Component, Node, Sprite } from 'cc';
const { ccclass, property, requireComponent, menu, help } = _decorator;

@ccclass('CustomClass')
export class CustomClass {
    @property()
    num:number = 0;
    @property({type:Node})
    node:Node = null;
}



@ccclass('NewComponent')
@menu('1/specialSprite')
@requireComponent(Sprite)
@help('https://www.baidu.com')
export class NewComponent extends Component {
    @property()
    num:number = 0;

    @property({type:CustomClass})
    customClass:CustomClass = null;

    start() {

    }

    update(deltaTime: number) {
        
    }
}
  • 数组类型
    当使用基础属性类型或者 cc 类作为数组元素时,可以被通过数组类型声明被编辑器所识别。例如 [CCInteger]、[Node] 将分别以整数数组和节点数组的形式在 属性检查器 中展示
    @property()
    num:number = 0;
    @property({type:[CCInteger]})
    nums:number[] = [];

    @property({type:[Node]})
    nodes:Node[] = [];

    @property({type:[CustomClass]})
    customClasses:CustomClass[] = [];

image
需要注意的是:
在编辑器 属性检查器 中展示的属性,属性名开头不应该带 _,否则会识别为 private 属性,private 属性不会在编辑器组件属性面板上显示。

    @property({type:[CustomClass]})
    _customClasses:CustomClass[] = [];

image

  • 一些常见的不同 cc 类型的属性声明
import { _decorator, CCInteger, Component, Enum, Node, Sprite } from 'cc';
const { ccclass, property, requireComponent, menu, help, integer,type,float } = _decorator;

enum A {
    c,
    d
}
Enum(A)
@ccclass('B')
export class B {
    @property // JavaScript 原始类型,根据默认值自动识别为 Creator 的浮点数类型。
    index = 0;
    @property(Node) // 声明属性 cc 类型为 Node。当属性参数只有 type 时可这么写,等价于 @property({type: Node}),后续还会介绍除type外的其他参数
    targetNode: Node | null = null; // 等价于 targetNode: Node = null!;
    // 声明属性 children 的 cc 类型为 Node 数组
    @property({
        type: [Node]
    })
    children: Node[] = [];
    @property({
        type: String,
    }) // 警告:不应该使用构造函数 String。等价于 CCString。也可以选择不声明类型
    text = '';
    @property
    children2 = []; // 未声明 cc 类型,从初始化式的求值结果推断元素为未定义的数组

    @property
    _valueB = 'abc'; // 此处 '_' 开头的属性,只序列化,不会在编辑器属性面板显示

    @property({ type: A })
    accx : A = A.c;

    //以下为之定义type参数的快捷声明cc类型的方式,等价于@property({type:xx})
    @integer // 声明属性的 cc 类型为整数
    index2 = 0;

    @float // 声明属性的 cc 类型为浮点数
    index3 = 0;

    @type(A)
    index4 = A.c;

    @type([Node])// 声明属性 children 的 cc 类型为 Node 数组
    childrens: Node[] = [];
}

@ccclass('NewComponent')
export class NewComponent extends Component {
    @property(B)
    b:B = null;

    start() {

    }

    update(deltaTime: number) {
        
    }
}


image

  • 内置类型
    引擎内部提供了一些常用的内置类型默认编辑界面
    比如:
  • Color
    @property(Color)
    self_color:Color;

image

  • 曲线编辑:用于保存曲线类型、样式以及采样数据
    @property(RealCurve)
    self_realcurve:RealCurve = new RealCurve();

image

  • 渐变色:记录渐变色的关键值和用于计算渐变色的结果
    @property(Gradient)
    gradient = new Gradient();

image

  • 其他如:曲线范围、渐变色范围等可以根据自身需要声明

2. visible

一般情况下,属性是否显示在 属性检查器 中取决于属性名是否以 _ 开头。如果是以 _ 开头,则不显示。
如果要强制显示在 属性检查器 中,可以设置 visible 参数为 true:

@property({ visible: true })
private _num = 0;

如果要强制隐藏,可以设置 visible 参数为 false:

@property({ visible: false })
num = 0;

也可以用于属性条件判断

import { _decorator, CCInteger, Component, Enum, Node, Sprite } from 'cc';
const { ccclass, property, requireComponent, menu, help, integer,type,float } = _decorator;

enum A {
    c,
    d
}
Enum(A)

@ccclass('NewComponent')
export class NewComponent extends Component {
    @property({type:A})
    self_type:A = A.c;
    @property({type:Sprite,visible(){
        return this.self_type === A.c
    }})
    temp_sprite:Sprite = null;
    
    start() {

    }

    update(deltaTime: number) {
        
    }
}


3. serializable

属性默认情况下都会被序列化,序列化后就会将编辑器中设置好的属性值保存到场景等资源文件中,之后在加载场景时就会自动还原成设置好的属性值。如果不想序列化,可以设置 serializable: false
除非一些特殊情况,大多数情况下都用不到

@property({ serializable: false })
num = 0;

4. override

override参数可以指定是否会被子类覆盖继承。
默认情况下所有属性都会被子类继承,如果子类要覆盖父类同名属性,需要显式设置 override 参数,否则会有重名警告:

typescript
@property({override: true })
id = "";

5. displayName、tooltip

displayName指定属性在 属性检查器 面板中显示为另一个名字
tooltip可以在 属性检查器 面板中添加属性的 Tooltip
例如:

import { _decorator, CCInteger, Component, Enum, Node, Sprite } from 'cc';
const { ccclass, property, requireComponent, menu, help, integer,type,float } = _decorator;

enum A {
    c,
    d
}
Enum(A)

@ccclass('NewComponent')
export class NewComponent extends Component {
    @property({type:A,displayName:'自定义枚举',tooltip:'c是大,d是小'})
    self_type:A = A.c;
    @property({type:Sprite,visible(){
        return this.self_type === A.c
    }})
    temp_sprite:Sprite = null;
    
    start() {

    }

    update(deltaTime: number) {
        
    }
}


image

6. readonly

指定在 属性检查器 面板中只读

@property({type:Sprite,readonly:true,visible(){
        return this.self_type === A.c
    }})
    temp_sprite:Sprite = null;

设置只读后无法在编辑器中添加引用
image

7. min、max、step

min限定数值在编辑器中输入的最小值
max限定数值在编辑器中输入的最大值
step指定数值在编辑器中调节的步长

    @property({min:-10,max:10,step:0.1})
    score:number = 0;

image

8. range

range参数可以一次性设置 min、max、step

    @property({min:-10,max:10,step:0.1})
    score:number = 0;
    @property({range:[-10,10,0.1]})
    score1:number = 0;

image

9. slide

slide是boolean类型,可以指定数值是否在编辑器中以滑动条的形式显示。

    @property({min:-10,max:10,step:0.1})
    score:number = 0;
    @property({range:[-10,10,0.1]})
    score1:number = 0;
    @property({slide:true,range:[-10,10,0.1]})
    score2:number = 0;

image

10. group

group可以指定属性在 属性检查器 面板中以分组的形式显示。
当脚本中定义的属性过多且杂时,可通过 group 对属性进行分组、排序,方便管理。同时还支持对组内属性进行分类。

group 写法包括以下两种:

  • @property({ group: { name } })
  • @property({ group: { id, name, displayOrder, style } })
    image
  • exp1:
    @property({group:"分数1",min:-10,max:10,step:0.1})
    score:number = 0;
    @property({group:"分数1",range:[-10,10,0.1]})
    score1:number = 0;
    @property({group:"分数2",slide:true,range:[-10,10,0.1]})
    score2:number = 0;

image
可点击切换查看:
image

  • exp2:
import { _decorator, CCInteger, Component, Enum, Node, Sprite } from 'cc';
const { ccclass, property, requireComponent, menu, help, integer,type,float } = _decorator;

enum A {
    c,
    d
}
Enum(A)

@ccclass('NewComponent')
export class NewComponent extends Component {
    @property({type:A,displayName:'自定义枚举',tooltip:'c是大,d是小'})
    self_type:A = A.c;
    @property({type:Sprite,readonly:true,visible(){
        return this.self_type === A.c
    }})
    temp_sprite:Sprite = null;

    @property({group:"分数1",min:-10,max:10,step:0.1})
    score:number = 0;
    @property({group:"分数1",range:[-10,10,0.1]})
    score1:number = 0;

    @property({group:"分数2",slide:true,range:[-10,10,0.1]})
    score2:number = 0;

    @property({group:{name:"分数3",id:'2',displayOrder:1},type:Sprite})
    score3:Sprite = null;
    @property({group:{name:"分数4",id:'2'},type:Node})
    score4:Node = null;
    start() {

    }

    update(deltaTime: number) {
        
    }
}


image
分数3指定了 displayOrder 为 1,且分数3和分数4一组,所以在属性编辑器中分数3和分数4的分组会排前面。

  • exp3
    displayOrder也可以对分组内的属性排序
    以分数1为例,有两个属性score和score1,上面score在score1的前面。
    @property({group:"分数1",min:-10,max:10,step:0.1})
    score:number = 0;
    @property({group:"分数1",range:[-10,10,0.1],displayOrder:1})
    score1:number = 0;

image
可以看到,score1排在了score的前面。

自定义渲染

如果以上方法不能满足需求,引擎还允许自定义渲染器。

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

  • 1.选中的物体主类型
  • 2.渲染主类型内容时,内容里包含的子数据类型
    当在 层级管理器/资源管理器 中选中一个 节点/资源 时,Cocos Creator 会将物体被选中的消息进行广播。当 属性检查器 接收到消息后,会检查被选中的物体的类型,例如选中的是节点,那么类型便是 node;如果选择的是图片,那么类型则是图片格式。

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

  • 主类型渲染器
  • 主类型渲染器接收数据开始渲染的时候,允许附带子类型渲染器
    比如选中的是 node,node 上携带了多个 component,所以主类型就是 node,而子类型则是 component。
    在 属性检查器 收到选中物体的广播消息后,首先确定类型,而后 属性检查器 会将收到的数据(物体的 uuid),传递给 node 渲染器,将渲染权限完全移交。
    在 node 渲染器里,会根据自身的实现,渲染到每个 组件 的时候,将该区域的渲染权限交给 子类型渲染器。

自定义 Component 渲染

如果内置的组件渲染器不能满足需求,就可以考虑自定义一个组件渲染器。
Cocos Creator 编辑器沿用了 Electron 的主进程和渲染进程的结构设计。进程间通信实际上就是在一个进程中发消息,然后在另外一个进程中监听消息的过程,在Creator编辑器内需要与其他功能进行交互的时候,需要通过 "消息机制" 进行交互。Creator封装了扩展进程间消息收发的方法,如果需要和其他功能交互,则需要对应功能开放对应的操作消息,在扩展内通过 消息系统 触发、查询和处理编辑器内的功能或者数据。
在自定义组件之前先明确一下需求,比如:在组件布局中显示一个按钮

  1. 首先创建一个自定义脚本组件
    创建一个脚本组件CustomComponent.ts
    然后创建一个空节点(用于测试),把脚本组件挂载到节点上。
    image

  2. 创建扩展,注册 contributions.inspector 信息
    我们需要通过扩展来通信。
    创建一个空白扩展,在contributions中注册inspector信息
    image

  3. 编写custom-component.ts脚本
    在扩展目录下source下创建一个custom-component.ts文件,然后拷贝以下内容:

'use strict';

type Selector<$> = { $: Record<keyof $, any | null> }

export const template = `
<ui-prop type="dump" class="test"></ui-prop>
`;

export const $ = {
    test: '.test',
};

export function update(this: Selector<typeof $>, dump: any) {
    // 使用 ui-porp 自动渲染,设置 prop 的 type 为 dump
    // render 传入一个 dump 数据,能够自动渲染出对应的界面
    // 自动渲染的界面修改后,能够自动提交数据
    this.$.test.render(dump.value.label);
}
export function ready(this: Selector<typeof $>) {}

其中,每一个 ui-prop 对应一条属性,若要显示多条属性需要定义多个 ui-prop
4. 编译、刷新扩展
上述创建扩展后忘记安装依赖了,需要打开扩展目录终端安装依赖:

npm install

然后编译

npm run build

刷新扩展后理论上CustomLabelComponent 组件的渲染被接管了。
以上自动渲染示例中使用了类型为 dump 的特殊的 ui-prop 进行渲染数据提交,它使我们可以快捷的接管组件的渲染,但若面临一些极端情况却很难处理一些细节问题,此时可以切换到手动渲染模式。
5. 尝试添加一个按钮,并添加点击事件
template 中的 html 布局是一个完全可以自由掌控的内容,理论上支持任何html样式,可根据需求制定出十分复杂的显示方式。
修改一下代码:

'use strict';

type Selector<$> = { $: Record<keyof $, any | null> }

export const template = `
<ui-button type="success" class="uiButton">success</ui-button>
`;

export const $ = {
    uiButton:".uiButton",
};
exports.style = /* css */`
.uiButton{
    width: 96%;
    margin-left: 2%;
    margin-top: 12px;
    height:25px;
}
`;

type PanelThis = Selector<typeof $> & { dump: any };

export function update(this: PanelThis, dump: any) {
    // 缓存 dump 数据,请挂在 this 上,否则多开的时候可能出现问题
    this.dump = dump;
    
}
export function ready(this: PanelThis) {
    // 监听点击事件并处理
    this.$.uiButton.addEventListener('confirm', () => {
        console.log(this)
        Editor.Message.send("scene", "execute-component-method", { uuid: this.dump.value.uuid.value, name: "_uiButtonEvent", args: [] });
    });
}

编译、刷新扩展
image

  1. 在自定义组件中注册按钮监听接口,注意事件名不要搞错了
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('CustomComponent')
export class CustomComponent extends Component {
    _uiButtonEvent():void{
        console.log("点击success!!!>>>");
    }
}


尝试点击自定义组件上的按钮
image
但是出现了一个问题,在自定义组件中声明其他的装饰器属性时不生效了。
image
image
test_node没有在属性检查器面板中显示。

  1. 装饰器兼容
    上述问题出现的原因是:组件内置的渲染器和注册的自定义渲染器无法并存。当注册了自定义渲染器后,内置的渲染器将被覆盖。
    参考:https://forum.cocos.org/t/topic/156927
    在engine/editor/inspector/components目录下找到引擎内置的组件渲染器,会发现一个base.js的基础组件渲染器
// This is the basic template for component editing
const { updatePropByDump, disconnectGroup } = require('../utils/prop');

exports.template = `
<div class="component-container">
</div>
`;

exports.$ = {
    componentContainer: '.component-container',
};

exports.update = function(dump) {
    updatePropByDump(this, dump);
};

exports.close = function() {
    disconnectGroup(this);
};

基础的组件渲染器有一个容器,并且会在update中去更新属性的显示
参考内置的基础组件渲染器来优化自定义的渲染器

  • 将engine/editor/inspector/utils下的prop.js脚本拷贝到自己插件的dist目录下
  • 修改自定义组件渲染器
'use strict';

const {updatePropByDump, disconnectGroup } = require('./prop');
type Selector<$> = { $: Record<keyof $, any | null> }

export const template = `
<div class="component-container">
<ui-button type="success" class="uiButton">success</ui-button>
`;

export const $ = {
    componentContainer:".component-container",
    uiButton:".uiButton",
};
exports.style = /* css */`
.uiButton{
    width: 96%;
    margin-left: 2%;
    margin-top: 12px;
    height:25px;
}
`;

type PanelThis = Selector<typeof $> & { dump: any };

export function update(this: PanelThis, dump: any) {
    updatePropByDump(this, dump);
    // 缓存 dump 数据,请挂在 this 上,否则多开的时候可能出现问题
    this.dump = dump;
    
}
export function ready(this: PanelThis) {
    disconnectGroup(this);
    // 监听点击事件并处理
    this.$.uiButton.addEventListener('confirm', () => {
        //console.log(this)
        Editor.Message.send("scene", "execute-component-method", { uuid: this.dump.value.uuid.value, name: "_uiButtonEvent", args: [] });
    });
}

编译、刷新
image

自定义 Asset 渲染

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

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

{
    "contributions": {
        "inspector": {
            "section": {
                "asset": {
                    "effect": "./dist/contributions/inspector/asset-effect.js"
                }
            }
        }
    }
}

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

scene - 场景文件
typescript - TypeScript 脚本文件
prefab - 预制体文件
fbx - FBX 文件
material - 材质文件
directory - 文件夹
image - 图片文件
可通过查看文件对应的 *.meta 中的 importer 字段获取该文件的类型定义。
接下来,在扩展目录下新建一个 src/contributions/inspector/asset-effect.ts 脚本文件,并编写如下代码:

'use strict';

interface Asset {
    displayName: string;
    file: string;
    imported: boolean;
    importer: string;
    invalid: boolean;
    isDirectory: boolean;
    library: {
        [extname: string]: string;
    };
    name: string;
    url: string;
    uuid: string;
    visible: boolean;
    subAssets: {
        [id: string]: Asset;
    };
}

interface Meta {
    files: string[];
    imported: boolean;
    importer: string;
    subMetas: {
        [id: string]: Meta;
    };
    userData: {
        [key: string]: any;
    };
    uuid: string;
    ver: string;
}

type Selector<$> = { $: Record<keyof $, any | null> } & { dispatch(str: string): void, assetList: Asset[], metaList: Meta[] };

export const $ = {
    'test': '.test',
};

export const template = `
<ui-prop>
    <ui-label slot="label">Test</ui-label>
    <ui-checkbox slot="content" class="test"></ui-checkbox>
</ui-prop>
`;

type PanelThis = Selector<typeof $>;

export function update(this: PanelThis, assetList: Asset[], metaList: Meta[]) {
    this.assetList = assetList;
    this.metaList = metaList;
    this.$.test.value = metaList[0].userData.test || false;
};

export function ready(this: PanelThis) {
    this.$.test.addEventListener('confirm', () => {
        this.metaList.forEach((meta: any) => {
            // 修改对应的 meta 里的数据
            meta.userData.test = !!this.$.test.value;
        });
        // 修改后手动发送事件通知,资源面板是修改资源的 meta 文件,不是修改 dump 数据,所以发送的事件和组件属性修改不一样
        this.dispatch('change');
    });
};

export function close(his: PanelThis, ) {
    // TODO something
};

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

注意:

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

posted @ 2025-07-17 19:47  EricShx  阅读(171)  评论(0)    收藏  举报