简介
Cocos Creator 的脚本主要是通过扩展组件来进行开发的。目前 Cocos Creator 支持 JavaScript 和 CoffeeScript 两种脚本语言。通过编写脚本组件,并将它赋予到场景节点中来驱动场景中的物体。
在组件脚本的编写过程中,你可以通过声明属性,将脚本中需要调节的变量映射到 属性检查器(Properties) 中,供策划和美术调整。于此同时,你也可以通过注册特定的回调函数,来帮助你初始化,更新甚至销毁节点。
创建和使用组件脚本
cc.Class({
extends: cc.Component,
properties: {
},
// use this for initialization
onLoad: function () {
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
},
});
使用 cc.Class 声明类型
定义 CCClass
var Sprite = cc.Class({
name: "sprite"
});
实例化
var obj = new Sprite();
判断类型
cc.log(obj instanceof Sprite); // true
构造函数
使用 ctor 声明构造函数:
继承
使用 extends 实现继承:
// 父类
var Shape = cc.Class();
// 子类
var Rect = cc.Class({
extends: Shape
});
父构造函数
继承后,CCClass 会统一自动调用父构造函数,你不需要显式调用。
var Shape = cc.Class({
ctor: function () {
cc.log("Shape"); // 实例化时,父构造函数会自动调用,
}
});
var Rect = cc.Class({
extends: Shape
});
var Square = cc.Class({
extends: Rect,
ctor: function () {
cc.log("Square"); // 再调用子构造函数
}
});
var square = new Square();
父类->子类顺序,以上代码将依次输出 “Shape” 和 “Square”。
组件脚本中声明属性
cc.Class({
extends: cc.Component,
properties: {
userID: 20,
userName: "Foobar"
}
});
简单声明
properties: {
// 当声明的属性为基本 JavaScript 类型时,可以直接赋予默认值
height: 20, // number
type: "actor", // string
loaded: false, // boolean
target: null, // object
// 当声明的属性具备类型时
target: cc.Node,
pos: cc.Vec2,
// 声明属性的类型继承自 cc.ValueType 时
pos: new cc.Vec2(10, 20),
color: new cc.Color(255, 255, 255, 128),
// 当声明属性是一个数组时,可以在声明处填写他们的类型或构造函数来完成声明
any: [], // 不定义具体类型的数组
bools: [cc.Boolean],
strings: [cc.String],
floats: [cc.Float],
ints: [cc.Integer],
values: [cc.Vec2],
nodes: [cc.Node],
frames: [cc.SpriteFrame],
}
完整声明
properties: {
score: {
default: 0,
displayName: "Score (player)",
tooltip: "The score of player",
}
}
常用属性参数
- default: 设置属性的默认值,这个默认值仅在组件第一次添加到节点上时才会用到
- type: 限定属性的数据类型,详见 CCClass 进阶参考:type 参数
- visible: 设为 false 则不在 属性检查器 面板中显示该属性
- serializable: 设为 false 则不序列化(保存)该属性
- displayName: 在 属性检查器 面板中显示成指定名字
- tooltip: 在 属性检查器 面板中添加属性的 Tooltip
数组声明
数组的 default 必须设置为 [],如果要在 属性检查器 中编辑,还需要设置 type 为构造函数,枚举,或者 cc.Integer,cc.Float,cc.Boolean 和 cc.String。
properties: {
names: {
default: [],
type: [cc.String] // 用 type 指定数组的每个元素都是字符串类型
},
enemies: {
default: [],
type: [cc.Node] // type 同样写成数组,提高代码可读性
},
}
get/set 声明
properties: {
width: {
get: function () {
return this._width;
},
set: function (value) {
this._width = value;
}
}
}
访问节点和组件
1、获得组件所在的节点 this.node
start: function () {
var node = this.node;
node.x = 100;
}
2、获得其它组件 getComponent
start: function () {
var label = this.getComponent(cc.Label);
if (label) {
label.string = "Hello";
}
else {
cc.error("Something wrong?");
}
}
3、获得其它节点及其组件
- 查找子节点 :
var cannons = this.node.children;
- 名称查找 :
this.node.getChildByName("Cannon 01");
- 路径查找 :
cc.find("Cannon 01/Barrel/SFX", this.node);
- 全局名字查找( 从场景根节点开始逐级查找 ) :
this.backNode = cc.find("Canvas/Menu/Back");
4、通过全局变量访问
- 定义全局对象
window.Global
,这个对象里面包含了backNode
和backLabel
两个属性
// Globals.js, this file can have any name
window.Global = {
backNode: null,
backLabel: null,
};
- 在合适的地方直接访问并初始化
Global
// Back.js
cc.Class({
extends: cc.Component,
onLoad: function () {
Global.backNode = this.node;
Global.backLabel = this.getComponent(cc.Label);
}
});
- 可以在任何地方访问到 Global 里的值
cc.Class({
extends: cc.Component,
// start 会在 onLoad 之后执行,所以这时 Global 已经初始化过了
start: function () {
var text = 'Back';
Global.backLabel.string = text;
}
});
5、通过模块访问
- 如果你不想用全局变量,你可以使用 require 来实现脚本的跨文件操作
// Global.js, now the filename matters
module.exports = {
backNode: null,
backLabel: null,
};
- 每个脚本都能用 require + 文件名(不含路径) 来获取到对方 exports 的对象
var Global = require("Global");
// 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 会在 onLoad 之后执行,所以这时 Global 已经初始化过了
start: function () {
var text = "Back";
Global.backLabel.string = text;
}
});
常用节点和组件接口
节点状态和层级操作
1.关闭/激活节点
this.node.active = false;
执行 组件上的onDisable
方法this.node.active = true;
执行 组件上的onEnable
方法
2.更改节点的父节点
假设父节点为 parentNode,子节点为 this.node,两种方式等价
this.node.parent = parentNode;
this.node.removeFromParent(false);
parentNode.addChild(this.node);
//更改节点锚点位置
this.node.anchorX = 1;
this.node.anchorY = 0;
或
this.node.setAnchorPoint(1, 0);
//颜色和
mySprite.node.color = cc.Color.RED;
//不透明度
mySprite.node.opacity = 128;
常用组件接口
cc.Component
是所有组件的基类,任何组件都包括如下的常见接口(假设我们在该组件的脚本中,以 this 指代本组件):
+ this.node
:该组件所属的节点实例
+ this.enabled
:是否每帧执行该组件的 update 方法,同时也用来控制渲染组件是否显示
+ update(dt)
:作为组件的成员方法,在组件的 enabled 属性为 true 时,其中的代码会每帧执行
+ onLoad()
:组件所在节点进行初始化时(节点添加到节点树时)执行
+ start()
:会在该组件第一次 update 之前执行,通常用于需要在所有组件的 onLoad 初始化完毕后执行的逻辑
PS : onLoad 初始化完毕后,在执行 start
生命周期回调
用户的声明周期回调函数主要有:
- onLoad
- onEnable :在onLoad 之后,start 之前被调用。
- start
- update
- lateUpdate
- onDisable
- onDestroy
创建和销毁节点
创建节点 new cc.Node()
cc.Class({
extends: cc.Component,
properties: {
sprite: {
default: null,
type: cc.SpriteFrame,
},
},
start: function () {
var node = new cc.Node('Sprite');
var sp = node.addComponent(cc.Sprite);
sp.spriteFrame = this.sprite;
node.parent = this.node;
},
});
});
克隆已有节点 & 创建预制节点
cc.Class({
extends: cc.Component,
properties: {
target: {
default: null,
type: cc.Node,
},
},
start: function () {
var scene = cc.director.getScene();
var node = cc.instantiate(this.target);
node.parent = scene;
node.setPosition(0, 0);
},
});
销毁节点 node.destroy()
cc.Class({
extends: cc.Component,
properties: {
target: cc.Node,
},
start: function () {
// 5 秒后销毁目标节点
setTimeout(function () {
this.target.destroy();
}.bind(this), 5000);
},
update: function (dt) {
if (cc.isValid(this.target)) {
this.target.rotation += dt * 10.0;
}
},
});
destroy 和 removeFromParent 的区别
调用一个节点的 removeFromParent 后,它不一定就能完全从内存中释放,因为有可能由于一些逻辑上的问题,导致程序中仍然引用到了这个对象。因此如果一个节点不再使用了,请直接调用它的 destroy 而不是 removeFromParent。destroy 不但会激活组件上的 onDestroy,还会降低内存泄露的几率,同时减轻内存泄露时的后果。
总之,如果一个节点不再使用,destroy 就对了,不需要 removeFromParent 也不需要设置 parent 为 null 哈。
加载和切换场景
1、切换场景
cc.director.loadScene("MyScene");
2、跨场景不销毁
cc.game.addPersistRootNode(myNode);
取消 不销毁 cc.game.removePersistRootNode(myNode);
3、场景加载回调
cc.director.loadScene("MyScene", onSceneLaunched);
PS : 由于回调函数只能写在本脚本中,所以场景加载回调通常用来配合常驻节点,在常驻节点上挂载的脚本中使用。
4、预加载场景
cc.director.preloadScene("table", function () {
cc.log("Next scene preloaded");
});
//之后在合适的时间调用 loadScene, 就可以真正切换场景。
cc.director.loadScene("table");
获取和加载资源
资源的分类
- Asset
type 关键字
sset” 的资源类型,cc.SpriteFrame, cc.AnimationClip, cc.Prefab 等资源都属于 Asset
spriteFrame: {
default: null,
type: cc.SpriteFrame
},
- Raw Asset
url 关键字
图片(cc.Texture2D),声音(cc.AudioClip),粒子(cc.ParticleAsset)等资源都是 Raw Asset
textureURL: {
default: "",
url: cc.Texture2D
}
动态加载
注意事项
1. 所有需要通过脚本动态加载的资源,都必须放置在assets/resources
文件夹或它的子文件夹下
2. 资源动态加载的时都是异步的,需要在回调函数中获得载入的资源
动态加载 Asset
Asset/resources
用cc.loader.loadRes
加载- loadRes 一次只能加载单个 Asset
- 传入路径,不能包含文件扩展名
eg :
// 加载 Prefab
cc.loader.loadRes("test assets/prefab", function (err, prefab) {
var newNode = cc.instantiate(prefab);
cc.director.getScene().addChild(newNode);
});
// 加载 AnimationClip
var self = this;
cc.loader.loadRes("test assets/anim", function (err, clip) {
self.node.getComponent(cc.Animation).addClip(clip, "anim");
});
// 加载 SpriteAtlas(图集),并且获取其中的一个 SpriteFrame
// 注意 atlas 资源文件(plist)通常会和一个同名的图片文件(png)放在一个目录下,
// 所以需要在第二个参数指定资源类型
cc.loader.loadRes("test assets/sheep", cc.SpriteAtlas, function (err, atlas) {
var frame = atlas.getSpriteFrame('sheep_down_0');
sprite.spriteFrame = frame;
});
// 加载单个 SpriteFrame
var self = this;
cc.loader.loadRes("test assets/image", cc.SpriteFrame, function (err, spriteFrame) {
self.node.getComponent(cc.Sprite).spriteFrame = spriteFrame;
});
资源释放
// 传路径
cc.loader.releaseRes("test assets/image", cc.SpriteFrame);
cc.loader.releaseRes("test assets/anim");
// 传实例
cc.loader.releaseAsset(spriteFrame);
动态加载 Raw Asset
Raw Asset 可以直接使用 url 从远程服务器上加载,也可以从项目中动态加载。
cc.url.raw
Raw Asset 加载成功后,如果需要传给一些 url 形式的 API,还是需要给出完整路径才行。你需要用 cc.url.raw 进行一次 url 的转换:
// 原 url 会报错!文件找不到
var texture = cc.textureCache.addImage("resources/test assets/image.png");
// 改用 cc.url.raw,此时需要声明 resources 目录和文件后缀名
var realUrl = cc.url.raw("resources/test assets/image.png");
var texture = cc.textureCache.addImage(realUrl);
资源批量加载
// 加载 test assets 目录下所有资源
cc.loader.loadResDir("test assets", function (err, assets) {
// ...
});
// 加载 sheep.plist 图集中的所有 SpriteFrame
cc.loader.loadResDir("test assets/sheep", cc.SpriteFrame, function (err, assets) {
// assets 是一个 SpriteFrame 数组,已经包含了图集中的所有 SpriteFrame。
// 而 loadRes('test assets/sheep', cc.SpriteAtlas, function (err, atlas) {...})
// 获得的则是整个 SpriteAtlas 对象。
});
加载远程资源和设备资源
// 远程 url 带图片后缀名
var remoteUrl = "http://unknown.org/someres.png";
cc.loader.load(remoteUrl, function (err, texture) {
// Use texture to create sprite frame
});
// 远程 url 不带图片后缀名,此时必须指定远程图片文件的类型
remoteUrl = "http://unknown.org/emoji?id=124982374";
cc.loader.load({url: remoteUrl, type: 'png'}, function () {
// Use texture to create sprite frame
});
// 用绝对路径加载设备存储内的资源,比如相册
var absolutePath = "/dara/data/some/path/to/image.png"
cc.loader.load(absolutePath, function () {
// Use texture to create sprite frame
});
资源的依赖和释放
- 加载完资源之后,所有的资源都会临时被缓存到
cc.loader
中 - 注意资源之间的依赖性
- JavaScript 的垃圾回收是延迟的,释放资源后,垃圾回收还没开始,又重启请求资源情况
-> 判断资源是否有多份
-> 检查游戏逻辑
// 直接释放某个贴图
cc.loader.release(texture);
// 释放一个 prefab 以及所有它依赖的资源
var deps = cc.loader.getDependsRecursively('prefabs/sample');
cc.loader.release(deps);
// 如果在这个 prefab 中有一些和场景其他部分共享的资源,你不希望它们被释放,有两种方法:
// 1. 显式声明禁止某个资源的自动释放
cc.loader.setAutoRelease(texture2d, false);
// 2. 将这个资源从依赖列表中删除
var deps = cc.loader.getDependsRecursively('prefabs/sample');
var index = deps.indexOf(texture2d._uuid);
if (index !== -1)
deps.splice(index, 1);
cc.loader.release(deps);
监听和发射事件
监听事件
1、on 监听,我们还可以使用 once 方法。once 监听在监听函数响应后就会关闭监听事件。
// 监听事件on 方法一
this.node.on('mousedown', function ( event ) {
console.log('Hello!');
});
// 监听事件on 方法二 使用第三个参数 this绑定调用者
this.node.on('mousedown', function (event) {
this.enabled = false;
}, this);
// 关闭监听 off, off 方法的 参数必须和 on 方法的参数一一对应,才能完成关闭
_sayHello: function () {
console.log('Hello World');
},
onEnable: function () {
this.node.on('foobar', this._sayHello, this);
},
onDisable: function () {
this.node.off('foobar', this._sayHello, this);
},
发送事件
1、emit
cc.Class({
extends: cc.Component,
onLoad: function () {
// 注册接收事件
this.node.on('say-hello', function (event) {
console.log(event.detail.msg);
});
},
start: function () {
// 发送事件
this.node.emit('say-hello', {
msg: 'Hello, this is Cocos Creator',
});
},
});
派送事件
- 采用冒泡派送的方式。冒泡派送会将事件从事件发起节点,不断地向上传递给他的父级节点
- 直到到达根节点或者在某个节点的响应函数中做了中断处理
event.stopPropagation()
- 一个抽象类
cc.Event
对象不能直接创建,请创建cc.Event.EventCustom
对象来进行派发
// 节点 c 的组件脚本中
this.node.dispatchEvent( new cc.Event.EventCustom('foobar', true) );
// 节点 b 的组件脚本中
this.node.on('foobar', function (event) {
event.stopPropagation();
});
触摸事件类型和事件对象
触摸事件在移动平台和桌面平台都会触发
枚举对象定义 对应的事件名 事件触发的时机
cc.Node.EventType.TOUCH_START ‘touchstart’ 当手指触点落在目标节点区域内时
cc.Node.EventType.TOUCH_MOVE ‘touchmove’ 当手指在屏幕上目标节点区域内移动时
cc.Node.EventType.TOUCH_END ‘touchend’ 当手指在目标节点区域内离开屏幕时
cc.Node.EventType.TOUCH_CANCEL ‘touchcancel’ 当手指在目标节点区域外离开屏幕时
玩家输入事件
键盘事件
cc.Class({
extends: cc.Component,
onLoad: function () {
// add key down and key up event
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
onDestroy () {
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
onKeyDown: function (event) {
switch(event.keyCode) {
case cc.KEY.a:
console.log('Press a key');
break;
}
},
onKeyUp: function (event) {
switch(event.keyCode) {
case cc.KEY.a:
console.log('release a key');
break;
}
}
});
设备重力传感事件
cc.Class({
extends: cc.Component,
onLoad () {
// open Accelerometer
cc.inputManager.setAccelerometerEnabled(true);
cc.systemEvent.on(cc.SystemEvent.EventType.DEVICEMOTION, this.onDeviceMotionEvent, this);
},
onDestroy () {
cc.systemEvent.off(cc.SystemEvent.EventType.DEVICEMOTION, this.onDeviceMotionEvent, this);
},
onDeviceMotionEvent (event) {
cc.log(event.acc.x + " " + event.acc.y);
},
});
动作系统简介
基础动作
// 创建一个移动动作
var action = cc.moveTo(2, 100, 100);
// 执行动作
node.runAction(action);
// 停止一个动作
node.stopAction(action);
// 停止所有动作
node.stopAllActions();
// 开发者还可以给动作设置 tag,并通过 tag 来控制动作。
// 给 action 设置 tag
var ACTION_TAG = 1;
action.setTag(ACTION_TAG);
// 通过 tag 获取 action
node.getActionByTag(ACTION_TAG);
// 通过 tag 停止一个动作
node.stopActionByTag(ACTION_TAG);
容器动作
// 一个复杂的跳跃动画
this.jumpAction = cc.sequence(
cc.spawn(
cc.scaleTo(0.1, 0.8, 1.2),
cc.moveTo(0.1, 0, 10)
),
cc.spawn(
cc.scaleTo(0.2, 1, 1),
cc.moveTo(0.2, 0, 0)
),
cc.delayTime(0.5),
cc.spawn(
cc.scaleTo(0.1, 1.2, 0.8),
cc.moveTo(0.1, 0, -10)
),
cc.spawn(
cc.scaleTo(0.2, 1, 1),
cc.moveTo(0.2, 0, 0)
)
// 以1/2的速度慢放动画,并重复5次
).speed(2).repeat(5);
动作回调
方法名,对象,参数
var finished = cc.callFunc(this.myMethod, this, opt);
// cc.callFunc 第一个参数是处理回调的方法
// 第二个参数指定了处理回调方法的 context(也就是绑定 this)
// 第三个参数是向处理回调方法的传参
var finished = cc.callFunc(function(target, score) {
this.score += score;
}, this, 100);//动作完成后会给玩家加100分
缓动动作
var aciton = cc.scaleTo(0.5, 2, 2);
action.easing(cc.easeIn(3.0));
使用计时器
1.开始一个计时器
component.schedule(function() {
// 这里的 this 指向 component
this.doSomething();
}, 5);
2.更灵活的计时器
// 以秒为单位的时间间隔
var interval = 5;
// 重复次数
var repeat = 3;
// 开始延时
var delay = 10;
component.schedule(function() {
// 这里的 this 指向 component
this.doSomething();
}, interval, repeat, delay);
3.只执行一次的计时器
component.scheduleOnce(function() {
// 这里的 this 指向 component
this.doSomething();
}, 2);
4.取消计时器
//开发者可以使用回调函数本身来取消计时器:
this.count = 0;
this.callback = function () {
if (this.count === 5) {
// 在第六次执行回调时取消这个计时器
this.unschedule(this.callback);
}
this.doSomething();
this.count++;
}
component.schedule(this.callback, 1);
下面是 Component 中所有关于计时器的函数:
- schedule:开始一个计时器
- scheduleOnce:开始一个只执行一次的计时器
- unschedule:取消一个计时器
- unscheduleAllCallbacks:取消这个组件的所有计时器
脚本执行顺序
统一的控制脚本来初始化其他脚本
在 Player.js
, Enemy.js
和 Menu.js
中需要实现 init
方法和 update
,并将初始化逻辑放进去。
// Game.js
const Player = require('Player');
const Enemy = require('Enemy');
const Menu = require('Menu');
cc.Class({
extends: cc.Component,
properties: {
player: Player,
enemy: Enemy,
menu: Menu
},
onLoad: function () {
this.player.init();
this.enemy.init();
this.menu.init();
}
update: function (dt) {
this.player.updatePlayer(dt);
this.enemy.updateEnemy(dt);
this.menu.updateMenu(dt);
}
});
控制同一个节点上的组件执行顺序
属性检查器
里的排列顺序来控制,Move Up
和 Move Down
,排列在上的组件会先于排列在下的组件执行。
代码设置组件执行优先级
- executionOrder 越小,该组件相对其它组件就会越先执行。
- executionOrder 默认为 0,因此设置为负数的话,就会在其它默认的组件之前执行。
- executionOrder 只对 onLoad, onEnable, start, update 和 lateUpdate 有效,对 onDisable 和 onDestroy 无效。
// Player.js
cc.Class({
extends: cc.Component,
editor: {
executionOrder: -1
},
onLoad: function () {
cc.log('Player onLoad!');
}
});
// Menu.js
cc.Class({
extends: cc.Component,
editor: {
executionOrder: 1
},
onLoad: function () {
cc.log('Menu onLoad!');
}
});
标准网络接口
我们支持 Web 平台上最广泛使用的标准网络接口:
- XMLHttpRequest:用于短连接
- WebSocket:用于长连接
1.XMLHttpRequest 简单示例
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && (xhr.status >= 200 && xhr.status < 400)) {
var response = xhr.responseText;
console.log(response);
}
};
xhr.open("GET", url, true);
xhr.send();
2.WebSocket
ws = new WebSocket("ws://echo.websocket.org");
ws.onopen = function (event) {
console.log("Send Text WS was opened.");
};
ws.onmessage = function (event) {
console.log("response text msg: " + event.data);
};
ws.onerror = function (event) {
console.log("Send Text fired an error");
};
ws.onclose = function (event) {
console.log("WebSocket instance closed.");
};
setTimeout(function () {
if (ws.readyState === WebSocket.OPEN) {
ws.send("Hello WebSocket, I'm a text message.");
}
else {
console.log("WebSocket instance wasn't ready...");
}
}, 3);
使用对象池
在运行时进行节点的创建(cc.instantiate
)和销毁(node.destroy
)操作是非常耗费性能的,因此我们在比较复杂的场景中,通常只有在场景初始化逻辑(onLoad
)中才会进行节点的创建,在切换场景时才会进行节点的销毁。如果制作有大量敌人或子弹需要反复生成和被消灭的动作类游戏,我们要如何在游戏进行过程中随时创建和销毁节点呢?这里就需要对象池的帮助了。
初始化对象池
this.enemyPool = new cc.NodePool();
从对象池请求对象
// ...
createEnemy: function (parentNode) {
let enemy = null;
if (this.enemyPool.size() > 0) { // 通过 size 接口判断对象池中是否有空闲的对象
enemy = this.enemyPool.get();
} else { // 如果没有空闲对象,也就是对象池中备用对象不够时,我们就用 cc.instantiate 重新创建
enemy = cc.instantiate(this.enemyPrefab);
}
enemy.parent = parentNode; // 将生成的敌人加入节点树
enemy.getComponent('Enemy').init(); //接下来就可以调用 enemy 身上的脚本进行初始化
}
将对象返回对象池
// ...
onEnemyKilled: function (enemy) {
// enemy 应该是一个 cc.Node
// 和初始化时的方法一样,将节点放进对象池,这个方法会同时调用节点的 removeFromParent
this.enemyPool.put(enemy);
}
使用组件来处理回收和复用的事件
let myBulletPool = new cc.NodePool('Enemy');
// 传入组件名
清除对象池
象池中的节点不再被需要,我们可以手动清空对象池,销毁其中缓存的所有节点 myPool.clear();