游戏开发论坛

 找回密码
 立即注册
搜索
查看: 4743|回复: 1

[原创] 游戏数学建模工程手册

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2020-5-18 18:34:30 | 显示全部楼层 |阅读模式
QQ图片20200518171254.png

声明:abraxas71投稿,转载需经作者本人同意

他告诉一位朋友:之所以故意把《原理》弄得尽可能艰涩难懂,是“避免受到对数学一知半解的人打扰”

——《牛顿传》

本手册有一个雄心勃勃的目标:将游戏数学建模的工作变得专业、规范和标准,同时像其他工程行业的手册一样,可供相关人士翻阅、应用或得到启发。如果目标能够达成,好处是显而易见的:

1】标准化有利于游戏数学建模的规范、可靠、高效,以及人员的甄别
2】专业性的提高,不仅降低项目开发尤其是数值调试阶段的成本,往往也伴随着收入水平的提高
3】直接或间接的令游戏数学建模发展、发现新的方法,进一步促使其专业化

为达成上述目标,本手册采取了尽可能数学的阐述方式,这种阐述方式具有工程的可操作性,并且避免了不必要和非专业的争论。

手册由如下内容组成:

【评估建模】如同各数学应用领域存在对算法的稳健、无偏、精度、稳定性、一致等指标以评价算法,这部分讨论了什么才是好的模型,又如何评估,它包括:

——时间复杂度
——可操作性
——充分近似

【不好的建模】这部分内容讨论了一些建模方式,这些方式或时间复杂度过高,或缺乏理论联系实际,甚或基于错误的理解,它包括:

——随意添加、滥用参数和运算法则
——滥用战力、价值
——反推数值
——技能循环

【有用的建模】这部分内容体现“手册”一词的含义,讨论了一些游戏实际模型的构建与算法:

——减法伤害公式的近似与应用
——战力的认识与计算
——技能建模:三种算法的综合
——自由分配属性点:最值问题
——经济建模:齐次方程组与样条插值解
——广义PVE模型

【数学建模与游戏开发】作为本手册的最后一节,这部分讨论了游戏数学建模工作者在游戏开发中的作用与位置。

【评估建模】


时间复杂度


坏的设计会招致在它上面叠加坏的设计



——《架构之法》


【时间复杂度】时间复杂度是用数学形式来表达、对比同一功能但不同代码之间的程序运行时间,用以横向评估程序的质量。该方法同样可以评估广泛使用excel的游戏数学建模工作的质量。

我们将非重复的写入、修改1个单元格的操作时间花费视为常数1,非重复是说通过如复制、依序拉出、excel函数结果可变的操作仍视为常数1。

先从一个简单的例子开始。属性/道具有两种分配方法,一种是给出总的量分给各系统模块(下称总-分),另一种是系统模块单独给定然后汇总(下称分-总)。那么,这两种建模方法,哪种更好呢?

使用总-分方法时,给出总量所需操作时间为常数1,总计有n个系统模块进行分配各操作1次,累积操作时间为n*1,每个系统模块需要改名来让函数从总量里提取到正确的分配额度同样花费n*1,总-分方法时间复杂度为T(n)=1+2n。使用分-总方法时,除了有总-分方法的操作步骤外,还需在汇总函数里加入n个系统模块的单元格链接来花费n*1,于是分-总的时间复杂度为1+3n。两者在渐近时间复杂度上是一个量级的O(n),因此n足够大时,两者时间复杂度相差无几,但在实际工作中n不会太大,于是,总-分的时间复杂度更低、更可取。

再以养成模块进度规划(下称游戏节奏)的3种建模方法进一步阐述时间复杂度在建模/模型评估上的应用:根据产耗计算养成模块进度、齐次方程组解析解、齐次方程组插值解。

根据产耗计算养成模块进度——当我们为一个系统模块给定产出和消耗后,这个系统模块的游戏节奏就被确定了,我们需要知道这个游戏节奏情况如何,好知晓游戏内容被玩家消耗的情况、怪物们应该匹配的强度。我们有n个系统模块产出需要规划,其时间复杂度为k*n*m,其中k表示平均而言1个系统模块的时间花费常数,m为道具种类,相应的,我们有m个道具消耗要规划,其时间复杂度为k*m,我们需要计算出生命周期为t的游戏节奏,并且要先汇总产出,则花费t*n*m,于是“根据产耗计算养成模块进度”这种经济建模方式的时间复杂度为T(n)=k*n*m+k*m+t*n*m。1个道具至少安排1个系统模块负责产出,故n>=m,我们取最小值n=m,则T(n)=k*n^2+k*n+t*n^2,其渐近时间复杂度为O(n^2),随着道具种类和系统模块的增多,这种建模方式耗费的时间以平方级别增长。

齐次方程组解析解——1个方程组代表了1个系统模块的游戏节奏,产出消耗均由齐次方程组的解提供。因此我们需要先规定生命周期为t的游戏节奏,有n个系统模块要规定,其复杂度为t*n,然后设定齐次方程组的系数a_00.........a_tm*2,m*2里的2分别代表产出和消耗,系数用来表达方程组里的本行方程(节奏)的产耗是第一个方程(节奏)产耗的多少倍,花费时间为2*t*n*m。最后,齐次方程组的解析解是基础解系,我们还需线性放大缩小来匹配特定道具数量所需的量级,我们要放缩m个道具的产耗,该操作时间复杂度为m,于是,“齐次方程组解析解”的时间复杂度为T(n)=t*n+2*t*n*m+m,取n=m,则T(n)=t*n+2*t*n^2+n,渐近时间复杂度为O(n^2),该建模方式耗费的时间仍以平方级别增长,但比起“根据产耗计算养成模块进度”它只有1个高阶项。

齐次方程组插值解——每个系统模块的全部进度必须完整的由齐次方程组表示,插值解方法将其改进为:在一些我们关注的时间节点才规定具体进度,其余节点由插值解自动估计。这样的改进要求我们必须规划产出,用插值求解消耗从而匹配所需的游戏节奏,则规划产出的时间复杂度为k*n*m。关注的时间节点远小于t,因此规定游戏节奏的复杂度仅为n,我们还要汇总m个道具,复杂度为m,则“齐次方程组插值解”的时间复杂度为T(n)=k*n*m+n+m,取n=m,则T(n)=k*n^2+2n,渐近时间复杂度仍为O(n^2),比起“齐次方程组解析解”少1个线性项,并且没有t。当t较大时,“齐次方程组解析解”用方程组去描述游戏节奏耗时以线性方式积累,当t较小时,“齐次方程组解析解”仍要放缩m个道具。因此,改进方法减轻了时间复杂度里一次项的影响,相对高效。

我们最后讨论一种建模方式的时间复杂度来结束本节。同一用途的道具存在内部品质/级别区别,是不同养成阶段所需。一种方式是将其视为不同道具,然后分布在产出规则细节中(下称细分道具),另一种方式是将所有高级品质/级别的道具视为最低品质/级别道具的特例,与最低品质道具是一个常数k_1......k_n线性放大的关系,其中n表示品质/级别的数量,然后将最低品质道具分布在产出规则细节中,当从某个节点要求特定品质道具时,则将该道具除以k求出高级品质道具(下称广义道具)。

细分道具——我们有n个同一用途不同品质的道具要投放在a个节点上,a起到隔板作用,故a=n-1,复杂度为n-1。平均而言每个节点之间有m个投放点(比如关卡数),需操作n*m的时间复杂度,显然n<=n*m且有n<=n^2,取n=m。于是“细分道具”的时间复杂度T(n)=n-1+n^2。渐近时间复杂度为O(n^2)。

广义道具——在这种方式下,我们仅需投放1个道具并依序给到全部投放点,花费时间为常数1,设置a个节点复杂度为n-1,我们使用以lookup为核心的函数来令所有投放点知晓自己是否应当除以常数k_1......k_n,函数时间花费常数1,要设置n个常数k复杂度为n-1(因为k_1始终为1无需设置)。举例来说第1个节点填写k_2,由于k_n>k_n-1....>k_1,因此lookup固定头部单元格后道具只能查找到最后的非空值k来除以它。于是,“广义道具”的时间复杂度仅为T(n)=n-1+1+1+n-1=2n,渐近时间复杂度为O(n),时间复杂度为线性的“广义道具”方法优势明显。

不同于程序输入不同规模的数据实际的查看算法的运行时间来精确比对,游戏数学建模只能使用T(n)和渐近量级的O(n)来讨论。时间复杂度是一个衡量、评估数值策划建模效率的工具,如果太多建模部分的方法时间复杂度较高,尤其是多项式或指数级的(以及模型面对的目标庞大的),那么整体的时间花费同样较高,这也是多数数值策划工作的现状。

可操作性


测量一切可测之物,并把不可测变为可测


——伽利略


【可操作性】和常识用语甚至哲学讨论不同,所有工程应用均强调可操作性,并且以如何操作的描述来确立名词,称呼为操作性定义。同为工程应用数学的游戏数学建模,也应遵循这一原则:充分描述建模的操作过程、细节使得他人可以复现。

用一个简单的例子来引出我们将讨论的可操作性:饥饿这一说法不具备可操作性,我们使用未进食t小时来描述它。这个例子包含了如何测量以及量化的单位,接下来让我们看看更多的例子。

战斗平衡是一个经常会提及的词汇并且我们需要为之建模,大体上我们对它的理解并不含糊,尽管有时会被滥用在与其含义擦边的地方。我们将战斗平衡描述为:在有限的时间t内,2个战斗单位均以100%生命值为代价击败对方。它如何具备可操作性?已知描述2个单位战斗过程的基本公式:

1.png


将所有参数移到左边:

2.png


作为工程应用数学,右边的1我们赋予其现实意义:100%生命值(在后文我们还会看到它有另一现实意义)。根据这个公式,2单位的战斗平衡意味着调整左边任意参数的线性放缩,使得等式恒为1。在得知平衡的操作方法后,自然延伸出战斗优势的可操作性:1个单位仅需p且0<=p<1的生命比例为代价,我们称这个单位有1-p的优势。战斗优势有一个不够中立的称呼“不平衡”,然而不平衡也意味着角色通过成长、付费等途径获得好处,所以本手册以中立的战斗优势来称呼这种情况。

另一个可自然延伸出的是战斗体验的可操作性。显而易见,战斗体验由诸多因素组成,许多因素难以操作和量化,如果我们苛求这些并一头扎进繁杂的细节里,除了得到一堆猜想外只会无从下手。任何模型都是对现实的简化和比喻,我们对战斗体验的建模用2个指标来逼近这一现实:在一个有限的时间t内损失生命比例p。t和p都小,量化了一场简单/新手的战斗。t小p大,量化了一场容错率低的战斗,从而很适合玩家体现自身操作水平。t大p小量化了一场检验玩家输出是否达标的战斗,超时后怪物将秒杀玩家或根据输出决定奖励分配。t和p都大则是一场充满史诗感的对决。

