用 Cocos Creator 做一个物理画线游戏!支持 UGC 关卡创作和微信关卡分享
共 4356字,需浏览 9分钟
· 2022-11-29
引言:在 3D 跑酷游戏和 3D 三消游戏之后,本次孙二喵带来了一个 2D 物理画线游戏源码,使用 Cocos Creator 3.6.2 开发。源码见文末。
![](https://filescdn.proginn.com/710808514bd538b1e43f2c2a6a7fa717/a289ab489d7db525245a45bbe3858ab3.webp)
演示效果
物理画线是一种比较经典的 2D 游戏玩法,这几年也出了一系列的爆款产品。本文将从立项、游戏逻辑和具体功能点的实现,来讲解如何基于 Cocos Creator 3.x 开发一款物理画线游戏,并实现 UGC 关卡创作和微信关卡分享。
立项与准备
游戏立项
游戏的核心玩法是玩家通过画线保护自己的小鸡,让其免受黑洞中掉下的障碍小鸡、或地图上的其他障碍伤害。
![](https://filescdn.proginn.com/36e5b595422202463e47808306c2c2c7/f86b927192769dd560f24ea54bd58dd4.webp)
游戏关卡
考虑到制作成本,游戏美术使用了简单的手绘风格,用 paletton 选择类似纸张的颜色,拖动色盘,把比较顺眼的方案保存起来,确定游戏整体的风格。
![](https://filescdn.proginn.com/37d6935728c32c956718740e20a5405b/9de712528f1c0e102e04f95dbfd1579c.webp)
色板中用到的颜色统一可以使用圆角矩形缩到最小(比如 15px,就输出成 15+15+2=32px,预留2个 px 给九宫格拉伸用),游戏所有的 UI 框架加起来就只有几 KB,减少显存的同时,还可以提高加载速度。
![](https://filescdn.proginn.com/75005155ef5b558fb6830572b8535586/63937a64ff813a0842a9c75ce2441e48.webp)
考虑到 spine 专业版数千元的价格,游戏内的动画效果统一使用了 Cocos 内置的动画编辑器+序列帧动画。序列帧动画使用 PS 制作,在画好我们的角色后,PS 内复制数个分组,调整角色的五官,输出序列帧,一些相似的做了剔除,减少动画的体积占用。
![](https://filescdn.proginn.com/0d125577c375b52148fd80a9a8668281/b21127082d4d49d5e165cd9527c141c2.webp)
在 Cocos 内制作序列帧动画比较简单。帧动画使用30帧,新建好动画,并在精灵上面创建好动画组件,在对应的关键帧替换图片即可。
![](https://filescdn.proginn.com/2f2c34fe3b4f3ff343127ec072a564a3/a1329ab8bac5bae822e1091b8e5bb9c0.webp)
制作好的序列帧动画无需进行合并,拖入到一个文件夹内,使用 Cocos 自带的自动图集打包即可。
![](https://filescdn.proginn.com/4af6b103adfb539961ec4c1218e87298/f28eed368024f1260e14e55e7b4eeb7c.webp)
自定义网格背景
考虑到游戏的背景是一个纸上世界、同时需要支持 UGC 和简单的 AI,我们需要一个图片背景+网格。然而使用图片背景+网格图片不够灵活,同时做出来的效果会比较死板,缺少随机性,因此我从 shadertoy 上移植了一个纸张网格的效果。
原 shader 的 for 循环比较多,且风格比较写实,这里进行了一定的简化。
![](https://filescdn.proginn.com/fa2d573747f4b72b23cf1e4658a2cda3/b8be93fed6e85bc9f244de86df7e7940.webp)
这里把 shader 中常用的属性都进行了暴露,整体风格调整到和色卡一致。
![](https://filescdn.proginn.com/bd51e378afbd22cf1ecc0b6b5ebe8151/b24edfb4f4853427b53c13307f0cea6d.webp)
游戏内的3个场景(游戏界面、游戏内、游戏编辑)都用到了这个 shader 的图片作为背景,这里使用精灵图自带的颜色 a_color 来控制网格颜色。
![](https://filescdn.proginn.com/207af721473971a3aa75cea8ebd4e436/b2b5f9d63a0838c67b1d2903be12f3d4.webp)
并使用了简单的脚本接受全局事件,可以修改网格的颜色(这里只修改了透明度)。
![](https://filescdn.proginn.com/68d426eed9ed2b3ce4bda5ec296d225f/ec3af65e0bbcba567236563e2e7fb77d.webp)
效果如下:
![](https://filescdn.proginn.com/729e198fab2d00900af214ccf0386e73/5a3a907e6d7d7c176d7fc3b5453e712e.webp)
考虑到游戏的 UGC 地图编辑需要使用到格子,Astar 网格导航也需要。以图片的视觉方向为例,下图左是在 shader 里 uv 的 xy 方向,下图右是 astar 算法的格子序号从小到大。
![](https://filescdn.proginn.com/e3131ae78a48b6bc67d259233b080d51/f8eee12eca8db7975a1ccfa714839195.webp)
所以我们需要在 shader 里对格子进行转换。
![](https://filescdn.proginn.com/ce1a3e049fe418eac0c889bd88b8c0d7/e9149e25de4a8966ba6f41b66ae124d9.webp)
同时考虑到算法简单,这里不做屏幕分辨率的适配了,默认的设计分辨率是 750x1334,支持的最大显示高度是 750x2.2倍=1650(绝大多数全面屏手机的最大高度)。
uv 统一后,我们需要设置一下网格显示,默认黄色是安全区域坐标,表示可以放置物体或者有物体,红色是危险区域,表示不可以放置物体。
![](https://filescdn.proginn.com/4c314ec2a49859c7888ca644f7ad1208/ec66177baa13f328ff068f8c85cbcbf0.webp)
定义好颜色和颜色的顺序,在 shader 中使用简单的 aabb 四个方向进行判断,在范围内的就会把颜色改成黄色安全色或者红色警告色。
![](https://filescdn.proginn.com/06bad63bcef005b767b4eb0729243b68/bf3d6d6ec0464c89c3b2975a041d8a97.webp)
同时 shader 把 worley 褶皱效果也加进材质的宏里,方便自定义开关。
![](https://filescdn.proginn.com/93e1703f080e1587b3536d580a3f9d8b/1da285020a214a3307c0ae95ef7c0382.webp)
如果需要使用自己的图片作为背景,可以打开 Use_Texture 的宏,使用 750x1650 的背景拖即可。
有了上述的准备工作,我们就可以开始地图编辑器的制作了。
地图编辑
在地图编辑之前,我们先确定整体的网格大小为75像素,设计的最大分辨率是 750x1650,也就是 10x22 个格子,这里定义一个全局的 class 去记录下这些配置。
![](https://filescdn.proginn.com/a0aa2743e129b4eeb250622cf071ba44/a1043d9c3f81b1131fd03dd4720d15b0.webp)
每个物品的格子数量,可以用物体的图片长和宽分别除以75,然后四舍五入。
![](https://filescdn.proginn.com/ba8fa7163df99719311696c1f1f81052/5ef779e4badf3b20dbe3414195ecf617.webp)
如尖刺,是 60x148 分辨率,正好是 1*2 个格子
这部分数据我们在初始化物体时候会提前处理,避免重复计算。
![](https://filescdn.proginn.com/79e1391cf7972b5c8cf0211d156dc97b/4f67bfb1149521cb0b4960bc5bfa2441.webp)
接下来根据格子的奇数或者偶数决定落点的位置,可以分为 x 和 y 两周轴向分开处理。
如果是偶数就不需要做偏移,在格子中间落点即可,如果是奇数,需要偏移1个格子单位,同时偶数的格子用 Math.floor 求最小的整数格子,奇数用 Math.round 四舍五入求最近的格子。
![](https://filescdn.proginn.com/a7f226fbf19b69d54ae0f7803364b8b8/b27f0ebfca1d9d5702da3524214b62ff.webp)
检测上使用了 map 做查询,当检测到数据时候我们要把有碰撞的格子范围乘0.1,这样格子的 x 范围从 0-10,变成了 0-1,y 范围从 0-22,变成了 0-2.2,正好和我们 shader 的 uv 对应(shader 内部对原始的 uv.y 也乘了2.2,并不是 0-1了)。
![](https://filescdn.proginn.com/0cfebbe7e0ae4c4cbac8ba34d38e2525/079a02f8eb5e34a2620c02700c031138.webp)
当我们检测到碰撞体时候,当前物体的周围就会变红,存在碰撞体的周围位置会表黄。
游戏内定义了几种物体的名字的枚举,当他们的名字一致时候,初始化时候就会标记为是画线工具,删除工具或者旋转工具,当使用这些工具检测到结果时候,就会对选中物体标亮并进行对应操作。
当保存关卡时候,我们只需要记录物件的格子开始和结束范围,也就是之前 map 保存的数据,同时记录物件的 scale.x 和 scale.y 记录左右和上下方向的旋转,保存为 json 数据,再把 json 数据 Stringify 即可。
![](https://filescdn.proginn.com/d7e03712c5534b72342e32d935de607d/dc7525ef50f7c2b05785f8696734d78c.webp)
在地图编辑器和游戏内读取关卡都采用相同的逻辑,如果是 runtime 时候,我们会顺便初始化一下 Astar Graph 里的网格权重。
![](https://filescdn.proginn.com/fd467398b2b8ec6df8b53df6eb5a1da1/7db3bb3d03aebe969c4481b398e4e384.webp)
这样即便是在复杂的关卡中,障碍小鸡也可以精准定位我们的游戏角色。
![](https://filescdn.proginn.com/cb7cf6ce87ed898ccde8f1a08b073ade/b35478ad902dd03602bc79427f19713a.webp)
玩法核心逻辑
画线算法
下面介绍游戏的核心逻辑部分。
游戏的核心逻辑是使用 Graphics 画线,并把画线的路径点记录下来,同时我们的地图编辑器也会使用到这个功能来画辅助线。
这里使用了曼哈顿距离来记录移动的距离。当距离大于设定值,我们就会存储一次路径点,同时使用 graphics 绘制一次路径。
![](https://filescdn.proginn.com/a078633047bf16c4f4f66d9db2767b08/0a1fd846e67b9f93d98b604ed5b07c81.webp)
游戏环境和编辑器环境使用了不同的长度设置,编辑器会更短,来保证储存的关卡长度足够小。
画线的第一个点,我们使用了开销最低的 testPoint,记录是否存在碰撞体。第二个点开始,从上个点到当前点发射一条射线进行检测。
![](https://filescdn.proginn.com/8208e815f89e3e4c8d132163d205e5b9/90f1f34eec7c4b598080f57b656d889c.webp)
需要注意的是,这里的坐标点需要使用物体的世界坐标。
当画线结束,就可以根据路径点去生成碰撞体。这里直接使用了 polygon 多边形生成碰撞体,已经生成的碰撞体在关卡开始的时候,会回收使用到的 vec2 类,减少 GC。
我们首先把每2个点连成一条线,再对比每条线之间方向向量的斜率。考虑到性能,这里没有使用三角函数,斜率在一定范围内,就判断为是平行的,只会去推第二条线的最后一个点。
![](https://filescdn.proginn.com/a6d8bbdd964450d9768f0656748f75aa/275bfc62c26eb06c24faeeee66f14a33.webp)
这样我们参与计算的路径点就可以减少 30%-40%,我们用优化过的路径点再来算需要生成的 polygon 多边形,这里把起始点定为1,结束点定为2,其他中间点是0。
先计算出2个点之前的方向向量。
![](https://filescdn.proginn.com/947a106f5136dd74ef0a64d82fa8f864/0d1288b497fb220f387c104022b5c24b.webp)
再计算方向向量上,2个垂直方向的向量,分别乘以我们线段一半的宽度,最后起始点和结束点分辨加上这2个向量,就可以得到3个或者4个路径点,可以构建出一个三角形或者平行四边形(考虑到起始点和结束点可能贴边,这里使用了三角形,避免碰撞冲突的修正)。
生成了多边形的路径点后,我们需要额外 apply() 一下,我们所有的 vec2 都从 VecPool 单例内存储,减少 GC。
这里额外支持了 Graphic 的自定义材质,玩家可以在游戏内更换画笔的颜色。
![](https://filescdn.proginn.com/6dbd5a18ad3a4966752cc89893a65842/848e51a292d22822c5376967421835ba.webp)
我们把材质的颜色和材质名在入口脚本里进行配置,商店和 gameplay 根据配置进行加载即可。
![](https://filescdn.proginn.com/f39da9920bb56dccc079e4454a5cce7c/44bd419da0e8dd93f47f08df9c3005cb.webp)
感谢社区大佬的 Shader~
![](https://filescdn.proginn.com/190d1331ca5d2a50d40d5c08aed14557/c6a3bd555097574474cd706743cfa4cb.webp)
状态机
画好线后,就可以通知障碍出发去攻击我们的角色了。
![](https://filescdn.proginn.com/2c818d66633e339ec22bc13476f087c8/38db0ea3d1635bef03ede66629a6c0ba.webp)
这里使用了 Cocos 原本的 update,当切换状态时候会先执行 onEnter 方法,再执行 onUpdate 方法,这里可以把当前状态和之前状态传入,方便做逻辑切换,这里会记录下 dt 和 duration 时长,方便继承状态机的类使用。
小黑子的 AI 继承了 FSM 状态机,整个状态比较简单,只有寻路和攻击两个状态。
![](https://filescdn.proginn.com/fe6f43b0c47ebb2008ef04dc2da443e7/f7d40b8908e39db97b2f599931a69fb1.webp)
寻路阶段使用了 Astar 算法,每找到一个路径点,就会向下一个路径点前进,寻路时候会使用人物的方向向量乘以一定距离,来检测是否存在物理画线。
![](https://filescdn.proginn.com/ed437f720fe3c95925b55e8130b5f77e/08ca25d78cc9a2ad838627cc1193fe3c.webp)
当检测到物理画线的时候就会对物理画线的 rigidbody2d 进行攻击,对 rigidbody2d 施加速度向量,就可以出现线条被抬起来的行为了。
![](https://filescdn.proginn.com/35849afa9d6cf8ad54bfc1f6af9b875e/ef31d202f7d99a2992e3d9b145c82efc.webp)
我们会在 x 方向上做左右的随机,y 方向以向上围住,也会随机一定的数值。
![](https://filescdn.proginn.com/86ed85927514a61cbf281d5f63971287/120a2db415f18068d2855e06c5a95463.webp)
小鸡的状态机就相对比较简单,只有基本的碰撞检测,当检测到危险的碰撞体就会触发受伤然后游戏失败。
![](https://filescdn.proginn.com/bef0c370051c4a6ae7bcc0b9fc1ad674/d66aa5e1c50c0ecc9ae1b47d5a715aa7.webp)
寻路导航
这里使用到了和之前 EasyNavmesh 同款的 A* 导航算法,不同的是我们使用了一个单例进行管理。
![](https://filescdn.proginn.com/1ae78cc2f9b1eeff12bf2ce7e04e4051/8220a48fa7205dbb7ee77c9d8536b2dd.webp)
这里把 Astar 翻译成了 TS 版本,方法都加了强类型判断,同时把距离算法改成了曼哈顿距离。
![](https://filescdn.proginn.com/3e04a692821b3a1a7c235d0a607a4a83/5ac19f01a30145adbc4b2d453022d908.webp)
游戏内 FSM 同样使用了曼哈顿距离,当我们的路径点走完,且离目标超过2个格子距离时候就会再次寻路一次。
![](https://filescdn.proginn.com/09b65013f36b4f622a8b4bb903a4b666/c79f2c9b1ae164f090e2e46690b01526.webp)
到这里整个游戏大的逻辑就分解完了,下面介绍一下游戏关卡分享的逻辑。
关卡分享逻辑
游戏内拍照
游戏内把物体和 UI 分辨分成了 Default 和 UI_2D 分组,并创建了2个相机,Screencam 平时的可见分组为空,只有当拍照时候才能看到 Default 分组。
![](https://filescdn.proginn.com/22035090242d3b703a61e888e39a22e9/9c96d71a6fea035f78c86b30d836759b.webp)
截图后一瞬间,我们会读取相机 RenderTexture 的像素点,并把角色的偏移量传进来,保证截图范围不会太大,同时保证相机对着角色不会出框。
![](https://filescdn.proginn.com/ae11a96a95d8cb7d214d1916d101f744/99c499aaf50db6dc747ea73bce7522dd.webp)
关卡分享逻辑
关卡分享并没有使用云服务器,这里使用了微信分享里的 query 参数,query 最大可以存储2048长度的 string(测试中4096也可以跑,担心部分手机可能存在兼容问题,这里最大设置成了2046)。
![](https://filescdn.proginn.com/68d39bead8fe4af2e7808d3c36604b8e/5889cb1176571fc7cd4caafde969f3b0.webp)
我们在分享时候可以把当前关卡都转换成 string,放到 query 里。
![](https://filescdn.proginn.com/8bbec2a93c60d8c45666b2dec6c89171/1da3b12a05e42c20411d79e0a1dd31c5.webp)
当我们分享给其他玩家,其他玩家打开后,在游戏初始化和微信 onshow 时候就会检测到是否有对应的 query。
微信的 getLaunchOption 是第一次进入游戏时候会有的,wx.onShow 是后台切换回来触发的时间,当检测到 query 且 query 内的关卡数据有效,就可以初始化这个关卡了。
![](https://filescdn.proginn.com/8559615bb394471fa1e024becebd4dbb/473d93b13d7ab4f296da3c24e3cf15bf.webp)
游戏内显示好友 UGC 关卡效果:
![](https://filescdn.proginn.com/30e00310b774b81e7263efeee63bebac/f987990f9f9eb03cedfbdae0922bb5d4.webp)
资源链接
源码下载:
https://store.cocos.com/app/detail/4240
在线体验:
http://learncocos.com/chick
论坛专贴:
https://forum.cocos.org/t/topic/142673
demo 源码现已发布到 Cocos Store,希望可以对大家有所帮助!如有疑问或者其他想交流的,欢迎移步论坛专贴。
![](https://filescdn.proginn.com/c97616d2e63f16d505449ccd92a6692f/87fd4c7970fee82e2f4a4bee89074618.webp)
![](https://filescdn.proginn.com/ce03566c0a82816c9cb08c84735bf0ce/a9592813848436409838fd16665f4bf9.webp)
![](https://filescdn.proginn.com/584b52541502c75d0fe1c4564941f1fe/a45f08218d50419f762db89133a7166e.webp)