cocos creator 项目总结六(战斗性能优化2)

本来项目是计划上原生平台,但是中途决定还是先上小游戏平台,然后发现性能惨不忍睹。为此就只能继续开始漫漫优化之路。

1、优化不必要的ui操作

        前面文章 cocos creator 项目总结二(战斗帧同步解析)_xzben‘blog-CSDN博客 提到过的战斗设计是逻辑和ui解耦的设计方式,也就是界面层不能通过直接拿到数据层的对象去更新位置,而是通过逻辑层每个逻辑帧 dispatchMessage 给ui层去 创建/删除/更新 显示节点。一开始的设计时候觉得更新节点位置信息这些操作应该是基本无消耗的,但是现实是残酷的。节点位置更新,特别是 绑定在3d对象头上的血条这些更新位置的时候转换坐标系统这些操作目前引擎消耗还是不能忽视的。所以为了不必要的开销。我数据层通知ui层的显示更新也做类合批处理。原理就是在短线重连和追帧 甚至每一帧,逻辑层生成的ui更新message 不立即通知ui刷新。而是 先cache起来,每次派发的消息都会找之前同类型的消息去尝试合并。最后再统一通知到界面层去刷新界面。这样就能够将很多无意义的界面更新过滤省下大量时间(实践发现性能提升不止一倍)。这个和批的设计原理也比较简单。

        首先将每种更新界面的message 按功能特新分配 cacheid, 在派发过程中如果cache队列中存在相同cacheid 的消息就尝试合并,合并方式有三种,1、直接新消息替换旧消息,2、新消息和旧消息相互抵消两者都删除掉(比如 创建节点消息,和删除节点消息合并就是直接两者抵消)3、两个消息的数据内部合并,就是用新消息的里面的成员覆盖旧消息的成员(之所以不是直接替换因为有些消息每次带的数据不是全的可能分多次更新不同属性)。最终再将合批后的更新消息统一派发给界面层更新界面。这个时候已经将很多不必要的界面操作过滤了,比如多次重复更新节点位置,多次更新血条数值,创建节点又被删除掉等等。具体实现参考代码如下

export enum MessageMergeResult{
    DELETE, //两者抵消了都不要了
    REPLACE, //后来者顶替新的
    MERGE, //数据内部合并了
}

export abstract class ECSMessage{
    protected m_type : ECSMessageType;
    protected m_isValid : boolean = true;

    constructor( type : ECSMessageType){
        this.m_type = type;
        this.m_isValid = true;
    }

    public abstract getCacheId():string;
    public abstract mergeMsg( msg : ECSMessage):MessageMergeResult;
    public mergeMsgField( msg : ECSMessage, keys : string[]){
        if(msg == null) return;
        let msgMap = msg as any;
        let thisMap = this as any;
        for(let i = 0; i < keys.length; i++){
            let key = keys[i];
            let value = msgMap[key];
            if(value != null){
                thisMap[key] = value;
            }
        }
    }
}

export class CreateNode extends ECSMessage{
    ... 删除了一些不相关的代码突出重点
    public getCacheId(): string {
        return "node_"+this.uuid;
    }

    public mergeMsg(msg: ECSMessage): MessageMergeResult {
        switch(msg.getType())
        {
            case ECSMessageType.M2V_CREATE_NODE:
                {
                    return MessageMergeResult.REPLACE;
                }
            case ECSMessageType.M2V_REMOVE_NODE:
                {
                    return MessageMergeResult.DELETE;
                }
            case ECSMessageType.M2V_UPDATE_NODE:
                {
                    if(this.mergeUpdate == null )
                        this.mergeUpdate = msg as UpdateNode;
                    else
                        this.mergeUpdate.mergeMsg(msg);
                    return MessageMergeResult.MERGE;
                }
        }
        
        return MessageMergeResult.REPLACE;
    }
}

export class UpdateNode extends ECSMessage{
    public getCacheId(): string {
        return "node_"+this.uuid;
    }

    public mergeMsg(msg: ECSMessage): MessageMergeResult {
        switch(msg.getType())
        {
            case ECSMessageType.M2V_CREATE_NODE:
                {
                    return MessageMergeResult.REPLACE;
                }
            case ECSMessageType.M2V_REMOVE_NODE:
                {
                    return MessageMergeResult.REPLACE;
                }
            case ECSMessageType.M2V_UPDATE_NODE:
                {
                    this.mergeMsgField(msg, UpdateMsgMergeField);
                    return MessageMergeResult.MERGE;
                }
        }
        
        return MessageMergeResult.REPLACE;
    }
}

export class RemoveNode extends ECSMessage{
    public getCacheId(): string {
        return "node_"+this.uuid;
    }
  
