13674307070 发表于 2026-8-20 08:34:04

组队系统是 VV 引擎最小的社交模块,但细节里藏着不少门道

一、触发体系:生命周期拆得比行会还细
组队系统的触发有十个,比行会系统还多两个。看起来奇怪 —— 组队功能比行会简单,触发怎么反而更多?

仔细看就明白了:组队的生命周期节点拆得更细。

行会的触发是按操作分的 —— 创建、加入、退出、解散,每个操作前后各一个。组队的触发除了创建、加入、退出前后各一个之外,还多了 "邀请" 这个独立节点,以及 "添加成员"" 删除成员 "两个和加入退出不完全等同的节点,再加上一个组队专属的" 杀怪 " 触发。

逐个来看。

创建前 OnStartGroup,玩家发起创建队伍的时候触发。这个触发的示例代码很有意思 —— 里面演示了如何阻止创建:判断等级不够,提示玩家,然后调用 creategroupfail 打断。注释里还特别提到,23.08.30 之前的版本需要调用 TXT 命令 callscriptex(actor,"creategroupfail") 来打断,之后的版本可以直接调 Lua 函数。

这个细节说明两件事:第一,创建组队是可以被脚本拦截的,和行会的 Before 系列一样;第二,引擎在迭代过程中把原来需要走 TXT 命令的操作逐步改成了原生 Lua 接口,这是一个持续完善的过程。实际用的时候要注意版本差异,老版本可能还得走 TXT 命令。

邀请 这个触发的文档有个小问题 —— 标题写的触发名是 OnGroupCreate,但代码示例里的函数名是 OnInviteGroup。两个名字对不上。这种文档和实际不一致的情况在开发中很常见,实际用的时候要以引擎实际触发的函数名为准,最好写个测试函数打个日志确认一下,不要光看文档。

邀请触发带两个参数:邀请人和被邀请人。这是组队系统里少有的带两个玩家对象的触发,因为邀请本身就是两个人之间的交互。你可以在这里判断邀请人有没有权限、被邀请人是不是在黑名单里、两个人是不是在同一个地图,不满足就阻止邀请。

加入前和加入后, OnBeforeJoinGroup 和 OnJoinGroup,都是只带一个玩家对象。这两个触发和行会的加入前后逻辑一样 ——Before 管拦截,After 管后续处理。比如加入后给个组队 buff、发个欢迎提示,都写在 After 里。

离开前和离开后, OnBeforeLeaveGroup 和 OnLeaveGroup。这里有个设计细节:离开后的触发多了一个 groupowner 参数,告诉你队长是谁。其他触发大多只有玩家自己的对象,唯独离开后带了队长对象。

为什么?我的理解是:玩家离开队伍之后,他自己已经不在队伍里了,这时候如果想给队长发个通知(比如 "某某退出了队伍"),或者处理队长相关的逻辑(比如退队后扣除队长的什么状态),就需要知道队长是谁。如果不带这个参数,玩家退队后就拿不到队伍信息了,因为他已经不是队员,调 getgroupmember 也拿不到。

这个参数设计很贴心,考虑到了 "退队后还需要和原队伍交互" 的场景。

添加成员和删除成员, OnAddGroupMember 和 OnDelGroupMember。这两个触发和加入退出看起来重复,其实不完全一样。加入退出是玩家主动行为的触发,添加删除可能是队长操作的结果 —— 比如队长邀请一个人,对方同意后,系统执行 "添加成员" 这个动作,这时候触发 OnAddGroupMember。队长踢人,系统执行 "删除成员",触发 OnDelGroupMember。

这两个触发的存在,意味着组队的人员变动不管是主动还是被动,都有对应的通知点。做组队相关的逻辑(比如组队 buff 的添加和移除),挂在这两个触发上比挂在加入退出上更准确 —— 因为不管什么原因导致的人员变化,都会走添加删除。

组队杀怪 OnGroupKillMon,这是组队系统最有价值的一个触发。当队伍中的成员杀死怪物时触发,带玩家对象和怪物名字。这个触发是做组队经验分配、组队掉落分配、组队任务进度的核心。

比如做 "组队击杀 100 只白野猪" 的任务,就挂在这个触发上,每次触发判断怪物名字是不是白野猪,然后给队伍里所有人加进度。做组队经验加成,也是在这里算经验然后分配给每个队员。

