无视我233 发表于 2020-4-8 12:20:06

如何制作类似影子马里奥的跟踪型物件

早上有人问我影子马里奥怎么实现,因为三言两语很难讲清楚,所以我想开贴来进行说明。
【制作所需的基础】
本帖默认你懂得如何在MMF创建元件、创建事件,以及知道可变值(数值变量)是干什么的及怎么用。


无视我233 发表于 2020-4-8 12:27:02

首先我们要知道跟踪的原理。虽然在视觉效果(或者说,空间层面)上,跟踪是跟踪物不断地按被跟踪物的轨迹进行移动,但是这种解释对我们的制作没有帮助,所以我们要从另一个角度去看这个问题,那就是时间的层面。
以影子马里奥为例,影子马里奥的运动是固定地走马里奥在第N帧前的轨迹,那么针对每一个马里奥被走过的位置,如果在第T帧被马里奥走到了这个位置,那么在T+N帧就会被影子马里奥走,而我们需要做的,就是在T时刻记录下马里奥的位置信息(以及连带的动画及其他),并且在T+N时刻根据信息改变影子马里奥的位置,动画等。
这个角度来看,跟踪,简便地说就是“在某一时刻记录下被跟踪物的信息,并且在之后的另一时刻应用到跟踪物上”。

无视我233 发表于 2020-4-8 13:02:49

那么接下来就是如何实现了。我们知道数值变量(可变值)提供了存储信息的空间(并且可视作是连续的),那么假设每一帧的信息需要一单位的空间,而影子马里奥相比马里奥滞后了N帧,我们最少需要N单位的空间来存储。当然影子马里奥还是稍微复杂了一些,我们选一个更简单的示例来解释如何进行存储。
假设这里有一个每帧可变值A+1(只是便于观察,也可以可变值依照其他的公式变化)的物件并且可变值A用一个counter显示出来,而另外有一个物件,其可变值A为15帧前被跟踪物的可变值,也用一个counter显示出来,像这样:

而这是被跟踪物以及两个显示可变值的counter的事件:

接下来就是如何写跟踪物的事件的问题了。因为可变值A是用来跟踪的,然后前15帧不跟踪,所以我们用可变值C来判断是否需要跟踪(可变值B暂时留空,用于我后续演示)。我写了这个事件,之后就可以判断当可变值C等于15时,才会设置可变值A。


无视我233 发表于 2020-4-8 13:21:22

那么接下来就是核心代码的编写了。我们暂时先空出可变值D(可变值3)和E(可变值4),由于至少同时需要记录15帧下可变值A的信息,因此我们至少需要15个可变值来记录信息。而且出于内存考虑可变值应该避免无限向后使用,我这里就暂时取35个(35是我随便定的数字,其实出于节约空间的目的以及其他考量16个甚至15个就已经足够,不过适当放宽不是问题)可变值。因为可变值A~E也就是可变值0~4我们事先保留了,所以我们取可变值5~39。
接下来我怎么做呢?每帧都要记录被跟踪物的可变值A,而且要被记录在不同的可变值内。因此我们使用跟踪物的可变值D来统计需要把这一帧的记录值分配到哪个可变值里。因为我们用了35个可变值,因此每35帧这个值就会被覆盖,但是因为这个值只需要保留15帧,因此在35帧后覆盖是安全的。可变值D我们希望它能够在5~39之间循环,而下面这种写法就可以考虑:

这个mod是取余数。因为D在5~39之间,所以D-5就应该在0~34之间,将(D-6)每帧加1并且求除以35的余数的话,一旦(D-5)加1之后大于等于35,那么就会被重置回0,这样就做到了(D-5)在0~34间循环,而再加上5,也就是D在5~39之间循环了。
如果觉得太难不会,也可以用条件判断的方法,其实是一样的。
然后呢,我们就可以用可变值D来分配每一帧记录的被跟踪物的信息了,像这样:

(使用Use expression来做到可变的可变值序号的效果)

