翔鹰帝国网|帝国时代论坛|帝国时代系列|神话时代
 找回密码
 注册翔鹰会员(昵称)
搜索
查看: 1301|回复: 1

[教程] 旧“英雄式回血”的原理

 关闭 [复制链接]

417

主题

19

精华

8万

积分

教皇

耕战
13421
鹰币
41665
天龙币
18
回帖
6021

特级嘉禾勋章三级帝国勋章十字军勋章翔鹰建站十周年纪念章

附庸关系0
发表于 2016-8-19 13:28:52 | 显示全部楼层 |阅读模式
本帖最后由 我是谁004 于 2016-9-10 12:55 编辑

大多数做战役的人都知道,曾经有一种技术“英雄式回血”,能够让非英雄单位每秒回复一点生命值,而且不超出生命值上限。虽然现在已经被UserPatch更简单的的不超出上限的回复效果所替代,但在过去是很有意义的,而且它可以自定义回复的上限。
自然,也会有许多人觉得神奇,为什么几个损坏单位的效果能够限制住生命值不超过上限?是什么原理呢?最近忙于汇编的我,重新阅读了一遍英雄式回血的实现方法,理解了其中的奥妙。

浮点数的存储方式
所谓浮点数,就是“有小数点的数”,指的是在计算机中保存的一类非整数类型的数值。
x86架构下的浮点数有两种,单精度和双精度。它们的区别在于存储的位数不同,单精度使用32位(4个字节),而双精度是64位(8个字节)。
如果学过科学计数法,那么您就会知道,科学计数法分为三部分:符号、指数和底数,例如+1.5×10^5。而浮点数也一样,它的存储分为三部分:符号位、阶码和尾数。
符号位只有一位,它的值也只可能是0或1,其中0表示正数,1表示负数;
阶码这一段,则表明了浮点数的数量级。从全0到全1增长中,指数的数量级也从负最大至正最大。例如单精度的8位,即从00000000(2^127)到01111111(2^0),再到11111111(2^128);
最后一段——尾数,便是数的具体取值。它的位数确定时,实际上等同于+1位,因为前面隐含了一个1。为什么要隐含1呢,大家应该知道,科学计数法中,尾数a是满足1≤a<10的,取大于等于10或小于1的数是没有必要的,因为可以通过改写指数部分来标准化。所以,尾数的取值从全0到全1,实际上是从1.00……0到1.11……1,可以在相当高的精度上满足需要。
而特殊情况是0.0,它的尾数为0,这种在浮点数的存储中显然无法直接表达。但设计者们另有办法,他们使用正号、最小的阶码和全0尾数的方法来表示0,或者,你可以理解为“无穷小”。而且如此一来,表示0的浮点数所有二进制位均为0,也易于记忆。
有无穷小也有无穷大,帝国2中杀不死的单位就是通过除以0(最大生命值)得到无穷大生命值实现的。其表示方法是阶码最大,尾数全0。
32位的浮点数有1个符号位、8个阶码位和23个尾数位。64位浮点数有1个符号位、11个阶码位和52个符号位。