这个触发只带了杀怪的那个玩家和怪物名字,没有带整个队伍列表。如果要给所有队员发奖励,需要自己调 getgroupmember 拿队伍列表,然后遍历处理。这个设计是合理的 —— 触发只提供最核心的信息,队伍列表需要的时候自己去拿,避免每次触发都传一整个列表浪费性能。

二、接口设计:极简到只有四个
组队系统的接口只有四个,是所有模块里最少的。

创建队伍 creategroup,就一个玩家对象参数,调用就创建。没有人数上限参数,没有分配模式参数,没有队伍名称参数。创建出来的队伍就是引擎默认的配置。

添加队员 addgroupmember,传玩家对象和组员的 UserId。注意这里用的是 UserId,不是玩家对象。 删除队员 delgroupmember 同样用 UserId。

为什么添加删除用 UserId 而不是玩家对象?我的理解是:组队的成员管理本质上是按身份标识来操作的,UserId 是玩家的唯一身份标识,比玩家对象更稳定。玩家对象可能因为下线、重连而变化,但 UserId 是不变的。而且 UserId 可以持久化存储,做离线组队、队伍数据保存的时候更方便。

但这也带来一个问题:你要添加或删除队员,得先拿到对方的 UserId。如果只有玩家对象,需要先调 getbaseinfo 之类的接口去查 UserId,多一步操作。而且 UserId 是字符串类型,传的时候注意不要传成数字。

获取队员列表 getgroupmember,传玩家对象,返回队员列表的 table。这个接口是组队系统最常用的接口 —— 做组队 buff 要遍历队员,做组队经验分配要遍历队员,做组队传送要遍历队员,基本所有组队相关的批量操作都要先拿列表。

四个接口,覆盖了组队最核心的操作:建队、加人、踢人、查人。但也仅此而已。

组队系统里很多常见功能,接口里是没有的 —— 比如设置队伍人数上限、切换物品分配模式(自由拾取 / 轮流拾取 / 队长分配)、转让队长、开启 / 关闭自动接受邀请、设置队伍密码。这些功能要么走传统 TXT 脚本命令,要么引擎内置了但没暴露 Lua 接口,要么根本不支持。

这个 "接口极少" 的特点,其实反映了组队系统在整个引擎中的定位 —— 它是一个基础的、轻量的社交模块,引擎只提供最核心的组队机制,复杂的组队玩法(比如组队副本、组队竞技场、组队经验加成规则)全部交给脚本层去实现。

这种定位有好有坏。好处是组队系统足够简单,不会出什么复杂的 bug,引擎底层稳定。坏处是做复杂组队玩法的时候,很多东西要自己模拟 —— 比如想做 "队伍人数越多经验加成越高",引擎没有内置,你得自己在 OnGroupKillMon 里算人数、算加成、发经验。想做 "队长分配模式",引擎没有,你得自己拦截拾取事件,然后根据队伍设置决定物品给谁。

所以用组队系统的时候,心态要摆正:引擎给你的是一个 "能组在一起" 的基础框架,所有玩法层面的东西都要自己做。 不要指望引擎内置了经验分配、掉落分配这些东西,大概率没有。

三、组队战斗:最有价值的联动点
组队系统和战斗系统的联动,主要就靠 OnGroupKillMon 这一个触发。但这一个触发,能撑起几乎所有组队战斗玩法。

最基础的用法是组队经验分配。玩家组队杀怪,经验怎么分?引擎没有内置分配规则,你得自己写。在 OnGroupKillMon 里,拿到杀怪玩家和怪物名字,然后调 getgroupmember 拿队伍列表,根据人数平均分配经验,或者按等级加权分配,或者队长拿大头队员拿小头,规则完全由你定。

组队掉落分配也是类似。怪物死了掉东西,谁来捡?如果要做 "队伍拾取" 模式,需要在怪物掉落的触发里判断击杀者有没有队伍,然后根据队伍的分配规则决定物品归属。这个逻辑引擎没有内置,要自己组合怪物掉落触发和组队接口来实现。

组队任务进度是另一个高频场景。"队伍中任意一人击杀怪物,所有队员任务进度 + 1",就在 OnGroupKillMon 里判断怪物名字,然后遍历队员列表,每个人都加进度。反过来 "必须队伍所有人都在附近才算组队击杀",也在这里做距离判断,不在附近的不加进度。

