ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

War3技能伤害继承英雄属性:触发器重算实战指南

War3技能伤害继承英雄属性:触发器重算实战指南 做了快两年的War3 RPG地图被群友问得最多的问题之一就是“技能伤害怎么跟英雄属性走”直接回答“用触发器重算”很容易真正在编辑器里落地的时候面板伤害该填多少、事件选哪个阶段、属性快照什么时候存、飘字怎么控制在正确时间每一步都有讲究。这篇文章就把我在一张防守图里搭技能伤害继承系统的完整过程拆开讲从底层原理到具体触发器写法再看几个我自己踩过的坑给同样在折腾地图编辑器的朋友一个可以直接抄作业的参考。1. 为什么技能伤害必须继承英雄属性一个数值崩塌的测试现场先说场景。当时我在做一张单英雄防守图核心玩法很常规打怪、掉装备、加三围然后挑战更高波次。技能是英雄的主要输出手段所以我给山丘之王做了两个技能其中一个就是最经典的风暴之锤。初始测试版本里技能面板上的伤害我直接填了500。英雄30级左右的时候力量大概到300普攻一下能打两三百情况还行。等我把装备系统和属性成长数值拉高英雄力量冲到800以后问题就彻底暴露了普攻暴击可以打到1500往上而风暴之锤砸下去还是固定500。更尴尬的是这个技能还有冷却时间和蓝耗在一场持续输出里性价比已经不如平A。这就是War3技能伤害继承英雄属性这个需求的典型场景。对RPG、防守、对抗地图来说英雄成长的核心反馈就是战斗力提升而技能又是战斗力的主要出口。如果技能伤害跟属性完全脱钩玩家堆属性就没有正反馈游戏体验会在中后期迅速崩塌。这个需求听起来简单但并不是在地图编辑器里勾一个选项就能解决的。原因也很直接War3原版的技能伤害结算压根就没有“读取英雄属性”这一步。你必须在默认结算之外加一层自己的计算逻辑也就是用触发器把英雄的力量、敏捷、智力读出来套一个公式再把这个结果作为伤害砸出去。所以这篇文章会有三条线。第一帮你把War3默认的技能伤害机制看清楚明白为什么必须绕开它第二给出三种真能落地的实现路线并且告诉你各自的代价第三重点讲我实际使用的“施法重算”路线从技能数据改造到触发器写法到踩坑排查完整走一遍。2. 面板伤害字段的真实面目War3技能伤害的两条计算链路很多人第一次做RPG图时都有个误解以为技能伤害会自动和英雄的攻击力或者主属性挂钩。实际上完全不是这么回事。打开物体编辑器选中风暴之锤你会看到一个“数据 - 伤害”字段。这里填的数值比如500就是一个彻头彻尾的常量。单位施放技能时游戏引擎直接拿这个500参加伤害结算先判断技能是否命中是否魔免、是否落空然后根据攻击类型和伤害类型与目标的护甲类型做一个减免计算最后从生命值里扣掉结果。这个过程里完全不会去查这个施法单位的力量是多少、敏捷是多少、智力是多少。英雄属性真正影响的是另外几个维度。力量决定生命值上限和生命回复敏捷决定攻击速度和护甲智力决定魔法值上限和魔法回复。这些都是战斗力的组成部分但它们跟技能伤害的计算链路是平行的没有交叉点。还有一类更容易误导人的技能比如“重击”、“粉碎”、“战棍”这类物理附加技能它们的数据字段里有一个“伤害倍率”或者“攻击力加成百分比”这种技能确实会读单位的攻击力。于是很多作者就产生一个错觉那我只要给英雄加攻击力技能伤害也能涨。问题在于War3里绝大多数法系技能根本没有读取攻击力的字段风暴之锤、火球、闪电链、暴风雪清一色是常量伤害。你在这个基础上想继承属性就必须自己动手翻译。我用一张表把这两条计算链路说清楚计算链路伤害来源是否读英雄属性典型技能面板字段结算物体编辑器里填的固定数值不读风暴之锤、火球术、暴风雪物理附加结算施法者当前攻击力/攻击伤害读极少数技能重击、粉碎触发器施加减伤由触发器按公式计算完全可控自定义技能、RPG成长技能有一个很经典的误传是“给技能设置每级8伤害然后让英雄升力量就能提高技能伤害”。这其实是把技能等级成长和英雄属性成长搞混了。技能等级成长是技能本身升级后数值变大这个过程确实存在但它和英雄的属性成长互不相干。就算英雄力量从100变成500只要技能不升级面板伤害还是那个值。所以结论非常明确想实现技能伤害继承英雄属性本质上是在默认结算之外再加一个“属性→伤害”的换算层。怎么加有下面三条路线。3. 三条实现路线以及对它们的冷静评价3.1 路线A面板伤害清零 施法瞬间用触发器重算这是最主流的做法也是我自己在坚持用的方案。核心思路是把技能面板的伤害字段直接设为0然后新建一个触发器监听“任意单位发动技能效果”判断触发技能是不是目标技能如果是就读取施法者的力量/敏捷/智力套一个你设计的公式再用“命令单位对目标造成伤害”这个动作把计算出来的伤害施加出去。这个路线的优点非常明显属性怎么变伤害就怎么变完全连续、实时、平滑。而且公式掌握在你自己手里你可以写线性公式、二次曲线公式也可以让不同技能吃不同属性。缺点也有。第一技能原生效动画还在但伤害是后补的时序上要处理好第二如果同时做很多个技能每个技能都要维护一段触发逻辑后期容易乱第三飘字、暴击、抗性这些都要自己扩展否则伤害显示就只是原始数值。3.2 路线B多等级模板 工程升级切换War3里有一个叫“工程升级”的技能Engineering Upgrade它可以动态替换单位身上的某个技能为另一个技能ID。利用这个机制你可以给同一个技能做多个模板版本比如“风暴之锤-LV1”“风暴之锤-LV2”一直到“风暴之锤-LV10”每个模板的伤害分别填100、200、300……然后当英雄力量达到某个档位时用触发把当前技能替换成对应模板。这个路线的优点是伤害完全由引擎结算不吃触发器时序技能数据和面板表现是自洽的做弹道、做跳字都不用特殊处理。缺点也明显属性成长是连续的但技能模板是档位的。力量881和力量919如果都落在“800档”伤害完全一样堆属性的反馈感会被卡顿。而且每个技能要做多份模板地图大了以后数据量非常恐怖。这个路线更适合防御塔、召唤物这类“属性变化不频繁、按档位成长”的单位不太适合英雄技能的平滑成长。3.3 路线C保留原伤害在受伤害事件里二次修正有些作者会说我不想动原技能面板伤害也不想每个技能写一套触发能不能在“任意单位受到伤害”这个事件里统一改这里必须泼一盆冷水War3的受伤害事件只能读取伤害值不能直接修改这次伤害的数值。你只能在这个事件触发后再额外补扣一次或者回血一次来模拟修正。比方说原技能面板伤害是200你希望按英雄属性计算后应该造成500那么事件触发时系统已经扣了200你只能在事件动作里再补扣300。问题是补扣的这300会再次触发“任意单位受到伤害”事件如果你没有做好循环保护伤害会无限递归直到把单位打死或者游戏卡死。这条路线的适用场景很窄一般只在地图已经有成熟的全局伤害统计、暴击、飘字系统时才会考虑而且是作为“伤害修正层”存在不是从零开始做继承。3.4 选型参考表我根据自己的经验给一张选型表帮助你在做决策时快速判断方案属性平滑度数据维护成本是否需要伤害事件保护适合场景施法重算平滑连续每个技能一段触发不需要但要注意二次伤害英雄技能、RPG成长技能工程升级模板档位跳变每个技能多份模板不需要防御塔、召唤物、固定档成长受伤害事件修正平滑连续全局统一修改需要严格防递归已有伤害统计系统的大地图我的建议很简单如果是新做的RPG图优先选路线A。它虽然前期写代码多一点但成长手感最好后面扩展暴击和抗性也最顺。4. 实战让风暴之锤和雷霆一击随力量成长的完整改造接下来进入实操环节。我用一个具体例子把路线A完整走一遍让风暴之锤继承力量属性再让雷霆一击也走同样的逻辑同时把伤害飘字钉在目标头上。这个例子用GUI思路描述附带JASS写法两种习惯的作者都能看懂。4.1 动手前先改技能数据伤害字段归零这是最容易被忽略的一步。很多人写完触发后发现怪物掉血是“面板伤害公式伤害”的总和就是因为没把原技能的伤害清零。打开物体编辑器找到你的风暴之锤技能把“数据 - 伤害”字段从500改成0。注意这里改0之后技能施放动画、眩晕效果、弹道效果依然保留游戏引擎会照常播放整段技能演出只是不再产生伤害。你感受到的技能表现不会变难看只会变成一个“空壳演出”伤害完全交给触发器来补。同理雷霆一击也把这个技能的伤害字段清零。如果你还有其他想走属性成长的技能统一在这个步骤清理干净。4.2 单体技能风暴之锤的施法触发器新建一个触发器命名为“伤害系统-风暴之锤”。事件、条件、动作如下事件部分我建议用“任意单位发动技能效果”Player Unit Starts The Effect of An Ability而不是“开始施放技能”。前者的触发时机在技能真正生效时如果施法者在施法前摇阶段被打断这个事件不会触发不会出现“技能没放出来但伤害已经算了”的尴尬情况。条件判断施放技能是不是你的风暴之锤。如果你希望这个伤害系统是多技能共用的这里可以用“施放技能属于单位组”之类的条件后续我会讲统一入口的写法。动作部分的核心是读取属性、计算伤害、造成伤害。以“力量×3技能等级×20”为例事件 单位 - 任意单位 发动技能效果 条件 (施放技能) 等于 风暴之锤山丘之王 动作 设置变量 Caster (触发单位) 设置变量 Target (技能施放目标) 设置变量 Dmg (((转换 ((触发单位) 的力量值(包括加成)) 为实数) x 3.00) ((转换 ((风暴之锤 的等级对 (触发单位))) 为实数) x 20.00)) 单位 - 命令 Caster 对 Target 造成 Dmg 点伤害,攻击类型: 英雄 伤害类型: 魔法这段逻辑里有一个关键点GetHeroStr取的是“包括加成”的力量也就是白字装备/状态提供的绿字都会计算在内。如果你希望技能只看英雄基础属性、不受临时Buff影响就把获取函数里的参数改成“不包括加成”。装备是否参与技能伤害是技能设计里很重要的一个分野一定要提前想清楚。对应的JASS写法如下技能ID以你地图里的实际ID为准function StormBoltDamage takes nothing returns boolean local unit caster GetTriggerUnit() local unit target GetSpellTargetUnit() local real dmg local integer str local integer lvl if GetSpellAbilityId() AHtb then set str GetHeroStr(caster, true) set lvl GetUnitAbilityLevel(caster, AHtb) set dmg I2R(str) * 3.00 I2R(lvl) * 20.00 call UnitDamageTarget(caster, target, dmg, true, false, ATTACK_TYPE_HERO, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS) endif set caster null set target null return false endfunction function InitTrig_StormBoltDamage takes nothing returns nothing local trigger t CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition(t, Condition(function StormBoltDamage)) set t null endfunctionUnitDamageTarget这个动作必须把攻击类型、伤害类型、武器类型都传明白。攻击类型用英雄伤害类型用魔法这样普通护甲、英雄护甲都会按常规公式结算如果你希望某些技能无视魔免伤害类型可以换“通用”。4.3 范围技能雷霆一击的选取与伤害风暴之锤是单体技能施法目标直接取就行。但雷霆一击这类以施法者为中心的范围技能没有明确的施法目标必须在动作里自己选单位。思路是以施法者位置为圆心选取一个圆形范围内的所有单位然后用单位组逐个检查阵营和生存状态对敌方单位造成伤害。事件 单位 - 任意单位 发动技能效果 条件 (施放技能) 等于 雷霆一击山丘之王 动作 设置变量 Caster (触发单位) 设置变量 DamagePoint (Caster 的位置) 设置变量 DamageGroup (以 DamagePoint 为中心,半径 350.00 内的所有单位) 设置变量 Dmg (((转换 ((触发单位) 的力量值(包括加成)) 为实数) x 2.00) ((转换 ((雷霆一击 的等级对 (触发单位))) 为实数) x 15.00)) 单位组 - 选取 DamageGroup 内所有单位做动作 如果 ((选取单位) 是 (Caster 的敌对单位) 为真) 且 ((选取单位) 存活) 为真 单位 - 命令 Caster 对 (选取单位) 造成 Dmg 点伤害,攻击类型: 英雄 伤害类型: 魔法 否则 单位 - 什么都不做 单位组 - 删除 DamageGroup 点 - 清除 DamagePoint这里要注意两个容易漏掉的细节。第一选取单位组和位置点都属于“会占用内存的临时对象”用完必须清空和删除否则多次施法后会累积点位泄漏地图越玩越卡。第二范围技能的伤害要自己判断敌我否则会把中立生物、友方单位甚至自己都打进去。雷霆一击本身带减速效果如果只想要继承伤害、不想让触发逻辑影响减速节奏可以把技能面板的伤害设为0保留减速字段这样技能演出和效果分离互不干扰。4.4 伤害飘字也要跟着公式走当技能伤害变成触发器计算后默认的红色伤害飘字会出现两种情况要么因为原技能面板伤害是0而完全不显示要么显示的数字依然是原技能的那点固定值。这两种都不是你想要的。最稳妥的做法是关闭游戏默认的伤害显示然后自己创建浮动文字。主菜单“游戏平衡常数”里可以关闭“显示伤害”这样所有系统默认的飘字都会消失。然后在你计算完Dmg之后直接在目标位置创建浮动文字把Dmg转成字符串显示出来。漂浮文字的关键参数包括字号、颜色、持续时间和上升速度。我的习惯是普通技能用白色12号字暴击用橙色15号字显示后让它往上飘1秒再消失。你可以在触发器里设置“漂浮文字 - 创建漂浮文字在(目标位置)”设置生命周期。顺便提醒一下如果走的是受伤害事件修正那条路线默认飘字会先弹一个200的原伤害再弹一个300的补扣伤害视觉上非常割裂。这也是我强烈建议在触发器重算路线里直接关闭系统飘字的原因。4.5 升级、洗点、换装对伤害的影响很多人在这一步就以为完事了但实际测试时会发现一个问题英雄的装备如果是在施法瞬间给力量加成的那没有问题因为GetHeroStr(caster, true)读的是当前值装备刚换上去力量就会立刻生效。真正麻烦的是技能学习等级。风暴之锤这类技能是需要手动升级的。假设英雄5级时技能升到2级公式里的“技能等级×20”会自动按当前等级计算创建触发器时不需要额外写升级事件。这一点比较友好因为GetUnitAbilityLevel读的就是实时等级。但有一种情况你要注意如果你的技能支持“洗点”比如通过某种道具把技能点重置那么技能的等级会动态变化。只要公式逻辑里用的是实时技能等级就没事。怕的是你自己把技能等级缓存到某个变量里然后技能重置后忘记更新变量伤害就可能按旧等级算。除非必要不要缓存技能等级直接读。5. 伤害继承路上的经典事故五次翻车与修复过程再往下写都是我在实际开发中踩过的坑。每一条都真实发生过并且都导致过或大或小的测试事故。5.1 第一次翻车伤害直接翻倍触发器写好后测试我发现风暴之锤一下砸过去目标掉了两段血总和明显比公式算出来的伤害高。排查了很久才意识到原技能面板伤害没有清零。那时候公式算出来是320原技能面板是300两个加起来就是620玩家看到飘字620还以为是公式的问题。修复方法很简单把物体编辑器里该技能的数据-伤害设为0只保留触发器的伤害。这个教训让我在后面每个技能接入伤害系统时都先检查一遍原面板伤害是否已经被清零。5.2 第二次翻车伤害事件死循环当时我在测试“受伤害事件补扣伤害”的思路。新建了一个触发器事件是“任意单位受到伤害”动作里判断伤害来源是某个英雄就再次用UnitDamageTarget补扣一次差值。第一次测试就把游戏弄崩了。原因也清楚UnitDamageTarget造成的伤害再次触发“任意单位受到伤害”事件里的条件又满足于是又补扣又触发……形成无限递归直到地图卡死。后来我把这条路线彻底放弃了改成施法时重算。如果实在要在受伤害事件里做修正必须用一个布尔型全局变量当“伤害锁定开关”进入伤害修正前先置真修正完成后置假嵌套调用时检测到开关为真就直接跳过。这个开关只能缓解递归解决不了飘字重复、二次结算等麻烦所以我建议一般地图别碰它。5.3 第三次翻车施法时算好的伤害0.5秒后对不上了这个坑出现在做投掷类技能的时候。技能本身有弹道从施法者扔出一个火球飞行0.6秒后命中目标。我在事件里立即算了伤害但真正扣血发生在命中那一刻中间这0.6秒可能会发生很多事目标给自己上了个减伤Buff、英雄触发了某个临时加力量的被动、甚至英雄自己换了件装备。结果就是玩家看到面板上写“伤害继承力量”实际打出来的数值却和当时力量对不上观感非常奇怪。标准解法是快照机制。施法瞬间把计算好的属性快照或者直接计算结果存进哈希表等弹道命中用计时器或者单位进入范围事件时再从哈希表读出来。哈希表以施法者HandelId为主键配合一个唯一的技能实例标识可以支持同一单位连续施放多个预结算技能而不互相覆盖。5.4 第四次翻车伤害类型选错魔免怪免疫了法术技能刚开始做继承伤害时我把所有技能伤害类型都设成“魔法”。结果遇到一个身上带魔免的单位雷霆一击过去什么都没发生怪物一滴血没掉。War3里魔法伤害类型会被魔免直接免疫这个规则对触发器造成的伤害同样生效。这里要根据技能设计意图选择伤害类型。英雄技能想被魔免完克的话伤害类型用魔法攻击类型用一个合适的常规类型如果希望技能无视魔免、稳定输出伤害类型可以用“通用”通用伤害不会因为目标魔免而直接消失。伤害类型和攻击类型是两层判断互相独立最好在建立伤害系统时就定好每类技能的类型组合不要每个技能随便填。5.5 第五次翻车两个英雄同时放技能伤害串了这张地图支持多人联机后来组队测试时发现两个玩家同时放技能伤害时高时低飘字也会串。问题出在我在GUI里用了全局变量存储“触发单位”“技能目标”等中间数据。War3的GUI动作里变量多数是全局的多个玩家同时触发同一个事件时后一个触发会覆盖前一个的变量值前一个触发后面的动作读到的就已经是别人的单位了。这也是老地图作者常说的“全局变量并发冲突”。解决办法是把关键的中间量改成局部变量。GUI原生不支持局部变量但在YDWE等增强编辑器里可以用“局部变量”插件或者直接用自定义脚本声明local变量。对于一张地图有大量技能伤害计算的场景我建议优先用JASS的局部变量它不会互相污染也能避免多人同图时变量满天飞。6. 从“继承属性”到“伤害引擎” 一次到位的架构升级等到一张图里有了十几个需要继承属性的技能继续为每个技能单独写一段触发会变得非常痛苦。新加一个技能要复制粘贴一大段逻辑改公式系数还得满地图找。这时候就应该做一次架构升级把“技能伤害继承属性”从功能变成一个平台。6.1 用哈希表存储技能公式配置War3的哈希表Hashtable是1.24之后引入的数据结构非常适合做这种配置管理。你可以用技能ID作为主键在哈希表的对应位置存储这个技能的基础伤害、力量系数、敏捷系数、智力系数、伤害类型、是否无视魔免等参数。配表的逻辑可以全部放在地图初始化时执行。比如function InitDamageConfig takes nothing returns nothing local integer table InitHashtable() set udg_DamageTable table // 风暴之锤基础30力系数3.0敏0智0 call SaveReal(table, AHtb, 0, 30.00) call SaveReal(table, AHtb, 1, 3.00) // 雷霆一击基础25力系数2.0敏0智0 call SaveReal(table, AHhb, 0, 25.00) call SaveReal(table, AHhb, 1, 2.00) endfunction这样每个技能的公式数值集中在一个地方想调平衡只需要改初始化函数不用翻触发器。6.2 统一入口新技能接入只需加一行配置有了公式配置表之后可以把所有技能的伤害计算收敛到一个全局触发器事件 单位 - 任意单位 发动技能效果 条件 (施放技能) 在 伤害公式配置表 中 动作 设置变量 Caster (触发单位) 设置变量 SpellID (施放技能) 设置变量 Base (从哈希表读取 SpellID 的键0) 设置变量 StrFactor (从哈希表读取 SpellID 的键1) 设置变量 AgiFactor (从哈希表读取 SpellID 的键2) 设置变量 IntFactor (从哈希表读取 SpellID 的键3) 设置变量 RawDmg Base 力量*StrFactor 敏捷*AgiFactor 智力*IntFactor // 后面统一走暴击、抗性、飘字、造成伤害新技能的接入流程就变成了四步复制技能模板、把面板伤害改成0、在伤害公式配置表里加一行参数、完事。不需要为这个新技能新建任何触发器不需要复制粘贴任何逻辑。这一步带来的维护收益在技能数量超过10个后会非常明显。我后期给地图加技能时平均一个技能从做技能到接入伤害系统只需要十几分钟而之前每个技能都要花半小时以上处理触发、飘字、变量清理。6.3 暴击、抗性、特殊效果如何挂在公式后面伤害引擎建好之后暴击和抗性就变得很自然了。计算完RawDmg之后按顺序挂在公式后面第一步判断暴击。用一个独立变量存每个英雄的暴击率可以是随机数比较。暴击时RawDmg乘以暴击倍率并对飘字单独着色。第二步判断目标抗性。抗性可以存成单位自定义值也可以挂在哈希表里。最终伤害等于RawDmg乘以(1-抗性比例)。如果你不想让系统默认的护甲减免再掺一脚就把UnitDamageTarget的攻击类型设为“普通”、伤害类型设为“通用”这样实际扣除生命值的部分和飘字显示最接近。第三步是触发特殊效果。比如吸血、连锁、击杀计数都放在伤害产生之后判断。统一引擎还有一个好处玩家看到的飘字、PvP战斗日志、伤害统计面板可以全局一致地工作不用每个技能各自为政。地图做大了以后这会让战斗反馈非常舒服。最后分享一点个人体会技能伤害继承英雄属性这个需求表面上是一个触发器写法问题本质上是个数值架构问题。如果一开始就规划好统一的伤害引擎后面加技能、调平衡、做暴击抗性都会非常顺反过来如果图省事让每个技能各写各的图越大越痛苦。我现在做这张防守图面板伤害字段永远写0玩家看到的每一个数值都是公式引擎计算出来的真实结果。这套思路也推荐给所有想认真做RPG/防守地图的作者别怕前期多花一小时搭框架它会在后面无数个技能上帮你把时间省回来。
返回列表