既然付费是获得战斗优势的一种途径,那么我们当然可以将付费体验可操作化,给定付费额度可额外获得多少属性,使得:

3.png


分子位置为非付费玩家,分母位置为付费玩家。当付费额度是一个常数时,p越小,战斗优势越明显,付费体验便越好。

以上例子表明,可操作性要求我们给出建模达成的量化方式、如何达成这个结果以及最后但并非最不重要的:结果是工程应用性质的。工程应用性质意味着建模结果是玩家容易感知、理解的(如上述的生命比例)。接下来提及的2个例子进一步阐述“工程应用性质”的含义。

有一种经济上收益相关的建模方法,即在养成模块的某个阶段发生突变,比如给的属性更多但所需消耗更少从而改变玩家的收益感,并画出折线图来可视化,有时被称做收益拐点。这种设计没有工程应用性质。玩家到达收益拐点需要花费时间、金钱成本,并且很难确保玩家记住拐点前的收益进而对比,我们从上帝视角一厢情愿认为玩家会为此感到高兴、引起兴奋,而未能更“接地气”的从感知有限游戏信息的玩家角度建模。与此相对的是另一种做收益差异的方法,将道具以不同养成模块分组,然后通过组间整体价格放缩做到养成模块之间不同的收益。通常而言,玩家很快接触到这些养成模块,进而迅速的发现收益差距作为付费的决策依据。

比起泛泛而谈的“为体验服务”、“考虑走位和玩家操作水平”、“数值是调出来的”诸如此类的口号而言,可操作性描述了工程实现细节,如同程序员通过编程语言实现功能,数值策划则通过数学工具来得到结果,进而考察结果的充分近似程度,这就是下一节我们要讨论的。

充分近似


我所接受的工程训练教导我要容许近似,有时候我能够从这些理论中发现惊人的美,即使它是以近似为基础的...我持续在之后的工作中运用这些不完全严谨的工程数学


——保罗·狄拉克


【充分近似】游戏在商业上的成功与许多因素相关,游戏数学建模的好坏作为其中一个因素可以合理的猜想是正相关的,尽管玩家感知游戏里数学计算的表现在敏感度上有待商榷、正相关强度甚至因果强度如何尚未能量化,但是我们仍然要有一些检验解析解可靠性和适用范围的方法,毕竟等待玩家反馈的时间周期太长且已经带来成本损失。自然的,这些方法是仿效工程和物理的:数值模拟实际游戏

数值策划出现在大约20年前,早期游戏开发对数学计算的需求并不大,经过粗略计算后程序开发出AI,如果AI在操控单位、种族、阵营对抗时没有明显问题,那么剩下的就留待版本更替解决,这种数值模拟现在仍然是检验解析解的有效方式。然而,随着游戏内容的增加,通常我们不能亲自或等待开发全部游戏功能,所以设置一个理想环境如将战斗的概率表现用期望计算或编写代码模拟战斗是一个可选方案。由于涉及较多的推导、证明,以下仅举一例,更多的例子将出现在【有用的模型】里。

我们有,

4.png


该式在说如果单位生命和伤害能力都变化到k倍,则p的变化与k^2有关,记为p(k)。我们用p衡量付费额度或养成模块属性量对玩家的影响,需要知道额外得到的属性与k的关系。存在诸如防御、闪避、暴击、穿透等类生命和类伤害的属性使得k的解析解计算时间复杂度较高,这迫使我们寻找近似解。

已知类生命和类伤害属性只有2种处理:非负实数映射到[0,x]区间(如闪避率取值0%到100%、暴击伤害提高300%);直接运算(如真实伤害是额外的伤害值不被免伤率抵消)。对于后者,根据具体运算规则,可以视为生命和伤害的线性叠加,对于前者,其取值范围我们不会在不同角色间差距太大,因此我们可以说,

5.png


HP(·)是将类生命属性等价为生命值的函数,DPS(·)同理。即全体属性变化到k倍约等于原本的生存和伤害能力变化到k倍,于是p(k)≈1/k^2。这种近似是否足够好呢?如图所示:

6.png


解析解得出单位B以16.8%的生命为代价击败单位A,近似解为20.66%,比为81.31%,对用于评估属性变化相比起标的单位有多大的战斗优势,进而得出付费体验和属性量是否合适的判断而言,该精度足以胜任。较低的时间复杂度得出较好的精度,我们称建模是充分近似的。上述例子仅针对乘法伤害公式,因为只有乘法伤害公式才会将防御值映射到[0,1]区间并和攻击方属性无关。

一来,编写代码模拟亦或理想环境计算终究是对现实的简化,二来有些解析解无法列出计算公式检验或编写代码检验需要太多的时间。这就要求在实际游戏里进行工程实验并收集数据来比对算法预期,所谓实际游戏不仅仅像普通玩家那样进行游戏,还需排除其他因素进行工程实验,检验结果是否充分近似。比如存在闪烁、昏迷、放逐等等特殊的技能效果,如何建立这些效果对战斗的函数关系可查阅【技能建模:三种算法的综合】,我们选取已经做好战斗平衡但不同效果的各个角色互相对抗,排除基本属性带来的影响,从而确定技能效果建模的解析解可靠性。

道具的产出与规划将影响玩家的养成情况,养成情况又与战斗强度关联,进而影响PVE里怪物强度的匹配。所以,一个能良好估计玩家战斗强度的建模方法尤为重要,这个方法我们将在【经济建模:齐次方程组与样条插值解】里看到。换言之,实际游戏理应验证估计的玩家强度,存在偏差太远时,应检查养成是否充分,如果充分则检查是否匹配经济的产出和规划。通常来说,玩家强度的估计错误往往是经济规划存在问题,使得玩家不能充分接近规划的游戏节奏,也就无法达到相应的强度。

【不好的建模】


我们力图尽快证明自己错了,只有这样我们才能进步



——理查德·费曼


随意添加、滥用参数和运算法则


在工程应用领域总会存在理论滞后于实际现象和需求,这迫使物理学家和工程师从实验数据里建立经验公式,比如根据探测到的地震波强度估计地震中心地带距离探测点有多远。要说这些经验公式是强行凑出来的不为过,但只要它们是基于数据的、有一定预测价值的,我们就会使用它们直到理论得到足够发展。

然而,一些数值策划在公式上的使用则除了凑出来外,与工程或物理的经验公式出发点毫无相似之处。除法伤害公式,一个生生凑出来的伤害计算方法:

7 (2).png


变换为形如“攻击*(1-免伤率)”:

8.png


而减法伤害公式为:

9.png


免伤率比起减法伤害公式添加了对方防御的反向约束,这加剧了减法伤害公式本有的非线性特征,使得攻击、防御的变化不能有效预测对方的受损情况,或根据目标损血比例逆运算出攻击、防御。解析解的带入具体值当然可以做到,但面对复杂的战斗设计时,解析解的计算相当的繁琐甚至无从下手(如增减属性的技能可施加在任何角色上)。此外,它的防御不够直观,减法伤害公式的玩家知道多少防御就能抵消多少攻击,乘法伤害公式则转换为免伤率展示在界面。除法伤害公式不论在时间复杂度与可预测,还是玩家的易理解上没有任何优势。

除法伤害公式凑出来的思路不得而知,但从滥用运算法则的现象上可一窥一二。这些运算法则的添加可以出现在包括伤害公式的任何公式里,它出现的理由仅仅是希望快速增长便添加多项式、指数或参数间相乘,希望抑制变化时便添加根号和除法,甚至还有使用三角函数来得到周期变化的,仿佛运算法则不是描述现象间关系和推导得来,而是用来获得虚假的操控感,这大大增加了模型的非线性特征。

参数的滥用尤为值得指出,简单的如升级经验公式要用携带多个参数的多项式,而改变这些参数仅仅用来查看公式计算结果是否满足所需。复杂的则有在PVE怪物每个属性上配以用来线性放缩的参数,原因在于实际战斗时常不满足理论预测,调节这些参数更仅有依据属性影响方向来大概逼近需求结果的作用——然后打开游戏再看看情况。设置所谓普通怪、精英怪的参数模板加剧了这点,因为模板是固定的对玩家角色造成伤害,而关卡策划使用多少个这类怪物则有其设计考虑,据此,怪物的数量就大大影响了实际战斗表现,从而进一步依赖对应属性的参数调节,如果其他因素诸如经济建模不够精确,更使得怪物的表现如同云雾一般难以看清。

滥用战力、价值


由于目的、定义和计算上的含糊,战力这个概念被用于许多奇奇怪怪的处理。有一种方式是根据战力计算出属性、平衡,而属性在平衡上的基本指标是“有限的时间t”、“100%生命值”,违反基本指标根本不可能得到正确的结果。当然,将各个属性的战力系数理解为某种投放比例,然后给某个养成模块战力额度,以战力额度的大小来决定养成模块的重要性并期待玩家对此进行判断并非不可取,然而玩家还存在战力能精确表达角色强度的要求,高战力被低战力击败是一件挫败玩家信心和感到不满的事情。此外,玩家互相看到的战力往往是一个总值而不是每个养成模块的战力细节,于是,用战力额度决定养成模块的重要性和引导玩家判断的真正效果就显得可疑了。

还有一种更可疑的使用方法,用售卖价格除以战力,并把这种计算结果叫做性价比。仿佛玩家花钱只是为了让战力数字上涨一般。正如上文所谈论的玩家对战力的实际感知和需求,这种处理方法是真的在面向玩家建模吗?

价值也是一个被滥用的对象,它有时指售卖的价格,有时又代表道具、技能效果、属性强度等之间的大小关系。这使得它甚至出现在和经济无关的地方,比方说技能价值。一些情况下谈论的技能价值是指一个养成模块携带了基本属性和技能,那么这个养成模块的售价会因技能的存在变得不同。另一些情况下却指技能之间的平衡,用价值来衡量技能之间的强度。当它指代售卖价格时,或许其定义和计算还存在不少共通处,但当它指代某种含糊的平衡性时,则干脆不知所云,建模方法也含混不清。

反推数值


任何一名了解统计回归的人都会对所谓的反推数值感到迷惑不解。排开一厢情愿的目的:复制一款商业成功的游戏数值就能确保数值乃至盈利上的可靠性。在统计学工具上如何实现则停留在excel趋势线拟合上——普通最小二乘法。反推的人并不关心普通最小二乘法的假设和适用情况,自然也不懂得其他的回归分析。这种知识和技术上的匮乏招致了对实实在在困难的低估,采集一款游戏的数值数据所耗费的大量时间甚至金钱,如果他愿意付出代价,还要面临这些问题:假设过强、模型预测、模型扩展。

