用cocos实现的《我的世界》已经上线微信小游戏,分享一下主要技术

摘要

本文介绍了用cocos creator3.x开发的《我的世界》小游戏版《像素空间3d》的主要内容和涉及到的技术概述。✨

欢迎大家扫码体验😍
(体验前,也可以先看看下方游戏实录视频)

前言

22年末,我参加了cocos论坛的第五期征文,有幸凭借此作品获得了最终大奖:华为平板,再次感谢官方大佬们的认可!让我能够投入更多的精力来完善此作品。期间经历了多次重构优化优化优化,真是“朝发夕拾”。

晒一下第五期征文的奖品,哈哈哈😄

游戏介绍

《像素空间3D》是一款仿照《我的世界》用cocos creator3.7.1开发的一款小游戏。目前包体2.5M。游戏中,实现了诸如地形随机、地图随机、体素物理、体素亮度、流水、合成、熔炉、战斗等等功能。虽然相较于《我的世界》还有缺少非常多的内容和功能,不过还是可以进行一番探索和生存了。虽然性能还有一些缺陷,不过还是可以跪求体验的🤣,虽然…

下方视频是手机录屏,试玩的体验版
《像素空间3D》微信小游戏实录演示

核心技术简介

体素空间

我所理解的体素非常简单,就是给定任意一个坐标,对其进行Math.floor就可以获得其所在方块的坐标。以此建立起整个3D像素空间

地形随机

  • 地形随机是用的npm里的一个柏林噪声函数(perlin-simplex),将其改成typescript形式进行使用。其可以进行2d和3d的随机。返回-1到1之间的渐变随机数,(r+1)*0.5就可以返回0~1。地形的起伏用的noise2d,矿洞、矿物的生成用的noise3d。
  • 每构造一个噪声对象,需要传递一个随机对象,用来构造最初的随机数。使用的是cocos的pseudoRandom函数构造随机对象。只需要给一个世界种子即可。世界种子基于某局游戏是一定的,所以就能保证每次进入都是相同的地形。

地图随机

游戏里主要使用了岛域的方式来划分地图,256*256范围为1个岛域。每个岛域内是一组定义好的地貌。岛域的随机是用pseudoRandom生成噪声二维数组来实现的,基于不同的世界种子,生成不同的数组。也能保证每次进入都是相同的岛域。不过因为版本的变化,可能会增加不同的岛域地貌,所以缓存了已经修改过的岛域,没有修改过的岛域就会随着版本对地貌的影响而改变。

地形展示视频
用cocos实现《我的世界》之雪、山洞、湖泊

体素存储

  • 整个游戏空间里,主要有chunk(区块)和block(方块)两个概念。区块包含方块。在刚开始的版本里,方块也会是一个具体的对象。这导致了大量对象的创建,在pc上经过优化还能接受,不过到了小游戏平台,就完全不好使了。所以最后进行重构,block在chunk里用多个arraybuffer进行存储,包含类型arraybuffer,亮度arraybuffer等。结合上微信小游戏的sharedarraybuffer,就可以利用worker进行大量数据的处理和共享。
  • 每一个chunk是一个对象。为了只用一维数组来存储所有chunk。使用了螺旋曲线的算法,给定chunk坐标,算出唯一id。然后写了一下给定长度,反解坐标的函数。我感觉很多需要用二维数组去存储对象的地方,都可以使用这种方式来减少数组的创建。

