Creator模块介绍—领略模块化的力量

从Cocos2d-js到Creator,一直以来都有模块裁剪的能力,可以将游戏中没有用到的功能代码,在编译输时移除,从而减少包体大小,这对H5游戏来说影响比较明显。Creator比Cocos2d-js做的更好,通过主菜单->项目设置->模块设置有一个图形化的界面,可以方便模块配置。

image.png

image.png

下面简单说一下各模块的功能和作用,以及对应的组件。

一、模块配置

在Creator的模块设置界面中,可以看到Core、Canvas、Sprite这三个模块是禁止编辑的,他们是构建Creator应用的基石,没有他们就没法构建游戏。

其实Core能不算是模块,而是一类模块的集合,里面包含了Canvas和Sprite,Label、Button等等,引擎提供的组件。

打开CocosCreator的安装路径有一个modules.json

/Applications/CocosCreator.app/Contents/Resources/engine/modules.json

此文件中描述了模块设计中界面信息,内容如下:

[
  {
    "name": "Core",
    "locked": true,
    "entries": []
  },
  {
    "name": "Canvas",
    "locked": true,
    "entries": [
      "./cocos2d/core/components/CCCanvas.js"
    ]
  },
  {
    "name": "Sprite",
    "locked": true,
    "entries": [
      "./cocos2d/core/components/CCSprite.js"
    ]
  },
  {
    "name": "Label",
    "entries": [
      "./cocos2d/core/components/CCLabel.js"
    ]
  },
  ...

浏览这个文件,可以看到一个模块通常对应一个或多个js文件。

这里我编写了一个脚本代码,通过解析这些代码路径,简单统计了一下模块文件的大小、代码行数。而且你可由些找到Creator的组件源代码,了解组件实现的具体细节,而且通过学习源码是提高开发水平的重要途径。

在Web调试中也能读到源码:

Ctrl + P搜索代码

二、Creator模块说明

根据module.json文件中的信息,我们大概了解一下CocosCreator有那些模块,这些模块提供了那些功能。

1. Core模块

功能:Core其实指的是CocosCreator下的engine/core目录,里面内容很丰富。
字节数: 0 代码行数: 0 文件名: []

2. Canvas模块

功能: 屏幕适配,作为UI根节点,为所有子节点提供视窗四边的位置信息以供对齐,另外提供屏幕适配策略接口,方便从编辑器设置。
字节数: 9059 代码行数: 285 文件名: CCCanvas.js

3. Sprite模块

功能:场景中渲染精灵,支持九宫、拉升、平铺、裁剪等功能。
字节数: 19825 代码行数: 650 文件名: CCSprite.js

4 Label模块

功能:文字标签组件,实现系统字体、fnt、艺术字的渲染。
字节数: 18686 代码行数: 599 文件名: CCLabel.js

5 Mask模块

功能:遮罩组件,为子节点提供遮罩裁剪能力支持圆形、椭圆、图像模版三种模式。
字节数: 11155 代码行数: 348 文件名: CCMask.js

6.  CCSpriteDistortion模块

功能:扭曲效果组件,用于改变SIMPLE类型sprite的渲染,只有当sprite组件已经添加后,才能起作用.
字节数: 4268 代码行数: 118 文件名: CCSpriteDistortion.js

7. LabelOutline模块

功能:描边效果组件,用于字体描边,只能用于系统字体。
字节数: 3943 代码行数: 118 文件名: CCLabelOutline.js

8. ParticleSystem模块

功能:粒子系统组件。
字节数: 29961 代码行数: 1017 文件名: CCParticleSystem.js

9. TiledMap模块

功能:TileMap地图组件,渲染tmx格式的Tile Map。
字节数: 26241 代码行数: 885 文件名: CCTiledMap.js

10. Spine Skeleton模块

功能:Spine骨骼动画渲染模块,与Spine相关的所有的类,函数,属性,常量都在sp这个命名空间中定义。
字节数: 4082 代码行数: 139 文件名: index.js

11. Widget模块

功能:Widget 组件,用于设置和适配其相对于父节点的边距,自动调整坐标和宽高,Widget通常被用于UI界面适配。
字节数: 19549 代码行数: 613 文件名: CCWidget.js

12. Button模块

功能:Button按钮组件。
字节数: 20454 代码行数: 670 文件名: CCButton.js

13. ProgressBar模块

功能:进度条组件
字节数: 9581 代码行数: 295 文件名: CCProgressBar.js

14. ScrollBar模块

功能:滚动条组件。
字节数: 11525 代码行数: 352 文件名: CCScrollBar.js

15. ScrollView模块

功能:滚动视图组件。
字节数: 56872 代码行数: 1630 文件名: CCScrollView.js

16. Toggle模块

功能:Toggle 是一个 CheckBox,当它和 ToggleGroup 一起使用的时候,可以变成 RadioButton。
字节数: 6843 代码行数: 230 文件名: CCToggle.js

17. ToggleGroup模块

功能:不是一个可见的 UI 组件,它可以用来修改一组 Toggle  组件的行为。当一组 Toggle 属于同一个 ToggleGroup 的时候,任何时候只能有一个 Toggle 处于选中状态。
字节数: 4513 代码行数: 130 文件名: CCToggleGroup.js

18. PageView模块

功能:页面视图组件,实现分页功能。
字节数: 19105 代码行数: 611 文件名: CCPageView.js

19. PageViewIndicator模块

功能:页面视图每页标记组件,常用与PageView配合使用。
字节数: 6106 代码行数: 200 文件名: CCPageViewIndicator.js

20. Slider模块

功能:滑动器组件
字节数: 7770 代码行数: 232 文件名:  CCSlider.js

21. Layout模块

功能:Layout 组件相当于一个容器,能自动对它的所有子节点进行统一排版。
字节数: 32508 代码行数: 933 文件名: CCLayout.js

22. EditorBox模块

功能:EditBox组件,用于用户进入文本录入
字节数: 19490 代码行数: 643 文件名: CCEditBox.js

23. VideoPlayer模块

功能:Video组件,用于在游戏中播放视频
字节数: 13910 代码行数: 460 文件名: CCVideoPlayer.js

24 WebView模块

功能:WebView组件,用于在游戏中显示网页
字节数: 6333 代码行数: 201 文件名: CCWebView.js

25. RichText模块

功能:富文本组件
字节数: 25732 代码行数: 772 文件名: CCRichText.js

26. AudioSource模块

功能:音频源组件,可进行音频剪辑
字节数: 9432 代码行数: 359 文件名: CCAudioSource.js

27. Animation模块

功能:Animation 组件用于播放动画。
字节数: 22206 代码行数: 652 文件名: CCAnimation.js,index.js

28. MotionStreak模块

功能:运动轨迹,用于游戏对象的运动轨迹上实现拖尾渐隐效果。
字节数: 9727 代码行数: 296 文件名: CCMotionStreak.js

29. Collider模块

功能:碰撞检测模块,包含多个文件。

require('./CCCollisionManager');
require('./CCCollider');
require('./CCBoxCollider');
require('./CCCircleCollider');
require('./CCPolygonCollider');

字节数: 150 代码行数: 7 文件名: index.js 

30. Action模块

功能:Node动作模块,例如:cc.MoveTo

require('./CCActionManager');
require('./CCAction');
require('./CCActionInterval');
require('./CCActionInstant');
require('./CCActionEase');
require('./CCActionCatmullRom');

字节数: 191 代码行数: 6 文件名: index.js

31. Audio模块

功能:cc.audioengine是单例对象,主要用来播放音频。
字节数: 14543 代码行数: 528 文件名: CCAudioEngine.js

32. Graphics模块

功能:绘图组件,提供绘制线条、矩形、圆形等绘图方法,它对应cocos2dx上的DrawNode。
字节数: 1768 代码行数: 42 文件名: index.js

33. DragonBones模块

功能:DragonBones动画模块。
字节数: 2832 代码行数: 105 文件名: index.js

34. Physics模块

功能:物理引擎,使用Box2d
字节数: 955 代码行数: 32 文件名: index.js

35. StudioComponent模块

功能:CocosStudio资源转换模块,构建时默认会去除。
字节数: 6430 代码行数: 267 文件名: CCStudioComponent.js

36. RenderTexture模块

功能:纹理渲染模块。
字节数: 29687 代码行数: 858 文件名: CCRenderTexture.js、CCRenderTextureCanvasRenderCmd.js、CCRenderTextureWebGLRenderCmd.js

37. Chipmunk模块

功能:Chipmunk物理引擎。
字节数: 173736 代码行数: 6183 文件名: chipmunk.js

38. Camera模块

功能:摄像机在制作卷轴或是其他需要移动屏幕的游戏时比较有用,使用摄像机将会比移动节点来移动屏幕更加高效。
字节数: 10358 代码行数: 383 文件名: CCCamera.js

39. Intersection模块

功能:碰撞检测辅助类,用于测试形状与形状是否相交
字节数: 8122 代码行数: 334 文件名: CCIntersection.js

三、模块化的探究

CocosCreator让我对模块化又有了更多的认识和理解。乐高积木大多数人都玩过,没玩过至少看到过吧!一个个标准的积木方块,可以拼出变化无穷的造型,令人爱不释手。

还有我们现在的电脑,有主板、CPU、内存条、硬盘、显示器等部件,通过规范的接口将他们连接拼装起来,你只需要会使用这些部件的功能,具体怎么实现的可以不用管太多。

对于软件开发来说,对于模块化的声音一直不绝于耳,模块化本质到底是什么呢?

1. 分工

image.png

对模块化的论述可以追溯到亚当·斯密所说的:模块化最原始的形式就是分工。
这里分享一个亚当·斯密《国富论》中阐述分工的经典故事:

亚当·斯密的从事教学的大学所在地——格拉斯哥又是当时苏格兰的工业中心,制铁工业和纺织工业都很发达,这使斯密有可能实地观察工业区的经济,为他进行写作提供依据。

他考察了一个最平凡的生产大头针的工场。当时制针的过程,从绞铁条到拔细抛光,再磨细针头,装针后边的那个小球,然后包装到一个纸盒里,共18个环节。

在分工不完善的工场里,至少有一个工人师傅要干两个以上的环节。通常每天最多生产不会超过20根针,有可能每人每天仅仅生产出1根针。

但是,有一个典型的手工工场,它有18位师傅,都没受过多少教育,但他们组成一个有效的团队,分工做18样工作,每人只做一件事:
第一个工匠拉出铁丝
第二个工匠把它弄直
第三个工匠把它剪断
第四个把它磨尖
第五个把另一头磨平

这样下来,每个工人平均每天能生产4800根针。这意味着劳动生产率提高了至少240倍。

分工使每个人专门从事某项作业,可以节省与其生产没有直接关系的时间;分工有利于发明创造和改进工具。

这让我想到,很多时候,在网上看到不少招聘广告,要求一个工作了一两年的程序员,十八般武艺,样样都要会,要一个人独立负责一个项目(客户端或服务器)的方方面面。表面上看是为了减少成本,但实际中不管是在工作效率还是产品质量上可能与原来的初衷却是背道而驰。

2. 狭义与广义模块化

模块

在《设计规则模块化的力量》中提到,模块化有狭义和广义之分。

狭义模块化:是指产品生产和工艺设计的模块化。

广义模块化:是指把一系统(包括产品、生产组织和过程等)进行模块分解与模块集中的动态整合过程。

对于软件开发来说,一个项目可以分成多个模块,一个模块由多个子模块组成,一个子模块可以是一堆模块化的类、函数等组成,一个类又是由一系列的函数、变量构成。

模块化不仅可以用于生产,也能用于生产组织的过程。一个公司有多个部门,每个部分都是一个模块;一个公司也相当于一个模块,在社会活动中又有自己的分工。

3. 自律性与协作

协作

日本产业经济学者青木昌彦引用经典的制针的例子来说明模块化的含义,他给“模块”下的定义是:

“模块”是指半自律性的子系统,通过和其他同样的子系统按照一定规则相互联系而构成的更加复杂的系统或过程。

半自律性:一个模块可能刚开始只能处理简单的问题,但允许实现内部进化与创新,最终可以让整体获得较高收益。

协作:为什么模块是半自律性?因为它需要按照一定规则与其它模块协作构成更加复杂的系统。

在当我发现模块的自律性协作的两个特点时,我非常的惊讶!这不仅道出了模块的特质,也启发了我在参与社会活动中人需要具备的能力。

4. 效率

我的世界

分工、自律、协作,说了这么多模块化到底是为了什么呢?模块化是在为构建复杂系统时减少依赖,降低耦合,从而提高效率。

记得在吴军老师的《硅谷方法论中》讲到,如果要制造一张桌子或椅子,一般人会直接去做,而具有模块化思维思的人,首先会制作几个非常简单的的积木块如:木条或板子,然后将他们组装成一张桌子,就像组装乐高玩具一样。这样,你可以用这些积木块组装成桌子或椅子、床等其它家具,这样就大大提高了做事情的效率。这样处理问题就不是一个一个地,而是找出问题的共同因子,处理这一类事情 。

之前的做法是在做加法运算,而后面一种是在做乘法,比如:

1.5+1.5+1.5=4.5
1.5×1.5×1.5=3.375

前几次做加法运行是要快一些1,但做到后面,就完全不是在一个数量级的了。

1.5+1.5+1.5+ … 1.5,就算加100次也就150
1.5×1.5×1.5×1.5x … 1.5,1.5乘10次57.665, 乘100次为4.0656E17

摩尔定律告诉我们计算机硬件每18个月性能会翻一倍,而且价格还会更低,因为每一代的CPU都是在前一代的基础上研发出来的。十年前的iPhone1代与现在的iPhoneX正好10年,在性能上提升了100倍。

结语

如果将成果模块化,以及如何利用现有的模块提高我们的工作效率,是我最近思考的最多的问题。回想我所编写的代码、组件那些是可以被复用的,可以像乐高积木一样可以自由组合,而且还可以自我进化,这样的代码才更具价值!

庆幸的是在2017年年初,辗转到CocosCreator阵营,我将曾经在coco2d-js中的一些开发经验进行总结和提炼涉及:UI编程、网络、异步动画编程、MVC框架、工程自动化方面。

在UI编程方面,我的uikiller库在最近又做了一翻更新,增加了Thor类(我称之为雷神组件),使用会更加的简单,测试范例都已经更新,欢迎大家体验!
github: https://github.com/ShawnZhang2015/uikiller

英雄与魔灵

这次换一种方式聊聊Creator的UI开发,目的是想让学习那么枯燥,如果你是一个爱玩游戏的朋友相信你会很有感觉。

#一、奎特尔星球
奎特尔星球上,有着无数的英雄,身怀绝世魔力,在创世之主的统之下维护着奎特尔世界的安定与和平。

#1. Node英雄传
cc.Node是构建“奎特尔”星的基石,上至星体下至尘埃,无处不在,无论是可见的或不可见的,都由有他们默默的在支撑着。

英雄在层级管理与场景编辑器
上图层级管理器和场景编辑器中,我们看到的都是英雄Node,请由我来逐一介绍:
1. 名为Canvas的英雄,像盘古一样开天辟地化身出了一个世界,定义了世界的边界和范围。
2. Background英雄,用自己的整个身体去美化这个世界(虽然只是单一的银白色)。
3. Advanced统帅着他的部队(子节点)构建出这个的世界功能。
4. Top Left队长带领着勇士Label和Tips完成具体的任务。
5. Label小英雄显示出小分队的名字。
6. Tips小英雄显示出小分队的详细信息。

众多的Node英雄们不分大小、地位同心协作,创造出炫丽多彩的奎特尔星球。

##2. 魔灵
英雄最为骄傲的能力能是可以装佩各种神奇的魔灵(Component)。
魔灵附身的英雄

在UI开发中,魔灵们大至分为两类:光与暗

光系魔灵

可以直接在场景编辑器中感知到他的存在,常见的有:
cc.Sprite、cc.Label、 cc.Button、 cc.EditBox

暗系魔灵

一般在场景编辑器中很难感知到他的存在,当选中node节点后,通过属性检查器中才可以看到,常见的有:
cc.Canvas、cc.Widget、cc.Layout、cc.Mask

“奎特尔”星球上的魔灵是最具有魔力的生灵,他们也可以像Node英雄一样化出无数分身,并附身在Node上一起并肩作战,生死与共。

#三、 创世之主

六道仙人

在地球上有一类人,他们可以通过“奎特尔引擎”创建出各种奇特的、丰富多彩的奎特尔星球,他们被称之为Cocos程序员,也称之为奎特尔星球的创世之主
不论你是谁,只要你能驾驭奎特尔引擎,与奎特尔的英雄、魔灵们建立起联接,你就能开创出一片新的世界。

#二、英雄原力
英雄原力

要想成为奎特尔星求的创世之主,你需要了解Node英雄的一些能力和特性。
###1. 英雄三围
每一个node英雄都具有任意改变自身外型能力,以适应不同的生存环境,我们来看看英雄的三围参数。

image.png
Node有着控制自己外型尺寸的能力,通过上图我们一一介绍下这些属性。

  1. 输入框中可以为英雄取个名字(node.name),左边的复选框选中可以显示或隐藏英雄(node.active)。
  2. Position:Node在二维世界中的位置(node.x、node.y、node.position)
  3. Rotation:旋转角度,范围0~360(node.rotation)
  4. Scale:缩放比例,可以根据宽、高缩放,值相同就是等比缩放(node.scale)
  5. Anchor:位置与旋转的基准点,中心点(0.5, 0.5)左下角(0,0),右上角(1,1),(node.anchor)
  6. Size:英雄的宽、高体型大小(node.width、node.height、node.getContentSize、node.setContentSize)
  7. Color:每一个英雄都是变色龙(node.color)
  8. Opacity:英雄可以将自己透明,范围0~255 (node.opacity)
  9. Skew:扭曲角度 (node.skew)

Node英雄除了上面这些可以在属性检查器中可以看到参数外,还有几个重要的属性只能由魔咒(代码)来控制:

  1. zIndex: Node英雄要冲锋在战场的最前面,就需要有足够大的zIndex值。(node.zIndex)
  2. tag: tag是一个int属性的标签(tag、setTag、getTag),在父节点上可以使用getChildByTag获取子节点。
  3. isValid: 这是个只读属性,表示英雄是否还活着。
  4. parent: Node英雄之间通过parent建立父子关系,形成一颗树型结构。
  5. children: 一个Node小队长可以通过children获取他带领的小组成员。

###2. 英雄的感知能力

所有node都是感知型忍者

所有的英雄都能感知外界面对自己的指指点点,能过感知这些指指点点,英雄们可以做出让人叹为观止的各种反击,从而演绎现出一幕幕精彩纷呈的星球大战,感知力我将他们分为三类:
1. 触摸事件、鼠标事件:最常用的有TOUCH_END

node.on(cc.Node.EventType.TOUCH_END, 事件处理函数)
  1. 属性改变事件:当一个node英雄的属性发生变化时,他会发出信号。我们可以通过感知其它英雄的属性变化即时给予支援
this.node.on('position-changed', 事件处理函数)
  1. 自定义事件:创世之主可以自由定义事件,通过emit发送出去。
this.node.emit('事件名字符串', {参数});
...
node.on('事件名字符串', 事件处理函数)

###3. 英雄会跳舞
image.png

node是舞者action是舞步

每一个Node英雄都是一个天然的舞者,只要呼唤他的runAction,他就会为你舞动奇迹。

//移动步法
let moveTo = cc.moveTo(1, cc.p(100, 100));
//让英雄舞动起来
node.runAction(moveTo);

在这里node是就是舞者,moveTo是舞步,而舞步需要由创世之主来编排。
舞步是英雄的必杀技能之一被称之为Action,由于篇幅我们以后专门介绍舞步的设计与编排,以及单人舞步、多人舞步,舞步同步等绝技。

###4. 魔灵附身
魔灵附身
英雄另一个超能力就是“魔灵附身”,英雄已然很强大,注入魔灵后的英雄才能真正上战场,魔灵与英雄建立联接有以下几种方法:
– 利用奎特尔引擎,你可以直接召唤出装备有魔灵的英雄。
– 通过属性检查器为node添加注入魔灵。
– 使用魔咒(addComponent)为node注入魔灵。
– 创世之主还可以自己制造魔灵,将其附身在英雄身上。

要成为奎特尔的创世之主,必须清楚英雄们的能力,并能灵活运用,组织他们相互协作,才能战胜敌人。

#三、魔法英雄联盟
Node是忍者,尾兽是魔灵
奎特尔星球上虽然有Node英雄们,但英雄们有一个问题,他们自己不会行动,必须靠魔灵们驱动node完成任务。
不同的魔灵注入到node,就可以让node具有不同的外表、特性和能力。
##1. 光系魔灵
前面提到过,光系魔灵可以直接在场景编辑器看到它们,这里我们介绍几个光系魔灵的代表。
###cc.Sprite
Sprite魔灵可以说是众魔之首,在一场战斗中不论是重要度和规模数量都是其他魔灵不可企及的。Sprite主要特点是通过美化node,色诱敌人,扰乱其心使其沉迷不可自拔。
###cc.Label
魔灵中的第二把交椅应该属于Label,Label在战斗中将node的躯体化身为文字,诱敌深入。cc.Label还有一个姐妹是cc.RichText。
###cc.Button
Button老三,当敌人触摸到Sprite所装扮的node后,Button则顺势摆出一娇羞的动作(按钮动画:图片切换、颜色切换、缩放切换),并通过事件函数报告敌军行踪。
###cc.EditBox
老四EditBox,通常也是与Sprite配合,诱使敌人输入文本,从而窃取情报(最为常见的是用户名、密码)。

色诱术与幻术
##2. 暗系魔灵
暗系魔灵在场景编辑中是很难直接看到他们的,他们身藏在Node英雄与光系魔灵的背后,用自己独特的能力为整个战事服务。

火影-暗部忍者

###cc.Widget
Widget魔灵虽然不能被看见,但它可以帮助node在不同战场确定自己的位置(屏幕适配、相对定位),如果一只队伍要在不同尺寸的战场上作战,那Widget绝对少不了,特别是在上层Node节点。
###cc.Layout
Layout魔灵像是一个严格的教官,可以约束node下的小分队(子节点)以纵向、横向、网格方式进行自动排列,以及间距和边距。还可以根据子节的排列改变自己所附身的node的大小。
###cc.Canvas
Canvas魔灵为整体战场划出界,限并化身出一道结界,与其他装备有Widget的魔灵让战场更加的和谐。

奎特尔引擎还提供了大量的其他魔灵,按奎特尔的传统将他们分为:

UI系、渲染系、物理系、碰撞系、用户系

我们这里介绍的光系、暗系其实是属于UI、渲染系中的成员。更为强大的是创世之主可以自己创造魔灵,集从魔之力面对奎特尔星球来范之敌。
成为六道仙人自创魔灵

Creator组件编码心得(下)

这次是《组件编码心得》的最后一篇,第一次我们讲到将组件分为两大类:功能型控制型;第二篇介绍了功能型组件与控制型组件的编码上需要注意的地方,最后还提到控制型组件与预制件的组合形成独立模块,这次分享我在预制件上编码、编辑时的一点心得。

1. 预制件与控制组件的关系

简单梳理一下配套的预制件与控制型组件的关系是:

肉体与灵魂
显示与控制

预制体由1~n个节点构成,就像人体的骨骼,挂载到各个节点的功能型组件则为血脉、神经以支撑整体模块的运作,而控制组件就是这个模块的灵魂。

还有一种更有意思比喻,预制件与控制组件是朋友与朋友的关系,到底那男女如何对应大家可以思考一下,欢迎留言讨论!

2. 控制组件编码心得

其实前一篇只介绍了控制组件的对内、对外要点,在编码上具体要如何去做没有细说,请看下图:

控制组件编码心得

3. 预制件编辑心得

上面讲的是控制组件的编写,我们再来看预制件的编辑,需要注意些什么:

预制件编辑心得

  1. 配套的组件与预制件文件同名、放在同一路径是为了方便管理,要走一起走,要留一起留下,天生一对!看下面的例子:

预制与组件同步

将有关联的一组预制件与组件脚本放在一个路径下,相互配套的则命名一至。

  1. 还有一点就是将控制组件挂载到预制件的根节点上,最好只挂载一个组件,由它来统领当前预制件,看下图:
    组件挂在预制根上

上图中预制体文件名预制体根节点名字组件的名字三者保持一至,当把这个预制实例化出来时,在编辑器上直接可看到它的控制组件,继续看图:

实例化预制时方便看到组件

在使用代码实例化预制件时,可以用预制体的名字索引到匹配的组件对象,看下面代码:

实例化预制,获取控制组件

  1. 最后节点命名要有意义、保持清晰层级关系这些是为了让预制件经后可维护,一套规范化的节点命名规范还是很有必要的。

我自己习惯以下划线”_”为前缀,在代码中会获取这类节点,将规范告诉UI编辑人员,遇到以下划线”_“开头的节点不需要随便删除修改,其它非下划”_“的节点可以随便操作。

保持稳定的UI树结构是因为控制型组件中会使用到getChildByName、cc.find、getComponent等函数来获取子节点,所以节点树不能随意改动,如果要修改同时也需要修改组件代码。

4. 小结

本篇主是介绍组件与预制体的结合,这也是《组件心得三部曲》的最后一篇分享。单纯写好代码并不是最重要的,探索如何高效率、高质量产出代码的方法提升生产力才是关键,且更具价值,希望我的一点经验能起到抛砖引玉的作用,欢迎留言讨论!


果觉得公众号上的文章对您或您身边的朋友有帮助,请分享给他们,愿我们一起成长!

image

Creator组件编码心得(中)

接上次教程中的内容,讲到功能型组件与控制型组件,这里再简单回顾一下他们的要点:
功能型&控制型
1. 功能型组件:以装饰宿主节点为己任,常用的有Sprite、Labe、Widget属于这类。
2. 控制型组件:管理和控制子孙节点,比如:ScreollView、ToggleContainer,它们内部是由多个子孙点节点组合而成。

今天分享我在组件编码上一点经验,供大家参考。

1. 功能型组件设计思路

核心:以装饰宿主节点为己任

具体要怎么去做呢?我下面整理了三个要点:
1. 专注自身节点和当前组件对象自己,尽可能少的去控制其它的对象。尽可能少不是说不能控制,比如cc.LabelOutline,它需要依赖cc.Label组件控制Label的渲染;物理碰撞组件需要依赖刚体组件。
2. 谨慎设计properties属性,减少对外部组件、节点、资源的依赖,也就是说对游戏设计师无用的属性都不要暴露到属性面板上。组件暴露的接口不应该是程序员相当然的,而是对游戏内容设计相关的属性才应该暴露到属性面板上。
3. 功能型组件内部代码中杜绝对外部场景树的假设,避免出现cc.find、getChildByName等方法获取外部节点。

功能型组件一般会以纯脚本形式存在,如果做到了以上三点,我们编写的组件就可以像引擎内置的组件一样可以被随处使用了。甚至还可以跨越不同的项目,Shawn在CreatorPrimer配套代码仓库中的组件大多都是这类,供大家参考:https://github.com/ShawnZhang2015/CreatorPrimer.git

在实际项目中,不可避免的会存在有相互依赖的组件,比如:上面代码中的几个物理组件。 Shawn建议将有相互依赖的组件代码,归类存放同一路径方便管理。

功能型组件,我们提出了不少制约条件,它只能解决功能点的问题,
而不能解决,业务流程、相对复杂逻辑问题。这些问题的处理办法
我们需要使用控制型组件来完成。

2. 控制型组件设计思路

核心:管理和控制子孙节点

控制型组件,他的职责没有像功能型组件那么单纯,还需要从他们两方面来进行说明。

控制型组件对内要点

控制型组件对内要点

控制型组件对内它管理和控制子孙节点,这是划定了他的控制的范围,不是兄弟、父级组件。其次,控制型组件管理的手段是调用子孙节点上的组件方法、属性或监听子孙节点发出的事件,尽量不参杂功能细节上的代码,这些应该交由子孙节点或组件完成。

控制型组件对外要点

内部安定好,我们看怎么面对外部,看下图:

控制型组件对外要点

站在更高的层次去看控制型组件,它也可以被上层控制组件所管理,从上层视角看控制型组件也是功能型组件,解决点上的问题,比如最常见的:cc.ScrollView。

控制型组件同样遵守部分功能型组件的一些规则:
1. 不要暴露内部节点和组件,也就是说,不要为了在代码中访问内部子节点或组件方便,将它们设计成properties属性,他们对游戏设计师没有任何帮助,反面形成了干扰,这里需要关注的是封装性。
2. 控制型组通常会与预制件结合形成独立的模块,并能与其它组件相互组合嵌套,形成更复杂的业务模块,以达到模块化能力。

以这种方式设计组件,尽可能为项目生产出一个个相对独立的模块,再以各个模块搭建完整的游戏。

3. 小结

本篇主要介绍了功能型组件与控制型组件的一点心得,最为主要的还是对面向对象设计原则的应用。下一次继续为大家分享,控制型组件与与预制件编辑的一些心得,欢迎关注。


如果觉得公众号上的文章对您或您身边的朋友有帮助,请分享给他们,愿我们一起成长!

image

Creator组件编码心得(上)

Cocos Creator的核心是组件化,如何编写出高质量的组件代码值得程序员们不断探索,Shawn今天分享一点组件编码的心得供大家参考:“怎样才是一个合格的组件?”。

1. 组件编码常见问题

Shawn在2年的Cocos Creator项目经验和案例中总结了两条组件编码问题:
1. 滥用properties属性:把暴露到编辑器上的组件属性当成成员变量的一种实现方式;或将properties属性当成访问外部节点、资源的便利的通道。不必要暴露的属性,为上层使用者造成负担。
2. 场景树结构假设:组件代码中存对场景树的硬编码,导致组件只能工作在这种特定的场景树结构下,失去了重用能力,同时也限制了场景树不能轻易变量动。

上面两个问题带来的后果是:组件与组件组件与外部节点组件与资源组件与场景树结构形成高度耦合,如下图所示:

高度耦合的组件设计

组件与外部对象产生了千线万缕的关系,这样的设计让组件、界面都完全动弹不得,完全背离了组件化开发的本质,陷入了高度耦合的泥沼之中。

2. 合格组件参考标准

怎样才算是一个合格的组件?

这个问题困扰Shawn很长一段时间,其实答案近在眼前,那就是:模仿Cocos Creator内置组件,以引擎内置组件为参考标准。

Cocos Creator的内置组件绝大部分都是可通用的,可以挂载到任意节点,这里简单总结三点:

  1. 简单易用:程序员要将设计师看见是你的客户,提供给他简单好用的组件。
  2. 复用性强:编写一次可以在更多的地方使用,解决普遍性问题。
  3. 易于测试:不管是程序员还是设计师都要能方便的营造组件测试预览环境。

有了好的参考的标准,就有了行动的指南针,接下来看内置组件给我们的启发。

3. 组件的类型

之前Shawn的教程中就提到,组件分为两类:神器与结界。随着教程的不断升级,Shawn也在思考使用更为贴切的用词,庆幸得到引擎组大神们的帮助,规范用词,将两类组件定义为:功能型组件控制型组件,请看下图:

功能型&控制型

  1. 功能型组件:以装饰宿主节点为己任,常用的有Sprite、Labe、Widget属于这类。
  2. 控制型组件:管理和控制子孙节点,比如:ScreollView、ToggleContainer,它们内部是由多个子孙点节点组合而成。

在编写自定组件时,需要明确我们是要提供什么类型的组件去解决问题,比如我们教程Demo中的:节点ZIndex控制、节点可拖动、点击节点切换图片,它们都是功能型组件,通常是一个纯组件脚本文件。

在项目中,我们做的:提示对话框、玩家头像、背包道具,它们通常是由背景、前景、图片、边框、文字等等节点构成,我们就需要为它们使用定制各自的控制型组件脚本。

功能型组件解决“点”上的问题,控制型组件解决“线”“面”上的问题,它们之间又可以相互嵌套、组合从而解决“体”上的问题。

4. 小结

本篇教程主要是分享Shanw在组件编程中发现的问题,思考“怎样才是一个合格的组件?”。探索编写合格组件的指导思想,总结了功能型控制型两类组件模型,供大家参考。

下一次我们再继续这个话题,如何去编写简单易用、复用性强、易于测试的组件,具体说明功能型和控制型组件的编码心得。

编写高质量的组件的目的是为了提高开发效率和产品质量,在这条道路上任重道远,大家一起努、加油!


奎特尔星球

触摸事件冒泡

这两天正在愁公众号写点什么,打开微信看到uikiller用户「悦雨」遇到了一个问题:

地图拖动与子节点触摸事件产生冲突,表现为在子节点上拖动,地图不能动

一句话不太好描述问题,在征得「悦雨」同意后,将这次交流的内容截图出来:

第一话

问题描述

第二话

ScrollView解决方案

在与「悦雨」的交流过程中,我用ScrollView+TileMap+Button+AudioSource花了五分钟做了一个小测试,将TiledMap放在ScrollView中,在TiledMap中又放值了一个按钮,验证了一下曾经的经验是否任然有效,结果是OK的,于是将测试场景发给了「悦雨」。

第三话

结果是OK的,于是将测试场景发给了「悦雨」同学,但ScrollView不是想要的,继续聊这个问题:

第四话

不想用ScrollView,还有什么方案呢?触摸事件捕获!继续对话:

快速原型

有了这个案例今天就以这个地图场景为例,看看不写代码,利用引擎内置组件,如何快速实现一个原型或组件功能测试 ,请看下面视频:

从源码中学习

为什么在ScrollView中拖动,不会触发子节点的TOUCH事件呢?先看下ScrollView上的一个关键属性:
ScrollView.png

有了Cancel Inner Event这个线索,我们直接从ScrollView组件源码入手,看看它是什么实现的。

以cc.ScrollView组件为例,看如何定位组件源码:
1. 使用Chrome浏览器启动游戏预览
2. 打开Chrome DevTools工具
3. 键盘快捷键:ctrl + p 或 cmd + p
4. 输入:ccscrollview (引擎组件原文件名公式:cc + 组件名)
5. 从显示的列表上找到要查看的源码文件

选择CCScrollView.js文件,自动跳转到Sources标签,打开文件内容,键入ctrl + f 或 cmd + f 在当前文件中搜索:cancelInnerEvents,找到关键代码:

ScrollView源码.png

  1. 可以看到976行中,当this.cancelInnerEvents变量为真可能会执行到下面的代码,设置成员变量this._touchMoved = true
  2. 再看1006行_onTouchEnd函数,在这里判断了_touchMoved这个变量,停止TOUCH_END事件的传播,这样子节点的触摸事件就不会被触发了
  3. 993行_onTouchMoved函数最后一行代码this._stopPropagationIfTargetIsMe(event)它是在有条件地停止TOUCH_MOVE事件的传播。

通过上面的分析,再通过断点跟踪,在ScrollView和Button组件中分别打上断点,我们在Button组件上做点击,ScrollView组件的_onTouchEnded居然先被断下来,它是怎么做到的呢?

在CCScrollView.js源码中搜“TOUCH_END”关键字,找到TOUCH事件注册的代码:

image.png

看看这里有与自己平时注册TOUCH事件有什么不同?相信你已经发现了,关键在最后一个参数:useCapture,用于是否捕获子节点事件,又称之为向下冒泡(默认是向上冒泡),下面以TOUC_END事件为例,简单说明一下:

this.node.on(
  cc.Node.EventType.TOUCH_END,   //触摸事件类型
  this._onTouchEnded,            //事件处理函数
  this,                          //事件处理函数的this上下文(使用箭头函数时通常被省略)
  true                           //是否捕获子节点Touch事件
);

为了帮助大家更好地理解,我做了个简单的小组件,请看代码:

cc.Class({
    extends: cc.Component,
    properties: {
        useCapture: false, //是否启用捕获
    },
    onLoad () {
        this.node.on(
            cc.Node.EventType.TOUCH_END,
            () => cc.log('touchend', this.node.name), //测试时观察日志输入出
            this, 
            this.useCapture
        );
    }
});

把这个组件挂到两个父子关系的节点上,在父节点上开启捕获,看下面截图:

运行点击红色节点,看看日志输出:

从日志中看到白色节点先响应,然后是红色节点,我们把白色父节点的UseCapture关闭,再看看日志输出:


这次是红色子节点先响应,白色父节点后响应,更多细节可以参考Cocos Creator官方文档:https://docs.cocos.com/creator/manual/zh/scripting/internal-events.html?h=%E5%86%92%E6%B3%A1

还有对应的官方范例:TouchPropagation

题外话

这次除了教程,还想再聊一个事情,经常会有同学通过微信、QQ、公众号向Shawn咨询问题,首先感谢大家对shawn的信任,如果是在自己的能力范围内且对大家帮助的内容,Shawn一定真诚对待,这也正是「奎特尔星球」内容的重要来源。但是因为个人能力和时间有限,不是每一个人的问题Shawn都能解答,还望大家见谅。

微信、QQ很容易让人在工作时分心,一般我在做事的时候会将手机静音或离远一点,公众号上偶尔也收有留言,但有时会忘记去公众号上查看,超过24小时的留言,看到了想回复也无没办法,很是无奈。为了能把公众号做好,Shawn特地定制了一个邮箱:shawn@creator-star.cn,如有问题讨论或投稿欢邮件联系,公众号需要大家的支持和帮助,愿我们一起成长!