假设过强是指适用条件太苛刻,现实情况极少满足。普通最小二乘使用简单的多项式拟合数据,这在常规统计和机器学习应用里用于刻画现象是很好的工具,现象背后真实的模型常常是未知的,现象背后的因素也未能全部纳入,我们用方程来尽可能的描述它。然而不同于自然和社会现象,游戏数学建模还没有人能用一个多项式来做全部工作,反推数值仿佛在假设游戏数值在各个模块的建模仅仅是简单的多项式方程得出,无视其本身有着数学建模的原理和过程,不去构建它却去拟合它无异于缘木求鱼。

时间和金钱的有限决定了认同反推数值的人不可能对一款游戏的数值全面测试、采集数据。当他使用有限的数据,却要做总体推断工作来进行模型预测时,excel的趋势线拟合还远不能胜任。在统计与机器学习的应用中,我们使用K折交叉验证来检验模型的外推/泛化能力,这方面的知识足以让人消化相当长时间。此外,模型的解释变量未必对泛化有正面贡献,这就涉及到稀疏估计的变量选择问题。对于一名在统计与机器学习应用上乏可陈列的反推数值者来说,模型在预测未采集的后期数值变化方面和双眼一抹黑相差无几。

复制一款游戏的数值就意味着要几乎全面模仿它的系统规则,这一点对程序开发没有多大困难。但对于广泛存在的或大或小变动的需求以及克服上述反推数值的重重困难后的模型而言,则几乎没有模型扩展能力。要么拿着这摇摇欲坠的模型继续外推,要么主观修改模型参数来分段处理,或者:认真开始建模——那为什么一开始不这么做呢?

技能循环


不同于高度抽象的纯粹数学,我们使用函数是为了建立、描述和预测现象。然而,很多时候这些现象的函数表达式难以直接得出,但是现象的变化却容易用高阶导数描述,这时,高阶导数与现象存在简洁的关系,而这便是微分方程的建模思路。

技能循环是其反例,一些数值策划“勇敢”的直面繁多的技能,穷举它们,然后纳入一个有限的时间里一字排开,或数值模拟或excel呈现。不论哪种方法他们都在焦头烂额、疲于奔命的实测-调整-实测里浪费大量时间,结果也往往不如人意。相反的,如果我们将技能的伤害、治疗、能量消耗等均用每秒1个常数来描述,那么情况就大大简化了:一阶导是一个常数,与技能表现是线性关系。以伤害技能为例,我们有:

10.png


tdps表示随时间累积的伤害量,t为时间(默认单位秒),C表示每秒伤害是一个常数。一个技能的时间表现有施法、冷却、间隔生效,其中普通攻击是技能的特例:

11.png


atk是普通攻击的每秒伤害,conjure为施法时间,cd为冷却时间,x为技能的一次伤害量,I(·)为示性函数,满足条件取1否则取0,该示性函数表示如果一个伤害技能的conjure不小于cd,那么玩家无需发动普通攻击,而普通攻击的行为只能发生在cd期间,因此从秒伤角度看它实际秒伤仅为cd/(conjure+cd)的比例。给定其他参数,求出x就是我们的目标。这个式子阐述了一个事实:任何技能的施法和冷却时间不能同时为0。这很容易理解,如果为0,那么C是一个无穷大的量,角色将在玩家操作反应时间内消灭任何敌人。

正如前文所述,模型是对现实的简化和比喻。真正的技能表现并非连续可导函数,而是高度离散的,平均为每秒伤害只是一种近似或平滑处理。更多的讨论我们将在【技能建模:三种算法的综合】展开。

【有用的建模】


所有的模型都是错的,但有些是有用的



——乔治·博克斯


减法伤害公式的近似与应用


伤害公式是一种函数或运算规则,旨在将生存与输出属性换算成具体的伤害量,一个好的伤害公式应当具有如下性质:

1、线性组合是可逆的。总伤害由不同强度单位的伤害线性组合而成,可以从总伤害逆运算每个单位的伤害进而知道它的攻击力。其中N是第i个怪物的伤害,存在一个系数β满足逆运算得到怪物的攻击力a,可逆性便于根据要求的战斗结果计算出相应属性。。

12.png


2、输出型属性与伤害结果是线性关系。这种线性关系可以是与伤害值线性变化,也可以是与伤害结果的一阶导线性变化,如兵力损失的变化量。

本节旨在讨论减法伤害公式的应用(下称减法公式),由于非线性、不破防等性质,减法公式被认为只适用于没有不同武器的不同攻速、BUFF改变攻速这样的简单战斗,或是不便于进行战斗平衡的处理,并且不难发现,减法公式不满足上述“好”的伤害公式的性质。尽管存在这样那样的困难,我们将推导并证明,在一定前提条件下减法公式将具备上述性质,表现得如同乘法伤害公式,由于没有乘法公式的免伤率换算——这会带来对等级压制的设计要求——进一步扩大简单的防御抵消等值攻击的易理解性优势。

第一步是解决这样一个问题:给定任意攻击和防御值,若攻击变化到k倍、防御变化到j倍,伤害量变化到原来的多少?由于伤害量的变化与生存时间是线性相关的,所以解决该问题将直接影响模型的预测和控制力。诚然,带入具体的攻击和防御不难得出解析解,但如果一个改变攻击、防御百分比的BUFF可能施加在任何单位身上,或是给定副本内玩家的总hp损失比求全体怪物的相应属性时(只有乘法公式攻击和伤害的关系满足线性可加性),解析解的实用性是大打折扣甚至无从下手的。数值模拟经验性的表明,在最高免伤率<0.8,初始免伤率<0.45的情况下,攻击变化率与伤害变化率的关系是一个非初等函数:

13.png


rdps表示伤害的变化率,k为攻击的变化率,rdam为受伤率,1.5是从数值模拟中得到的经验分界点,在分界点分别使用这2个公式计算是充分近似的(由于不是在制造原子弹,我们可接受的误差最多在20%左右)。我们首先证明第二个公式,它仅需基本的极限知识,其思路为,若k趋于∞,则rdps也趋于∞,两者的比值是否仍然趋于∞呢?

14.png


a和b分别表示攻击与防御值,变换为:

15.png


因为k趋于∞,所以分母的b可以忽略,于是:

16.png


这正是我们需要的。不过,k是在渐近意义上与rdps线性相关,实际中k通常不会特别大,它有实用性吗?幸运的是,数值模拟表明,拜非线性性质所赐,k无需太大就能收敛,而且加入暴击、闪避等属性,该式仍近似的相当不错。

17.png


为什么一个仅有攻击和防御的假设环境且渐近线性,仍然很好的近似到真实的存在暴击、闪避等类生命/伤害属性的游戏环境中呢?其实不难理解,因为这些属性生效规则不一样但互相对抗,这使得近似解的误差不会总朝着一个方向发散。

遗憾的是,在极限意义上防御只会令伤害趋于无穷小,这使得生存时间趋于∞,即发散的,我们无法用同样的方式找到防御变化率与rdps的渐近关系。不过,第一个公式将为我们带来有限范围的近似解。考虑:

18.png


近似一个函数的方法是模仿它的0,1.....n阶导数,这便是泰勒展开背后的直观几何。不过,如果直接对这个式子里的k求导,它只会告诉我们dps’=k。用k近似效果非常的差,但根据第二个公式推导出的k与rdps存在着与受伤率的关系,我们为什么不尝试工程数学上的粗暴处理呢?舍掉会推出dps’=k的参数,仅保留括号内的受伤率信息,我们有:

19.png


对k求导得:

20.png


同除b:

21.png


显然,1/(a/b)是一个不大的数字,我们忽略掉,于是仅剩1/k^2。左右两边同乘k^2,于是:

22.png


即dps变化到k的平方,这正是我们第一个公式已经给出的。数值模拟结果如图所示:

23.png


让我们进一步利用免伤率、受伤率在近似解里的价值。

24.png


因为b/a是免伤率且为倒数关系,于是防御变化到j倍是:

25.png


将防御的变化近似为对方攻击量的变化,然后逼近伤害量,这种二次近似误差只会更大,我们用开方去平抑误差:

26.png


接下来我们证明用开方平抑误差的合理性,即我们只需证明误差函数是非线性的增函数。我们首先证明误差不是线性的,由于:

27.png


是在k趋于∞时渐近线性的,注意到不等式:

28.png


所以在k^2的近似下,误差不可能是线性的,k^2>=k也意味着误差不可能收敛到0,而是发散的,因此不可能是减函数。误差函数不是线性减函数,则只能是非线性的增函数。由于我们不清楚误差函数的表达形式,但对非线性进行根号逆运算会是一个不错的逼近,如图所示,数值模拟表明j在1+0.23内,以及j<1时再度开根运算可近似足够充分:

29.png


30.png


j不能超过1.23是一个数值模拟经验上界,同时也是理论分析支持的:j如果一直增大,那么免伤率趋于100%,生存时间趋于∞。∞是发散的,我们无法近似一直在变大的数。

有了上述近似求解方法,我们可以得出减法公式下的战斗平衡和战斗优势的估计。若一个单位的整体属性提高m倍,那么:

31.png


将m移到分母位置:

32.png


于是:

33.png

我们查看数值模拟:

34.png


近似效果一般,超出我们容忍的20%左右误差。不过,继续数值模拟会发现近似解几乎都在低估,所以,近似解可以作为下界,提高了m倍的单位至少能够对抗的数量或至多损失的生命比例。在通常用来评估强度差异时,已经够用了。

攻击速度指间隔t秒攻击1次,取倒数变成每秒攻击n次称为攻击频次。显然,攻击1次是1倍伤害,攻击2次是2倍伤害,以此类推我们知道攻击速度想要保持平衡,只需减法公式运算后面乘以相应攻速即可保证。然而根据这样的理解,这不能适用在不同攻速的武器上,否则玩家会看到相同的攻击值但不同攻速,这会给玩家带来困惑。

我们知道,攻击值是各个养成模块的加和:

35.png


s是养成模块的数量。加入攻速,我们有:

36.png


speedatk表示攻速,b为防御值,dam为伤害量。我们已知k和rdps的关系是渐近线性的,那么speedatk可以表示rdps提高到了等值倍数,求k是多少:

37.png


于是:

38.png

我们仅需武器模块依不同攻速保持平衡并面板展示攻击值给玩家,其他养成模块在代码中仍乘以武器攻速(因为其他养成模块不会随着携带不同武器而改变自己提供的攻击值)。如果atk_1是武器模块,乘法分配律告诉我们:

39.png


已知rpds求k,数值模拟的经验分界点是2:

40.png


