Cocos Creator(5)---添加属性和生命周期

283 阅读8分钟

上一篇文章中,通过layout实现了scroollview的滚动,此时数据是固定的,为了使数据活起来,就需要写代码来实现, Cocos Creator 支持JavaScriptTypeScript。 一般使用的是TypeScropty

关于TypeScript与JavaScript的区别,可以访问教程 | 为什么选择使用 TypeScript ?

如果新建的cocos项目用的是Hello World,那么他默认是JavaScript的语言,此时需要添加一些基本配置文件,让VS Code 更好的支持TypeScript。

开启方法很简单:
如果希望在原有项目中添加 TypeScript 脚本,并获得 VS Code 等 IDE 的完整支持,需要执行主菜单的 开发者 -> VS Code 工作流 -> 更新 VS Code 智能提示数据 和 开发者 -> VS Code 工作流 -> 添加 TypeScript 项目配置

结合官网的入门教程和阮一峰老师的TypeScript 教程的一些内容,先简单的写一些基础代码来熟悉TypeScript的写法。

设置脚本编译器

由于Cocos Creator本身不支持脚本编辑,我们编码的工具需要接触第三方的代码编辑器,比如常用的Vim, Sublime Text, Web Storm, VS Code...等。刚下载安装的Cocos Creator创建的脚本,双击打开,会跳转到网页,需要对其进行设置默认编译器。

image.png

属性

    const {ccclass, property} = cc._decorator; // 从 cc._decorator 命名空间中引入 ccclass 和 property 两个装饰器

    @ccclass // 使用装饰器声明 CCClass
    export default class NewClass extends cc.Component { // ES6 Class 声明语法,继承 cc.Component


        @property(cc.Label)     // 使用 property 装饰器声明属性,括号里是属性类型,装饰器里的类型声明主要用于编辑器展示
        label: cc.Label = null; // 这里是 TypeScript 用来声明变量类型的写法,冒号后面是属性类型,等号后面是默认值

        // 也可以使用完整属性定义格式
        @property({
            visible: false
        })
        text: string = '这是一个属性';

        string1 : string = '1'
        
        // 成员方法
        onLoad() {
            
        }
    }

对比这2行代码,不会报错,但是表示的意思有不一样。

    string1 : String = '1'
    string2 : string = '1'

ChatGPT是这样解释的

在 TypeScript 中,String 和 string 是两种不同的数据类型表示方式。

  1. StringString 是 JavaScript 中的原始包装对象,它是一个构造函数,可以用来创建字符串对象。因此,string1 : String = '1' 这行代码创建了一个字符串对象,而不是一个普通的字符串。
  2. stringstring 是 TypeScript 中表示字符串类型的关键字,它表示的是普通的字符串类型。因此,string2 : string = '1' 这行代码创建了一个普通的字符串类型变量。
    总的来说,String 是 JavaScript 中的字符串对象构造函数,而 string 是 TypeScript 中表示字符串类型的关键字。因此,在大多数情况下,我们应该使用 string 来表示字符串类型。

类型的声明

官网介绍

properties 的方式声明


    properties: {

        // 声明的属性为基本 JavaScript 类型时,可以直接赋予默认值:
        height: 20,       // number
        type: "actor",    // string
        loaded: false,    // boolean
        target: null,     // object

        // 声明的属性具备类型时(如:cc.Node,cc.Vec2 等),可以在声明处填写它们的构造函数来完成声明
        target1: cc.Node,
        pos: cc.Vec2,

        // 当声明属性是一个数组时,可以在声明处填写它们的类型或构造函数来完成声明
        any: [],      // 不定义具体类型的数组
        values: [cc.Vec2],
        nodes: [cc.Node],
        frames: [cc.SpriteFrame],


        // 完整声明
        // 有些情况下,我们需要为属性声明添加参数,这些参数控制了属性在 属性检查器 中的显示方式,以及属性在场景序列化过程中的行为
        score: {
            default: 0,     // 设置属性的默认值
            displayName: "属性检查器上展示的文案",
            tooltip: "鼠标放上去的提示文字",
            visible : false, // false的情况下, 属性检查器上不会显示该属性
            type    : number,  // 限定属性的数据类型
            serializable : false , // 设为 false 则不序列化(保存)该属性
        }


        // 数组的 default 必须设置为 [],如果要在 属性检查器 中编辑,还需要设置 type 为构造函数,枚举
        names: {
            default: [],
            type: [string]   // 用 type 指定数组的每个元素都是字符串类型
        },
    
        enemies: {
            default: [],
            type: [cc.Node]     // type 同样写成数组,提高代码可读性
        },
    }

property 的方式声明


// 声明String

   //显式声明类型String
    @property(cc.String)
    string2 = '';

    //显式声明类型String
    @property({
        type:cc.String
    })
    string3 = '';

    // 隐式声明类型,根据默认值匹配其类型
    @property string4 = '';


// 声明数组
    // 指定数据中元素的类型为node
    @property([cc.Node])
    public myNodes: cc.Node[] = [];
    
    // 指定数据中元素的类型color
    @property([cc.Color])
    public myColors: cc.Color[] = [];
  
    @property
    public colors = [];
    
    
    @property({
        type : [cc.color]
    })  color3 = [];
    

方法

显式声明方法的返回值

// 有返回值
function addNum(params:number ) : number {
    params = params ++;
    console.log(params.toString);
    
    // 此时如果不return 会报错
    return params;
}

