13674307070 发表于 2026-8-20 08:21:22

啃完 VV 引擎人物系统百余个 API 后,我总结出了这套设计逻辑

这两天把 VV 引擎人物相关的接口从头到尾过了一遍,数量不少,从改名改色到技能释放,从货币操作到状态叠加,零零总总一百多个函数。刚看的时候头大,觉得就是一堆零散的命令。但全部过完之后再回头看,发现这些接口背后其实有一套很清晰的设计逻辑。

这篇不做参数罗列,那些文档里都有。我想聊的是学完之后的理解—— 这些接口为什么这么设计、分类的逻辑是什么、实际用的时候有哪些坑和心得。

一、属性系统:数值与状态的两条线
人物属性这块,最核心的设计思路是把 "基础数值" 和 "临时状态" 分开处理。

基础数值走的是一套体系。获取单条属性用 gethumability,传一个属性 ID 就能拿到对应的值,生命、魔法、攻防道术全是一个接口搞定,靠 ID 区分。修改属性则走 addattrlist,用字符串批量加,还能分组管理,一组属性对应一个名字,要清除的时候直接按组删。这种设计比 "每个属性一个 get/set" 要简洁得多,新增属性也不用加接口。

刷新属性有单独的 recalcabilitys,这个细节很重要 —— 你改了装备、加了 buff,属性不是自动变的,得手动调一次刷新。刚开始容易忘,改完属性发现面板没变化,查半天就是没调刷新。这其实是引擎在告诉你:属性计算是有成本的,不要频繁触发。

临时状态走的是另一套体系,核心是 changemode。无敌、隐身、冰冻、麻痹、吸血、护身…… 二十多种状态全塞在这一个接口里,靠模式 ID 区分。这个设计的好处是统一,所有状态的施加、计时、到期清除走的是同一套底层逻辑。但坏处也明显 —— 参数含义不统一,有的模式 param1 是几率,有的是属性值,有的是范围,写的时候必须对着文档查,记不住。

这里有个心得:状态类效果尽量用 changemode,不要自己用定时器模拟。引擎底层的状态系统会处理到期清除、死亡清除、地图切换清除这些边界情况,自己写定时器很容易漏,到时候 buff 永久存在或者卡状态,排查起来很痛苦。

改名这个功能单独拿出来说,因为它的设计很有意思。 changehumname 不是直接改,而是走了一整套流程 —— 先查询、再过滤、再检查重名、最后执行改名,每个阶段都有对应的回调函数可以挂逻辑。这说明引擎把改名当成一个有状态的事务来处理,而不是简单的赋值。实际用的时候,这些回调就是你插提示、插特效、插记录的地方,不用自己去判断改名成功还是失败,引擎会在对应阶段调你的函数。

二、货币与装备:经济系统的底层支撑
货币这块接口不多,但设计得很务实。

最基础的是 querymoney 和 changemoney,靠货币 ID 操作,1 到 100 号,对应货币表里的配置。然后又补了一套按名称操作的 getbindmoney 和 consumebindmoney,直接传 "金币"" 元宝 " 这种汉字名称。

两套并存的原因很现实:写脚本的时候记 ID 容易错,记名字更直观。但按名称查多了一层映射,性能上略差。所以高频调用(比如战斗中反复扣金币)用 ID,低频调用(比如 NPC 对话里查一下有没有元宝)用名字,按场景选。

changemoney 有个 send 参数,控制是否推送到客户端。这个细节很关键 —— 批量操作货币的时候,比如一次性加十种货币,前九次都设 send=false,最后一次设 true,客户端只刷新一次。如果每次都推送,界面会闪十下,体验很差。

装备系统的接口体现了一个原则:位置驱动。不管是穿戴 takeonitem、脱下 takeoffitem、还是改外观 changeitemshape、加特效 changedresseffect,第一个参数永远是玩家,第二个就是位置。装备在哪个格子,比装备是什么更重要。这和传奇类游戏的底层数据结构一致 —— 装备数据就是按槽位存的,操作自然也按槽位来。

背包格子数可以动态扩, setbagcount 支持 46 到 126 格。这个功能看起来简单,但背后意味着引擎的背包数据结构是动态的,不是写死的数组。实际用的时候要注意,扩格子是即时生效的,但缩格子的时候如果玩家包里东西超过了新上限,会发生什么文档没说,这种边界情况一定要自己测试。