即,一个武器的攻速小于2时,用武器攻击乘攻速的开方,否则乘攻速和受伤率即可。实际中,speedatk在2的附近进行开方可能大于speedatk*ram,这是经验公式的问题所在,可以自行根据具体攻速决定取哪个来保证攻速大的攻击值也大,也可给予攻速大的在时间劣势上的额外攻击值奖励。

战力的认识与计算


在战斗建模里,一个单位的强度是在它的敌人面前能生存多久、造成敌人多少比例的生命损失。不过,玩家面对众多的属性是不能直观理解到单位强度的,此外,有一个明确的数字甚至进度看到在确确实实的成长是游戏设计在奖励反馈感上的基本法则。战斗力(下称战力)概念就在这种背景下诞生,一个好的战力估计应当有如下性质:

1、线性的,便于玩家理解。
2、充分近似的,战力能够表达单位的真实强度。

基于这2个性质,有一些战力计算方式是不可取的。一种简单粗暴,直接将每个属性的战力系数当做这个属性的单价或单价的中间系数。一种是打分数,给标准模板的每类属性打分,用分数除以属性值得出单个属性的分值作为战力系数,这相当于将战力理解为玩家成长得有多标准。

目前已知的上述两种方式或其他方式,均不具备第2点的性质要求,易出现战力绝对差额大的,战斗结果却是颠倒的,尤其是胜利方还剩余大量生命值,这大大增加了玩家对单位强度如何的迷惑感。本节提出的方法将同时满足这2个性质:

1、推导并证明,对伤害运算里的各个属性求偏导组成全微分加和,将偏导值作为战力系数,充分近似是一阶精度的。

2、推导并证明,在容易满足的前提条件下可以将精度提升至近似二阶、三阶甚至更高阶精度并仍然是线性的。

3、不在伤害运算里的属性战力系数估计,如回合制的速度。

已知伤害运算F(a0,a1,...an)是一个多元函数,其中a表示具体属性,在k处进行泰勒展开至一阶,有:

41.png


f(1)(·)是F(·)对a的一阶偏导表达式,h表示a和ak的差额,也叫步长,O(h)表示截断误差与步长有关。所谓k处,指我们选择哪个单位/职业/角色作为攻击方和被攻击方,称对标单位,它的具体属性值就是k处,代入具体值很容易算出f(1),从而得到各个属性的偏导值作为战力系数,并将这些战力系数同等的应用在其他单位/职业/角色身上,而其他单位的每个属性值与对标单位之间存在差额,差额即步长,由于截断误差与步长有关,并且我们只取了一阶线性项,显然其精度是一阶的。

线性性质已经满足,充分近似是一阶精度的,但除非步长充分小,否则我们的误差以二次方的速度累积。在实际中存在着多个兵种、职业,无可避免的一些兵种、职业的各个属性与对标单位的属性差额很大,那么保持线性来改进一阶导的值,进而提高精度就被自然引出了。

我们将多个兵种、职业视为多个可展开点k1,k2....kn,n表示兵种/职业数,k1是对标单位。如果我们选取k2也作为展开点,一阶展开为:

42.png


又有k1的展开点,其偏导表达式为:

43.png


而k2的偏导表达式为:

44.png


将两者取均值,则:

45.png


如果我们将k2处的偏导视为是在k1处的展开,则有:

46.png


将其中的一阶项代入取均值的式子中:

47.png



合并同类项:

48.png


而F的二阶泰勒展开是:

49.png


显然,我们在避免二阶导的非线性处理下,通过加入另一个兵种/职业的一阶导和对标单位的一阶导取均值提高了精度,忽略混合偏导数项,则截断误差介于二阶和三阶,精度在一阶和二阶之间。比较精确解与估计间的误差,如图所示:

50.png


估计伤害的误差从1.313下降到了0.234,受伤的误差从0.781下降到了0.004。

既然考虑进新的单位可以将精度提高到近似二阶,那么再加一个单位是否能提高到近似三阶呢?仿照龙格库塔法,如果:

51.png


λ表示F在ki处偏导数的权重,即多个单位作为展开点处的一阶导,通过加权平均得到一个更好的一阶导。这个λ有无穷多解,所以一般而言取值为:

52.png


其中k2单位生存和击杀时间必须介于k1和k3之间。精度改善如图所示:

53.png


误差列从上到下分别是三阶、二阶、一阶估计后与精确解的误差,三阶的误差是最小的。然而我们还能看到二阶估计反而不如一阶估计,其根本原因在于,我们的精度并不是严格二阶、三阶乃至更高阶的。因为一个多元函数的泰勒展开还包含混合偏导项,我们却使用应用在一元函数上的泰勒方法分析,忽略混合偏导,使得误差并不总能缩小,根据多元泰勒展开的混合偏导表达式,可以知晓忽略带来的误差随着步长呈指数级增长,指数等于选取的单位数。这提示我们,除非对标单位与其他所有单位间的属性差额(步长)尽可能的小,否则误差会很轻易的增大。显然的,中位数是离其他所有数据点差额最小的值,如果我们将所有单位的生存与击杀时间排序,时间在中位数位置的就是最佳对标单位,中位数附近的是最佳高阶展开点。比起当下常见做法,随意选取一个对标单位,如稻草人。泰勒展开的分析告诉我们,应当选取距离其他所有单位差额最小的。

接下来我们讨论不在伤害函数里的属性,甚至没有伤害概念的如女性向对拼衣着打扮的属性如何估计其相应系数。事实上,这可以转化为一个最小二乘问题。以回合制常见的速度值决定出手顺序为例,每个单位都有不同的速度值,若速度值s满足不等式:



那么,出手顺序满足:

54.png


即不论计算函数如何,速度值大的就是出手靠前的,或将速度槽最快累积满的。我们假设出手速度会影响单位的强度表现,那么我们在求如下优化问题:

55.png


θ是我们需要估计的每1点速度值增加的战力,tdps为累积伤害量,指在对标单位面前生存回合内总伤害值,b为截距,s为速度值,n为单位数。公式是最小二乘的优化目标函数,求出使得式子达到最小值的θ,这一点,excel趋势线便能解决,如图所示:

56.png


得出速度的战力系数约为27,这是个令人惊讶的数字,完全与在伤害函数中的属性战力系数差距很远。这个问题在于,不同单位的速度值、伤害量之间的量级可大可小,甚至不同游戏之间也是如此,数字大的被认为影响更大,但实际单位之间强度并非如此。比方说,A比B强2倍,A的输出为100点伤害,B为50,在另外的数学建模中A的输出为20000,则B是10000,然而A保持是B的2倍强度,因此,我们需要回归分析里的一个数学处理方法——标准化。将速度值和累积伤害都标准化到均值为0,方差为1的正态分布下。标准化公式为:

57.png


速度值减去其均值然后除以其标准差,累积伤害同理,标准化后估计出的系数如图所示。

58.png


每1点速度提供的战力系数为0.418,这便显得合理多了。

标准化后的最小二乘估计还能应用在没有伤害概念的游戏里。比方说一些女性向游戏只通过衣服品质来比对高低,如果搭配品质不一则根据其具体的魅力、时尚之类的属性值判断。可以将衣服品质赋予数值,然后进行最小二乘估计。

技能建模:三种算法的综合


在所有战斗相关的建模中,我们面对的最复杂的情形之一便是技能。技能的设计充满了想象力,这对数学计算发起了挑战,以当前游戏数学建模的发展应用情况来看,尚未发现关于技能的尽可能抽象、统一的模型构建思路和方法,本手册将在本节提出。

技能可以分为三大类加以讨论:1)增减属性;2)治疗;3)常规。

我们从最简单的增减属性开始。在前文【可操作性】里,我们将战斗平衡数学的描述为有限的时间t内对战双方付出100%的生命为代价。根据定义,永久或暂时的增减属性显然已经不平衡。因此,实际的做法是扩大对平衡的理解,设定增减属性后的战斗优势单位付出1>p>>0生命为代价,求满足要求的增减属性最大值为多少,P取值小于1但远大于0。如75%生命为代价,让增减属性技能显得有用,又不至于对平衡影响过大。那么,最大值是增减属性的技能的最高分配,超过最大值的部分用函数给出警告效果即可。

通过前文的铺垫,我们有:

59.png


且:

60.png


我们给出p求k,HP和DPS均可消去,因而变成一个简单的倒数后开方逆运算。以p=0.75为例,k可得(1/0.75)^0.5=1.154,优势战斗单位增减属性后整体属性最大只能是敌人的1.154倍,永久属性最大增加15.4%,最大减少(1-1/1.154)=13.3%。

我们进一步分析它的界,由于增减属性最大只能给全属性,并且一般而言只提供部分类型的属性,因此我们可以肯定的说:理论上,增减属性后,战斗优势单位至多付出100%生命,至少付出75%,真实情况在两值之间。

若属性是暂时的,存在一个冷却时间和持续时间。显然当持续≥冷却时,等价于永久拥有,当持续<冷却时,冷却减去持续的时间差里没有增减属性影响,因而影响的占比是一个加权平均:0*(冷却-持续)/冷却+属性百分比*持续/冷却。此外,还需指出的一点是,有些看似没有冷却和持续时间的技能比如光环、被动属性增益,我们可以认为它们的冷却时间等于持续时间,仿佛刚冷却好立刻自动释放,或理解为它们只有在战斗中有意义,战斗结束没有意义等同没有被施加效果,则冷却时间=持续时间=标准战斗时间。

接下来,我们进入治疗技能的算法讨论。考虑2个强度相当的单位,单位A只能治疗自己,单位B攻击单位A。无治疗时,存在一个有限的时间t,单位B将杀死单位A,有治疗时,A在t内累计恢复生命上限p,其中0<p<1。显然的,单位A的生存时间t将延长t*p,在t*p的时间内单位A仍将继续治疗自己,继续延长t*p*p,我们罗列出公式:

61.png


我们注意到这个熟悉的函数结构——无穷等比数列:

62.png


由于p<1,因此无穷等比数列收敛,可以用收敛公式求出t*。换言之,只要t内A单位的回血量小于生命上限,那么A将被击败。所以,我们可以根据t*比t的倍数,来衡量治疗技能的重要性程度,并用p/t得出每秒治疗生命的百分比来计算出一个确切冷却时间的治疗技能一次性可治疗的生命值。吸收伤害的护盾同理,它的实际效果根据持续时间和冷却时间进行加权平均即可,超出p的部分称呼为越界。

值得一提的是,另一种思路是将治疗理解为伤害的负数,扣减后的结果和无穷等比数列的收敛公式解一样。不过,无穷等比数列的角度可以让我们知晓模型存在一个隐含假设:治疗动作是连续不间断的,实际的治疗动作是高度离散的,收敛值事实上是一个上确界。

分类为常规的技能包含伤害技能,我们用一个简单的微分方程描述了它:

63.png


