前段时间一直在更 vue2的源码系列,最近换了换口味,学了一下 cocos
,照猫画虎的写了一个「挑战1024」小游戏。
学习一门新语言或者新框架其实就是一个堆时间的过程了,整个过程就是结合已有经验进行不同的猜测,然后验证,搞不定就去官网或者搜索引擎找答案,99.9%
的问题应该都能找到。
cocos
网上很多是视频教程,虽然对新手友好,但是信息密度太低了,这里我总结一下 cocos
专有的或者不太符合直觉的一些点,前端的同学看完以后能更快的进入 cocos
的开发中。
建议先跟着官方的 快速上手 先一步一步实现一个小游戏,再读下边的文章效果会更佳。
介绍
cocos
提供了游戏引擎,一些常用的操作,碰撞检测、重力模拟、变换位置、旋转、缩放、粒子系统等都可以通过配置一键实现,游戏引擎最终会帮我们把界面渲染到 canvas
节点上。
因为是渲染至 canvas
,当然很自然的可以支持跨端,一套代码可以编译至 h5
、微信小游戏等平台。
同一个功能不同平台之间有不同的 api
,比如 localstorage
的使用会有所不同,cocos
会帮我们在上层抹平,只需要按照 cocos
的语法编写,编译的时候选择相应的平台就会转成对应平台的 api
。
编辑器
cocos
开发和平常的前端开发不太一样,它是代码结合 UI
拖拽来实现的,通过拖拽我们可以快速的布局、添加组件、设置属性等。
基于此,项目和编辑器就有了强绑定的关系,如果下载别人的项目,还需要下载相应的编辑器。
打开项目的时候需要选择相应的编辑器。
当然,如果编辑器差的版本比较小,Cocos
也可以帮我们自动升级项目的编辑器版本。如果是 2.x
升到 3.x
就会有 break changes
,需要手动进行一些代码的兼容。
ps
:MAC
M1
版本不支持 2.4.5
以下的版本。
场景/Scene
游戏的 ui
、逻辑都挂载在某个场景(Scene
)下,可以在资源管理器右键创建场景,然后双击打开。
接下来我们就可以在当前 Canvas
添加各种节点和代码逻辑了。
游戏如果有多个页面,可以新建多个场景各自维护。
ps:如果从导入网上下载的 cocos
项目,场景不会自动加载,需要双击一下场景然后再预览。
节点/Node
我们可以通过右键创建节点,除了空结点,还帮我们预设了其他的很多节点,比如 Label
、Button
等。
节点是树状关系,每个节点可以得到它的父节点,也可以得到它的子节点。
比如我们可以通过 getChildByName
得到它的子节点。
1 | this.node.getChildByName("message"); // 得到相应的 Node 节点 |
通过 this.node.parent
拿到它的父节点。
组件
一个空结点只有一些位置、大小属性。
我们可以在 Node
节点上挂载一些组件让 Node
拥有样式和功能。
Label 组件
如果我们创建一个 Label
节点,会自动挂一个 Label
组件。
通过 Label
组件我们可以设置文案 、字体大小等,展示到场景中的就是一个普通的 Label
。
图片组件
我们可以通过将「资源管理器」中的图片拖动到「层级管理器」中生成一个带背景的 Node
节点。
拖过去之后会生成一个带有 Sprite
组件的节点,并将该图片设置为 Sprite Frame
属性的值,这样这张图片就会展示到场景中了。
如果想要更改图片,只要把 Sprite Frame
属性清空,重新拖一个图片上去即可。
脚本组件
这个是最重要的,我们可以编写游戏逻辑,设置一些点击监听、节点之间联动等逻辑,然后挂到 Node
节点上。
先新建一个 js
文件,会自动帮我们生成带有生命周期的一些代码。
双击打开新建的 js
文件,我们可以把文件和 VSCode
关联,用 VSCode
进行代码的编辑。
1 | // Learn cc.Class: |
properties
是脚本组件的属性,写在这里的属性可以在 Cocos
的界面上看到。
比较重要是 OnLoad
和 update
两个生命周期,OnLoad
会在组件渲染前进行执行,这里我们可以进行一些初始化的操作,update
生命周期会在每一帧渲染前执行,这里我们就可以更新节点的位置让一些节点动起来。
文件编写好以后,我们可以以组件的形式逻辑挂载到相应的 Node
节点上。
Widget 组件
这个比较简单,它可以设置和边界的相对距离。
碰撞组件
两个 Node
节点相撞,我们可以根据它们的坐标手动进行判断,也可以在 Node
节点上挂载碰撞组件,设置它们的分组,然后在脚本组件中增加 onCollisionEnter
回调函数即可。
添加 BoxCollider
组件。
设置 Node
中的 Group
属性。
Group
我们可以手动进行管理,并且设置哪些 Group
产生碰撞。
接下来还需要在游戏最开始的时候开始碰撞检测,可以给层级节点中的 Canvas
节点添加一个用户脚本组件 game.js
,然后修改脚本组件的 OnLoad
中调用下边的方法。
1 | const collisionManager = cc.director.getCollisionManager(); |
最后在相应的 Node
节点的用户脚本中添加 onCollisionEnter
回调函数进行碰撞后的逻辑即可。
1 | onCollisionEnter(other) { |
通过回调参数 other
可以拿到碰撞的节点。
刚体组件
这里通过刚体组件我们可以实现物体受到重力的效果。
首先给节点添加一个 RightBody
组件,并且将类型设置为 Dynamic
。
和碰撞组件一样,我们在 Canvas
对应的用户脚本组件的 OnLoad
中调用下边的方法开启重力模拟即可。
1 | const instance = cc.director.getPhysicsManager(); |
这样相应的节点就会受到重力的作用了。
动画组件
在层级管理器选中相应的节点,点击「动画编辑器」,然后添加一个 Animation
组件
接着添加一个 Clip
,并进行编辑,设置动画的关键帧等,有点像 photoShop
里的动画编辑器。
保存后将新建的 Clip
拖到到对应的属性上即可。
防穿透组件
button
被弹窗盖住,此时 button
依旧会响应到点击时间,此时可以通过给弹窗增加 BlockInputEvents
防止点击穿透。
需要点击的时候激活,关闭的时候取消激活。
1 | this.node.getComponent(cc.BlockInputEvents).enabled = true; // 点击的时候激活 |
坐标系
设置的 positon
是在父节点坐标系下的位置。
如果它有子节点,它的子节点设置的 positon
就是基于上边的红线和绿线为坐标轴进行排布。
脚本组件属性
我们可以脚本组件中添加一些属性。
1 | properties: { |
这样在 cocos
编辑器中我们可以通过拖动进行属性的初始化。
值的注意的是,如果我们在 properties
外边写属性,比如下边的 num
。
1 | cc.Class({ |
此时我们在 onLoad
打印该值只会是 undefind
,如果想在当前实例上挂载属性,我们可以选择在 onLoad
中进行值的初始化。
1 | onLoad () { |
这里需要注意的是如果改变脚本代码,保存后我们需要重新切到 Cocos
的编辑页面 才会重新进行编译。
Prelab
节点可以在编辑器生成,当然也可以通过代码动态生成。对于需要重复生成的节点,我们可以将它保存成 Prefab
(预制)资源,作为我们动态生成节点时使用的模板。
做法就是在「层级管理器」随便新建一个 Node
节点,并且添加所需要的组件和自定义的脚本组件,最后将该 node
节点拖动到「资源管理器」即可。
之后我们就可以层级管理器中刚新建的节点删除。当然,为了后续方便编辑,该 Node
节点也可以保留,但需要将其放到画面外,并且将脚本组件取消勾选一下,不执行逻辑。
有了预制资源后,我们可以通过下边的代码来动态生成 Node
。
1 | let newStar = cc.instantiate(this.starPrefab); // 根据预置资源生成 node 节点 |
对于画面中移动的 node
,当移出画面后我们可以进行重复利用,这里可以引入 NodePool
,出画面后加入节点池,需要的时候再从里边拿。
1 | this.pool = new cc.NodePool(); |
通过节点池我们可以节省内存的开销。
Node 和 组件
Node
和组件的关系最开始的时候有点懵逼,慢慢的调试后大致了解了,下边讲一下我的理解。
在定义 properties
的时候我们需要定义对象的属性,它可以是 type: cc.Node,
,也可以是自带的组件类型 type: cc.Label
,也可以是我们定义的脚本组件类型,可以先将编写的脚本代码引入 const Bird = require("Bird");
,然后将其作为一种类型 type: Bird
。
一个节点属于复合类型,它既是本身的 cc.Node
类型,如果添加了相应的组件,它也是相应的组件类型。
以下图为例,它既是 cc.Node
类型,也是 cc.Label
类型,还是 test
类型。
下边以动态修改 Label
的值,讲一下 Node
和组件之间的关系。
首先新建一个 canvas
的脚本组件 game.js
,将该组件挂载到 canvas
节点中。
game.js
中添加一个 label
属性,类型为 cc.Node
。
1 | cc.Class({ |
选中 Canvas
节点,将 FirstLabel
节点拖动添加的属性中。
虽然 firstLabel
属于三种类型,但因为我们定义的类型是 cc.Node
,因此拿到的是一个 Node
对象,我们打印看一下。
1 | onLoad() { |
如果想要在运行的时候改变当前节点的位置,调用 setPositon
即可。
1 | cc.Class({ |
如果我们想改变组件的文案,我们需要先通过 getComponent
拿到 Label
组件的实例对象,然后更新 string
属性即可。
1 | onLoad() { |
初值设置的是 设置文案
。
运行起来会发现是我们在 onLoad
中设置的值。
当然,我们也可以在开始的时候将组件类型设置为 cc.Label
,这样我们开始拿到的就是 Label
实例对象,就不需要再通过 getComponent
方法了。
1 | cc.Class({ |
改为代码后我们重新拖动,更新下属性的值。
那么如果我们想改变 node
位置该怎么办呢?
获得的组件实例中有一个 node
属性,我们可以直接拿到当前的 node
对象实例,然后继续调用 setPosition
就可以了。
1 | cc.Class({ |
为了更深刻的理解,我们再绕一下,实现通过当前节点的 Node
,调用自定义脚本组件的方法,来动态修改 Label
的值。
首先编写自定义组件的代码,提供一个方法
1 | // test.js |
当前脚本添加到相应的属性中。
接着我们只需要在 canvas
的脚本组件中调用 getComponent("test")
拿到上边的脚本对象实例,调用 setLabelValue
方法即可。
1 | cc.Class({ |
小结一下,使用对象的时候,我们需要明确当前是 cc.Node
类型,还是某种组件类型,每一个种类型都有自己的方法。
如果想从 cc.Node
对象中拿到相应的组件,调用 getComponent
方法即可。
如果想从组件中拿到 cc.Node
类型,不管是自带的组件,还是自定义的脚本组件,可以直接通过 this.node
拿到当前的 node
实例对象。
显示隐藏
最直接就是设置 node
对象的 active
属性即可。
1 | cc.Class({ |
上边的方式类似于 vue
的 v-if
,会直接把节点销毁掉。
如果想保留节点,实现 vue
的 v-show
,我们可以设置 opacity
透明度属性弯道实现,只需要将值设置为 0
实现隐藏。
1 | cc.Class({ |
这里需要注意的是,虽然通过透明度可以隐藏组件,但是此时的点击事件还是存在的,需要处理一下。
编译
编译的时候我们选择微信小游戏,填写 appId
,编译完成后通过微信开发者工具导入 build
出来的文件就可以了。
菜单 ->项目 -> 构建发布:
我们可以设置初始场景、设备方向等。
需要注意的是,微信主包有 2M
大小的限制,如果预览的微信小游戏遇到超包的情况,我们可以将没用到的组件在编译设置中去除。
菜单 -> 项目 -> 项目设置 -> 模块设置:
微信小游戏排行榜
微信为了防止好友的关系链泄露,提出了一个子域的概念,在子域中可以调用 wx.getFriendCloudStorage
方法拿到好友数据。
为了实现排行榜,我们需要再创建一个空项目,实现排行榜的显示逻辑,和正常项目开发是一致的。
添加 message
回调函数,供主项目调用。
1 | private onMessage(msg: any) { |
编译的时候需要选择 微信小游戏开发数据域
,名称自己定义,我写的是wxSubContext
,路径选择之前项目编译的文件夹。
然后在主项目中我们需要添加一个空节点,并且添加一个 SubContextView
组件,将这个节点作为排行榜项目的容器节点。
如果想要调用排行榜的方法通过 postMessage
。
1 | wx.postMessage({ |
编译的时候指定一下排行榜项目之前设置的名称 wxSubContext
。
上边就是实现排行榜的整个逻辑了,详细的可以参考 这篇文章,相应的 代码仓库,clone
下来可以直接用。这个项目的 cocos
编辑器是 2.3.3
,如果升级到 2.4.5
会出现无法滚动的情况,谨慎升级。
需要注意的一点是,当子项目的容器节点显示的时候,子项目才开始初始化,这就会导致主项目 postMessage
先调用,排行榜项目的onMessage
后调用,导致错失了消息。
解决这个问题的话,我们的显藏可以通过设置透明度的方式实现,让子项目提前加载。
发布到微信
个人开发者提交的资料基本不用啥,有个自审资料网上找个模版在 word
填完截图就可以。
但提交审核的道路比较坎坷,除了慢以外,甚至被拒了两次。
第一次周日提交,周三还没有结果在微信社区平台催了一下审核,下午收到结果审核失败。
小游戏涉嫌侵权,请参考示例截图标记点全面自查游戏内容,请于下个版本有效整改或举证,在微信公众平台-版本管理-提交审核-授权书/版本更新说明提交,包括但不限于游戏内容说明及对应截图、原创证明或有效授权书 主体信用分扣除-3分
原因是一开始是准备仿 Flappy bird
的,直接用了相应的素材,就被驳回了,客服截图如下:
第二次周三早上提交,周五晚上收到结果,又被拒了,这次就无法理解了:
开发者你好,经平台审核,你的小游戏《挑战1024》未通过审核,具体原因如下:
1、小游戏需具有完整的游戏玩法,不能为简单的素材堆砌
网上搜了搜,可能是因为我的游戏只有一个界面,点击就开始了。据说加个菜单就会好,于是又改了改,不同场景也换了换背景。
第三次周六晚上提交,周二晚上收到结果,同上次,审核被拒,原因为「小游戏需具有完整的游戏玩法,不能为简单的素材堆砌」。
已经不知道该怎么改了,周三早上点了审核失败那里的提交反馈,写了一段感人肺腑的话(* 的内容这里就省略了)。
本游戏为益智类游戏,需要分数吃到 1024 才能获得胜利。
游戏场景分为菜单、第一关、最终关、好友排行,不同关卡也会通过背景色来区分。
菜单提供了分享好友、查看排行的功能。
第一关主要是为了体验游戏流程,星星的分数都是×2,因此只需要不停的吃分即可取得胜利。
最终关需要通过自己的策略,除了躲避陨石,还需要吃到星星上不同的分数,才能获得胜利。
游戏过程中,星星的速度、分数的出现会实时通过当前的状态进行变化,主要涉及到一些算法,也是本游戏的核心。
虽然素材都是星星,但结合算法上边的分数会一直变化,同时星星和陨石的比例也在不断变化。
除此之外玩家还需要躲避陨石,同时设定了策略,如果******。
在用户挑战失败的时候,增设了复活功能、重开功能。
游戏名为「挑战1024」,属于*******,来最终取得胜利。
希望审核大大可以再看一下,设计整个流程和算法确实花了很多心思。
周四早上显示反馈成功。
开发者你好,感谢你向小游戏审核团队反馈异议,经平台评估:我们已更正你的历史审核记录,如有需求,可重新提交审核
周五早上进行了重新提审,周二下午终于通过了。
小游戏相比于小程序审核严格好多,前前后后花了有半个多月了,简单游戏竟然不让上线,这是我想不通的。
总
整体就是这样了,整个 cocos
项目可以理解为一棵树,整个树就是一个场景,根节点是一个包含 Canvas
组件的 node
,接下来可以创建自己的 node
,每个 node
又可以挂载想要的自带组件和用户脚本组件。
希望对大家有帮助,如果错误也欢迎指出,也可以体验一下我这次开发的小游戏,哈哈: