写在前面
用这个记录一下在使用cocos creator中的坑和解决问题的办法,一来以后再遇到此类问题有迹可循,二来方便以后面试之前看看
问题
ScrollView/ListView在节点item较多的情况下,drawcall飙升,造成严重的耗电和卡顿
解决方案
1.复用节点item
复用节点是解决ScrollView/ListView卡顿的根本方法;具体实现可以参考官方的ListView例子
update: function(dt) {
this.updateTimer += dt;
if (this.updateTimer < this.updateInterval) return; // we don't need to do the math every frame
this.updateTimer = 0;
let items = this.items;
let buffer = this.bufferZone;
let isDown = this.scrollView.content.y < this.lastContentPosY; // scrolling direction
let offset = (this.itemTemplate.height + this.spacing) * items.length;
for (let i = 0; i < items.length; ++i) {
//获取item相对view的坐标
let viewPos = this.getPositionInView(items[i]);
//如果Item已经超出view,用新的itemid对应的数据,刷新该item的显示
//最好在item中实现一个自刷新的方法,在收到超出view的通知后自动刷新显示
if (isDown) {
// if away from buffer zone and not reaching top of content
if (viewPos.y < -buffer && items[i].y + offset < 0) {
items[i].y = items[i].y + offset;
let item = items[i].getComponent('Item');
let itemId = item.itemID - items.length; // update item id
item.updateItem(itemId);
}
} else {
// if away from buffer zone and not reaching bottom of content
if (viewPos.y > buffer && items[i].y - offset > -this.content.height) {
items[i].y = items[i].y - offset;
let item = items[i].getComponent('Item');
let itemId = item.itemID + items.length;
item.updateItem(itemId);
}
}
}
// update lastContentPosY
this.lastContentPosY = this.scrollView.content.y;
this.lblTotalItems.textKey = "Total Items: " + this.totalCount;
},
2.控制节点摆放顺序,尽量避免将sprite和label穿插,以免打断合批
降低drawcall的有效手段就是合批,将很多贴图合批一次渲染,但label并不能合批,所以当sprite和label穿插时,就会打断合批,从而导致drawcall增加(即使所有的贴图都是来自一张大图),还有cocos提供的mask,也会打断合批,在使用mask的时候需要格外注意,例如mask实现圆形头像遮罩等,可以通过其他方法折中实现
错误例子:
正确实现:
当然在开发的过程中,肯定会有无法避免的节点层级问题,就仁者见仁了
问题
cocos creator中事件传递提供了node.on和node.emit,但是只能在同一个节点上传递,如果想跨节点甚至不依赖UI节点去传递事件,就比较困难了
解决方案
cc.game.on和cc.game.emit,哈哈哈,简单粗暴,但是好像性能消耗比较大,可以自己在cc或window上,声明一个全局变量,通过此全局变量来传递事件
//GlobEventProcess.js
(function () {
var list = {};
var __EventMgr = {
//注册消息
on: function (key, func, node) {
if (list[key] == null) {
list[key] = [];
}
var d = {};
d.func = func;
d.node = node;
list[key].push(d);
},
//移除一个消息
remove: function (key, func, node) {
if (list[key]) {
var data = list[key];
for (var i = 0; i < data.length; i++) {
if (data[i].node == node && data[i].func.name == func.name) {
data.splice(i, 1);
--i;
}
}
}
// list[key].push(func);
},
//移除node所有消息
removeByNode: function (node) {
for (var p in list) {
var data = list[p];
for (var i = 0; i < data.length; i++) {
if (data[i].node == node) {
data.splice(i, 1);
--i;
}
}
}
},
//移除key注册的所有消息
removeByKey: function (key) {
delete list[key];
},
//遍历通知消息
emit: function (key, arg) {
if (list[key]) {
var funcs = list[key].concat();
for (var i = 0; i < funcs.length; i++) {
if ((funcs[i].node instanceof cc.Component)) {
if (cc.isValid(funcs[i].node)) {
funcs[i].func(arg, funcs[i].node);
}
}
else {
funcs[i].func(arg, funcs[i].node);
}
}
}
}
};
window.EventMgr = __EventMgr;
})();
问题
js的this指针问题,在开发过程中,经常会遇到此this非彼this的情况,刚开始着手时很懵,无从下手。(还是js基础差的原因)
解决方案
call和apply是js常用的绑定方法,可以自行学习,我在开发的过程中,主要是跨脚本传回调的时候用的比较多,此类解决方法我一般统一在回调后.bind(this),妈妈再也不用担心我的this找不到正确的路了
//事件接收组件
reciver.js
cc.Class({
extends: cc.Component,
properties: {
avatar: cc.Node,
registerBtn: cc.Button,
loginBtn: cc.Button,
loginPrefab: cc.Prefab
},
onLoad: function () {
//此处EventMgr用上一个问题的全局事件管理类
EventMgr.on('login', this.refreshUI.bind(this), this);
},
refreshUI() {
this.avatar.active = true;
this.registerBtn.node.active = false;
this.loginBtn.node.active = false;
}
});
//事件发送组件
send.js
cc.Class({
extends: cc.Component,
properties: {
loginBtn: cc.Button,
},
onLoad: function () {
this.loginBtn.node.on('click', () => {
//此处EventMgr用上一个问题的全局事件管理类
EventMgr.emit('login');
this.node.destroy();
})
},
});
或者用ES6的箭头函数
//事件接收组件
reciver.js
cc.Class({
extends: cc.Component,
properties: {
avatar: cc.Node,
registerBtn: cc.Button,
loginBtn: cc.Button,
loginPrefab: cc.Prefab
},
onLoad: function () {
//此处EventMgr用上一个问题的全局事件管理类
EventMgr.on('login', ()=>{
this.avatar.active = true;
this.registerBtn.node.active = false;
this.loginBtn.node.active = false;
}, this);
},
});
//事件发送组件
send.js
cc.Class({
extends: cc.Component,
properties: {
loginBtn: cc.Button,
},
onLoad: function () {
this.loginBtn.node.on('click', () => {
//此处EventMgr用上一个问题的全局事件管理类
EventMgr.emit('login');
this.node.destroy();
})
},
});
问题
cc.runAction时,一个action的target只能有一个
解决方案
如果想让多个节点执行同一个动作,可以用action.clone();
let action = cc.moveTo(1,0,100);
node1.runAction(action);
node2.runAction(action.clone());
问题
安卓返回键监听
解决问题
相信很多游戏可能都有这个需求,什么弹窗打开用返回键关闭,退出游戏巴拉巴拉,反正有这个需求就是了; 先看官方文档:docs.cocos.com/creator/api…, 查找自己想用的keyCode
//响应事件
onKeyDown(event) {
switch (event.keyCode) {
case cc.KEY.back:
//处理返回键逻辑
this.processBack();
break;
default:
break;
}
},
//添加事件,一般在节点初始化时,切记一个节点只添加一次
addSysEvent() {
//只有安卓有,在此判断一下os类型
if (cc.sys.os == cc.sys.OS_ANDROID) {
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
}
},
//移除事件,注意在节点隐藏或销毁后,一定要移除此事件,因为是系统级别的事件监听,返回键按下后会通知所有的监听回调
removeSysEvent() {
//只有安卓有,在此判断一下os类型
if (cc.sys.os == cc.sys.OS_ANDROID) {
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
}
},
问题
屏幕适配,移动端开发绕不过去的问题
解决方案
首先明确几个点:
1.设计分辨率
2.适配策略
cocos creator提供的适配策略有四种,Fit Height(按高适配),Fit Width(按宽适配),和 Show All(同时勾选fit height和fit width,等比缩放显示整个场景),No Border(fit height和fit width都不勾选,这个适配模式是根据屏幕比例等比拉伸画面,
- 有两种结果:
- 当高度拉满,宽度超出屏幕的时候,裁切宽度。
- 当宽度拉满,高度超出屏幕的时候,裁切高度。
)这部分官方文档:docs.cocos.com/creator/man… 说的很清楚了,我就不再重复了,理解消化一下;
3.对齐策略
官方说的很清楚:docs.cocos.com/creator/man…
那根据上面3点,选择自己对应的解决方案就很容易了,举一个小例子说明:
在这个场景中,头像,状态(时间,电量,网络),菜单都在靠近屏幕边缘的位置,如果不借用widget,就会出现部分超出或者离屏幕边缘较远的,不符合我们预期的表现;