tdps表示随时间累积的伤害量,t为时间(默认单位秒),C表示相对于普攻秒伤的倍数是一个常数。其中:

64.png


即C由普攻部分和伤害技能部分组成。其中atk是普攻的秒伤,conjure为施法时间,cd为冷却时间,x为技能的一次伤害量为普攻的x倍,I(·)为示性函数,满足条件取1否则取0,示性函数表明一个伤害技能的conjure不小于cd,则无需发起普攻,且普攻行为只能发生在cd期间,于是从秒伤角度看实际秒伤仅为cd/(conjure+cd)的比例。此外,后半部分的值必须大于1,1是普攻是自身的1倍。

然而,在设计师的“倾力协助”下,常规技能并不仅仅是伤害的,还存在诸如闪烁、复制技能、隐身等非伤害效果,这种情况普遍到数学计算不可能无视,尤其一个技能或一个战斗单位既有伤害又携带这类技能效果时,x的计算结果不应和一个单纯的伤害技能相同。于是,自然而然的提出了一个简单的想法:能否将这类效果的强度ε等价为C的后半部分呢?我们将提出公理、推导及算法帮助实现这一点。

首先是公理部分:

【公理一:非负性】任意非伤害效果,其强度不论多么小,至少存在一个战斗单位的视角观察下该技能强度是正面的,即ε>0。

换言之,设计师不会设计出绝对负面的技能,如果单位B携带的技能是负面的,则单位A看来对A是正面的。如果存在一个技能场对任意战斗单位负面的生效,则在对抗双方的视角而言,在数学上抵消,等价于技能场不存在从而不违背公理前提。

【公理二:有界性】每秒伤害是一个有限值,存在一个建模规定下的最大值C,使得任意技能强度ε<C。C值是有限的但大于普攻,而普攻伤害是自身的1倍,即C>1。

建模会给定一个最高的每秒伤害值,且为表达技能强于普攻,技能的秒伤必须大于普攻的秒伤。

基于上述公理,我们可以推出如下定理:

【定理一:下确界是一个等于1的值】普攻伤害是自身的1倍,则任意战斗单位拥有的任意技能与普攻加和后大于1,从非负性可以推出1+ε>1。

即,只要有技能效果,这个战斗单位的强度就一定大于只有普攻的基础属性强度相当的另一战斗单位。

【定理二:任意技能强度的取值范围是一个左右均开的实数区域】从C的公式定义、定理一、公理二可以显而易见这一点:C>1+ε>1。

这是说,技能强度是有界的,并在一个可知的界里,这个界的最值是由我们指定但绝对有限的。

基于上述共识和推导,我们可以开始细节性的证明存在一个依据强度大小排序的技能序列,从序列可以估计出这些技能效果的强度相当于多少C的后半部分。

让我们先从昏迷开始,战斗平衡的甲乙,甲被乙施加t秒的昏迷效果,则甲对应的损失想了t秒的输出量,问甲最大受损的每秒输出量是多少?根据公理二,乙只能令甲最大损失C,即昏迷强度等于C,然而,基于下述共识和推导使得昏迷只能永远接近C无法等于C:

【公理三:非零性】任意战斗单位在战斗中总是有机会打出输出的,即C*>0,C星表示战斗单位经各类影响后平均的每秒输出且根据公理二可推出C>=C*。

一个正常、自然、战斗平衡的战斗环境,不可能让某一方完全没有机会还以颜色导致C*=0,比如自始至终令对方昏迷,仿佛在攻击一个不会还手的假人。于是,我们推出C>昏迷完成了第一个排序。

昏迷令对方无法移动、施法、普攻,由于C>1+ε,根据公理二C>ε,所以,昏迷>沉默,也就是说沉默无法阻止对方普攻打出伤害,作用范围小于昏迷。同理的,沉默>定身。定身只能令对方无法移动,难以直接影响其输出机会。沉默>缴械可由公理二推出,技能建模规定的最大值C必须大于普攻,于是,缴械这种只能影响普攻并少量影响需要特定武器才能释放的技能,其强度一定小于广泛影响技能的沉默。

定身>移动减速,因为从极限的角度考虑,减速至多和定身无法区别。昏迷>>闪烁,将闪烁从极限角度考虑,闪过去攻击对方在对方反击之前立刻闪烁躲开,反复如此。这种令对方无法反击的情况和昏迷无法区别,且因公理三的存在,闪烁不可能设计成这样,故闪烁强度远小于昏迷,由于无法限制对方移动,可排序为昏迷>沉默>定身>闪烁。类似的,移动加速的极限情况是闪烁但无法逾越障碍,所以闪烁>移动加速。

击退和拉近可视为同一效果,且在趋于0的时间内令对方昏迷,故击退>定身。一般而言,定身的持续时间往往比击退长很多,所以依据持续和冷却时间的计算后击退的强度较为微弱——这符合直觉。

嘲讽>下确界,昏迷>无敌。因为嘲讽完全不能影响C值,但根据公理一和设计目的必然是一个正面效果,无敌无法限制对方移动,故小于昏迷。如此林林总总的技能效果不再一一列举,需要指出的是一类较为特殊的技能效果,比如攻击加速、技能暴击等。这类从简单的代数变换、期望上可以求得它们最大只能等于C-1,其中1是普攻的伤害倍数。

目前为止,我们已经尽力证明了为数不少的技能效果虽然强度未知,但可以用不等式排列出强度大小的关系,而且,其上确界是单纯伤害下的C值,下确界为1。那么,我们可以估计这些技能效果的强度了,将C和1视为已知的2个数据点,根据插值基本定理,2个数据点只能进行线性估计,分为2种线性估计法:线性插值半对数模型

线性插值十分简单,以次序的序号为自变量,C和1为已知的因变量,使用拉格朗日或牛顿插值法即可依序估计出上述技能效果的强度,其斜率表示我们认为技能排序每上升一个序号,技能强度以一个绝对增量变大。

半对数模型
是自变量为序号,但将C和1取对数,再线性插值的结果。该模型的斜率表示我们认为技能排序每提高一个序号,技能强度以一个百分比的增量变大。公式如下:

65.png


y表示多少倍于普攻秒伤的技能强度,已知的是C和1。β0为截距,β1为斜率,x为序号。两边隐式求导:

66.png


整理并依据链式法则:

67.png


由于x的步长或增量为1,所以:

68.png


这正是前文已告知的结论。实践表明,如果技能效果数量较少,那么线性插值和半对数模型的估计结果区别很小,除非技能效果很多,半对数模型下的非线性估计结果才值得采用。

我们知晓,以微分方程对技能建模是对真实情况的简化和比喻,真实的技能释放是离散的,而非微分方程隐含的连续性假设。此外,它无视了一个至关重要的事实,单位的生命值是有限的,承受的累积dps也是有限的,按照C的公式定义,如果一个技能冷却时间很长,那么计算出的x将相当的大,技能出手的那一刻会出现不乐见的秒杀对手,这脱离了设计师的目的。

基于插值定理,2个数据点只能线性插值,然而,如果我们用线性方程拟合,将得到完全一样的结果,半对数模型同样如此。换言之,从拟合的角度可以给出等价计算,而拟合本质是一种概率模型,它假设数据是随机的,方程给出的是期望、平均意义上的拟合结果。

同样的,设计出怎样的技能可以视为随机过程,产生的强度是随机变量,这些随机变量被证明在一个我们约束的界里。既然是随机变量,便总存在着非0的概率超出一般规律的值,在拟合应用中,我们称呼为异常数据。拟合算法怎样对抗异常数据,是现代稳健回归的主要研究内容,如普通最小二乘法是不稳健的、均值是不稳健但有效的等等。稳健一词由统计学大师博克斯在1953年的一篇探讨均值和方差稳健性的文章中提出,他对稳健的解释是真实的情况与假设的概率分布存在偏差,或观测数据存在诸多“异常”时,统计方法得出的结果仍然较为不错。

异常数据是相对普遍的,这是稳健回归期望解决的。存在冷却时间很长的技能这类“异常数据”也是相对普遍的,因此纳入叫做崩溃点的概念加以解决。在稳健回归里,崩溃点是指统计指标、方法能容忍总数据中百分之多少比例的异常数据不受其影响估计出好的结果、识别出数据里的一般规律。

本手册将崩溃点定义为:技能出手能打掉的最低生命值百分比。我们知道,战斗时间是有限的,并会给出一个强度相当的平衡战斗下的标准战斗时间。显然,如果一个技能的冷却时间占标准战斗时间的较大比例,根据C的公式计算会让技能伤害在普攻配合下打掉同样比例的生命(而真实的情况还要更遭,因为存在非普攻的伤害技能)。因此,一个技能的冷却时间如果大于等于最低生命值百分比*标准战斗时间,那么我们称呼该技能是“异常的”,或更平凡的说:一个大招技能。

自然的,最大崩溃点也需引入,它被定义为:我们容忍的技能出手打掉的最大生命值百分比,通常必须远小于1。一旦有技能依据冷却时间被判定为大招,我们从C的公式按照崩溃点和最大崩溃点给出的冷却时间作为这个大招的计算参数得出结果,即这个大招的伤害能力被约束在了崩溃点和最大崩溃点的区间内,我们可以任选区间内的值作为大招最终的伤害能力,然后伤害能力除以技能的真实冷却时间得出每秒伤害,这个每秒伤害作为评价众多大招之间的强度是否差距太大的指标,由下列公式评判:

69.png


RSD为相对标准差,x杠为所有大招每秒伤害的均值,s为其标准差。根据美国医学统计学会的建议,相对标准差不大于0.3那么数据的离散程度是较低的。即:大招之间的强度差距不大。如果超过0.3,则说明这些不同冷却时间的大招技能有个别的强度弱或强过大多数大招。一言以蔽之,RSD用来平衡大招之间的强度差异。

在上述讨论中,我们得出了包括非伤害技能的C公式定义,:

70.png


技能的设计存在伤害效果,又携带非伤害效果,这隐含了一个前提:伤害效果和非伤害效果是不互斥的,同时出现的。那么,不同技能之间可以不互斥吗?

如果一个技能释放后存在一定持续时间的给予对方杀伤,则这个技能不影响你释放另外的技能,那么2个技能的秒伤是加和的,这就是不互斥的数学定义。若存在1个或多个不互斥的伤害技能,则C的公式存在2个或多个x,解是不唯一的,我们引入自由度的概念加以解决。自由度来自统计学,它是指存在n个数据、变量,若我们已知其中的n-1个值,从已知的一阶和二阶统计量可以计算出剩下的1个,这1个的取值是不自由的,该数据、变量的自由度为n-1。同样的,存在n个未知数x,则我们必须指定n-1个值,最后一个由计算得出。经验表明,不互斥的技能一般是无需施法者引导的DEBUFF、昏迷等效果技能,这类技能的x最好给的比较低,如此直接伤害技能求出的x才能和不互斥技能少的其他单位差不了太远,否则玩家视角看来较为不自然。

