dasasdhba 发表于 2022-8-4 14:22:18

【杂谈】游戏开发中的数值变量技法

游戏开发,无非是把我们所希望设计的「行为」转换为「逻辑」,在这个过程中,变量可以说是发挥着关键的作用。不过关于变量在游戏开发中怎么去用,似乎没有什么理论参考,于是我打算在本帖将我的经验作一个简单总结。考虑到社区内目前最常用的还是 Clickteam Fusion,本文主要谈一谈关于数值变量(换句话说,int 型或者 double 型变量?)在游戏开发中的相关技法。

zqh——123 发表于 2022-8-4 14:23:12

这个很不错,适合代码入门

dasasdhba 发表于 2022-8-4 14:46:43

一、常用小功能
为了作一些铺垫,先介绍一些数值变量的常用功能吧。
1. 计时与计数
整数数值变量可以用于计时,如下图:
https://s1.ax1x.com/2022/08/04/vewFVU.png
不过这样做的话,单位就是帧了,MF 的默认帧数是 50,也就是 50 帧为一秒啦。
值得一提的是 CTF 自带的 Timer 是帧率无关的,这也是造成卡机高跳的原因之一,所以修复方式就是改用此种方式计时。
计数也是几乎同理,把 always 改为某种条件,就能在需要的情况进行计数了。

2. 暂时存储特定信息
很多时候我们暂时需要存储一些信息。
比如石盾,我们需要存储它的初始 Y 坐标用于判定什么时候返回;
比如探照灯,我们需要存储它的中心坐标以使用参数方程使其旋转;
那么数值变量很自然可以做这件事情了。

此外,还有一种特殊情况是随机数,我们有时候可能需要一个随机的过程。
此时可以用一个变量来存储这个随机值:变量 R = Random(3)。

3. 追加「属性」
举个例子,CTF 中每个 Active 都有一个坐标属性(Positioin),我们可以像变量一样去设置它,将它与特定数值进行比较。
换句话说,物件的 XY 坐标跟变量没什么区别(不过 CTF 的坐标是 int 型,需要注意),只是它被赋予了坐标的属性,程序会根据这个值去设置其位置。
但我们也可以人为地赋予一些变量以属性,比如下图中,我赋予了变量 V 横向速度的属性(为什么?):
https://s1.ax1x.com/2022/08/04/ve0Vw8.md.png
赋予变量以属性一般可以带来两个好处:
1. 接下来你就可以像操作物件的坐标一样来操作物件的横向速度了;
2. 你可以将这个属性参数化,并方便地进行调整,比如 RE 的组 1 就赋予了变量 A 以横向速度的属性。
注:对于 CTF 而言,还有第三个好处:避免指代问题。
具体来说,当你用变量去作比较的时候,是有筛选效果的,但直接比较一些属性的时候却没有筛选效果,比如,下图没有筛选效果:
https://s1.ax1x.com/2022/08/04/ve0rm6.png
angle 与坐标类似,是物件的一个属性,但由于 CTF 限制,直接比较没有筛选效果,而如果我们赋予变量 W 以 angle 的属性,这个问题就解决了:
https://s1.ax1x.com/2022/08/04/ve0f1A.md.png

那么铺垫工作就结束了,接下来我们将会谈到一个比较核心的内容。

dasasdhba 发表于 2022-8-4 15:33:27

二、核心思想:利用数值变量「阶段化」行为
这一部分也可以算是数值变量的一个功能,只是我个人认为这个功能极为核心,所以就单独谈了。
先解释一下「阶段」:当我们需要实现一个复杂的行为时,这个行为往往可以分为若干个小的阶段,因此我们只需要将这些小的阶段各个击破即可。
(有点类似于将一个大目标分解为若干个小目标之类的说法。)

我们以食人花为例,食人花的行为可以简单分为这样的阶段:

[*]在水管内,并检测是否可以出水管;
[*]出水管;
[*]在水管外,能喷子弹就先喷子弹,然后计时以决定什么时候钻回去;
[*]进水管。