小游戏worker

  • 大量的block数据,如果只是在主线程里进行运算,想要在小游戏里刷新地图时保持60帧那是很困难的。所以使用了微信小游戏的worker。核心是构造sharedArrayBuffer,然后主线程和worker线程可以共享这部分数据。在worker里进行各种随机,水流,光照,面优化,网格构造等功能,主线程直接读取buffer里的数据即可。但是数据量还是很大,一个面优化经常需要耗费worker的好几帧,一次日夜更替更是耗费1秒以上。所以在体验的过程中,会有效果延迟的现象。还在努力研究中。(上线后,发现微信小游戏ios正式版sharedarraybuffer无法正常传递,研究未果,所以只得暂时关掉IOS小游戏的worker功能,导致IOS在体验的地图刷新的时候,会有强烈的卡顿感。等到研究明白了,再加回来~)
  • 在开发过程中,需要保证可以本地pc测试,也可以打包到微信小游戏。所以需要worker做的功能是在项目的assets目录里编写的,在项目外增加了worker的单独目录,编写入口文件,引入assets里的相关文件,最后通过tsc打包到build-template/wechatgame/workers里。因为小游戏worker不认识cc,所以和cc相关的功能都需要抽出去(只是worker用到的功能,不是所有功能)。比如vec类就需要自己复制出来一些用到的函数,自己构造类。

动态网格

  • 之前版本动态网格只使用了一个submesh,不能同时构造土地和流水,所以重构版本里变成了多个submesh。抽离出了基础组件,可以适用于各种体素的动态网格现显示。不同的submesh使用meshrenderer上对应的material。并且重写了包围盒,避免更新时多余的Vec3类的创建。
  • 动态网格在渲染的时候,需要提供typearry,如果变化频繁,可以只用一个相对较大的arraybuffer,然后用slice来截取数据,避免每次申请空间。
  • 玩家角色手中持有的物品,也大量使用了动态网格。比如下图的火把,就是由动态网格实现的。其流程是在aseprite里用像素画好道具,再用脚本导出像素,然后拷贝到项目里,优化面后使用。这样就可以减少使用建模工具、创建材质的时间和消耗。(这个脚本完全是gpt整的)

火把道具,用动态网格实现

体素物理

  • 新版本增加了水的浮力和阻力,使用的是简化的浮力加速度(重力加速度*实体淹没高度比/密度)和阻力加速度(水的粘度*实体速度)公式。
    具体效果可以看下面的视频:

用cocos实现《我的世界》之浮力与第三人称弹簧臂

  • 关于射线检测,之前版本使用的是八叉树来查找射线碰撞的方块。后来一想,不需要啊。虽然方块是多,但是都是均匀整齐排布的。我使用了一种步进式的检测方法,步骤是由射线原点开始,查看是否处在空气块里,如果不是则直接返回。如果是则,用cocos的intersect.rayAABB方法获得aabb内的射线距离射线方向最近面的距离,然后将射线原点按照射线方向移动这么远的距离,然后步进一个非常小的数,然后再进行检测,直到返回找到,或者超出检测范围。这样的话会省去非常多八叉树的创建和更新成本。检测性能也非常高!💪

光照系统

  • 这个使用的是预计算光照,假设太阳光始终从上到下,计算所有块的基础日照亮度。然后再迭代计算所有非太阳光亮度的块,迭代过程中逐步减小亮度阈值,直到所有块的亮度设置完成。即使掉光头发优化,性能仍然一般,将就能跑。
  • 在破坏、放置实体方块或者光源时,会BFS查找所有被影响的方块,再次迭代亮度。这个进行优化后,性能还可以接受。
  • 针对亮部和暗部的对比,使用了自定义无光照shader。随着24小时变化,改变主光源位置,在shader里用法线和光源方向进行点乘,再叠加到主颜色里。这会导致一个问题,就是明明太阳光是从上到下,但是亮暗部却一直在变化。没有找到性能又好,效果又好的招式。😭
  • 阴影,除了预计算的阴影,动态阴影暂时没有。在预计算过程中,不同的块的透光度不一样,光线从一个块穿过的时候,因为透光度的不同,光线经过的三个块可能不一样,从上到下照射太阳光的时候,比如经过了树叶,树叶不是完全透光,就会导致下方变暗,最终到地面上,就会相对暗一些,就呈现除了阴影。
  • 角色在不同的地方,身上的亮度是不一样的。比如夜晚,在没有光源的地方,很黑,靠近光源就会亮。但是角色的shader是无光照,我用的是instancedAttribute,每0.1秒检测一下当前角色所在方块的亮度,然后设置到shader里,最后同亮暗功能一起加成。
    可以看下方视频了解光照系统:

用cocos实现《我的世界》之熔炉火把与日夜

海、湖泊及流水

  • 海是区块的表面高度到海平面的所有块会被设置为海水。
  • 湖泊是检测区块的最低点,然后用BFS查找空气区域。
  • 湖泊的水量是一定的,水会往空的块进行流动,水量为1的水无法流动。流动是在worker里每1秒一次执行当前区块的流水检测。
  • 海水的水量是无穷的,只要附近有空气块,就会被海水填满。
  • 水面的渲染,是通过查询每个顶点周围的4个方块的水量求平均值,设置成块的渲染高度,最后还要乘以一个缩放,来让水面低于方块,看起来更真实一些。
  • 水下的效果是屏幕后处理,检测摄像机的位置是否有水,如果有则shader里乘上一定的蓝色。
    可以看下方视频了解流水功能:

用cocos实现《我的世界》之流水功能展示

背包、合成、熔炉

  • 所有背包的UI操作都继承自一个操作组件。即使像合成的目标格子就1个格子,那也是1个背包。背包可以选择是否支持放入和拿出。在所有当前打开的背包里,设置了一个当前激活背包变量,用来处理两个背包的交互。
  • 合成功能配置了一个对象,对象的每个key都是一个道具名,value都是一个函数,每次合成背包或者工作台进行了变化,就会遍历这个配置里是否有符合要求的key。具体的逻辑是遍历配置对象,对每个函数传入当前背包,函数自己判断是否符合合成要求,如果是,则返回合成数量。比如木棍的检测逻辑是背包里仅有两格有物品,并且物品上下排列,平且都是木板,如果都符合,就返回4,代表合成了4个木棍。
  • 熔炉的功能类似,也是配置燃料、材料和产品。数据很多都是问GPT得到的。
    可以看下方视频了解合成功能(熔炉功能视频在光照那里有):

用cocos实现《我的世界》之背包与合成

NPC和状态机

  • 目前生物NPC仅有羊和僵尸两种。不过也方便扩展。NPC现在分为三类:动物、怪物和特殊。动物现在就是羊,怪物现在就是僵尸,特殊现在就是船(对,我把船也变成了NPC,目前看没啥不好的,等发现不适合了再说)。
  • 现在设想的是所有动物公用1个材质和贴图,使用实例属性来设置材质偏移。怪物也是,特殊NPC也是。
  • 写了一个简单的注册状态机。每个动物自己注册想要的状态,比如闲呆,巡逻,逃跑,被击,死亡。注册状态时配置相关参数,然后每帧执行当前状态的逻辑,并判断是否结束跳到下一个状态。
    可以看下方两个视频来了解当前NPC:

用cocos实现《我的世界》之杀羊取肉
用cocos实现《我的世界》之AK47打僵尸

其它次要内容

  • 比如一个可以绘制ICON的画板,用graphics组件实现的。
  • 比如摄像机弹簧臂,每帧检测是否第三人称,检测最近并且小于最大摄像机距离的实体方块,然后设置摄像机位置。
  • 比如破坏粒子,是用体素物理来实现的。
  • 比如破坏裂缝,是用shader偏移贴图实现的。
  • 比如…好像也没啥了。哈哈哈。

其它基建

  • UI管理器,是基于prefab名称的UI管理器,管理加载,卸载,传参,关闭回调等基础逻辑,方便快捷。
  • 事件管理器,发布订阅模式的事件管理器,可以定义事件名称,回调参数,返回参数,在其它地方调用的时候,有很好的代码提示。

写在最后

欢迎大家加入我创建的微信群聊,一起探讨开发技术和游戏内容,共同成长!没事发发图,摇摇花手都是挺好的。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值