自由分配属性点:最值问题


早期游戏和现在的少部分游戏里有让玩家自行分配属性点的设计。这样的设计不论出于估计玩家角色的强度令PVE战斗去匹配的目的,还是玩家试图找到最好的加点方式,都可以归结为一个数学目标:求约束条件下的最值。

已知伤害运算F(a0,a1,...an)是一个多元函数,n+1为属性个数,a为属性类型。显然,可分配的属性点总计为100%是一个约束条件且分配的属性点必须>=0,因为玩家可以不分配某个属性类型但不能扣减原有的。而这,是求约束条件下的多元函数最值问题,拉格朗日乘数法是这类问题的基本算法。我方的伤害运算求最大值,而对方对我方的伤害运算是求最小值,一般来说,最好合并改写为如下公式:

71.png


合并后的公式可一次性求得最值。需要略微说明的是我们设置的约束条件:

72.png


换言之,不论具体的总属性点为多少,均可视为100%。任意具体点数互为线性缩放,不影响计算的最值结果,即对任意可分配点结论均成立,因为:

73.png


若存在一个k,使得:

VMK@{NY0U%U6KFJ}Z4E{6DM.png


那么:

2.png


k是一个常数,在对F的各变量求偏导并消元解方程的过程中完全不影响结论,我们证明了设总分配点为100%得出的最值结果对具体的任意总分配点均成立。

经济建模:齐次方程组与样条插值解


如果战斗建模面对的最复杂情形是技能,那么经济建模面对的最复杂情形是其本身。与技能建模一样,游戏数学建模领域未见有关经济建模抽象、统一的模型构建思路与方法,而是零散的分布在诸如线性规划、概率期望、产耗比、价值等概念里。本节试图提出基于联立方程组的数学工具解决这一问题。

在经济建模中,我们面对的最根本难题是通过产出和消耗得出玩家在各个养成模块的成长进度(下称游戏节奏),计算这一进度随着产出、养成模块日渐复杂的规则而变得愈发复杂。然而,恰当的给出玩家的游戏节奏,几乎是战斗——经济建模成败的关键,因为它不仅预期控制了游戏内容的损耗程度,也影响了PVE强度对标玩家各个阶段强度的可靠性。事实上,许多游戏数学建模存在的反复调试一个常见原因是计算出的游戏节奏,无法和经济的产出消耗相匹配,进而导致PVE战斗完全不清楚是否如预期那般工作。

建模思路的切入点是:若产出大于等于消耗,则玩家至少可以完成1个进度,并且进度之间有着关联性。方程可以描述前半段,方程组则描述了后半段,我们写出方程组:

74.png


不等式左边的x表示产出,右边的x表示消耗,a为各个产出消耗的线性缩放系数,n为养成模块数,m+1为进度节点数也是组内方程个数,整理为:

75.png


正负性无关紧要且可由x前面的a来负责,于是:

76.png


立刻观察到,当不等式取0,形式与齐次方程组相同,齐次方程组形如:

77.png


此前写出的2n个未知数x,隐含了广义道具建模的概念,广义道具建模是指所有养成道具看似不同,实则可以视为名叫养成道具的特例。不过,因为一些操作细节问题,我们规定1个方程组仅负责1个养成道具:

78.png


紧凑的写为矩阵形式:

Ax=0

我们解释为什么不等式不取大于0的值。因为这使方程组变为非齐次的,它会带来如下问题:

1、需要指定非0值b,这将增加游戏节奏规划的时间复杂度。

2、非齐次方程组的解由通解和特解线性组合而成,相对齐次方程组求解繁琐。

3、值b可以理解为我们希望这个进度完成后盈余多少道具,盈余的道具会影响后续进度,带来了复杂的递推求解。

我们写出了方程组,解释了其字母的现实含义,但在1个方程组仅负责1个养成道具的设定下,很快会发现实践上的困难:

1、若一个或多个养成模块的节点很多,如强化等级。我们要一一安排对应方程来联立,规划这些节奏的时间复杂度是较大和令人沮丧的。

2、存在唯一性定理告诉我们,当且仅当至少存在一个自由变量时,Ax=0有非平凡解。如果我们计算的是游戏货币可以使用在2个以上的养成模块上,那么有大于2个未知数,这种情形很好,它将一次性为我们解出货币产出值和这些模块的货币消耗值。然而,更多时候我们面对的是基于可控目的的1个道具用于1个养成模块,这意味着我们必须给出其中一个未知数的值,才能求出另一个未知数,比如给定产出求消耗,这违背了期望同时解出产出和消耗的初衷。

3、几乎可以肯定,方程组个数一定会大于未知数个数,这种超定方程一般是无解或无穷多但没有工程意义的解,x前面的系数a会决定具体是哪种情况。稍加讨论是有用的,a的设定致使方程组无解暗含了一个事实:当产出和消耗曲线被确定,有且仅有唯一的游戏节奏。换言之,我们既然给出的是游戏节奏去求解产出和消耗,那么实际上并不能人为设定a的值,或说只能设定产出的a值或消耗的a值,不能两者都人为设定,否则很难存在匹配设定的解。

幸运的是,超定方程组存在唯一的最小二乘解,而最小二乘满足“2”的已知自变量的要求,可以避免“1”的全部规划进度而是设定关键节点从而降低时间复杂度,“3”带来的麻烦也将不复存在。然而了解最小二乘拟合的人会立即注意到以下可能:

1、最小二乘是假设一个多项式方程描述了现象,次数是可选的,存在一定主观性,通过给定的产出曲线形式来选择多项式可以一定程度的消除主观性。

2、真实的玩家养成很难是正态分布的,尤其存在排名奖励这类竞争设计时,长尾是几乎肯定存在的,假设误差是正态分布的最小二乘对此是不稳健的。

3、游戏节奏的关键节点一般不多,稀少的数据点难以交叉验证拟合效果。

4、关键节点意味着我们要求玩家恰好是这样的进度,最小二乘的原理是整体逼近,不一定经过节点,而是越过节点,如果从上方越过节点,那意味着计算出的消耗大于产出,致使关键节点的设定形同虚设。

样条插值是一种综合了曲线连续性和规避龙格现象的插值方法,我们使用它来解决齐次方程组和最小二乘的困境:

1、通过分段插值规避了龙格现象,算法相对稳健。

2、一般使用三次多项式,经过了各领域工程的可靠性证明。

3、插值必定经过关键节点,只要产出是较少不确定性的(比如概率、排名是不确定的)。即因模型选择的系统误差一般小于因产出消耗规则带来的误差。

在讲解计算细节前说明模型的基本假设是合适的。我们假设特定进度的获取量等于消耗量,并且这两个量不为0,,为0意味着线性无关,违反产出与消耗存在关联的设定。另一不太引人注意的是模型假设了消耗量满足的前提下,再获取的资源是没有意义的(就成长而言)。这种假设在交易系统的情形下存在偏差,一个自由或近似自由的交易系统游戏,可以使用经济学的比较优势来设计产出,因该算法需游戏规则和经济学理论的配合本手册不予讨论这个过于具体的应用。此外,最小二乘和样条插值作为估计算法还有一个关键假设:产出消耗曲线是单调增函数的——如果不是这样,那么稀少的数据点未必能捕捉到周期、一段增一段减的模式,这会带来极大误差。

在前文描述里,我们隐含了模型求消耗而非求产出,产出由我们给定并汇总给模型,这里说明为什么不能反过来。原因在于工程实践中,产出的规则较复杂、多样、多途径,如果产出是一个自由变量更容易应对,消耗规则的复杂无外乎大成功、成功、失败的概率设计,它的多变性很低,更易被模型纳入。

完成关键节点设定后,便可以汇总产出得到这些节点的累积产出量,依据齐次方程组:

Ax=0

我们得出节点的累积消耗量恰巧等于累积产出量,于是:

X0=X1

使用累积量是将某些每日常数的产出转化为满足算法假设的单调增函数的必要选择,常数为非负实数的积分形式是线性增函数。此时此刻,我们仅知道关键节点的进度,没有规划的进度产出和消耗是未知的。调用数学软件或编程语言的样条插值算法求出函数表达式后,应用在未规划的进度上可得出对应进度的累积消耗量,将顺序靠后的进度减去靠前的进度便得到消耗矩阵X1的其他未知数。

【吉布斯震荡】在正式使用样条插值的计算结果前会注意到,得出的消耗值有时比前面的消耗值低和高太多,经验的说这种情形一般集中发生在前期进度里。这种现象本手册称呼为吉布斯震荡。该术语来自物理学,由物理和数学家吉布斯试图用傅里叶变换拟合矩形脉冲波时发现。这种现象是因为我们使用连续函数逼近实际是离散变化的现象时产生的不适配,我们的数学工具往往比较理想化,真实现象并不严格连续,微观到如能量的传递以一个普朗克常数为间断。游戏里的玩家成长也是离散的、突变的,因为:

1、养成进度是离散的,要么达成要么没有,不存在中间过程。

2、进度之间消耗的最小间隔一般是较大的数字而非1,离散程度明显。

3、两个节奏间的设定过快或过缓,产出则过缓或过快,两者的高阶导数不够接近。理论上可给出两者较为接近的高阶导数信息,运用分段埃尔米特插值法,这会增加时间复杂度。

4、养成模块要么开放要么没有,多数养成模块在前期集中开放,造成显著离散,随着后续未开放的养成模块变少,连续在逼近时会改善表现,这在玩家强度的样条插值估计里尤为值得注意,后文会讨论这点。

解决经济的吉布斯震荡有如下方法:

1、更换插值方法,使用样条保凸插值法。这种方法比较新颖,工程界应用较少,数学软件目前未见封装,可以查阅相关论文自行编程实现。

2、更换插值方法,使用分段线性插值。不推荐,该方法在关键节点不紧凑时误差难以接受。

3、对震荡处使用线性插值,从而保持消耗需求是不减的。

4、先对样条插值结果升序排序,后对震荡处使用线性插值。经验上,这种处理基本可以解决吉布斯震荡。

上文我们使用样条插值作为齐次方程组的数值解法,较少的去设定游戏节奏的节点以减轻时间复杂度进而提高工作效率。这种方法不仅用于解决几乎所有有着进度概念的游戏经济建模,也可用于有战斗或类战斗玩法的游戏:估计玩家特定阶段的战斗属性。

稍加说明为什么讨论经济建模的算法会涉及到战斗,因为广义、抽象的说,经济是战斗的一个线性变换或映射,存在一个规则T(x)使得经济道具变换到战斗属性所在的空间,这个规则或桥梁对游戏而言就是玩家通过消耗道具达成进度得到的战斗属性成长。一个有效的玩家战斗属性估计表明了如下性质和事实:

1、游戏节奏的规划被正确的产出和消耗计算结果很好的实现了。

2、游戏内容的损耗被正确的通过经济控制了。

3、PVE和PVP对手的战斗属性可以正确的匹配和计算,从而控制了战斗感受、难易、卡点等。

已知游戏节奏若干节点的养成进度,若有进度提供战斗属性,则可知该进度的玩家战斗属性,未规划进度的战斗属性未知,应用样条插值可估计出未知进度的战斗属性。就战斗属性的样条插值解而言,它的吉布斯震荡比经济建模的解在前期更为剧烈,因此,除上文所述方法解决外,还需考虑原则:

1、集中的新手引导在多少等级或阶段结束?

2、多数产出模块的集中开放在多少等级或阶段结束?

找出该等级令等级加一,然后不小于该等级的战斗属性除以经验系数2到3区间的任意实数抵消吉布斯震荡带来的误差。通常来说,吉布斯震荡产生的误差和拟合脉冲波那样趋向于一个常数,经验发现是大体2到3,实测选取最合适的经验系数。而成长为离散、突变导致的震荡需考虑到成长存在时间滞后性故等级加一,时间滞后性是说产出模块在X等级开放,玩家自然的行为是一边获取产出一边获取升级经验导致升级,而不是X等级立即得到这些产出带来的成长。

我们如何解释样条插值估计出的玩家战斗属性?考虑一个事实,同样等级的玩家战斗属性不可能恰好一致,A强化了所有部位,B仅强化一部分,C则未感觉PVE有阻碍所以没有强化。只要游戏没有强制规则的要求养成进度必须怎样才能继续升级(也没有哪个游戏这么做),那么一定存在随机性使得不同玩家在相同等级下战斗属性有所不同。我们称估计结果是平均意义上的,同等级全体玩家平均起来就是我们的估计结果。随机性或不确定性意味着,我们可以通过内部人员跑游戏、收集线上真实玩家数据,检验估计结果的误差。

然而,一般令升级模块所需时间远小于其他养成模块以制造付费空间。这便出现长尾效应:接近最高等级的少部分玩家付费获得更大的强度,同等级大部分玩家挣扎于免费产出缓慢提升。对于这种现象,我们用2次估计解决,第1次估计的最终节点是刚到最高级时对应的其他养成模块进度,第2次估计的最终节点是战斗建模投放的最大属性。第1次估计我们称为常规成长线,第2次估计称为付费成长线。可以证明,任意付费额度下的战斗属性是2个成长线的线性组合,因:

79.png


r表示付费额度,只可能取0和最大付费深度的闭区间任意实数。由于第1次估计基于免费玩家r=0的游戏节奏设定,进而有:

80.png


换言之,付费额度带来的战斗属性F(·)变化是一个单调非减函数,假设不考虑折扣的1分钱买1分货,则对于r的任意k倍取值,有:

81.png


k可以取值为负数在工程上对应游戏里的免费赠送付费货币。设u、v是非负实数,且u+v=1,则必定存在一个r使

82.png


等式成立,或说任意r是免费玩家和满付费玩家强度的线性组合,u表明了付费玩家同时具有免费玩家获得的成长属性然后加上v倍的最大付费得到的属性。让我们证明这个等式,免费玩家成长的资源可以换算成相当于多少r,记为r*,免费玩家是付费情况的特例,r必定是r*的k倍、rmax的j倍,于是:

83.png


进而:

84.png


令u=k/2,v=j/2,我们有:

85.png


这就得出了我们想要的。接下来我们证明u+v=1,显然:

86.png


又:

87.png


于是:

88.png


且:

89.png


代入:

90.png


于是u+v=1。对v>=0.5,在r*未知时值r的界为:

91.png


进而知道r的大体范围。v<0.5时界为:

92.png


v接近0时,r也接近0。通过计算r*可改善v<0.5时的界,但一般过于繁琐没有必要,大体知道r更接近免费玩家即可。将任意付费额度玩家的战斗强度用免费和满属性玩家线性组合而成,这种建模思想有着如下好处:

1、理论上存在任意付费额度对应同等级(主要是接近或已是最高等级)无穷多战斗强度,线性组合可以很好的表示。

2、人为划分付费玩家层次为超R、大R、中R、小R假设了静态的付费额度,并假设不同层次各个养成模块的进度和付费投入,主观性强,假设过于脆弱,把付费行为视为动态的变量更有可操作性,也降低了计算的时间复杂度。

我们会在【广义PVE建模】里看到成长线估计的实用性。

广义PVE模型


战斗是众多游戏常见的内容,一场战斗有着怎样的表现可以通过数学语言描述,而表现有多种形式:多人战斗、陷阱、药瓶、护送NPC等等。传统做法往往止步于讨论1vs1,本节将说明这些看似不同的形式均可以统一的建模,这便是标题“广义”一词的含义。不仅如此,本节还提出了以择取量化战斗感受的指标为核心,逆运算对抗单位相关属性的思路,这一点是至关重要的,其余的仅是技巧。

在【评估模型】里,我们提出了可操作性的概念,它是指我们必须尝试量化我们的关注对象,这不仅有利于他人可重复你的方法,也有利于纳入数学的框架。广义PVE模型使用战斗时间回合数玩家损失hp%、兵力损失%来数学化的表示战斗感受、战斗节奏、战斗难易、卡点等文字用语。本节不会过多涉及已经充分认识的1vs1,而是将注意力集中在如下战斗形式上:

  • 多人战斗:涵盖1个角色对抗任意数量怪物、多个玩家角色对抗任意数量怪物、多兵力与多兵力战斗。
  • 陷阱与药瓶:陷阱是一种特殊的怪物,因此它的数学处理略有不同,药瓶可以视为等价的生命上限提高。
  • 非作战NPC:NPC指对玩家友好的单位,护送不能战斗的NPC、需要保护的基地、障碍物等属于这个范围。
  • 作战NPC:帮助玩家一同作战的友好单位,这将影响怪物们的强度计算结果。

【多人战斗】许多游戏设定让玩家主要操纵1个角色,所以1vs1是一个得到广泛应用的简单模型,奇怪的是却未见进一步引申1个角色同时对抗多个怪物,尤其是怪物强度不一时的分析。业界有一种通常做法是设定新手怪、普通怪、精英怪、BOSS怪的属性系数模板,然后这些模板在1vs1的框架下计算,因此1个角色对抗多个怪物仅仅是多个1vs1的线性叠加。这种方式可行但具有如下弱点:

1、模板意味着1vs1的结果是固定的,总是相同的战斗时间、玩家损失hp%。于是再加入每个属性可手动调节的偏移系数来解决这一问题,从而导致前文批判过的预期结果模糊及参数滥用。

2、需要人为汇总每个1vs1的战斗时间、玩家损失hp%。这不仅增加时间复杂度,还导致不同关卡使用同一怪物模板但不同数量时,关卡难度飘忽不定。

3、不利于快捷计算NPC存在的战斗形式。

本文并不将1个角色对抗多个怪物视为多个1vs1,而是将怪物们视为1个怪物,然后仅进行1次1vs1的计算,要做到这一点,我们必须从怪物角度建模而非玩家。我们用2个概念来修正“多个怪物视为1个怪物”时存在的一些与事实不符的情况。

第一个是集群,集群是指M个相同强度的怪物同时面对玩家,期望怪物们在生存时间内令玩家损失p%的生命值,每个怪物负责p%/M。不难发现,玩家会杀死怪物致使M集群带来的p%减少,真正的1vs1是不会因生命损失而衰减输出能力的。作为修正这一模型缺陷的开始,我们注意到M集群会损失的最大输出比例不超过(M-1)/M,数字1表示最后一个怪物不存在输出损失的问题,我们提前加上这个比例来对抗衰减,于是有:

93.png


即当M足够大,我们认为提前加上会损失的比例然后分配给每个怪物,在渐进意义上等于M集群平均每个怪物造成的玩家生命损失占比,左端等式分子的1是我们要求M集群令玩家损失的p%生命值比于自身是100%。证明这个等式成立,整理:

94.png


右端第一项显然相等,而和的极限等于极限的和,于是对第二项应用洛必达法则,分子分母同时求导:

95.png


从而左右两端趋于0,等式成立,右端第二项本质是误差项。但M要求足够大,是否符合实用性呢?与解析解对比如下:

96.png


图表说明M无需特别大,误差即可快速减小。解析解计算公式如下,M集群的无阵亡输出为M*x,但只能维持1/M比例的时间,x表示单个怪物的输出强度占总输出的比例:

97.png


求满足令玩家损失p/p=1的x,可整理为:

98.png


紧凑的:

99.png


100.png


分子分母同除M:

101.png


分母位置可使用首尾相加成1将求和转化为相乘,于是:

102.png


尽管我们有解析解的计算公式,但使用近似解的理由是相当实际的:解析解理想的假设怪物是一个一个均匀时间被杀死。考虑到怪物不如玩家有效率的打出输出、缺乏随机属性以及玩家具有AOE能力,高估怪物输出的近似解反而能适应真实战斗。

集群概念将1个角色对抗多个怪物转化为1vs1。我们只需给定p然后除以M分配到每个怪物身上即可,这种分配仅适用于乘法伤害公式和前文的减法伤害近似公式,因为它们满足线性可加性。波次则是另一个相似但不同的模型概念,它描述了关卡、副本怪物们分批次面对玩家,由于我们已经用集群“打包”了特定批次的怪物,所以不存在怪物损失输出的问题。如果批次怪物的强度相当,则简单除以波次,否则可以让战斗时间t和损失生命p按不同比例分配给不同批次的怪物,比例加和等于1。不难证明,从前文我们有:

103.png


分母位置是玩家强度,1表示强度相当则损失100%生命值。现在要求损失p,等式两端同乘p:

104.png


存在u和v,使下式成立:

105.png


则u和v分别称呼为生存力和威胁力,满足:

106.png


其自由度为1,我们需给定其中一个值,通常给定u,u满足:

107.png


hp*是怪物的生命值,我们知晓生命值和战斗时间t是线性关系,所以:

108.png


分母t是2个强度相当的角色对抗的战斗时间,t*由我们给定期望怪物在玩家面前生存多长时间,则威胁力:

109.png


已知u且hp已由样条插值估计得到,则hp*可得:

110.png


已知v,设a为玩家攻击力,则怪物攻击力a*:

111.png


现在有N波次怪物,每波次怪物强度不同,但合起来满足t*和p。我们用ki、ji分别表示第i波怪物负责t*、p的比例,那么第i波怪物的ui、vi计算公式为:

112.1.png


到此,我们完善了将多个怪物视为1个怪物但仅需1次1vs1计算的建模。值得指出的是,我们无需关心hp和dps,因为它们在过程中是约掉的,仅用u和v来表示相对于hp和dps的线性缩放系数。另一个需要说明的是,在集群建模中,我们假设M个相同强度的怪物同时面对玩家,实际中存在不同强度怪物同时面对玩家,这也会带来怪物阵亡造成输出能力损失,这种情形目前本文选择作为误差加以容忍,主要原因在于不同强度怪物阵亡的先后顺序不同带来的结果也是不同的,其排列组合之多难以计算,本文作者认为若视为一个均匀分布求损失期望或许可以解决,但还需完善落地可行性。

在“多个玩家角色对抗任意数量怪物”里,我们所需解决的是多角色问题。一个相当简单的方法是:

112.png


即玩家数乘以100%生命比例即可。尽管角色可能存在职业、战斗能力的不一,但一般而言我们在对职业建模时是取平衡为目标的,所以平均而言损失hp为1。

上文我们填补了1对多和多对多的模型空白,由于本质是纳入1次1vs1的计算框架中,所以我们并不能同样方法的处理“多兵力与多兵力战斗”。多兵力战斗的明显特点是双方兵力在持续损失,兵力损失带来了输出损失,因此战斗时间或回合数基本不是线性变化的。我们想知道给定任意兵力,战斗多长时间、兵力损失多少比例。比起直接建模,对兵力变化建模反而更简单,写出微分方程组:


113.png


双方在t时刻的兵力为x、y,x和y是关于t的函数,双方损失兵力dx、dy与对方在t时刻的兵力及双方的dps和hp有关。求解这个微分方程组,对第一个微分方程关于t求导:

114.png


把第二个微分方程代入消元,其中a=dpsy/hpx,b=dpsx/hpy:

115.png


整理成二阶齐次微分方程的形式:

116.png


它存在通解,列出特征方程:

117.png


于是x(t)的通解为:

118.png


对t求导并代回第一个微分方程:

119.png


%}U4GW8HFD@9G6JVC5WO8CM.png



