Cocos Creator(7)---节点/组件创建、拷贝、销毁

1,001 阅读5分钟

节点和组件

经过前面的脚本编写及属性挂载,对脚本和组件现在应该有了清晰的认知

在 Cocos Creator 中,节点(Node)  是承载组件的实体,我们通过将具有各种功能的 组件(Component)  挂载到节点上,来让节点具有各式各样的表现和功能。

在Cocos Creator中,在层级管理器中的都是节点,在节点中挂载的脚本、Widget等都是组件

image.png

节点

节点创建

代码创建图片

let spriteNode = new cc.Node;                               // 创建一个图片节点
let spriteCompent = spriteNode.addComponent(cc.Sprite);     // 添加图片组件

// 加载资源并赋值给图片组件
cc.resources.load("Image/icon2", cc.SpriteFrame, function (err, spriteFrame: cc.SpriteFrame) {
      if (err) {
          return
      }
       spriteCompent.spriteFrame = spriteFrame;
});

// 添加到当前场景的 【Canvas】节点中
var scene = cc.director.getScene();
spriteNode.parent = scene.getChildByName('Canvas');

代码创建label

{
    // let labelNode = new cc.Node;
    let labelNode = new cc.Node('CustomLabelNode');            // 创建一个名字为CustomLabelNode的节点
    let labelCompoment = labelNode.addComponent(cc.Label);

    labelCompoment.string = '这是一个label';
    labelNode.position = cc.v3(100,50);
    labelCompoment.fontSize = 15;
    labelCompoment.fontFamily = 'Arial';
    labelCompoment.node.color = cc.Color.WHITE;

    let scence = cc.director.getScene()
    labelNode.parent = scence.getChildByName('Canvas');
}

实例化预制件

1、定义属性并进行关联

    // 一般是用这中方式声明
        @property({
            type: cc.Prefab,
            displayName: '预制件',
            tooltip: '鼠标放上去的提示文字',
        })
        prefab_image: cc.Prefab = null;


        // properties: {
        //     prefab_image: {
        //         default: null;
        //         type: cc.Prefab;
        //         displayName: '名字';
        //     },
        // }

2、实例化

在关联了预制件之后,直接实例化

    {   // 实例化节点
        let prefab = cc.instantiate(this.prefab_image);
         prefab.position = cc.v3(-100, 0);
         var scence = cc.director.getScene();
         prefab.parent = scence.getChildByName('Canvas');
    }

1、普通创建 let labelNode = new cc.Node;

2、创建一个指定名称的 let labelNode = new cc.Node('CustomLabelNode');

3、创建预制件: 先确保属性已经被关联 cc.instantiate(this.prefab_image);

节点拷贝

在iOS中,如果要拷贝一个对象,一般是直接 obj.copy.

在Cocos Creator中,拷贝一个对象,相当于是在已有的节点中,再实例化一次(和节点实例化差不多)。

// 实例化原始的节点
let prefab = cc.instantiate(this.prefab_image);
prefab.position = cc.v3(-100, 0);
var scence = cc.director.getScene();
prefab.parent = scence.getChildByName('Canvas');

// 拷贝节点
let prefabCopy = cc.instantiate(prefab);

// 可以先屏蔽这行
// prefabCopy.position = cc.v3(-200, 0);
prefabCopy.parent = scence.getChildByName('Canvas').getChildByName('Main Camera').getChildByName('ground');

在这段拷贝中,为了方便区分其位置信息,将父节点设置为ground。

在拷贝中,不设置positon,发现其positon信息是原来节点的位置信息。
如果只设置positon,不设置其present,发现并没有显示在界面中

在节点拷贝中,
其位置信息也会被拷贝
其父节点信息并不会被拷贝

节点查找及多层查找

一般来说,在脚本中,如果需要查找节点

// 当前脚本所在的节点
let node = this.node;

查找其父节点

let node = this.node.parent;

如果节点在属性检查器,或者是一个预制件 ,比如这个场景的结构

场景的节点查找

image.png

var scence = cc.director.getScene();                // 当前场景
var node = scence.getChildByName('Canvas');         // 场景下的Canvas节点
node = node.getChildByName('Main Camera');          // Canvas节点下的Main Camera节点
node = node.getChildByName('TestLabel');            // Main Camera点下的TestLabel节点
let label = node.getComponent(cc.Label);            // TestLabel节点中的label组件
label.string = 'test';

这种多层结构,可以用另一个快捷写法对于这种情况,路径的第一级就是第一级的节点:Canvas

let findNode = cc.find('Canvas/Main Camera/TestLabel');
let label = findNode.getComponent(cc.Label);
label.string = 'test1';

预制件的节点查找

image.png

预制件的节点查找第一级是从Background开始

