植物大战僵尸吧 关注:557,607贴子:5,063,338

关于frs炮的简单讲解

取消只看楼主收藏回复

本来想等frs工具和例阵都搞完了再写,现在看来遥遥无期,还是先把关于frs炮的部分写一点吧。


IP属地:江苏1楼2024-12-22 23:30回复
    2楼备用,勿回。


    IP属地:江苏2楼2024-12-22 23:32
    收起回复
      数据结构与frs的效果
      我们先来看指针表(https://wiki.pvz1.com/doku.php?id=技术:内存基址),可以看到[[[6A9EC0]+768]+AC]处存放着植物属性,而[[[[6A9EC0]+820]+8]+0]处存放着动画属性,植物和它的动画是分开存放的,而植物的[+94]处标注着植物本体对应的动画的ID,从而将两者联系起来。
      动画的ID都由序列号(rank,key)+编号(又叫栈位,index,简写为idx)两部分组成。前者每次关闭游戏窗口再重新打开时,都会从头开始计数;后者就是这个动画在动画数组中的编号,每个动画的编号是唯一的,比如假设某个动画的ID是XXXX0005,那么它的地址就是[[[[6A9EC0]+820]+8]+0+A0*5],编号的获取符合栈位理论,关于栈位理论就不多解释了。
      实际上植物和僵尸不只有本体动画,植物有[+94]本体、[+98]豌豆头/三线上方头、[+9C]三线中间头、[+A0]三线下方头、[+A4]眨眼、[+A8]土豆雷闪灯、[+AC]zzz共7处动画,僵尸有[+118]本体、[+140]冰火球、[+144]植物僵尸头/旗帜、[+150]被小推车碾压共4处动画,不过绝大多数时只会用到本体动画。


      IP属地:江苏3楼2024-12-22 23:32
      回复
        Frs实际上就干两个事:
        1. 利用旗帜僵尸销毁一个动画(销毁的原理和方式这里就不提了),比如说销毁一个植物(豌豆头、僵尸什么的动画也可以)的本体动画,本来与动画捆着的植物仍然存在,但由于其属性中“动画ID”指向的动画空了,看上去就隐形了。

        如图,一个本体动画被销毁但是影子(不属于动画)和zzz(非本体动画)仍然存在的大喷菇
        2. 新种下一个植物,控制其动画ID与被销毁的植物的动画ID完全一样(一般是选择本体动画;也有只需要index一样的用法,不过不在本文所讲frs炮范围内了),从而两个植物共用新种下的植物的动画。

        如图,左边长得像大喷菇的玩意其实是个向日葵,从它偏小的影子和没有zzz和生产的阳光可以看出


        IP属地:江苏4楼2024-12-22 23:34
        回复
          概括一下,frs的作用其实就是俩:
          1. 销毁一个动画;
          2. 把一个植物/僵尸等对象的动画ID复制给另一个植物/僵尸等对象,然后共用这个动画。


          IP属地:江苏5楼2024-12-22 23:34
          回复
            常见frs炮的原理和特性
            我们把炮原本的动画销毁掉,再把一个睡莲的本体动画ID复制给炮,让炮也拥有睡莲的动画,恭喜你,一个frs炮造好了!

            如图,上面的睡莲其实是炮,眨眼的情况有些诡异,我也不知道原理。
            快捷制作frs炮的avz插件MakeSkip已经上传,目前只支持avz1,且只有销毁动画和复制动画ID的功能,先临时将就一下(。日后打算将功能扩大到更多skip、支持avz2、制作脱离avz环境的含UI窗口的工具,很久以后~


            IP属地:江苏6楼2024-12-22 23:38
            收起回复
              常规炮的冷却过程
              .
              首先要知道,游戏中判断“是否到了做xx事的时候”有两种常用的方法,一种是靠倒计时;另一种是[+4]处的动画播放进度/动画循环率(一个0.0~1.0之间的浮点数,可以理解为当前已播放的进度条在整个动画的占比,是匀速增加的),或者靠[+5C]处的循环次数,正常情况下,对于一次性播放的动画,循环次数>0就意味着动画进度=1.0,实质是一样的。
              动画的播放有多种方式,其中一种是循环播放,播放进度>1.0就-1.0从头再来;还有一种是只播放一次,播放完了就停留在最后一帧不再变化。


              IP属地:江苏7楼2024-12-22 23:39
              回复
                炮有四种状态:37待发射,38发炮(包括抬起和落下)、35冷却中,36填充。
                37待发射状态的炮接收到鼠标信息之后变为38发炮,并且开始播放发炮动画,如图是用avz逐帧打印的正常炮发射过程的相关数据(数据依次为:处于当前状态的时长、当前状态、炮弹冷却倒计时、炮弹发射倒计时、动画已播放次数、动画播放进度)。可以看到,此时发炮状态变为38,发炮倒计时改为206,开始播放动画,动画播放进度重置为0 。

                不过一般来说,播放动画的第一帧进度就不是0了,这里猜测是因为检测鼠标发生在窗口信息处理时,游戏单帧内的顺序为炮更新→检测鼠标→avz运行,因此鼠标重置动画之后还没到下一帧炮更新,avz就已经读了一次数据。也就是说,这一帧炮其实还是按37运行的,运行完了再改成了38,然后avz读到的是38,不过我图方便就按avz读出来的来计了。


                IP属地:江苏8楼2024-12-22 23:39
                回复
                  接下来在状态38,先是发炮倒计时从206减至1时发出炮弹(所以铲种炮时用的是205后铲炮),然后停在1直到动画播放进度达到1.0,下一帧再减到0并改变状态为35冷却中。38发炮状态一共持续351cs。

                  .
                  这个时间是怎么得到的呢?通过查看炮动画的.reanim文件或.fla文件,或者直接看恶魂总结好的ReanimationOut.xlsx(https://forum.pvz1.com/postid/8523/),可以查到发炮动画共有42个关键帧,动画速度为12fps=0.12f/cs,因此总用时为42/0.12=350cs(由于刚好整除,有可能会因为浮点数精度问题导致实际时间还要+1,这个需要实测一下,不过这里测试可得不用+1),再加上最初动画进度为0的1帧,即一共351cs。
                  .
                  顺便提一下每帧动画进度增量的计算,动画进度每帧的增量是本帧更新的关键帧在总关键帧中的占比,也就是0.12/42=0.00285714,再通过1/0.00285714=350也可以算出所需时长。其实这样来计算的话这么绕个圈子还不如直接用关键帧数算,不过动画进度毕竟才是能直接从游戏中读出来的数据,还是了解一下为好。


                  IP属地:江苏9楼2024-12-22 23:40
                  回复
                    状态改为35冷却中的同时将炮的冷却设为3000,但是在设定的同一帧就会-1,因此实际效果是从2999开始的,直到归0时将状态改为36填充并开始播放填充动画,状态35历时2999cs。

                    .
                    最后,播放填充动画循环次数达到1时填充完毕,状态改为38待发射。填充动画共15个关键帧,历时15/0.12=125cs。

                    .
                    总结一下,炮从发射到可以再次使用的整个过程包括发炮+冷却+填充,其中发炮由倒计时和动画进度共同控制,冷却由倒计时控制,填充由动画进度控制。常规炮的这一套流程总时长为351+2999+125=3475。


                    IP属地:江苏10楼2024-12-22 23:42
                    回复
                      睡莲炮的冷却过程
                      ..
                      现在我们把炮的动画换成了睡莲的,会发生什么呢?
                      首先鼠标点炮,进入状态38发射,发射倒计时设为206,这没有问题,但接下来需要调用发炮动画“anim_shooting”,但是睡莲没有叫这个名字的动画,这里有条游戏特性,当没有指定动画时,会播放它的第一个动画,而睡莲的第一个动画是闲置动画“anim_idle”,只有1个关键帧。
                      此外,炮和睡莲每帧分别会更新一次动画,每一次都是动画进度增加0.12个关键帧(或者说0.12/1=0.12的动画进度),因此实际上每帧的增量是2*0.12=0.24,1/0.24=4.17,向上取整得5帧,也就是状态38的第6帧动画循环次数就已经达到1,但是此时倒计时还在减小,一直到发射倒计时减小到1的时候才开始判定,结果发现动画循环次数已经到1了,于是下一帧直接转到状态35冷却,发炮状态持续206cs。
                      .
                      接下来的冷却状态由倒计时控制,固定为2999,无法改变。
                      .
                      冷却倒计时归零后进入状态36填充,播放填充动画“anim_charge”,但是睡莲没有叫这个名字的动画,于是睡莲再次播放闲置动画,持续时间我们前面算过了,5cs后动画循环次数达到1,于是改为状态38待发射。整个过程为206+2999+5=3210cs。
                      .
                      参考视频:【“既做到了无卡,也做到了无伤”】 https://www.bilibili.com/video/BV1yz421z75Z/


                      IP属地:江苏11楼2024-12-22 23:43
                      回复
                        Frs炮的通用结论
                        ..
                        我们还可以把一个炮和两个睡莲绑在一起,把睡莲A的动画ID复制给炮和睡莲B,这样每帧炮和两个睡莲分别更新一次动画,动画进度的增量就可以达到3*0.12=0.36,其他地方不变,播放完1个关键帧的睡莲闲置动画就需要1/0.36=2.78向上取整得3cs,总时长206+2999+3=3208。
                        .
                        这里其实只要动画真正的主人是个睡莲,一个炮和其他任何动画绑到一起都可以起到加速的效果。我们不妨再算一下最快的情况,也就是206+2999+1=3206,需要动画进度在1cs内达到1.0,1/0.12=8.33,向上取整得需要至少9倍速,也就是需要一个炮和8个睡莲(。
                        .
                        总结一下,frs炮冷却时间的通用公式为:
                        CD = max{ 206 , ceiling(动画关键帧数/(每帧更新轮数*0.12))+1 } + 2999 + ceiling(动画关键帧数/(每帧更新轮数*0.12))
                        一般来说我们都会让动画变得很短,往往播放时长不超过205,因此公式可简化为:
                        CD = 206+2999+ceiling(动画关键帧数/(每帧更新轮数*0.12))
                        = 3205+ceiling(动画关键帧数/(每帧更新轮数*0.12))


                        IP属地:江苏12楼2024-12-22 23:44
                        回复
                          但是必须注意的是,由于浮点数精度不足的问题,这个公式并不是完全准确的,如果“动画关键帧数/(每帧更新轮数*0.12)”刚好得到的结果在数学上是整数,也就是数学计算上动画进度刚好增加到1而没有小数部分留尾巴,那计算机用浮点数就有可能距离1还差一点。
                          例如选用咖啡豆作为被捆绑植物,更新轮数3,那么理论上咖啡豆的闲置动画播放一遍需要9/(3*0.12)=25,冷却=3205+25=3230,但是实际上闲置动画播放到第25帧时动画进度不是1.0,而是:

                          正常情况下动画进度每帧增量应该都是0.12/9*3=0.04,但是其中某一帧莫名其妙地出现了0.039999,于是实际冷却=3205+26=3231。遇到这种情况只能实际测试一下。


                          IP属地:江苏13楼2024-12-22 23:45
                          回复
                            另外,编号大小(实际上代表着同一帧内的更新顺序,小的先更新)对此并没有影响,不过会对avz读到的数据有影响。
                            以双睡莲炮为例,按编号从小到大的顺序有三种可能,每个植物每次更新的动画进度增量是0.12/1=0.12,从炮更新开始“发炮动画”或“填充动画”也才开始更新,下面列出了不同编号顺序下每帧的动画进度:
                            炮莲莲:[炮]莲莲0.36 [炮]莲莲0.72 [炮]0.84莲莲1.08 [炮]1.40莲莲
                            莲炮莲:莲[炮]莲0.24 莲[炮]莲0.60 莲[炮]0.84莲1.08 莲[炮]1.40莲
                            莲莲炮:莲莲[炮]0.12 莲莲[炮]0.48 莲莲[炮]0.84 莲莲[炮]1.40
                            表中1.08、1.40实际游戏中都应该是停留在1,这里这么写只是为了看着直观;最后一帧标1.40同样是为了看着直观,实际上此时动画已经重置并更新至0.36。
                            可以看到,avz在每帧的末尾读数,读到的数据是不一样的,但是对于炮而言,炮的属性更新只能发生在自己的回合,不论炮在哪个位置都没有任何区别。

                            如图,分别为炮<莲<莲和莲<莲<炮时avz的读取情况。


                            IP属地:江苏14楼2024-12-22 23:46
                            回复
                              Frs炮的视觉效果的原理及注意点
                              其实这也算上一章“frs的特性”,但是全堆一起太乱了。(
                              ....
                              动态方面的视觉效果
                              首先,由于frs炮至少有两个植物捆在一起,也就是动画播放至少会翻两倍,所以所有的动画和正常状态相比都会播放得更快。


                              IP属地:江苏15楼2024-12-22 23:46
                              回复