整理出y(t):

120.png


联立之:

121.png


联立方程组可求出任意t时刻x和y的兵力。存在x、y、C1、C2、t共计5个未知数,我们必须给定其中3个。如果x为玩家,那么我们用样条插值已估算出t=0时的兵力,所以是已知条件,接着我们给定战斗时长t,令y(t=t)=0表示我们希望在时长t内玩家消灭对方全部兵力,即可消元求出C1和C2,然后令t=0、C1、C2代回第二个方程求出y(t=0),将t、C1、C2代入第一个方程可计算出玩家剩余兵力来表达战斗的难易感受。类似的,我们也可以给定t、在t时刻x的剩余兵力比例、y(t=t)=0求出满足t且x剩余兵力比例双重条件的y(t=0)。

使用微分方程组建模隐含了一个连续性假设:双方最小的完整战力可以取任意非负实数。真实情况是1个单位具有特定生命值,在它生命值耗尽前不会丧失输出能力。因此,为了令微分方程组的解可以很好的近似真实战斗,我们必须让战斗过程“足够连续”。数值模拟表明,任意单兵的输出与生命之比不能大于0.2,否则微分方程的解不可用,如果在0.2附近,那么解乘以0.9进行离散修正,如果远小于0.2那么解充分近似。这个前提是符合常识的,当输出和生命之比高于0.2太多,那么1个单位杀死另1个单位仅需很少的攻击次数,少的攻击次数意味着兵力随t高度离散变化。这一前提对存在先后手机制的多兵力战斗尤其需严格遵守,而先后手从代码的数值模拟结果得知,并不是猜想的那样先手会更多的保留兵力从而拉大与后手的优势,事实上模拟证明,若输出与生命之比远小于0.2,那么方程解除以0.9-1可充分近似,否则除以0.75-0.8修正。