组队 buff 的管理也和这个触发有关。比如 "组队时每 10 秒回 1% 血量",不能在杀怪触发里做(不杀怪就不触发),得用定时器或者其他触发。但 "组队击杀怪物时给全队加一个临时攻击力 buff",就可以在 OnGroupKillMon 里做。

这里有个心得:OnGroupKillMon 触发的是 "队伍成员杀死怪物",不是 "队伍中的怪物被杀死"。也就是说,只有队伍里的人拿到了击杀归属,才会触发。如果怪物是被队伍外的人抢了最后一刀,这个触发不会触发。做组队任务的时候要注意这一点 —— 如果任务要求 "组队参与击杀" 而不是 "组队获得击杀",光靠这个触发可能不够,还需要结合怪物伤害记录来判断。

另外,这个触发带的是怪物名字,不是怪物对象。如果你需要怪物的更多信息(比如等级、血量、坐标),光靠名字拿不到。大部分场景下名字够用了 —— 判断是什么怪物、对应多少经验、掉什么东西,按名字查配置表就行。但如果要做 "根据怪物剩余血量决定经验加成" 这种逻辑,就需要在怪物死亡的其他触发里记录信息,然后在组队杀怪触发里关联使用。

四、几个实际使用中的心得和坑
把组队系统过完,结合实际开发场景,总结几个心得和容易踩的坑。

第一,触发名和文档可能对不上。 邀请组队那个触发,文档标题写的是 OnGroupCreate,代码示例写的是 OnInviteGroup,两个名字不一样。这种情况不要猜,写个空函数打日志测试一下,确认引擎实际调用的是哪个名字。猜错了的话,触发函数永远不会被调用,你还以为是逻辑有问题,查半天查不出来。

第二,创建组队的打断方式有版本差异。 OnStartGroup 里阻止创建,新版本直接调 creategroupfail,老版本要走 callscriptex(actor,"creategroupfail") 调用 TXT 命令。如果你的版本比较老,直接调 Lua 函数可能不生效。用之前先确认版本,或者两种方式都写上做兼容。

第三,添加删除队员用的是 UserId,不是玩家对象。 addgroupmember 和 delgroupmember 的第二个参数是字符串类型的 UserId。如果你传了玩家对象,或者传了数字类型的 ID,可能会操作失败。用之前先把 UserId 转成字符串,而且要确认你拿到的是 UserId 而不是其他什么 ID(比如角色 ID、账号 ID,这些可能不一样)。

第四,退队后拿不到队伍信息,所以 OnLeaveGroup 带了队长参数。 如果你需要在玩家退队后做和原队伍相关的处理(比如通知队长、扣除队伍状态),用触发参数里的 groupowner,不要试图自己去查 —— 玩家退队后已经不在队伍里了, getgroupmember 拿不到他的队伍。

第五,组队接口极少,很多功能要自己实现。 人数上限、分配模式、队长转让、组队经验、组队掉落,这些常见功能引擎都没有内置接口。做组队玩法之前,先列清楚你需要哪些功能,哪些引擎有、哪些要自己写,心里有底再动手。不要做到一半发现 "原来引擎没有这个功能",再想 workaround 就被动了。

第六,getgroupmember 返回的是 table,用之前先判空。 玩家没有队伍的时候,这个接口返回什么文档没说,可能是 nil 也可能是空 table。用之前先判断一下是不是有效的 table,再遍历。直接对 nil 做 ipairs 会报错。

第七,组队杀怪触发只带杀怪者和怪物名,要给全队发东西自己遍历。 OnGroupKillMon 不会自动给所有队员发经验或奖励,它只是告诉你 "队伍里有人杀了一只怪"。后续的分配、通知、奖励,全部要你自己拿队员列表然后循环处理。这个触发是起点,不是终点。

第八,人员变动的逻辑优先挂在添加删除触发上。 OnAddGroupMember 和 OnDelGroupMember 是所有人员变动的统一出口 —— 不管是主动加入、被邀请加入、被踢、主动退,最终都会走添加或删除。把组队 buff 的添加移除、队伍人数变化的处理挂在这两个触发上,比分别挂在加入退出上更全面,不会漏。


13674307070 发表于 2026-8-20 08:34:49

挠头的500贡献

13674307070 发表于 2026-8-21 08:24:06

挠头的500贡献
页: [1]
查看完整版本: 组队系统是 VV 引擎最小的社交模块,但细节里藏着不少门道