    public mergeMsg(msg: ECSMessage): MessageMergeResult {
        switch(msg.getType())
        {
            case ECSMessageType.M2V_CREATE_NODE:
                {
                    return MessageMergeResult.REPLACE;
                }
            case ECSMessageType.M2V_REMOVE_NODE:
                {
                    return MessageMergeResult.REPLACE;
                }
            case ECSMessageType.M2V_UPDATE_NODE:
                {
                    return MessageMergeResult.REPLACE;
                }
        }
        
        return MessageMergeResult.REPLACE;
    }
}


export class ECSEngine{

    public dispatchMessage( message : ECSMessage){
        if(this.m_cacheMessage){
            //只合并 逻辑层到数据层的消息
            let msgType = message.getType()
            if( msgType > ECSMessageType.V2M_MESSGE_BEGIN){
                this.m_messageDispatcher.dispatch(message.getType(), message)
            }else{
                let cacheid = message.getCacheId();
                let cache = this.m_cacheMsgs.get(cacheid);
                if(cache == null){
                    this.m_cacheMsgs.add(cacheid, message);
                }else{
                    let result = cache.mergeMsg(message);
                    switch(result){
                        case MessageMergeResult.DELETE: //两者抵消了
                        {
                            this.m_cacheMsgs.remove(cacheid);
                            break;
                        }
                        case MessageMergeResult.MERGE: //消息合并到旧消息,不需要再额外处理
                        {
                            break;
                        }
                        case MessageMergeResult.REPLACE: //将旧消息替换
                        {
                            this.m_cacheMsgs.add(cacheid, message);
                            break;
                        }
                    }
                }
            }
        }else{
            this.m_messageDispatcher.dispatch(message.getType(), message)
        }
    }
}

2、优化掉不必要的显示节点。游戏中由于场景是不止一个屏幕的大小。所以有些特效或者不重要的效果节点当我们看不到的时候那它就没有存在的意义了。那这种节点在逻辑层通知界面层去创建的时候我就判断它是在可视区域外就直接过滤不要这个节点了。

3、可怕的active 操作性能消耗。这个优化点是让我很意外的。因为我们平常在设计游戏的时候往往会想着通过节点缓存池避免大量重复节点的创建带来的性能消耗。这个时候我们的设计就是将节点在不要的时候回收到缓存池然后active 设置为 false,但是cocos 是个让人忧伤的引擎,简单的active 操作竟然伴随着很高的消耗。比如 Label 组件 在active 重新设置为 true 后它竟然暴力式的去重新构造了 updateRenderData 这个过程消耗还挺高的。另外比如3D 的粒子效果也存在类似问题,重新active 的时候也需要重新 updateRenderData 或着 MaterialsParam 之类的操作,都是特别耗时间的操作。所以为了避免这些不必要的浪费我们得把有这方面不必要的 active 更新操作换种隐藏的方式。

        针对 2D 相关的内容我选择的是通过 UIOpacity 修改透明度 = 0 来隐藏节点,3D 节点直接就设置一个很大的position 让他不可能出现在屏幕内。改完性能立马又提升不少。

4、合理使用字体的 cache 模式,能用艺术字体的经历做成艺术字然后再把对应的字体的图片打成合图这样就能优化到1个 drawcall。cache 模式 BITMAP 一定要小心使用,因为它的方式是比较暴力的,如果你的文字内容是经常变化的还是不要用这个,用Char 方式更好。

5、3D 模型优化,这方面就要美术同学配合了。将面数过高的模型压模型面数,如果是场景之类的模型可以把那些看不见的面全部删掉。

6、粒子优化,粒子特效尽量使用 GPU 模式,因为我们游戏本身就是个消耗CPU的游戏可以把计算压力分担一些给GPU 平衡两者的压力。另外粒子系统本身再 cocos 真心性能不怎么样,而且粒子本身材质也是不能合并drawcall的。所以使用需要尽量减少粒子数量。

7、材质效果参数更新方式。我们做草丛系统的时候当角色进入草地需要将草地透明,一开始我是直接修改草地 的 setUniform, 后面发现这个方式会导致本身设置了 instancing 的效果失效,drawcall不能合批了,后面我改成了直接做两个材质效果,然后动态替换材质实现透明和不透明的效果。

8、读取 MeshRender 的 materials 去修改参数的时候不要直接用 materials 这个域去操作,因为这个接口会重新生成 MaterialInstance 他的过程是直接重新构造 pass 的,特别耗时间,可以选择使用 sharedMaterials 去读取, 当然需要注意的是 sharedMaterials  同一个模型全部共享的,使用需要注意,如果实在要用 materials 建议在加载节点就读取出来,否则游戏中途读取会影响性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值