// 无返回值
function addNum(params:number ) : void {
    params = params ++;
    console.log(params.toString);
    
    // 此时return 会报错
    return params;
}

不声明方法的返回值,在这种情况下, 你给不给返回值都可以


function addNum(params:number ) {
    params = params ++;
    console.log(params.toString);
    
    // 此时return或不return 都不会报错
    return params;
}

不同参数个数的写法

    // 无参数
    function addNum1() {
        addNum2(1);
    }

    // 1个参数
    function addNum2(params : number) {
        addNum3(params,1);
    }

    // 2个参数
    function addNum3(params1:number,params2:number) {
        params1 = params1 + params2;
        console.log(params1.toString);
        return params1;
    }

可选参数

上面的方法 如果定义了参数, 就必须要传入相应的参数,否则报错。

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}
 
let result1 = buildName("Bob");                  // 错误,缺少参数
let result2 = buildName("Bob", "Adams", "Sr.");  // 错误,参数太多了
let result3 = buildName("Bob", "Adams");         // 正确

如果想要参数为可选,可选参数使用问号标识

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
 
let result1 = buildName("Bob");  // 正确
let result2 = buildName("Bob", "Adams", "Sr.");  // 错误,参数太多了
let result3 = buildName("Bob", "Adams");  // 正确

自定义属性挂载到属性检查器中

Hello.ts文件中,定义出多个属性。保存后,将其拖到属性检查器中,查看解析出来的自定义属性。

const {ccclass, property} = cc._decorator;

@ccclass
export default class HelloClass extends cc.Component {


// 这些会被解析成属性并展示在属性检查器中
    @property(cc.Label)
    label: cc.Label = null;

    @property({
        type: cc.String
    })
    string1 = null;

    @property 
    string2 : string = '属性String2';


    @property(cc.String)
    string3 = '属性String3';

    @property
    text: string = '属性Text';

    @property
    myOffset = new cc.Vec2(100, 100);


    @property([cc.Node])
    myNodes: cc.Node[] = [];
    
    @property([cc.Color])
    protected myColors: cc.Color[] = [];
  
    @property private text1 = '';

// 这些不会被解析成属性并展示在属性检查器中
    @property public colors = [];

    @property({
        type : [cc.color]
    })  color3 = [];
    
    // 成员方法
    onLoad() {
    }
    funLoad(){

    }
    start () {

    }
}

image.png

生命周期

官方介绍

onLoad

这个方法类似于iOS的- (instancetype)init; (?)

onLoad 回调会在节点首次激活时触发,比如所在的场景被载入,或者所在节点被激活的情况下。在 onLoad 阶段,保证了你可以获取到场景中的其他节点,以及节点关联的资源数据。onLoad 总是会在任何 start 方法调用前执行,这能用于安排脚本的初始化顺序。通常我们会在 onLoad 阶段去做一些初始化相关的操作。

// 简易写法
onLoad() {

}

// 标准写法
protected onLoad(): void {
        console.log('----------onload'+this);
}

start

这个方法类似于iOS的- (void)viewDidLoad; (?)

start 回调函数会在组件第一次激活前,也就是第一次执行 update 之前触发。start 通常用于初始化一些需要经常修改的数据,这些数据可能在 update 时会发生改变。

    protected start(): void {
        console.log('----------start'+this);
    }

update

这个方法类似于iOS的- (void)viewWillLayoutSubviews; (?)

游戏开发的一个关键点是在每一帧渲染前更新物体的行为,状态和方位。这些更新操作通常都放在 update 回调中。

我的Demo中放了一个Scrollview,上一篇中的步骤进行操作,但是在打印日志中,看到这个方法在不停的被调用。 查看打印日志的方法: 键盘按键F12

lateUpdate

这个方法类似于iOS的- (void)viewDidLayoutSubviews; (?)

update 会在所有动画更新前执行,但如果我们要在动效(如动画、粒子、物理等)更新之后才进行一些额外操作,或者希望在所有组件的 update 都执行完之后才进行其它操作,那就需要用到 lateUpdate 回调。

onEnable

当组件的 enabled 属性从 false 变为 true 时,或者所在节点的 active 属性从 false 变为 true 时,会激活 onEnable 回调。倘若节点第一次被创建且 enabled 为 true,则会在 onLoad 之后,start 之前被调用。

onDisable

当组件的 enabled 属性从 true 变为 false 时,或者所在节点的 active 属性从 true 变为 false 时,会激活 onDisable 回调。

onDestroy

这个方法类似于iOS的- (void)dealloc; (?)

当组件或者所在节点调用了 destroy(),则会调用 onDestroy 回调,并在当帧结束时统一回收组件。当同时声明了 onLoad 和 onDestroy 时,它们将总是被成对调用。也就是说从组件初始化到销毁的过程中,它们要么就都会被调用,要么就都不会被调用。

生命周期整个流程

一个组件从初始化到激活,再到最终销毁的完整生命周期函数调用顺序为:onLoad -> onEnable -> start -> update -> lateUpdate -> onDisable -> onDestroy

其中,onLoad 和 start 常常用于组件的初始化,只有在节点 activeInHierarchy 的情况下才能调用,并且最多只会被调用一次。除了上文提到的内容以及调用顺序的不同,它们还有以下区别:

节点激活时组件 enabled 时才会调用?
onLoad立即调用
start延迟调用

实战制作小游戏

有了之前的基础,可以开始自己动手根据官网的步骤制作一个小游戏,按照官网操作,除了的一些API调用,其他都很流畅。