外观相关的接口有个统一趋势: setfeature 一个接口搞定衣服、武器、翅膀、盾牌的外观和特效,靠 type 区分。比之前每个部位一个接口要清爽得多。但参数也多,time、param1、param2 各有含义,尤其是 param2 控制隐藏斗笠、头发、盾牌这些附属部件,做时装的时候经常要用,容易搞混。

三、技能体系:从数据到释放的完整链路
技能是人物系统里最复杂的一块,接口也最多。我把它分成四层来看。

第一层是技能数据。 getskillinfo 查等级、强化、熟练度, setskillinfo 改这些值, addskill 加技能, delskill 删技能, clearskill 全清, getallskills 拿整个技能列表。这一层是纯数据操作,没有副作用,改完就是改完了。

这里有个设计细节: getskillinfo 的返回值,没有技能的时候返回 nil 而不是 0。这个区别很重要,Lua 里 nil 和 0 是两回事,判断的时候要写 if lv and lv > 0,不能只写 if lv > 0,否则 nil 会报错。刚上手的时候很容易踩这个坑。

第二层是技能释放。 三个接口对应三种目标: releasemagic 对攻击目标或自身放, releasemagic_target 对指定对象放, releasemagic_pos 对指定坐标放。覆盖了所有释放场景,而且参数结构一致,只是目标参数不同。

这一层的心得是:脚本释放技能和玩家手动释放走的不是完全相同的逻辑。脚本释放可以指定是否显示施法动作,还能换技能外观(NewEffect 参数),这意味着你可以用一个技能的伤害判定配另一个技能的视觉效果,做自定义技能的时候非常有用。但反过来,脚本释放的技能可能不会触发某些只有手动释放才会触发的 buff 或被动,这个要实测。

第三层是技能冷却。 GetSkillRawCD 拿原始 CD, GetSkillCD 拿当前剩余 CD, SetSkillDecCD 减 CD, SkillResetCD 重置 CD。CD 的单位是毫秒,不是秒,这个一定要注意,传错了差一千倍。

减 CD 和重置 CD 是两个概念。减 CD 是在当前剩余时间上扣,适合做 "减少 5 秒冷却" 这种效果。重置 CD 是直接清零,适合做 "下一次技能无冷却" 这种效果。不要混着用。

第四层是技能强化。 setskillpower 加威力, setskilldefpower 加防御威力, setmagicskillefft 改特效。这一层是做自定义技能和装备特效的核心。威力支持按点数加和按百分比加两种模式,做装备词条的时候很灵活。

技能名字和 ID 的互转有 getskillname 和 getskillindex,写通用逻辑的时候建议用 ID,因为名字可能被改,ID 不会变。但配置表和脚本里写名字更可读,实际项目里可以做一层映射。

四、玩家对象与交互:对象化的便利
整个系统的基石是玩家对象。几乎所有接口的第一个参数都是 play/actor,就是这个对象。

获取玩家对象有三种方式:按名字 GetPlayerByName,按唯一 ID GetPlayerByID,拿全服列表 GetPlayerList。前两个是精确查找,第三个是遍历。做全服公告、范围技能、排行榜统计的时候用列表遍历。

这里有个坑: GetPlayerByName 和 GetPlayerByID 查不到的时候返回什么,文档没明说。按 Lua 的惯例应该是 nil,但调用之前最好判空,不然对一个 nil 对象调方法直接报错。

GetPlayerList 有个参数控制是否剔除离线挂机玩家。这个细节说明引擎把在线玩家和离线挂机角色是分开管理的,遍历的时候要想清楚你需不需要包含挂机的,不然统计在线人数会偏大。

攻击模式这块接口很简单,改、强制改、查,三个接口。但 setattackmode 强制修改有个时间参数,时间到了自动恢复。这个设计的应用场景很明确 —— 比如某个技能持续 5 秒内强制全体攻击,时间到了自动切回去,不用自己写定时器恢复。引擎帮你处理了状态回滚。

GM 权限的获取和设置是独立的接口, getgmlevel 和 setgmlevel。这个值和攻击模式、玩家状态都不挂钩,是单独的权限标记。做命令系统、管理员功能的时候靠这个判断。