{   // 实例化组件
let prefab = cc.instantiate(this.prefab_image);
prefab.position = cc.v3(-100, 0);
var scence = cc.director.getScene();
prefab.parent = scence.getChildByName('Canvas');

            
let preNode = cc.find('Background/Label',prefab);   // 查找里面的label节点
let label = preNode.getComponent(cc.Label);         // 取出label节点的label组件
label.string = '改了';
}

节点销毁

销毁destroy()

在iOS中,对象被销毁是在其超过作用域时,由系统将其销毁,或者设置obj = nil;
在Cocos Creator 中,主动销毁 可以直接使用 this.prefabtest.destroy();


    @property({
        type: cc.Node,
    })
    prefabtest: cc.Node = null;
    
    
    
    nodeTest() {
        {   // 实例化组件
            let prefab = cc.instantiate(this.prefab_image);
            prefab.position = cc.v3(-100, 0);
            var scence = cc.director.getScene();
            prefab.parent = scence.getChildByName('Canvas');

// 延时1s执行销毁这个节点
            this.scheduleOnce(this.destroyNode,1)
        }


    destroyNode () {
    // 销毁
        this.prefabtest.destroy();

// 延时1s后,尝试再把这个节点加入父节点
        this.scheduleOnce(this.restoreUse,1)
        
    }
    
    restoreUse () {
        var scence = cc.director.getScene();
        this.prefabtest.parent = scence.getChildByName('Canvas');
    }
    

再最后加入父节点的时候,发现并没有加到父节点上。但是此时这个节点是还在的,其active = false

移除removeFromParent

removeFromParent 通常需要传入一个 false,否则默认会清空节点上绑定的事件和 action 等。

移除removeFromParent 和iOS的 removeForSuperView 差不多。将此节点移除父节点。

用上面销毁的代码中,把destroy() 替换成removeFromParent(), 发现先移除,在添加,最终成功被添加到父节点上。

destroy 和 removeFromParent 的区别

调用一个节点的 removeFromParent 后,它并不会从内存中释放,因为引擎内部仍会持有它的数据。因此如果一个节点不再使用,请直接调用它的 destroy 而不是 removeFromParent,否则会导致内存泄漏。

总之,如果一个节点不再使用,destroy 就对了,不需要 removeFromParent 也不需要设置 parent 为 null

组件

组件的创建和获取

添加系统节点

以创建一个按钮的过程来看添加过程。

// 创建一个节点(Node)
let buttonNode = new cc.Node;

// 在节点上添加Button组件 ,使其具有按钮能力
let buttonComponent = buttonNode.addComponent(cc.Button);       // 节点上加一个button的组件


// 添加图片
// 1、创建节点
// 2、创建Sprite组件(承载图片用Sprite)
// 添加图片组件
let imageNode = new cc.Node;
let imageComponent = imageNode.addComponent(cc.Sprite);
imageNode.parent = buttonNode;

cc.resources.load("Image/icon2", cc.SpriteFrame, function (err, spriteFrame: cc.SpriteFrame) {
     if (err) {
           console.error('Failed to load image:', err);
           return;
      }
      imageComponent.spriteFrame = spriteFrame;
 });

// 添加label
// 1、创建节点
// 2、创建label组件
let labelNode = new cc.Node;
let labelCompoment = labelNode.addComponent(cc.Label);
labelCompoment.string = '按钮';
labelNode.parent = buttonNode;

// 获取组件
let label = labelNode.getComponent(cc.Label);         // 取出label节点的label组件
label.string = '改了按钮';

buttonNode.parent = this.node.parent;

添加自定义脚本组件

1、自定义脚本名CustomNode.ts
2、脚本中的类名CustomNodeClass

// 文件名:CustomNode
const { ccclass, property } = cc._decorator;
@ccclass
export default class CustomNodeClass extends cc.Component {
    // @property(cc.Prefab)
    // buttonPrefab: cc.Prefab = null;
    start() {
        console.log('加载了CustomNode组件');
    }
}
// 用脚本名称添加组件
let customNodeCompent = buttonNode.addComponent('CustomNode');

// 用类名添加组件
// 1、导入文件
// 2、用类名加载组件
import CustomNodeClass from "./CustomNode";
let customNodeCompent = buttonNode.addComponent(CustomNodeClass);

在上面这个实例中,可以看出,

1、添加组件之前都是先创建了一个节点

2、node.addComponent(cc.Label); 的方式添加组件
3、node.getComponent(cc.Label); 的方式获取

4、node.addComponent('CustomNode'); 参数为脚本名称
5、node.getComponent('CustomNode'); 参数为脚本名称

6、node.addComponent(CustomNodeClass); 参数为脚本中的类名称 需要先导入脚本文件
7、node.getComponent(CustomNodeClass); 参数为脚本中的类名称