依次循环,便完成了食人花的行为。

接下来谈谈具体实现,为了将这些阶段分解,我们便需要利用数值变量「阶段化」。
在上述食人花的例子中,我可以定义:变量 Z=0 为 1 阶段,Z=1 为 2 阶段,Z=2 为 3 阶段,Z=3 为 4 阶段。
接下来我们只需要分别完成这四个阶段的行为就可以了。
https://s1.ax1x.com/2022/08/04/veBf5F.png
不过需要注意的是,每一次转阶段的时候,你都要保证初始状态是你所预设的。
举个例子,如果你在即将要转入的阶段使用了变量 T 来计时,那么你需要保证转入这个阶段的时候,变量 T 被提前设置为了 0。

对于阶段一,我们只需要简单比较马里奥是否在害羞范围内,如果在就利用刚才提到的办法计时,达到设定时间后设置 Z = 1 进入下一阶段即可;
对于阶段二,简单地操作一下坐标就可以了,出水管结束之后进入下一阶段即可;
对于阶段三,我们又可以分为两个小阶段:

[*]喷子弹,如果喷完了/不喷子弹,则进入下一小阶段;
[*]计时,达到设定时间后设置 Z = 2 进入下一大阶段即可;

而对于「喷子弹」过程,我们又可以分为两个小阶段来实现:

[*]创建一个子弹,设置其坐标和速度等参数,并利用刚才提到的计数变量计数,随后立刻进入下一小阶段;
[*]计时,达到设定时间后判定刚才计数的数量有没有达到设定值,如果没有,返回第一小阶段进行循环,反之喷子弹过程结束。


最后我们到了阶段四,这个过程跟阶段二完全类似,归位之后回到阶段一即可。
不过你可能会发现我们也许需要暂时存储一下食人花的初始坐标,不然归位可能不太好办。

如果你理解了上述过程,不妨自己试试动手写一个食人花,看看是否顺利。
值得一提的是,RE 的食人花没有使用「阶段化」方法,因此其逻辑看起来十分混乱难懂,不过毕竟作者是在没有学过编程的情况下做出来的,也不必太过计较啦。

再举一个例子吧:石盾——同样是 RE 中没有使用「阶段化」方法的一个物件,让我们试试用这种方法来看,石盾实现有多简单:


[*]悬空,判定马里奥是否在触发范围内,如果在的话,进入下一阶段;
[*]下落,落地的时候播放音效,创建爆炸特效,并进入下一阶段;
[*]计时,达到设定值进入下一阶段;
[*]归位,回到初始位置或者中途遇到障碍的时候,回到第一阶段。


真巧,也是四个阶段,而且看上去比食人花要简单不少。

那我们来看一个稍微麻烦点的吧——库巴:
首先,我们注意到库巴的运动和其攻击有时候是并行的,有时候又是分开的,为了方便,我们可以将库巴运动用一个开关(布尔变量)控制。
接下来我们来处理库巴运动,其可以分为移动和跳跃。

先看移动吧:
首先移动有一个方向,我们可以用一个变量来代表方向,-1 为左,1 为右。
为了达到转向的目的,我们只需要在库巴运动到转向边界的时候,设置一下这个方向变量就好。
然后是恶劣的「急停」,可以分为三个阶段:

[*]计时,达到特定值进入下一阶段;
[*]利用刚才提到的方法将某个变量设为随机数,在某个范围内时进入下一阶段,否则回到第一阶段;
[*]急停并计时(此时可以关闭刚才提到的库巴运动开关以达到效果),达到特定值后返回第一阶段。


接下来是跳跃,其实完全可以照搬急停的模板:

[*]计时,达到特定值进入下一阶段;
[*]利用刚才提到的方法将某个变量设为随机数,在某个范围内时进入下一阶段,否则回到第一阶段;
[*]跳跃(初速度也可在转阶段时设为随机数),落地后回到第一阶段。


那么运动就解决了,接下来我们看看库巴的攻击(8-4),我们可以将每一个招式看作一个大阶段:

[*]喷几个火;
[*]喷三重火;
[*]丢一堆锤子;
[*]喷几个火;
[*]喷一堆火;


这里有重复的阶段(阶段一和阶段四),我们在用变量划分阶段的时候用 OR 处理一下就可以了(阶段变量 = 0 或 阶段变量 = 3)。
接下来我们依次看。

喷火:

[*]计时,达到特定值后进入喷火动画,并随即进入阶段二;
[*]喷火动画结束后,创建火焰,播放音效,计数,并随即进入阶段三;
[*]判断计数是否达到设定值,如果没有,返回阶段一;反之,喷火阶段结束,进入其他招式。


喷三重火与喷火类似,只是不需要我们计数了,略去。

丢锤子:

[*]关闭库巴运动开关,切换动画并计时,达到特定值后切换动画并进入阶段二;
[*]创建锤子,计数 A(扔锤子数),随即进入阶段三;
[*]计时,达到特定值后,如果计数 A 达到特定值则进入阶段四,否则返回阶段二;
[*]计数 B(波数),如果计数 B 达到特定值则丢锤子阶段结束,重启库巴运动开关,否则进入阶段五;
[*]计时,达到特定值后返回阶段二。


狂喷火球:

[*]关闭库巴运动开关,切换动画并计时,达到特定值后切换动画并进入阶段二;
[*]创建火球,播放音效,计数,随即进入阶段三;
[*]计时,达到特定值后,如果计数达到特定值则狂喷火球阶段结束,重启库巴运动开关,否则返回阶段二。


那么一个 8-4 的库巴的全部内容,也就是上面这些了,其实每一个阶段都不难实现,但合起来看上去就比较复杂了。
综上,我认为使用数值变量「阶段化」我们所需要设计的行为,是帮助我们清晰地、有序地去实现逻辑的一个重要手段。

dasasdhba 发表于 2022-8-4 15:44:34

本帖最后由 dasasdhba 于 2022-8-4 15:48 编辑

三、补充内容
最后再补充一些相对不那么重要的边角内容吧。

1. 利用二进制将数值变量作为布尔变量(开关)数组
这个方法好像有个术语叫「状压」,通俗地说就是将整数看作二进制,其每一位要么是 0 要么是 1,那么每一位都可以看成一个布尔值(开关)。
而通过二进制运算(与 &,或 |,异或 ^)我们可以很容易判别哪些位是 1,哪些位是 0,这样我们就可以把一个整数变量当开关数组用了。
值得一提的是 CTF 压根没有数组,所以这种方法还是有点实用价值的。

2. 绕过 CTF 指代问题
这个就纯属 CTF 专属的邪门技巧了(
我之前在问答帖讲过,以下写法中,不等式右侧是不具有筛选作用的:
https://s1.ax1x.com/2022/08/04/vecoxe.png
具体体现在不等式右侧的变量 B 一定属于某个特定的 Active 实例而非变量 A 所属的 Active。
但我们可以通过一种邪门的方式绕过这个问题:
https://s1.ax1x.com/2022/08/04/vegML9.md.png
呃……我不是很想评价这种做法,不过这种邪门技巧也只有在 CTF 下才有用了((

dasasdhba 发表于 2022-8-4 15:45:52

那么以上就是本帖的全部内容了,希望能对各位有些帮助吧。
如果你还有什么好的想法,欢迎提出和讨论。

囿里有条小咸鱼 发表于 2022-8-5 18:54:14

先补充一下第二个大标题的东西吧:
所谓的【阶段】,专业上叫状态机,顾名思义,就是表示状态的【机器】,而这里的机器就是条件。
然后就是我刚刚在想一个问题:能否用一个变量做出数组的效果,
就是,多个数数通过一定的函数关系加和进一个数值里,读取的时候按照一定的函数关系再逐一逆向读出。
不过唯一一个缺点就是不能超过int或者float的数值上下限
目前的缺陷就是:逆向读出的时候不能确定原来的函数关系
页: [1]
查看完整版本: 【杂谈】游戏开发中的数值变量技法