【陷阱与药瓶】陷阱的伤害与陷阱的生存时间无关,这是它有别于其他PVE战斗形式的一点。一般来说,我们可以在程序上设定逻辑,令陷阱总是以百分比对单位造成生命损失,甚至区分玩家和怪物造成的百分比。不过,如果没有这种机制或者陷阱会在BOSS战中出现,简单的百分比则不便于应对,此时需要数学建模。该模型非常简单:根据1次期望损失生命的值,通过伤害公式逆运算出陷阱的攻击力即可。只是需要注意的是,陷阱还应区分针对玩家还是针对怪物,关卡设计有的陷阱是希望玩家加以利用,有的陷阱则是为玩家准备的。对怪物准备的陷阱还应指明针对的哪个M集群,先除以M再逆运算。药瓶恢复的生命比例h可以视为怪物需要面对的是n*1+h个玩家,等价于等比放大怪物的输出,就可以满足t*和p。

【非作战NPC】一个障碍物、要保卫的基地、要护送的NPC。只要它们不能战斗都称呼为非作战NPC。通常情况下它们遭受来自怪物的攻击,但我们仅需知晓怪物令玩家损失的生命p就能计算出非作战NPC的生命值,非作战NPC生存力u:

122.png


123.png


这是前文已经给出的算法。由于p表示玩家在怪物面前损失的生命比例,那么从:

124.png


可知怪物强度相当于p名玩家,则hp*与p的关系是线性的:

125.png


特别的,当p等于1,可视为非作战NPC的受损来源是玩家,比如障碍物。

【作战NPC】与非作战NPC相同,我们仅需已知怪物令玩家损失的生命p。p不仅表达了损失生命的比例,也表达了相当于多少个玩家,因此给定作战NPC相当于w个玩家即可,然后找到满足以下式子的u、v:

126.png


一种方式是像怪物那样给出u,然后计算v,但这将增加辅助列的个数,并且作战NPC一般没有那么值得关注,所以推荐:

127.png


于是当作战NPC被怪物针对攻击时,怪物生存时间t*内,作战NPC损失生命值p*随p正比放大:

128.png


证明非常简单:

129.png


130.png


两者相除,于是:

131.png


132.png


令p*=p/w,证毕。

在上述内容里,我们反复使用了一个基本的模型:

133.png


不断的变换为其他等价形式,让我们从不同角度发现了它可以扩展的实用价值,并将等价形式赋予工程应用意义,如损失hp比例、相当于特定数量的玩家、相当于1名玩家角色多少比例的生存力等等。从这一点我们可以看到,数学运算的变换仿佛是换个角度看问题促成新的发现,这是本文致力于用数学语言阐述的重要原因之一。

【数学建模与游戏开发】


在终极的分析中,一切知识都是历史;


在抽象的意义下,一切科学都是数学;


在理性的世界里,所有的判断都是统计学


——C.R.劳


与数学在机械制造、化学、物理、机器学习的地位不同,游戏开发的数学建模工作在如今业界的专业性、系统性非常低下。一种言论可以瞥见一二,“高中数学知识就够了”,这种言论即便不是反智、反知识的,也是毫无益处的自我贬损,《法言》里说“人必其自爱也,而后人爱诸;人必其自敬也,而后人敬诸。”

除了数值策划专业、实践能力的低下外,有时也会碰到这样的遭遇:

本文作者曾使用中位数这个稳健指标计算生命与魔法瓶,当我向直属上司说明这个思路时,他问道:“什么是中位数?”“就是一组数值中间那个位置的数。”我回答。

该例说明一个专业而系统的数学建模工作者时常碰到这种知识体系上的沟通障碍,尽管中位数是一个高中数理统计会学习到的概念。很不幸,数值策划专业性的低下加剧了这种糟糕的局面,使业界充斥着含混不清、不统一的关于在游戏里如何数学建模的用语,也令决策者想当然和自以为对数学建模的了解出发下达一些要么含糊不清、要么不便于数学化的需求。不能指望决策者具备相应的数学知识,然后正确的给出方向,所以很多时候依赖于数学建模的专业知识对需求进行吸收、理解、提炼以及纠正存在的问题,正如游戏程序员那样,转化为自己掌握的编程语言(我们则用数学的形式),当程序需求存在矛盾,程序员需要纠正,同样的,当需求不便于数学化、量化或使问题复杂化,专业的数值策划理应加以纠正。举例来说,在前文里我们提及,付费体验、战斗体验等词汇只能用于日常交流,对于数学建模则必须择取指标加以量化,付费体验我们使用了战斗优势1-p来定义,战斗体验我们使用了战斗时间t、损失生命比例p和剩余兵力比例。换言之,数值策划要有将数学与实际联系起来的实践能力,并且不应活在认为某些指标有现实意义或对玩家而言利于感知的想当然里,这一点在手册前半段有着墨。

一个盈利上成功的游戏,不等于其数学建模的巧妙、高超,反之稳定、精妙的建模也不能促进游戏的盈利。玩家对游戏里的数值表现如何,他的感知或测量能力不是机械那般敏感,因此一些错误会被容忍。而且,即便一个专业和实践力低下的数值策划,往往不会犯充钱就会变得更强的方向性错误,更遑论游戏产品近似一个模拟环境可以反复调试了。但为什么提升数学建模的能力,习得更多的数学知识仍然对游戏开发是有利的呢?

1】时间复杂度低、充分近似的建模,大大减少了游戏开发的成本,至少在数值岗位这个工作分区上可以减少成本,尤其是缩短调试,进而提高项目表现的稳定性。
2】好的建模尽管不能促进盈利,但有时能帮助发现潜在的程序开发错误或系统规则上的不完善。这类错误往往会表现得战斗、付费的体验不符合预期,进而怀疑是数学建模的问题。本文作者碰到过多次这种情况,但每一次都证明模型是正确的,错误的产生来自代码逻辑、配置的引用数据以及系统规则的矛盾。
3】一个决策者若具有对数学建模正确的理解,如低时间复杂度、可操作性,那么可以甄别数值策划的优劣,从而促进团队与项目的稳定发挥。

对数值策划本身而言,也是有利的:

1】增加工作效率、稳健性。
2】数学作为万金油,可以让你找到更好的职业发展机会,或是领域内机遇的挖掘。
3】2019年一篇经济学论文《Job Tasks,Time Allocation,and Wages》证明,在所有高技术工作内容中,处理信息数据的高技术带来的工资正面影响是最大的。

希望本手册对业界广大从事游戏数学建模工作的人士有所帮助。


巴比伦派数值策划Q群:813299364


0

主题

1

帖子

28

积分

注册会员

Rank: 2

积分
28
发表于 2020-5-19 14:57:24 | 显示全部楼层
既然笔者开头有那么大的雄心壮志,想要让这个手册成为业界的规范和标准,起码来说你的手册也要平易近人一点吧,我全篇看下来除开那些公式模型意外,文字的内容确实有点晦涩难懂.也许是我实力不够,不能完全看懂你的手册,但是我的建议还是通俗易懂一点吧.
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-5-4 08:20

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表