(三)訪問節(jié)點(diǎn)和組件
你可以在 屬性檢查器 里修改節(jié)點(diǎn)和組件,也能在腳本中動(dòng)態(tài)修改。動(dòng)態(tài)修改的好處是能夠在一段時(shí)間內(nèi)連續(xù)地修改屬性、過渡屬性,實(shí)現(xiàn)漸變效果。腳本還能夠響應(yīng)玩家輸入,能夠修改、創(chuàng)建和銷毀節(jié)點(diǎn)或組件,實(shí)現(xiàn)各種各樣的游戲邏輯。要實(shí)現(xiàn)這些效果,你需要先在腳本中獲得你要修改的節(jié)點(diǎn)或組件。
在本篇教程,我們將介紹如何
- 獲得組件所在的節(jié)點(diǎn)
- 獲得其它組件
- 使用 屬性檢查器 設(shè)置節(jié)點(diǎn)和組件
- 查找子節(jié)點(diǎn)
- 全局節(jié)點(diǎn)查找
- 訪問已有變量里的值
獲得組件所在的節(jié)點(diǎn)
獲得組件所在的節(jié)點(diǎn)很簡(jiǎn)單,只要在組件方法里訪問 this.node 變量:
start: function () {
var node = this.node;
node.x = 100;
}
獲得其它組件
你會(huì)經(jīng)常需要獲得同一個(gè)節(jié)點(diǎn)上的其它組件,這就要用到 getComponent 這個(gè) API,它會(huì)幫你查找你要的組件
start: function () {
var label = this.getComponent(cc.Label);
var text = this.name + ' started';
// Change the text in Label Component
label.string = text;
}
你也可以為 getComponent 傳入一個(gè)類名。對(duì)用戶定義的組件而言,類名就是腳本的文件名,并且區(qū)分大小寫。例如 "SinRotate.js" 里聲明的組件,類名就是 "SinRotate"。
var label = this.getComponent("cc.Label");
在節(jié)點(diǎn)上也有一個(gè) getComponent 方法,它們的作用是一樣的:
start: function () {
cc.log( this.node.getComponent(cc.Label) === this.getComponent(cc.Label) ); // true
}
如果在節(jié)點(diǎn)上找不到你要的組件,getComponent 將返回 null,如果你嘗試訪問 null 的值,將會(huì)在運(yùn)行時(shí)拋出 "TypeError" 這個(gè)錯(cuò)誤。因此如果你不確定組件是否存在,請(qǐng)記得判斷一下:
start: function () {
var label = this.getComponent(cc.Label);
if (label) {
label.string = "Hello";
}
else {
cc.error("Something wrong?");
}
}
獲得其它節(jié)點(diǎn)及其組件
僅僅能訪問節(jié)點(diǎn)自己的組件通常是不夠的,腳本通常還需要進(jìn)行多個(gè)節(jié)點(diǎn)之間的交互。例如,一門自動(dòng)瞄準(zhǔn)玩家的大炮,就需要不斷獲取玩家的最新位置。Cocos Creator 提供了一些不同的方法來獲得其它節(jié)點(diǎn)或組件。
使用 屬性檢查器 設(shè)置節(jié)點(diǎn)和組件
最直接的方式就是在 屬性檢查器 中設(shè)置你需要的對(duì)象。以節(jié)點(diǎn)為例,這只需要在腳本中聲明一個(gè) type 為 cc.Node 的屬性:
// Cannon.js
cc.Class({
extends: cc.Component,
properties: {
// 聲明 player 屬性
player: {
default: null,
type: cc.Node
}
}
});
這段代碼在 properties 里面聲明了一個(gè) player 屬性,默認(rèn)值為 null,并且指定它的對(duì)象類型為 cc.Node。這就相當(dāng)于在其它語言里聲明了 public cc.Node player = null;。腳本編譯之后,這個(gè)組件在 屬性檢查器 中看起來是這樣的:

接著你就可以將層級(jí)管理器上的任意一個(gè)節(jié)點(diǎn)拖到這個(gè) Player 控件:

這樣一來它的 player 屬性就會(huì)被設(shè)置成功,你可以直接在腳本里訪問 player:
// Cannon.js
var Player = require("Player");
cc.Class({
extends: cc.Component,
properties: {
// 聲明 player 屬性
player: {
default: null,
type: cc.Node
}
},
start: function () {
var playerComp = this.player.getComponent(Player);
this.checkPlayer(playerComp);
},
// ...
});
利用屬性檢查器設(shè)置組件
在上面的例子中,如果你將屬性的 type 聲明為 Player 組件,當(dāng)你拖動(dòng)節(jié)點(diǎn) "Player Node" 到 屬性檢查器,player 屬性就會(huì)被設(shè)置為這個(gè)節(jié)點(diǎn)里面的 Player 組件。這樣你就不需要再自己調(diào)用 getComponent 啦。
// Cannon.js
var Player = require("Player");
cc.Class({
extends: cc.Component,
properties: {
// 聲明 player 屬性,這次直接是組件類型
player: {
default: null,
type: Player
}
},
start: function () {
var playerComp = this.player;
this.checkPlayer(playerComp);
},
// ...
});
你還可以將屬性的默認(rèn)值由 null 改為數(shù)組[],這樣你就能在 屬性檢查器 中同時(shí)設(shè)置多個(gè)對(duì)象。
不過如果需要在運(yùn)行時(shí)動(dòng)態(tài)獲取其它對(duì)象,還需要用到下面介紹的查找方法。
查找子節(jié)點(diǎn)
有時(shí)候,游戲場(chǎng)景中會(huì)有很多個(gè)相同類型的對(duì)象,像是炮塔、敵人和特效,它們通常都有一個(gè)全局的腳本來統(tǒng)一管理。如果用 屬性檢查器 來一個(gè)一個(gè)將它們關(guān)聯(lián)到這個(gè)腳本上,那工作就會(huì)很繁瑣。為了更好地統(tǒng)一管理這些對(duì)象,我們可以把它們放到一個(gè)統(tǒng)一的父物體下,然后通過父物體來獲得所有的子物體:
// CannonManager.js
cc.Class({
extends: cc.Component,
start: function () {
this.cannons = [];
this.cannons = this.node.getChildren();
}
});
這里的 getChildren 是 cc.Node 原有的一個(gè) API,可以獲得一個(gè)包含所有子節(jié)點(diǎn)的數(shù)組。
你還可以使用 getChildByName:
this.node.getChildByName("Cannon 01");
如果子節(jié)點(diǎn)的層次較深,你還可以使用 cc.find,cc.find 將根據(jù)傳入的路徑進(jìn)行逐級(jí)查找:
cc.find("Cannon 01/Barrel/SFX", this.node);
全局節(jié)點(diǎn)查找
當(dāng) cc.find 只傳入第一個(gè)參數(shù)時(shí),將從場(chǎng)景根節(jié)點(diǎn)開始逐級(jí)查找:
this.backNode = cc.find("Canvas/Menu/Back");
訪問已有變量里的值
如果你已經(jīng)在一個(gè)地方保存了節(jié)點(diǎn)或組件的引用,你也可以直接訪問它們,一般有兩種方式:
- 通過全局變量訪問
你應(yīng)當(dāng)很謹(jǐn)慎地使用全局變量,當(dāng)你要用全局變量時(shí),應(yīng)該很清楚自己在做什么,我們并不推薦濫用全局變量,即使要用也最好保證全局變量只讀。
讓我們?cè)囍x一個(gè)全局對(duì)象 window.Global,這個(gè)對(duì)象里面包含了 backNode 和 backLabel 兩個(gè)屬性。
// Globals.js, this file can have any name
window.Global = {
backNode: null,
backLabel: null,
};
由于所有腳本都強(qiáng)制聲明為 "use strict",因此定義全局變量時(shí)的 window. 不可省略。
接著你可以在合適的地方直接訪問并初始化 Global:
// Back.js
cc.Class({
extends: cc.Component,
onLoad: function () {
Global.backNode = this.node;
Global.backLabel = this.getComponent(cc.Label);
}
});
初始化后,你就能在任何地方訪問到 Global 里的值:
// AnyScript.js
cc.Class({
extends: cc.Component,
// start 會(huì)在 onLoad 之后執(zhí)行,所以這時(shí) Global 已經(jīng)初始化過了
start: function () {
var text = 'Back';
Global.backLabel.string = text;
}
});
訪問全局變量時(shí),如果變量未定義將會(huì)拋出異常。
添加全局變量時(shí),請(qǐng)小心不要和系統(tǒng)已有的全局變量重名。
你需要小心確保全局變量使用之前都已初始化和賦值。
- 通過模塊訪問
如果你不想用全局變量,你可以使用 require 來實(shí)現(xiàn)腳本的跨文件操作,讓我們看個(gè)示例:
// Global.js, now the filename matters
module.exports = {
backNode: null,
backLabel: null,
};
每個(gè)腳本都能用 require + 文件名(不含路徑) 來獲取到對(duì)方 export 的對(duì)象
// Back.js
// this feels more safe since you know where the object comes from
var Global = require("Global");
cc.Class({
extends: cc.Component,
onLoad: function () {
Global.backNode = this.node;
Global.backLabel = this.getComponent(cc.Label);
}
});
// AnyScript.js
// this feels more safe since you know where the object comes from
var Global = require("Global");
cc.Class({
extends: cc.Component,
// start 會(huì)在 onLoad 之后執(zhí)行,所以這時(shí) Global 已經(jīng)初始化過了
start: function () {
var text = "Back";
Global.backLabel.string = text;
}
});