cocos 3D自由世界游戏 - 开发
cocos 3D自由世界游戏 - 开发
- 序
- 概述
- 第一步 创建环境,XForge 框架
- 基础3D创建思路
- 场景
- 地形
- 树木等环境
- 人物角色
- 角色实现
- 实现代码篇
- 空间管理篇
- 动画逻辑篇
- 按钮实现篇
- 虚拟摇杆
- 多功能按钮
- 移动和目标篇
- AI实现
- 角色属性以及身份牌篇
- 装备篇
- 结尾
序
在同时浅入了unity 和 cocos以来,得到了以下个人观点
------------------------------
unity:
:优点:方便 好用,插件能帮你做80%,剩下的就是完善自己的逻辑,资源(免费)多,容易获取—我觉得这些已经够自己用了。
:缺点:由于个人游戏的环境来说,app也好、win也好,都不好传播,没有自然流量,只能通过各小游戏平台实现(或者H5),所以我选择了微信小游戏,unity有直接打包成微信小游戏的工具,打包以后,主包很小,分包10M+,也差不多,但是需要放服务器去加载,可能研究不足的原因,打包的过程以及结果,暂无法控制。 在运行的过程中,发热比较重,(跟者官方的教程走,优化不足,插件重导致运算高),控制也有插件(按钮控制,屏幕触控),但不能做到分手指进行使用,(在缩放或者移动时候,会和其他手指同时作用),想来还是得自己做轮子才能更合适自己用。
:总结:写的时候是很开心的,用的时候是比较糟心的(还有各糟心的理由就是团结要收费。。)而且发热比较重
------------------------------
cocos
:优点:界面操作简洁,编辑器加载快,(不知道怎么,确实编辑器面板看着比unity 会更舒心点),对于开发过程来说,流程是一条走的(ts 单线程的原因吧,无论是组件、脚本、单例也好,总会觉得就是顺序从上到下来的,这个对前端来说比较亲切),动画图对应unity的动画状态,效果也是很像很好的,暂时就想到这么多子。
:缺点:!!!啥都要自己做(大部分),社区环境也是可以的,但共享的、实用的东西大部分是没有的,也可能现在还是unity 等其他开发为主,池子就这么大,大佬也只有这么些。
:总结:基本上在开发和流程来说,是非常棒的 ,不过开发起来比较累,都要自己实现,有些确实不兼容微信小游戏,比如基于babylon的Navigation 寻路,在打包后就有问题,因为没有window,后面没研究, 就直接用胶囊角色控制器代替了。总之 很多要自己写。
概述
大概大家都想做一个和魔兽一样的开放世界游戏,而且要轻 随时能玩 (特别是自己的游戏可以给自己开挂~),所以历经一年多的空闲时间(当然不是所有…)进行想法实现,最后发现,要素材没素材,要逻辑太过庞大的,到真正完成,估计还需要···时间!大量时间!(一个人开发太难了。)。所以 我想记录下现在的开发进度,以及开发遇到的事情吧,
以上文字较多,各位可选择后面基本代码实现来看,其他的可以省略掉,没关系
守护域界-基础尝鲜版-3D自由开放世界逻辑基础
源代码-cocos商店
第一步 创建环境,XForge 框架
我用 的是XForge框架,第一 他是免费的,第二 他好用,第三 作者 是 这个 (๑•̀ㅂ•́)و✧棒 。
这个框架理顺了你在开发时候的2d和3d的关系, 只做纯2d也完美, 他的资源复用、分包、Manager控制、数据控制,封装的很让人舒心, 开发的时候你只要用就好了,我用下来没有发现问题,发现的都是方便、好用 推荐(XForge)
怎样搭建可以去看他的文档、也可以加qq,这里就不多说了 可以自行去看。
基础3D创建思路
一个平台 、几棵树、一个自己、几个敌人,构成我所想的游戏环境,在cocos分包中,资源的分配管理是决定大小的最重要的东西,如果把要的素材全部在scene中放置的话, 所有用到的资源其实会打包两遍–资源管理分包-使用的战斗分包,所以一切的加载都要在初始化好基本环境以后,使用预加载、分包加载 去进行资源的管理加载、 最后 一个scene 只需要几百k 就可以显示几M,甚至十几M的内容。如:
总大小有16M左右, 天空、角色、敌人、环境树木、Terrain等 占用比较大,不过没关系 微信小游戏可以30M以内,所以要控制每个分包要在4M以内,做好分包管理是最重要的事情,在我的想法中,就是角色、敌人、环境等, 都可以有多个分包,通过一个管理分包路径的目录来统一进行加载。
通过每次都访问这个文件进行加载对应的一类预制体以及分包。
做到这里,就差不多整个架构已经搭建好了,可以加上素材了
场景
地形
场景的话使用的是Terrain,Terrain资源很大,一个差不多100*100的都要1M 而且是在颜色贴图只有两个简单的几K的颜色块和塑型没有的情况下,所以 用动态方式进行加载
// 创建地形节点const terrainNode = new Node(option.name);// 先加入场景再添加物理组件this.PlaneNode.addChild(terrainNode);// 添加地形组件const terrainComp = terrainNode.addComponent(Terrain);terrainComp._asset = terrain;// 添加碰撞体组件(此时节点已在场景中)const collider = terrainNode.addComponent(TerrainCollider);collider.setGroup(PhysicsGroupMap.Terrain);collider.terrain = terrain; // ✅ 此时onLoad已执行terrainNode.position.set(option.position);load && load();
这种方式加载所以这1M的Terrain 不会在fight分包里面
效果正常
树木等环境
也是同样的道理,通过预加载和分包加载的方式来进行,
通过下面代码来实现同样的预制体有不同的呈现方式 角度 位置的偏差
/**加载node - 通过不同形式进行坐标的生成后调用 */loadNatureNode(opt: EnvironmentConfig) {// 随机选择资源路径const nodePool = NatureLoadManager.instance.createNatrueNode(opt.paths? opt.paths[Math.floor(Math.random() * opt.paths.length)]: opt.path);if (!nodePool) {console.error("节点创建失败,路径:", opt.path);return;}//缩放if (opt.offscale) {let scale =opt.offscale[0] + Math.random() * (opt.offscale[1] - opt.offscale[0]);nodePool[0].scale.set(scale, scale, scale);} else if (opt.scale) {nodePool[0].scale.set(opt.scale, opt.scale, opt.scale);}// 添加随机旋转(修复1:正确使用计算后的旋转值)if (opt.offRotation) {let randomRotation =opt.offRotation[0] +Math.random() * (opt.offRotation[1] - opt.offRotation[0]);this.randomRot_tmp.set(0, 0, randomRotation);nodePool[0].setRotationFromEuler(this.randomRot_tmp); // 使用临时变量而非原始数组}// 坐标计算(修复2:确保使用正确的坐标索引)if (opt.offSize) {opt.position[0] += Math.random() * opt.offSize[0];opt.position[2] += Math.random() * opt.offSize[1];}// 设置节点属性nodePool[0].position.set(opt.position[0], opt.position[1], opt.position[2]);this.PlaneNode.addChild(nodePool[0]);}
在资源配置上也用了分包管理的思想, 给每个关卡可以单独配置
包括圆形、范围、直线随机生成树木或其他
这样的方式 我只需要管理关卡的map数据 以及这个预制体的加入就好了,是不是很方便
最终效果
树是直线的 地面上的蘑菇草之类的是范围的, 后期也可以加上在放置的位置去获取Terrain这个位置的高度来确定位置(我的只是平面没考虑,有api是可以获取高度的)
环境已经搭建好了
人物角色
在多次从头开始的情况下,我始终觉得应该拆分角色(控制器,实际模型)。在初始化时候,只有这个控制器才代表当前的玩家,里面的模型或者技能之类的都是附加的,是通过这个控制器来进行整个使用过程的控制,所以这个控制器就应该是一个预制体,每个模型也应该是额外的预制体, 并且每个比如角色类型, 是一套适配控制器的(模型,动画,数据,属性)
暂只考虑只有一个角色。其中我每个敌人也是公共用这个配置的控制器, 和角色同理
其实所有角色都是通过以上两个预制体实例出来的。
期间我使用了两种方法,一个是用内存池的方式来加载,但是每次加都或多或少有上次的属性残留,很麻烦, 所以后面我直接实例预制体了,就不通过内存池, 这样就方便多了,因为每个是新的,性能问题,哎暂时不考虑把
我给角色和敌人配置了不同的分包,避免后期滥用分包的问题,超过4M再进行加分包
角色实现
实现代码篇
这步,我一开始是完全敌人和角色是一体的,但是发现,从0到1 的过程 变数太多,重构太多,毕竟不是专业而且不知道哪个公用哪个不同, 在这个版本中, 我使用两套(大部分代码还是相同的)的方式来做
这里说一句 ,整个js我都放在主包里,不进行分包,一方面做个2M以上的代码也比较多了,没必要,另一方面 ,运行时候不用太考虑js分包,是很爽的, 一开始我把js都放分包,有的地方主包又要用,代码会重复部分,后期代码成熟后再考虑
基本上一个文件夹就一个功能的ts,文件名之所以都加上角色和敌人的区分,是为了编程时候不搞错,名字相同不好找而且容易混掉。
在总控实现加载各部分,reroop
export class Actor extends Component {/**挂载组件 *//**移动组件 */_moveControl: ActorMoveControl;/**动画组件 */_animControl: ActorAnimActiveControl;/**武器组件 */_weaponControl: ActorWeapon;/**攻击组件 */_attackControl: ActorAttackControl;/**属性组件 */_attribute: ActorAttribute;/**AI大脑实例 */_aiBrain: ActorAiBrain;/**事件控制器 */_eventControl: ActorEventControl;// 初始化AIthis._aiBrain = new ActorAiBrain(this, opt