不超过上限的原理
帝国2的当前生命值是以浮点数方式存储的,这也给予了我们可乘之机。若没有用浮点型,过去还真就没有简单的办法去实现了。
有一个规律叫做“大数吃小数”。当两个数的数量级差异很大时,它们之间进行加减运算,就会导致小数被“吞没”。例如,1000000加上1,你会立即说等于1000001,但是实际中最后的那个1极容易被忽略掉,好比1米长的棍子比标准长了1微米,微不足道。
而浮点数由于它本身的存储模式,也会造成大数吃小数的现象。23+1位的尾数,若撇开指数部分而视为整数,那么它的最大值就是11111111 11111111 11111111,十进制表示为16777215。这样的数,如果进行+1的运算,结果就会等于16777216,而它的二进制值是1 00000000 00000000 00000000。显然,25位是无法存储于尾数之中的,只有舍去最后的一位0,写作10000000 00000000 00000000×2^1。
既然最后一位,也就是个位被舍去了,那么在这一位上加1的运算,也就无法实现。因为,加1后,实际数应为1 00000000 00000000 00000001,而舍弃只能从尾部开始,所以刚加上的1也被弃掉了。
在英雄式回血的方法中,拢共分三步:
①损坏单位 数量-16777216+A(A为生命值回复上限)
②损坏单位 数量-1
③损坏单位 数量16777216-A
第一步就是将单位的生命值增加16777216-A点,而正常情况下单位的生命值只可能是0 ~ A,故而加法运算之后得到的结果范围为16777216-A ~ 16777216。结果小于16777216时,它的尾数有24位,因而效果②加1后会正确地得到结果(对于生命值为A-1的情况下而言,进位后舍去的位为0,所以同样正确)。但当生命值为A时,效果①已经将生命值变成了16777216,故而再+1也会被舍弃掉,也就不会超出上限。
至于效果③,就是将效果①增加的生命值再减回去,不必多说。
将①和②这两步运算合并后,即为生命值增加16777217-A点。其效果是,生命值为0 ~ A-1时,能够增加生命值,为A时,仍然保持为16777216。

多点回复生命值
那么有人就问了,为什么回复多点生命值要用多个效果,而不是合并呢?
假如回复2点,则增加当前生命值16777218-A点。在生命值为0 ~ A-2时都表现正常,但为A时,增加的生命值为16777218,其二进制为1 00000000 00000000 00000010,由于末位为0,不发生舍去误差。而后面减去的生命值为16777216-A,于是最终的生命值为A+2,也就超出了最大生命值上限。而为A-1时,第一次增加后的生命值为16777217,末位1又会被舍去,从而使得最后的生命值仍为A-1。所以,直接合并会导致最终的生命值不等于A,而有上下的差异。
如果回复的点数更多,例如回复5点,则为增加当前生命值16777221-A点。同样,在生命值为0 ~ A-5时都表现正常,但如果生命值为A-4 ~ A呢?根据原值,有可能最终的生命值为A、A+2、A+4之类的,并且还会继续向上增加,控制上限的目的便失败了。
但如果每一次只增加1点生命值,其效果就是,不断地从16777216变成16777217,然后舍去末位变为16777216,故可以控制上限。
至于每秒回复2、4、8……点的效果,是将16777216乘以2、4、8,拿来进行运算的。拿回复4点为例,16777216变为了67108864,二进制的100 00000000 00000000 00000000,于是会将最后3位都舍去,在增加量为4的时候不会进位,故控制了回复上限。但是,这样做也有一定条件,那就是单位的回复上限必须是2、4、8……的倍数,否则仍然会不准确(但不会继续增加)。
参考:
每秒回复 n 血, 而且不破血

评分

参与人数 1耕战 +100 鹰币 +100 收起 理由
_MZR_阔比多华 + 100 + 100

查看全部评分

004时代:战役时代
我很乐意看到有人在MOD技术上超过我。
回复

使用道具 举报

454

主题

65

精华

25万

积分

教皇

耕战
42935
鹰币
1441127
天龙币
10
回帖
5687

翔鹰建站十周年大纪念章特级帝国勋章特级翔鹰勋章特级嘉禾勋章一级皇家勋章鹰之智者蛟龙勋章十字军勋章大冒险家狂熊勋章

附庸关系2
发表于 2016-10-18 07:55:27 | 显示全部楼层
這要說多少次, 剛好跟TT發火燒連營一樣是11年前...

https://www.hawkaoe.net/bbs/thread-5375-1-1.html
回复

使用道具 举报

本版积分规则

排行榜|小黑屋|翔鹰帝国

GMT+8, 2024-12-23 09:50 , Processed in 0.288481 second(s), 37 queries , File On.

Powered by Hawk Studio  QS Security Corp.® Licensed

Copyright © 2001-2023, Hawkaoe.net All Rights Reserved

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