摘要
《我的世界》是一款非常流行的游戏,不过网上大多都是用unity还原实现的。那么用cocos实现一版,会是怎样的开发体验呢?
使用版本
使用最新的cocos creator 3.6.2版本
目前主要功能
- 生成地形
- 方块创建与销毁
- 角色移动、碰撞、重力和简单动画
- 地形延展
- 快捷物品栏
预览视频
用cocos creator 3.6.2开发我的世界
生成地形
基础概念
块:最小的地形单元,尺寸为1X1X1
面:构成块的元素,1个块最多有6个面,最少可以一个不拥有。
区块:尺寸为16X16X100 。由块组成。在各象限依次排开。
地形随机
地形随机生成使用的是2维柏林噪声函数。
输入x和z坐标,获得y坐标作为地表高度,然后从设定的最低高度到地表高度创建填充块。
目前简单的设定小于0为水,等于0为沙。其余为泥土草。最底层铺上石头。
中间填充泥土。
随机洞穴
洞穴随机使用的是3维柏林噪声。
输入x,y,z坐标,如果超过设定阈值,并且在地表之下,在底层之上,则判断为空气块。如果是空气块就不需要再填充泥土块了。自然就有了基础的洞穴(没有矿的洞穴…)
随机杂草和树
随机杂草和树直接用的纯随机。当2维柏林噪音对每个坐标处理后。就进行杂草和树的随机判断。还是设定的一个阈值,超过则随机杂草或者树。树是由许多块组成的固定形状。
地形网格生成
上面的内容都是生成的虚拟的对象。在场景中渲染的块我前后使用了两种方法。
1是系统默认块+instancing
使用setInstancedAttribute函数来区分不同的块。
其实效果还挺不错的。缺点就是实例和面很多。
2是用动态网格,也是现在的做法。
这个应该是3.6的新增功能。正好用上试试。
对每个区块创建一个动态网格。
这个的目前还没处理好更新,每次增删块都是整个区块重新构建网格数据。所以每次刷新附近的区块的时候,就会卡顿。
使用动态网格可以进行面优化。只创建没有相邻的面。但是进行面优化,也要遍历所有块,目前还未达到性能最优。。
还配置了每种块的每个面的UV值,用于显示不同的纹理。
一个性能优化
方块很多,每个方块是一个类,在类里没有用到vec3类来表示坐标,用的是数字。这样占用的内存会少很多。
方块创建与销毁
如果是用的默认块,创建和销毁显得就很简单粗暴了。这个刚开始开发还挺快的。
但是转到动态网格后,就要每次进行数据重构。对整个动态网格的数据基于虚拟数据重新构造一遍。
方块的选中
这里简单使用了一下八叉树来管理附近的块。使用射线AABB相交检测判断鼠标聚焦的是哪个块。
然后需要额外知道是聚焦的哪个面。刚开是用的6个AABB来判断,后来发现内置的检测函数里其实已经计算了到备选面的距离,复制过来,找到最小距离的就是当前面了。
选中框
选中框前后使用了4种方法。
1是使用摄像机的几何渲染。
这个的缺点是无法完全遮挡。被遮挡的边会半透显示。
官方也说了,用来测试的,也不想改源码,所以就算了。
2是使用shader
判断UV是否在边缘范围,如果是则显示边框颜色。
这个的缺点就是靠近后,比较粗,远了比较细。效果不是很美丽。也没有找到更好的办法。
继而放弃。可以看到,远处的很细,快要没了。
3是使用cube的Primitive线条描边
因为默认的cube的顶点顺序是左下、右下、左上、右上,所以如果用line_list或者loop无法形成一个完整的框。
4是使用的静态网格,也是目前的做法
这个就可以按照自己的想法设置顶点顺序了。改成左下、右下、右上、左上。然后我觉得我的世界里每次都框住一个块没有只框住聚焦的面来的好。所以最后只创建了4个顶点的边框了。使用line渲染模式,也能满足远处不会变细。
方块的纹理
本来是想直接找我的世界的纹理贴图的。不过限于能力不足,始终寻不到合适的。所以一狠心,直接ps开干。使用ps的像素化,可以很方便的生成类似的纹理。
将所有纹理放在一张512的图里,可以一起用。
破坏粒子
在破坏一个方块的时候,在其位置播放一个粒子动效。用的是mesh粒子,引擎自带的cube。
设置为重力模式,使用曲线来控制重力先小后大,让生成的粒子多待一会儿再下落。
然后就是自定义粒子材质,基于当前销毁的方块属性来设置材质的offset 。贴图使用的是和其它方块同一个。
角色移动、碰撞、重力和简单动画
这部分花的时间最长,但是效果也一直没做好。T_T
角色移动
方向键按下则记录移动方向,松开则重置方向。在update里基于移动方向进行移动。
node的translate方法,第二个参数传0是本地坐标系,传1是世界坐标系。我用的世界坐标系,需要对位移矢量进行按照y轴旋转。
角色碰撞
利用八叉树检测附近块,然后用圆和多边形相交检测是否碰撞。如果碰撞则沿着移动矢量反方向移动。营造挡住的效果。
角色重力
利用物理公式vt+0.5vtt来进行角色垂直方向的位移。然后同样利用碰撞原理,检测是否有碰撞。如果碰撞则置重力加速度为0,速度为0 。直到没有碰撞,恢复加速度。
因为是先水平碰撞矫正,所以执行重力位移后,如果有碰撞,肯定是嵌到土里了。可以进行矫正。
跳的时候,设置速度为一个正值,则可以实现跳跃。
现在的效果只是满足了基本的位移,碰撞和重力,比如在方块前跳的感觉,连跳的感觉都还不到位。碰撞太狠了,经常动不了。都是需要后续优化。
简单动画
角色在不做动作的时候,会有呼吸,就是手上下移动。在跑的时候,会有摄像机的晃动。在删除方块和添加方块的时候,会有手的动作。这些都比较简单。
地形延展
因为使用的是柏林噪音,所以只要有了区块的坐标,就可以随机出自然的,连续的地形。
这部分是目前性能最吃紧的地方。只要判断到了别的区块,就会查找附近的25个区块。然后创建新的区块并且替换旧区块到新区块的网格。这部分现在需要500ms的时间,非常卡顿。暂时用异步的方法缓解了一部分。然后加了雾,感觉提升了些。
并且到了新区块,还要创建新的八叉树。也会有一定消耗。
地形延展是目前最不满意的地方,近期应该会着重关注并优化。
卡顿主要是消耗在了八叉树的创建。之前是对基于中心区块的附近25个区块整体进行八叉树构建,所以每次到新的区块,都会有非常多的重复构建浪费,现在将八叉树构建限制到每个区块,在查询之前,先对区块进行一遍筛选,再对区块的八叉树进行查询,性能提升非常多。
快捷物品栏
这是属于UI的部分,难度不大。
监听键盘事件,然后切换单元格的状态。在需要的时候,调用推入物品和使用物品的接口来更新状态。还有和手的状态联动,因为在切换单元格的时候,如果单元格里有物品,则需要将手替换成物品模型。
难度在于绘制物品ICON。所以现在只有3个方块有ICON。
结语
核心技能包括八叉树查询用于场景管理,射线检测用于碰撞和交互,柏林函数用于地形随机,动态网格用于地形显示。后续需进行场景性能优化,增加资源、NPC、存档、红石等等等等内容。对于长期学习,还是非常不错的项目。
主要参考链接
麒麟子3.6渲染篇,动态模型部分
https://forum.cocos.org/t/topic/138915
bilibli goldeneggs 体素沙盒游戏
https://www.bilibili.com/read/cv17097421
知乎 Lost Lord关于unity实现我的世界的系列文章
https://www.zhihu.com/people/lost-lord-58