cocos 怎么能实现加载mod的功能

cocos 怎么能实现加载mod的功能,实现游戏的自定义代码和配置加载,可以使用远程包来实现吗?如果不提供完整源码可以打包吗?我的意思是官方的dlc和玩家mod 的区别

1赞

路过看到mod相关的话题,我没用cocos做过mod,不过随便说一下我对mod框架的理解。

支持mod的框架我认为要做下面几点:
1.(引擎)资源的顺序覆盖与新增
mod可以增加资源,也可以对游戏本身的资源进行覆盖,后加载的mod可以覆盖前加载mod的资源。
这一点最好是有一个VFS(Virtual File System),以路径索引资源(好覆盖),并支持按优先级挂载多个目录和pack包。godot就是这种形式,一般自研引擎也是类似这种方式。

2.(游戏)mod的加载列表
不管是用mod 管理器,还是steam,要加载mod必须支持一个配置列表,包括加载的顺序和其他参数。在游戏启动的时候根据这个列表来依次加载mod。如果想做的更好,就做一个mod launcher工具。

3.mod 本身的结构以及入口
mod 本身应该组织成一个包的形式,包括mod文件夹,里面的资源内容(资源路径和游戏本身一致好覆盖),还有mod根文件夹下的入口脚本(init.lua init.js 等),游戏加载mod后,从入口脚本调用mod的初始化。

4.(游戏)针对mod提供的API
游戏针对Mod提供的API分为几类:
a) 实体数据库(配置)的增删查改。例如增加一种敌人,修改某个道具的属性等。
b) 游戏的全局数据查询接口。玩家数据等查询接口。
c) 游戏战斗场景的查询与控制接口。包括场景信息查询,实体对象的操作(创建,销毁,动作)等,一般用于战斗类mod。
d) Hook,包括游戏生命周期,各种实体动作的回调,各种事件的回调。例如OnSceneLoad,OnAttack等。
e) (可选)UI界面相关的API,用于扩展和新增UI界面。
mod初始化的时候,根据情况注册Hook,修改配置,在Hook内,调用各种查询和控制接口。

5.(游戏,可选)Debug Console
如果需要支持mod,最好游戏里面要有一个debug console,用于执行各种命令,这个是测试mod不可或缺的部分。

6.(工具,可选)Mod Editor
提供一个专门给Mod的编辑器,或者一个专门给mod的模版工程。编辑器就不用说了,模版工程的话可以不用暴露游戏内的实际代码,仅提供游戏给与的接口即可,游戏核心部分可以以加密或者二进制(dll)的形式提供。

另外聊一下关于cocos 支持mod,我没用cocos做过mod,只能大致上聊一下方向。
cocos 支持mod最大的一点困难,是资源的顺序覆盖与新增,由于cocos 和 unity一样,是以uuid为资源索引,导致新增和替换资源比较困难。
如果顺着cocos本来的资源方案,资源包可以做成asset bundle的形式,符合cocos uuid的资源路径,并且可以通过config插入uuid到资源的映射表。顺序覆盖可以通过fileUtils.setSearchPaths来做。这个API支持挂载多个资源目录,原来是用于热更的资源覆盖,也可以作为支持mod的资源覆盖的基础。这个方式也还是要改一点引擎才能做好细节。
或者可以魔改cocos的资源索引方案,以loadRemote为基础,以资源路径为索引重新构建工程的资源引用。在之上构建一套Virtual File System即可。
执行脚本应该也没问题,js本身就有带着eval等可以执行字符串的能力。
其他基本上问题都不大,就是要定制不少细节。

另外还有一些外置的mod框架,例如unity的mod可以用HarmonyX等等,java的游戏也有,例如杀戮尖塔的mod启动器,如果在游戏内做不方便,也可以考虑这种外挂式的mod框架,不过cocos没有类似的。

2赞

我有尝试了一些方案,但是因为要依赖主项目的程序代码所以不是很顺利,我想到了一些解决方案,因为我要加载的代码脚本是纯函数所以问题不大,可以通过require的导入进来,我觉得达到我要的效果不是很难,至于bundle 这个功能不适合与开发mod,谢谢你的回答,欢迎加入cocos论坛

image
PC来说,就Bundle就可以了。只要代码做好规划,完全满足使用

bundle 怎么做到依赖主项目的情况下,不提供主项目源码可以让玩家制作mod

有接口声明就行吧,ts里使用微信的接口不就是

bundle 是被导入方,不是作为导入方。不行的。bundle加载主项目,不是这逻辑

MOD不需要依赖主项目
你可以看看AMXX这些框架

如果不依赖主项目的话,怎么运行的起来这个mod呢,不还是要启动一个主项目吗

其实提供主项目代码也没什么,编成简化合并版js或者加密混淆一下,再提供到mod模版工程里面。你可以看成其他游戏框架中,主项目提供native dll,Managed dll,jar一样的编译好的东西。这种是最方便的。不然就做专门的editor和外挂式mod框架,比较麻烦。

游戏的循环还是在跑的,最上层的window对象还是在的,你可以在最上层写一套消息系统,用来驱动逻辑,mod监听你规定好的消息,比如游戏开发,用户点击,然后触发mod自己的逻辑。
这一套很稳定的,CS插件就是这么做的,生化模式,大灾变都是在这个上面写出来的。
加载那些,只允许加载bundle自己的内部资源就好