复活 realive 和踢人 kick 是两个简单但高频的接口,没什么好说的,参数就一个玩家对象。但注意复活只是把人拉起来,血量蓝量回多少、有没有无敌保护,这些都要自己额外处理,不是复活接口全包的。

五、延时与伤害:异步逻辑的处理方式
delaygoto 是整个系统里我觉得设计得最巧妙的接口之一。

传统脚本里做延时逻辑很麻烦,要么用定时器,要么自己记时间戳轮询。VV 引擎的 delaygoto 直接把 "延时多久之后调用哪个函数" 封装成了一个调用,参数传进去,到点自动执行。还支持换地图是否删除这个延时,考虑到了玩家中途飞走的情况。

参数传递有两种写法,一种是把参数拼在函数字符串里,一种是单独传最后。两种都能用,但单独传的方式更清晰,也避免了参数里有逗号导致解析错误。

cleardelaygoto 可以取消还没执行的延时,这个很重要。比如玩家用了一个 5 秒后生效的技能,3 秒的时候他掉线了或者换地图了,这时候就得手动取消延时,不然人都走了效果还在原地触发,就出 bug 了。

范围伤害 RangeHarm 是另一个重量级接口。一个函数搞定了范围判定、伤害计算、附加效果(击退、冰冻、麻痹、吸血、真实伤害等十几种)、状态检查、目标筛选、特效播放。做 AOE 技能的时候基本靠它。

这个接口的参数很多,但逻辑是清晰的:先定范围和伤害,再加附加效果,最后控制目标类型和特效。心得是:附加效果的 addvalue 含义因 addtype 不同而不同,击退是距离、冰冻是时间、吸血是数值、百分比伤害是比例,写的时候一定要对着查,不要想当然。

飘血飘字 sendattackeff 单独拿出来说,因为它有个很实用的设计:hitter 参数传 "*" 就是广播,视野内所有人都能看到。不传的话只有攻击者能看到。做自定义伤害数字的时候,这个参数决定了是个人秀还是全服可见,效果完全不同。

六、状态与战斗:效果叠加的设计思路
ChangeState 是后来补充的状态接口,和 changemode 有点重叠,但更偏向战斗中的 debuff。石化、冰冻、蛛网、红绿毒、定身、瘫痪、禁锢、吸血吸蓝、间隔掉血、禁技能,十四种状态。

两个状态接口并存的原因,我的理解是: changemode 更偏向角色模式(无敌、隐身、管理模式这些), ChangeState 更偏向战斗 debuff。但实际边界有点模糊,比如冰冻、麻痹、禁锢两边都有。用的时候选一个体系就行,不要混着来,不然状态清除的时候可能漏。

速度相关有两套接口: changespeed 按等级改(-10 到 10,每级 10%), changespeedex 按百分比改(精确到 1%)。前者适合做装备词条("攻击速度 + 3"),后者适合做技能 buff("移动速度提升 25%")。按场景选,不要都用一套。

穿人穿怪 throughhum 支持单独穿人、单独穿怪、都穿,还能区分玩家和宝宝。这个功能做特殊地图(比如安全区穿人)和变身道具的时候很有用。注意是有时间限制的,不是永久的,要持续穿就得自己续。

目标设置 settargetcert 有个很重要的注释:玩家实际攻击目标由客户端锁定,服务端改了会失效。这个说明很诚实,告诉你这个接口对玩家可能不好使,主要用于怪物、英雄、宝宝这些服务端完全控制的单位。不要指望用这个强制玩家打某个目标,做不到的。

距离检测有三个接口:判断是否在范围内 CheckActorRange、获取具体距离 GetActorRange、判断是否在某个坐标范围内 CheckHumInRange。前两个是对象对对象,第三个是对象对坐标。做技能范围判定的时候用第一个就够了,需要显示具体距离(比如 "目标距离你 15 米")的时候用第二个。

自动寻路 AutoGotoXY 让玩家自动走到某个点,做任务引导的时候很实用。但寻路过程中玩家可以手动打断,这个要考虑到,不要假设寻路一定能到达。

七、杂项功能:细节见真章
剩下的接口比较散,但每个都有它的用处。

离线挂机 offlineplay 有个很重要的注意事项:使用前必须关闭所有定时器。因为离线后玩家对象还在,但客户端连接断了,如果定时器里有往客户端发消息的逻辑,就会出问题。这个坑不注意的话,离线挂机角色会导致服务端报错甚至崩溃。