看起来比较套娃,但是这就是动态分配可变值的精髓(?)。注意一下因为事件是从上至下执行的,所以记录被跟踪物应当在可变值D递增之前。
这样,记录的部分我们就完成了,事实上在计算机科学中,有一个专门的术语用来形容这个步骤,那就是“enqueue”(入列)——因为这看起来就像一堆数值排排队等待被读取,非常形象。
接下来就是读取的问题了。


无视我233 发表于 2020-4-8 13:32:50

其实读取的部分和写入是类似的,只不过滞后了15帧,这里我们用可变值E来作为决定读取哪一个可变值的数据的变量。就像这样:

注意用这个来动态引用可变值。

代码如这两行所示。第9行用于初始化,可以理解,第10行我稍微解释一下:条件不是Always而是可变值C为15,是因为我们需要滞后15帧进行读取,而前面对可变值C做的准备工作,就是为了保证在第15帧以后可变值C为15,才执行这个事件。而两个动作,第二个是可变值E进行循环递增,和写入是一样的,而第一个行为,就是将可变值A设置成可变值E所动态指示的可变值。
这就像一系列数值排队排好等待依次被读取,因此被相应地叫做“dequeue”(出列),因为这些被读取的数值相当于经过了漫长的等待终于到了队伍的头部办完了证(?),因此叫做“front”(队头)。
最后我们的程序实现的效果就像下图所示。

这样,基本的写法我们就掌握了。
当然你可能会问:这是一个最简单的范例,离哪怕是做出像影子马里奥这样同时记录多个参数的玩意也依旧差了一些距离,那影子马里奥怎么做呢?


无视我233 发表于 2020-4-8 13:56:56

对于影子马里奥来说,和这个的唯一区别,就是记录的内容稍多一些,每一帧会记录多个内容。那么,对应的改动也很简单。我们现在改一下我们的示例,现在每一次需要记录被跟踪物的可变值A和可变值B,并且在15帧后用跟踪物的可变值A和可变值B体现出来——如果这个会了,其实影子马里奥的做法也就会了。(只不过影子马里奥记录的是X坐标、Y坐标、方向和当前动画序列这么四个值,但是思路上两个和四个完全没有区别)
但是事实上,没人规定每帧只能进行一次enqueue和dequeue操作,而且只要写和读顺序一致,就不会错乱。所以我们只需要进行这样的改动,就可以了。

不过在用的时候务必注意,要保证空间足够。像这里,因为记录15帧,每帧2个数据,一共同时需要30个空间,而我留的是35个变量的空间,所以充足。当然因为读写操作的顺序等细节问题或者是在进一步挖掘用途时可能需要判断空间是否用完,所以我建议至少要比你预期的宽裕1个空间以上,例如这时候最少尽量安排31个可变值的空间。

既然enqueue和dequeue操作不是每帧一次,而是可以自行调节,我们就可以拿来做很多事情了——除了影子马里奥,还可以有例如MFIT6-3的跟踪炮弹,马里奥经过一个节点时enquque一次,炮弹经过一个节点时dequeue一次以达到炮弹完全跟着马里奥运动的轨迹的效果(当然实际上并不是这么实现的)……
总之,这一种方法还是有相当多的使用情景的。


无视我233 发表于 2020-4-8 14:02:15

如果你觉得这样写还嫌麻烦的话,事实上在MMF里面已经有一个扩展封装了这个功能——“Queue Object”(queue.mfx)。只不过在里面叫法略有不同——front还是front,但是enqueue被称作push操作,而dequeue被称作pop操作。虹原翼的影子马里奥扩展就是基于这个扩展来写的。在我进行了这样的解释以后,我相信你应该是可以看懂相关代码的。只不过扩展也有一定的局限性,例如一个Queue Object只能提供一个队列(也就是我们之前规划出来的连续的一串空间),如果一个关卡里面带跟踪属性的物件有多个的话,Queue Object就会显得很麻烦(要考虑创建、销毁、配对等在MMF里熟练的事件编写者和MMF爱黑者非常想要避免和吐槽的问题),因此掌握思路还是重要的。
那么这个教程就介绍到这里,喜欢的话记得给+3评鼓励!
页: [1]
查看完整版本: 如何制作类似影子马里奥的跟踪型物件