消息机制简单,主要是mod本身的逻辑,需要调用主项目的一些暴露给mod的api,例如战斗场景查询,玩家数据,实体对象的功能等。这些需要有主项目存在才行,不管是二进制形式还是加密混淆形式都可以。
在开发mod的时候几种形式引用主项目:
1.mod的模版工程,工程内必须要嵌入一个主项目。不管是二进制的还是加密混淆的都可以,有的语言也可以只提供声明文件,但运行就需要了。
2.编辑器形式,编辑器内嵌主项目,或者主项目内嵌编辑器。
3.外挂式,专门的mod启动器,启动主项目进程,并且注入其中,并反射出里面的类对象供mod使用。

不需要啊,直接消息传递就好了,mod开发都是HOOK的方式

开发mod的时候不运行调试的吗? 需要主项目就是因为要运行起来。所以分发mod开发环境的时候也要分发主项目,哪怕是二进制文件,cs不也都是要先有游戏exe吗?
而且APi用消息也比较低效,高效的是给一个dll和接口,或者是反射,直接调用就可以。

主项目是主项目,游戏自己跑起来就好,不需要给额外的东西给MOD,在顶层留一个消息对象就好,进入游戏后写一个BUNDLE检查,依次加载BUNDLE代码
BUNDLE统一一个写法,依次注册消息。
完毕


register_plugin(“插件名”, “1.0.0.0”, “作者”);

// FM_PlayerPreThink是fakemeta_const.inc中的一个枚举常量,对应玩家预思考事件,是用于添加挂钩的事件索引.
// CS1.6一般都是每秒大约刷新100次屏幕画面,而玩家也会随着这个频率,每秒触发100次预思考事件(bot大约每秒25次).
// 玩家预思考事件很实用,在这里可以实时检查玩家的属性变化,或修改玩家属性.
// 只要将它用作register_forward函数的第1个参数,我们就能为玩家预思考事件添加挂钩.
// 挂钩的目标函数会接收指定事件的所有参数,充当自己的参数.
// 玩家预思考事件只有1个事件参数,就是思考者(玩家的实体索引).

register_forward(FM_PlayerPreThink, "PlayerPreThink_PreHook", 1);



// Ham_TakeDamage是ham_const.inc中Ham枚举的成员,对应实体受伤事件,是用于添加挂钩的事件索引.
// 任何实体只要受伤便会触发受伤事件,但不包括某些直接修改生命值的情况.
// 实体受伤事件的挂钩常常被用于修改伤害值,阻止受伤,统计伤害量等等.
// 只要将它用作RegisterHam函数的第1个参数,我们就能为实体受伤事件添加挂钩.
// RegisterHam函数的第2个参数,用于设定实体原始类名,只有原始类名与其相同,才能触发钩子的目标函数.
// 1.8.2或低版本amxx并没有第4个参数,该参数用于支持bot这种没有类名的玩家实体.
// 实体受伤事件会将自身所有参数(受害者,加害者,攻击者,伤害值,伤害类型位标志)传递给钩子的目标函数.


RegisterHam(Ham_TakeDamage, "player", "PlayerTakeDamage_PreHook", 0, true);

// PlayerPreThink_PreHook会接收1个来自玩家预思考事件的参数:玩家实体索引
public PlayerPreThink_PreHook(playerEntId)
{
	server_print("实体索引为%d的玩家,触发了一次预思考事件", playerEntId);
}

// PlayerTakeDamage_PreHook会接收5个来自实体受伤事件的参数:受害者,加害者,攻击者,伤害值,伤	害类型位标志
public PlayerTakeDamage_PreHook(victimEntId, inflictorEntId, attackerEntId, Float:damage, damageFlags)
{
	// 如果保留同位上的1之后不为0,则设定钩子目标函数的计算结果为拥有阻止事件功能的宏定义常量HAM_SUPERCEDE,并提前退出函数
	if (damageFlags & DMG_FALL)
	{
		return HAM_SUPERCEDE;
	}
	return HAM_IGNORED;
}

可能你没理解我和上面那个w6166231 的意思,主项目就是游戏,游戏就是主项目的二进制形式,开发mod需要依赖这个游戏,也就是主项目的二进制形式。
然后这些注册的api,和枚举,其实就是主项目提供的接口

他说的是怎么把代码接口暴露给MOD,不需要。只需要构建一个完善的消息机制,把所有的数据对象放好,HOOK出来就好了

我前面也说了。提供二进制形式+接口就可以了。接口就是类似你这的fakemeta_const.inc。
回答楼主其实就是说,加密和编译后的代码其实也可以看成是二进制的,不需要那么严格,消息的形式需要包装,没那么方便。

消息通用百搭,可以跨主游戏。
比如我勾到一个引擎的参数,只要是用这个引擎的,都可以用。
这套哪怕是U3D,起源,都是如此

如果你真考虑所谓的“易用”
那就应该走 触发器逻辑 去构建项目
这样就真好用了

那其实算是注入式的那种了。主进程和mod相互发消息。
我看过比较多的是嵌入式的脚本,例如issac等,mod都会提供lua脚本,由游戏整体进行调度,游戏提供api。
注入外挂式的,可能需要跨引擎,不过类似这种脚本类的,基本上是每个游戏就每个游戏的做法。
另外杀戮尖塔也是注入式的,他就不是消息,而是用java反射来做的