cc 扩展在 cocos2dx C++ API 和 quick 基本模块的基础上,提供了符合脚本风格的事件接口、组件架构等扩展。
上面这是quick注释中的原话, 我根据自己的理解,这里组件分两种,一种是场景的UI组件,比如framework.cc.ui下面的类,另外一种是功能型组件,或者理解为部件,比如framework.cc.components,他们不能单独使用,往往是和其他对象相互配合。这个能使用部件对象也不是普通对象,需要经过处理,下面会详细说明。
关于这个包,有几个基类需要提前说明一下他们的关系,方便理解后续的说明。
framework.cc.components.Component 这个是quick功能型组件的基类。这个类不能直接使用,一般是使用他的子类。子类需要重新封装exportMethods_(methods)方法,一般子类封装后的方法名约定为exportMethods(methods)。这个方法是把设定好的若干组件函数绑定到目标对象上,注意,同名方法不覆盖。
framework.cc.GameObject 可以将任意一个table封装为功能型组件容器,GameObject.extend(target)。注意,在初始化的时候,这个方法被封装为全局的cc(target)。理论上一个容器可以添加多个功能型组件,实现类似多态的功能,但是实际使用并不好,下面会说到。
framework.cc.Registry 功能型组件的一个管理器,目前框架中用到的也只有Registry.newObject(name, …),会自动导入组件类并创建组件。个人感觉这个类有点废,没觉得有什么用处。
quick中封装了不少UI组件,UI组件除了本身的渲染功能,还需要提供其他额外功能,比如状态改变,事件分发等等。渲染可以做各种样式,但是基本功能是不变的,而这些功能分离出去,在管理代码、后期扩展、或者用户自己封装特定UI组件等等方便,都提供了很大的便利。所以就有了这些功能型组件,他们本身没法直接使用,但是将他们绑定到某个对象上,就等于这个对象拥有了这个组件的所有功能。如果同时将多个组件绑定到同一个对象上,那么就拥有了这些组件的所有功能。
以framework.cc.mvc.AppBase为例,源代码的构造方法里有这么一句:
cc(self):addComponent("components.behavior.EventProtocol"):exportMethods()
先用GameObject包装AppBase,使之成为一个功能型组件容器类cc(self),这样他就拥有添加组件的函数addComponent。添加组件需要输入完整的组件导入参数,也就是components.behavior.EventProtocol,这里不是完整的,是因为在framework.cc.init中已经用不完整的路径作为名称导入过了,具体可以看源码。这样AppBase就添加了一个组件,也就是EventProtocol的实例。再调用这个组件exportMethods(),就将预设好的public函数绑定到AppBase上。
<img title = 'QQ截图20150911151712.jpg' src='http://cdn.cocimg.com/bbs/attachment/Fid_56/56_460180_54b07c96e531718.jpg' >
这样,appBase:dispathcEvent(event) 与 appBase:getComponent("components.behavior.EventProtocol"):dispathcEvent(event) 意义是一样的。这样一套流程走下来,将一个组件绑定某个对象上,更方便外部进行组件相关功能调用,非常实用。
这样看起来没什么问题,如果出现同名方法,只要在对象的同名方法里调用一下组件的方法就行了。然而......
1.组件和对象的同名方法,功能完全不相干,根本不能写到一起,甚至调用的时候就必须分开调用;
2.同时添加多个组件,多个组件和对象也有同名方法,调用混乱。
这是个蛋疼的问题,提供以下几种方法:
1.一个容器只允许添加一个组件,而这个容器作为某个对象的变量对外开放使用。
以前是把某个对象包装成容器,然后添加n个组件,照这种方法,不是包装对象为组件容器,而是在对象上添加n个组件容器,然后每个容器添加唯一的一个对应组件。这样调用的时候就不能通过obj:function(params)调用,而是 obj.componentContainer:function(params)。这样不用出现同名函数冲突的问题,但是调用繁琐,还要关心取哪个组件,组件名称是什么,太不人道了,所以在这个基础上,找到下面第二种方式。
2.无论是组件和对象之间冲突,还是组件和组件之间冲突,只要有同名函数,就将这个函数在对象上重写,重写的函数里直接报错并提醒,然后将同名的函数重新再封装为其他几个函数。
这样在调用同名函数的时候,会出现报错提示,提示去使用其他几个对应的函数,然后就可以根据需求修正为调用其他函数,这样可以排除冲突问题,正常使用不同组件的不同功能。感觉好像问题解决了,就可以到此为止了?当然不是,上面的方法其实都是然并卵......
比如有个管理类,管理一堆绑定了EventProtocol的对象,统一使用EventProtocol里面的一些方法,然而这其中有个对象是用Node扩展的,恰巧Node自带dispathcEvent函数,因为同名方法不覆盖,所以实际上这个对象的dispathcEvent并不是EventProtocol的dispathcEvent,这样新的问题就又出现了,这时候总不可能让管理器去调用dispathcEvent2、dispathcEvent3什么的,那是扯淡。所以此时这种接口型的调用就不能用这个方法了,行不通。然后就有了理想化的第三种方法。
3.如果出现同名函数,在对象上重写此方法,并根据参数内容自动引导到不同组件调用,或者调用这个对象本身的这个方法,这个参数是这些同名函数的原始参数,不能额外另加参数,要不然会破坏接口的统一调用。
这是理想化的,只通过原始参数来判断到底应该调用谁,这个应该很难决定,理想很美好现实很残酷。
4.最后不是方法的方法,就是自己写这种功能型组件的时候,函数名尽量不要用那种比较通用的名称,长点没关系,好区分,另外一个对象也别绑定太多组件,尽量只用一个,这样就算重名也好区分。
再一个,用到的模块,一定要通读源码,要不然里面有坑都不知道。因为lua本身的关系,很多函数和变量都藏的很深,自定义变量和函数的时候一定要注意别重名了。
总结一下,感觉这是quick给我们画了一个看起来很有食欲但实际上并不怎么好吃的大饼。
扯了这么多,我对这一块还是很困惑,毕竟问题没有解决。不知道是不是我理解方向不对,求明白的朋友指导一下。