转生系统 四个接口:转生成败 renewlevel、加属性点 bonuspoint、查属性点 getbonuspoint、重置属性点 restbonuspoint。设计中规中矩,转生次数、转生后等级、分配点数三个核心参数一次传入。注意属性点的范围限制(0 到 1000),不要超。

骑马系统 三个接口:检查是否骑马 checkonhorse、上马 ridehorse、下马 dismounthorse。上马的时候要传坐骑外观、特效、人物骑马外观、坐骑类型(单人 / 双人 / 连体),参数不少。做坐骑系统的时候,这些外观值要和配置表对应好。

足迹特效 setmoveeff 是个很有意思的小功能,玩家走路的时候脚下播放特效。支持单步和两步两种播放模式,做高端称号或者特殊装备的地面特效的时候用。注意这个是纯视觉效果,不影响移动逻辑。

骰子功能 playdice 做赌博或者随机事件的时候用。骰子点数存在私人变量 D0 到 D5 里,动画结束后调回调函数。这个设计很巧妙,用私人变量传结果,不用额外的返回值机制。

宝箱开启 opendragonbox 直接传宝箱 ID 和次数,不读配置表的次数只认传入的参数。这个说明接口优先级高于配置表,做 GM 命令或者特殊活动的时候可以绕过配置直接开。

神佑系统 一组接口,开关神佑显示、开单个格子、关单个格子、全开全关、检测状态。还区分普通和时装两套。这个系统的接口设计得很规整,操作和检测一一对应,用起来不容易乱。

自定义广播 setotherparams 是个很灵活的功能,服务端设 5 个自定义属性值,客户端注册事件监听变化。可以用来做自定义数据同步,比如足迹特效的参数就是这么传的。相当于引擎给你留了 5 个自定义同步通道,不够用的话就得自己走前后端消息了。

八、整体感受与学习建议
把这一百多个接口全部过完之后,最大的感受是:VV 引擎的人物系统是 "够用就好" 的工程化产物,不是追求优雅的学术设计。

它有明显的历史包袱。比如 changemode 和 ChangeState 功能重叠,比如属性获取靠 ID 而不是具名函数,比如有些接口参数含义不统一。这些都是长期迭代积累下来的,不是一次性设计的结果。

但它也有很多务实的亮点。比如 delaygoto 把异步逻辑简化成一次调用,比如 addattrlist 用字符串和分组管理属性,比如货币同时支持 ID 和名称两种操作方式,比如改名拆成多阶段回调。这些设计都是从实际开发需求里长出来的,解决的是真实的痛点。

学习这套系统,我的建议是:

第一,不要试图记住所有接口。 一百多个函数,记不住也没必要。先建立分类框架 —— 属性、货币、装备、技能、对象、战斗、状态、杂项,八大块。用到哪一块的时候去查对应的接口,查两次就记住了。

第二,重点理解设计模式而不是参数。 参数文档里都有,查一下就行。但设计模式是通用的 —— 比如状态类效果用引擎自带的不要自己模拟,比如批量操作最后再推送客户端,比如延时逻辑要记得取消。这些理解了,换个引擎也能用。

第三,边界情况比正常逻辑更重要。 大部分接口正常调用都没问题,出问题都在边界 —— 玩家掉线了延时还在不在、换地图了 buff 清不清、缩背包了东西超了怎么办、技能对 nil 对象调会怎样。这些文档不一定写,要自己测。

第四,写脚本之前先想清楚数据流向。 一个功能涉及哪些属性变化、要不要刷新、要不要推客户端、有没有状态叠加、需不需要延时回调,先在脑子里过一遍再动手。不然写一半发现漏了东西,返工更慢。

VV 引擎的人物系统不完美,但足够用,而且该有的功能基本都有。理解了它的设计逻辑,写脚本的时候就不是在 "对着文档抄参数",而是在 "用一套工具解决问题"。这个转变,就是从新手到熟练的关键。


13674307070 发表于 2026-8-20 08:32:44

挠头的500贡献

13674307070 发表于 2026-8-20 08:53:12

挠头的500积分

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

挠头的500贡献
页: [1]
查看完整版本: 啃完 VV 引擎人物系统百余个 API 后,我总结出了这套设计逻辑