• CCClass 进阶参考
    • 术语
    • 原型对象参数说明
      • 类名
      • 构造函数
        • 通过 ctor 定义
        • 通过 __ctor__ 定义" level="4">通过 __ctor__ 定义
    • 判断类型
      • 判断实例
      • 判断类
    • 成员
      • 实例变量
      • 实例方法
      • 静态变量和静态方法
    • 继承
      • 父构造函数
      • 重写
    • 属性
      • 属性和构造函数
      • 属性参数
        • default 参数" level="4">default 参数
        • visible 参数" level="4">visible 参数
        • serializable 参数" level="4">serializable 参数
        • type 参数" level="4">type 参数
        • url 参数" level="4">url 参数
        • override 参数" level="4">override 参数
      • 属性延迟定义" level="3"> 属性延迟定义
    • GetSet 方法
      • get
      • set
    • editor 参数

    CCClass 进阶参考

    相比其它 JavaScript 的类型系统,CCClass 的特别之处在于功能强大,能够灵活的定义丰富的元数据。CCClass 的技术细节比较丰富,你可以在开发过程中慢慢熟悉。本文档将列举它的详细用法,阅读前需要先掌握 使用 cc.Class 声明类型。

    术语

    • CCClass:使用 cc.Class 声明的类。
    • 原型对象:调用 cc.Class 时传入的字面量参数。
    • 实例成员:包含“成员变量”和“成员方法”。
    • 静态成员:包含“静态变量”和“类方法”。
    • 运行时:项目脱离编辑器独立运行时,或者在模拟器和浏览器里预览的时候。
    • 序列化:解析内存中的对象,将它的信息编码为一个特殊的字符串,以便保存到硬盘上或传输到其它地方。

    原型对象参数说明

    所有原型对象的参数都可以省略,用户只需要声明用得到的部分即可。

    1. cc.Class({
    2. // 类名,用于序列化
    3. // 值类型:String
    4. name: "Character",
    5. // 基类,可以是任意创建好的 cc.Class
    6. // 值类型:Function
    7. extends: cc.Component,
    8. // 构造函数
    9. // 值类型:Function
    10. ctor: function () {},
    11. // 属性定义(方式一,直接定义)
    12. properties: {
    13. text: ""
    14. },
    15. // 属性定义(方式二,使用 ES6 的箭头函数,详见下文)
    16. properties: () => ({
    17. text: ""
    18. }),
    19. // 实例方法
    20. print: function () {
    21. cc.log(this.text);
    22. },
    23. // 静态成员定义
    24. // 值类型:Object
    25. statics: {
    26. _count: 0,
    27. getCount: function () {}
    28. },
    29. // 提供给 Component 的子类专用的参数字段
    30. // 值类型:Object
    31. editor: {
    32. disallowMultiple: true
    33. }
    34. });

    类名

    类名可以是任意字符串,但不允许重复。可以使用 cc.js.getClassName 来获得类名,使用 cc.js.getClassByName 来查找对应的类。对在项目脚本里定义的组件来说,序列化其实并不使用类名,因此那些组件不需要指定类名。对其他类来说,类名用于序列化,如果不需要序列化,类名可以省略。

    构造函数

    通过 ctor 定义

    CCClass 的构造函数使用 ctor 定义,为了保证反序列化能始终正确运行,ctor 不允许定义构造参数

    开发者如果确实需要使用构造参数,可以通过 arguments 获取,但要记得如果这个类会被序列化,必须保证构造参数都缺省的情况下仍然能 new 出对象。

    通过 __ctor__ 定义" class="reference-link">通过 __ctor__ 定义

    __ctor__ctor 一样,但是允许定义构造参数,并且不会自动调用父构造函数,因此用户可以自行调用父构造函数。__ctor__ 不是标准的构造函数定义方式,如果没有特殊需要请一律使用 ctor 定义。

    判断类型

    判断实例

    需要做类型判断时,可以用 JavaScript 原生的 instanceof

    1. var Sub = cc.Class({
    2. extends: Base
    3. });
    4. var sub = new Sub();
    5. cc.log(sub instanceof Sub); // true
    6. cc.log(sub instanceof Base); // true
    7. var base = new Base();
    8. cc.log(base instanceof Sub); // false

    判断类

    使用 cc.isChildClassOf 来判断两个类的继承关系:

    1. var Texture = cc.Class();
    2. var Texture2D = cc.Class({
    3. extends: Texture
    4. });
    5. cc.log(cc.isChildClassOf(Texture2D, Texture)); // true

    两个传入参数都必须是类的构造函数,而不是类的对象实例。如果传入的两个类相等,isChildClassOf 同样会返回 true。

    成员

    实例变量

    在构造函数中定义的实例变量不能被序列化,也不能在 属性检查器 中查看。

    1. var Sprite = cc.Class({
    2. ctor: function () {
    3. // 声明实例变量并赋默认值
    4. this.url = "";
    5. this.id = 0;
    6. }
    7. });

    如果是私有的变量,建议在变量名前面添加下划线 _ 以示区分。

    实例方法

    实例方法请在原型对象中声明:

    1. var Sprite = cc.Class({
    2. ctor: function () {
    3. this.text = "this is sprite";
    4. },
    5. // 声明一个名叫 "print" 的实例方法
    6. print: function () {
    7. cc.log(this.text);
    8. }
    9. });
    10. var obj = new Sprite();
    11. // 调用实例方法
    12. obj.print();

    静态变量和静态方法

    静态变量或静态方法可以在原型对象的 statics 中声明:

    1. var Sprite = cc.Class({
    2. statics: {
    3. // 声明静态变量
    4. count: 0,
    5. // 声明静态方法
    6. getBounds: function (spriteList) {
    7. // ...
    8. }
    9. }
    10. });

    上面的代码等价于:

    1. var Sprite = cc.Class({ ... });
    2. // 声明静态变量
    3. Sprite.count = 0;
    4. // 声明静态方法
    5. Sprite.getBounds = function (spriteList) {
    6. // ...
    7. };

    静态成员会被子类继承,继承时会将父类的静态变量浅拷贝给子类,因此:

    1. var Object = cc.Class({
    2. statics: {
    3. count: 11,
    4. range: { w: 100, h: 100 }
    5. }
    6. });
    7. var Sprite = cc.Class({
    8. extends: Object
    9. });
    10. cc.log(Sprite.count); // 结果是 11,因为 count 继承自 Object 类
    11. Sprite.range.w = 200;
    12. cc.log(Object.range.w); // 结果是 200,因为 Sprite.range 和 Object.range 指向同一个对象

    如果你不需要考虑继承,私有的静态成员也可以直接定义在类的外面:

    1. // 局部方法
    2. function doLoad (sprite) {
    3. // ...
    4. };
    5. // 局部变量
    6. var url = "foo.png";
    7. var Sprite = cc.Class({
    8. load: function () {
    9. this.url = url;
    10. doLoad(this);
    11. };
    12. });

    继承

    父构造函数

    请注意,不论子类是否有定义构造函数,子类实例化前父类的构造函数都会被自动调用。

    1. var Node = cc.Class({
    2. ctor: function () {
    3. this.name = "node";
    4. }
    5. });
    6. var Sprite = cc.Class({
    7. extends: Node,
    8. ctor: function () {
    9. // 子构造函数被调用前,父构造函数已经被调用过,所以 this.name 已经被初始化过了
    10. cc.log(this.name); // "node"
    11. // 重新设置 this.name
    12. this.name = "sprite";
    13. }
    14. });
    15. var obj = new Sprite();
    16. cc.log(obj.name); // "sprite"

    因此你不需要尝试调用父类的构造函数,否则父构造函数就会重复调用。

    1. var Node = cc.Class({
    2. ctor: function () {
    3. this.name = "node";
    4. }
    5. });
    6. var Sprite = cc.Class({
    7. extends: Node,
    8. ctor: function () {
    9. Node.call(this); // 别这么干!
    10. this._super(); // 也别这么干!
    11. this.name = "sprite";
    12. }
    13. });

    在一些很特殊的情况下,父构造函数接受的参数可能和子构造函数无法兼容。这时开发者就只能自己手动调用父构造函数并且传入需要的参数,这时应该将构造函数定义在 __ctor__ 中。

    重写

    所有成员方法都是虚方法,子类方法可以直接重写父类方法:

    1. var Shape = cc.Class({
    2. getName: function () {
    3. return "shape";
    4. }
    5. });
    6. var Rect = cc.Class({
    7. extends: Shape,
    8. getName: function () {
    9. return "rect";
    10. }
    11. });
    12. var obj = new Rect();
    13. cc.log(obj.getName()); // "rect"

    和构造函数不同的是,父类被重写的方法并不会被 CCClass 自动调用,如果你要调用的话:

    方法一:使用 CCClass 封装的 this._super

    1. var Shape = cc.Class({
    2. getName: function () {
    3. return "shape";
    4. }
    5. });
    6. var Rect = cc.Class({
    7. extends: Shape,
    8. getName: function () {
    9. var baseName = this._super();
    10. return baseName + " (rect)";
    11. }
    12. });
    13. var obj = new Rect();
    14. cc.log(obj.getName()); // "shape (rect)"

    方法二:使用 JavaScript 原生写法:

    1. var Shape = cc.Class({
    2. getName: function () {
    3. return "shape";
    4. }
    5. });
    6. var Rect = cc.Class({
    7. extends: Shape,
    8. getName: function () {
    9. var baseName = Shape.prototype.getName.call(this);
    10. return baseName + " (rect)";
    11. }
    12. });
    13. var obj = new Rect();
    14. cc.log(obj.getName()); // "shape (rect)"

    如果你想实现继承的父类和子类都不是 CCClass,只是原生的 JavaScript 构造函数,你可以用更底层的 API cc.js.extend 来实现继承。

    属性

    属性是特殊的实例变量,能够显示在 属性检查器 中,也能被序列化。

    属性和构造函数

    属性不用在构造函数里定义,在构造函数被调用前,属性已经被赋为默认值了,可以在构造函数内访问到。如果属性的默认值无法在定义 CCClass 时提供,需要在运行时才能获得,你也可以在构造函数中重新给属性赋默认值。

    1. var Sprite = cc.Class({
    2. ctor: function () {
    3. this.img = LoadImage();
    4. },
    5. properties: {
    6. img: {
    7. default: null,
    8. type: Image
    9. }
    10. }
    11. });

    不过要注意的是,属性被反序列化的过程紧接着发生在构造函数执行之后,因此构造函数中只能获得和修改属性的默认值,还无法获得和修改之前保存(序列化)的值。

    属性参数

    所有属性参数都是可选的,但至少必须声明 default, get, set 参数中的其中一个。

    default 参数" class="reference-link">default 参数

    default 用于声明属性的默认值,声明了默认值的属性会被 CCClass 实现为成员变量。默认值只有在第一次创建对象的时候才会用到,也就是说修改默认值时,并不会改变已添加到场景里的组件的当前值。

    当你在编辑器中添加了一个组件以后,再回到脚本中修改一个默认值的话,属性检查器 里面是看不到变化的。因为属性的当前值已经序列化到了场景中,不再是第一次创建时用到的默认值了。如果要强制把所有属性设回默认值,可以在 属性检查器 的组件菜单中选择 Reset。

    default 允许设置为以下几种值类型:

    1. 任意 number, string 或 boolean 类型的值
    2. nullundefined
    3. 继承自 cc.ValueType 的子类,如 cc.Vec2, cc.Colorcc.Rect 的实例化对象:
      1. properties: {
      2. pos: {
      3. default: new cc.Vec2(),
      4. }
      5. }
    4. 空数组 [] 或空对象 {}
    5. 一个允许返回任意类型值的 function,这个 function 会在每次实例化该类时重新调用,并且以返回值作为新的默认值:
      1. properties: {
      2. pos: {
      3. default: function () {
      4. return [1, 2, 3];
      5. },
      6. }
      7. }

    visible 参数" class="reference-link">visible 参数

    默认情况下,是否显示在 属性检查器 取决于属性名是否以下划线 _ 开头。如果以下划线开头,则默认不显示在 属性检查器,否则默认显示。

    如果要强制显示在 属性检查器,可以设置 visible 参数为 true:

    1. properties: {
    2. _id: { // 下划线开头原本会隐藏
    3. default: 0,
    4. visible: true
    5. }
    6. }

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

    1. properties: {
    2. id: { // 非下划线开头原本会显示
    3. default: 0,
    4. visible: false
    5. }
    6. }

    serializable 参数" class="reference-link">serializable 参数

    指定了 default 默认值的属性默认情况下都会被序列化,序列化后就会将编辑器中设置好的值保存到场景等资源文件中,并且在加载场景时自动还原之前设置好的值。如果不想序列化,可以设置serializable: false

    1. temp_url: {
    2. default: "",
    3. serializable: false
    4. }

    type 参数" class="reference-link">type 参数

    default 不能提供足够详细的类型信息时,为了能在 属性检查器 显示正确的输入控件,就要用 type 显式声明具体的类型:

    • 当默认值为 null 时,将 type 设置为指定类型的构造函数,这样 属性检查器 才知道应该显示一个 Node 控件。

      1. enemy: {
      2. default: null,
      3. type: cc.Node
      4. }
    • 当默认值为数值(number)类型时,将 type 设置为 cc.Integer,用来表示这是一个整数,这样属性在 属性检查器 里就不能输入小数点。

      1. score: {
      2. default: 0,
      3. type: cc.Integer
      4. }
    • 当默认值是一个枚举(cc.Enum)时,由于枚举值本身其实也是一个数字(number),所以要将 type 设置为枚举类型,才能在 属性检查器 中显示为枚举下拉框。

      1. wrap: {
      2. default: Texture.WrapMode.Clamp,
      3. type: Texture.WrapMode
      4. }

    url 参数" class="reference-link">url 参数

    如果属性是用来访问 Raw Asset 资源的 url,为了能在 属性检查器 中选取资源,或者能正确序列化,你需要指定 url 参数:

    1. texture: {
    2. default: "",
    3. url: cc.Texture2D
    4. },

    可参考 获取和加载资源: Raw Asset

    override 参数" class="reference-link">override 参数

    所有属性都将被子类继承,如果子类要覆盖父类同名属性,需要显式设置 override 参数,否则会有重名警告:

    1. _id: {
    2. default: "",
    3. tooltip: "my id",
    4. override: true
    5. },
    6. name: {
    7. get: function () {
    8. return this._name;
    9. },
    10. displayName: "Name",
    11. override: true
    12. }

    更多参数内容请查阅 属性参数。

    属性延迟定义" class="reference-link"> 属性延迟定义

    如果两个类相互引用,脚本加载阶段就会出现循环引用,循环引用将导致脚本加载出错:

    • Game.js

      1. var Item = require("Item");
      2. var Game = cc.Class({
      3. properties: {
      4. item: {
      5. default: null,
      6. type: Item
      7. }
      8. }
      9. });
      10. module.exports = Game;
    • Item.js

      1. var Game = require("Game");
      2. var Item = cc.Class({
      3. properties: {
      4. game: {
      5. default: null,
      6. type: Game
      7. }
      8. }
      9. });
      10. module.exports = Item;

    上面两个脚本加载时,由于它们在 require 的过程中形成了闭环,因此加载会出现循环引用的错误,循环引用时 type 就会变为 undefined。

    因此我们提倡使用以下的属性定义方式:

    • Game.js

      1. var Game = cc.Class({
      2. properties: () => ({
      3. item: {
      4. default: null,
      5. type: require("Item")
      6. }
      7. })
      8. });
      9. module.exports = Game;
    • Item.js

      1. var Item = cc.Class({
      2. properties: () => ({
      3. game: {
      4. default: null,
      5. type: require("Game")
      6. }
      7. })
      8. });
      9. module.exports = Item;

    这种方式就是将 properties 指定为一个 ES6 的箭头函数(lambda 表达式),箭头函数的内容在脚本加载过程中并不会同步执行,而是会被 CCClass 以异步的形式在所有脚本加载成功后才调用。因此加载过程中并不会出现循环引用,属性都可以正常初始化。

    箭头函数的用法符合 JavaScript 的 ES6 标准,并且 Creator 会自动将 ES6 转义为 ES5,用户不用担心浏览器的兼容问题。

    你可以这样来理解箭头函数:

    1. // 箭头函数支持省略掉 `return` 语句,我们推荐的是这种省略后的写法:
    2. properties: () => ({ // <- 箭头右边的括号 "(" 不可省略
    3. game: {
    4. default: null,
    5. type: require("Game")
    6. }
    7. })
    8. // 如果要完整写出 `return`,那么上面的写法等价于:
    9. properties: () => {
    10. return {
    11. game: {
    12. default: null,
    13. type: require("Game")
    14. }
    15. }; // <- 这里 return 的内容,就是原先箭头右边括号里的部分
    16. }
    17. // 我们也可以不用箭头函数,而是用普通的匿名函数:
    18. properties: function () {
    19. return {
    20. game: {
    21. default: null,
    22. type: require("Game")
    23. }
    24. };
    25. }

    GetSet 方法

    在属性中设置了 get 或 set 以后,访问属性的时候,就能触发预定义的 get 或 set 方法。

    get

    在属性中设置 get 方法:

    1. properties: {
    2. width: {
    3. get: function () {
    4. return this.__width;
    5. }
    6. }
    7. }

    get 方法可以返回任意类型的值。

    这个属性同样能显示在 属性检查器 中,并且可以在包括构造函数内的所有代码里直接访问。

    1. var Sprite = cc.Class({
    2. ctor: function () {
    3. this.__width = 128;
    4. cc.log(this.width); // 128
    5. },
    6. properties: {
    7. width: {
    8. get: function () {
    9. return this.__width;
    10. }
    11. }
    12. }
    13. });

    请注意:

    • 设定了 get 以后,这个属性就不能被序列化,也不能指定默认值,但仍然可附带除了 default, serializable 外的大部分参数。

      1. width: {
      2. get: function () {
      3. return this.__width;
      4. },
      5. type: cc.Integer,
      6. tooltip: "The width of sprite"
      7. }
    • get 属性本身是只读的,但返回的对象并不是只读的。用户使用代码依然可以修改对象内部的属性,例如:

      1. var Sprite = cc.Class({
      2. ...
      3. position: {
      4. get: function () {
      5. return this._position;
      6. },
      7. }
      8. ...
      9. });
      10. var obj = new Sprite();
      11. obj.position = new cc.Vec2(10, 20); // 失败!position 是只读的!
      12. obj.position.x = 100; // 允许!position 返回的 _position 对象本身可以修改!

    set

    在属性中设置 set 方法:

    1. width: {
    2. set: function (value) {
    3. this._width = value;
    4. }
    5. }

    set 方法接收一个传入参数,这个参数可以是任意类型。

    set 一般和 get 一起使用:

    1. width: {
    2. get: function () {
    3. return this._width;
    4. },
    5. set: function (value) {
    6. this._width = value;
    7. },
    8. type: cc.Integer,
    9. tooltip: "The width of sprite"
    10. }

    如果没有和 get 一起定义,则 set 自身不能附带任何参数。

    和 get 一样,设定了 set 以后,这个属性就不能被序列化,也不能指定默认值。

    editor 参数

    editor 只能定义在 cc.Component 的子类。

    1. cc.Class({
    2. extends: cc.Component,
    3. editor: {
    4. // requireComponent 参数用来指定当前组件的依赖组件。
    5. // 当组件添加到节点上时,如果依赖的组件不存在,引擎将会自动将依赖组件添加到同一个节点,防止脚本出错。
    6. // 该选项在运行时同样有效。
    7. //
    8. // 值类型:Function (必须是继承自 cc.Component 的构造函数,如 cc.Sprite)
    9. // 默认值:null
    10. requireComponent: null,
    11. // 当本组件添加到节点上后,禁止同类型(含子类)的组件再添加到同一个节点,
    12. // 防止逻辑发生冲突。
    13. //
    14. // 值类型:Boolean
    15. // 默认值:false
    16. disallowMultiple: false,
    17. // menu 用来将当前组件添加到组件菜单中,方便用户查找。
    18. //
    19. // 值类型:String (如 "Rendering/Camera")
    20. // 默认值:""
    21. menu: "",
    22. // 允许当前组件在编辑器模式下运行。
    23. // 默认情况下,所有组件都只会在运行时执行,也就是说它们的生命周期回调在编辑器模式下并不会触发。
    24. //
    25. // 值类型:Boolean
    26. // 默认值:false
    27. executeInEditMode: false,
    28. // 当设置了 "executeInEditMode" 以后,playOnFocus 可以用来设定选中当前组件所在的节点时,
    29. // 编辑器的场景刷新频率。
    30. // playOnFocus 如果设置为 true,场景渲染将保持 60 FPS,如果为 false,场景就只会在必要的时候进行重绘。
    31. //
    32. // 值类型:Boolean
    33. // 默认值:false
    34. playOnFocus: false,
    35. // 自定义当前组件在 **属性检查器** 中渲染时所用的网页 url。
    36. //
    37. // 值类型:String
    38. // 默认值:""
    39. inspector: "",
    40. // 自定义当前组件在编辑器中显示的图标 url。
    41. //
    42. // 值类型:String
    43. // 默认值:""
    44. icon: "",
    45. // 指定当前组件的帮助文档的 url,设置过后,在 **属性检查器** 中就会出现一个帮助图标,
    46. // 用户点击将打开指定的网页。
    47. //
    48. // 值类型:String
    49. // 默认值:""
    50. help: "",
    51. }
    52. });

    继续前往 属性参数参考。