最近在用 Ren’Py 做点击解谜游戏,点击,变更状态,播放动画,Ren’Py 的 screen 很好用。
一段最简单的示例代码长这个样子,点击触发 action 会进入到动画状态;动画状态使用 timer 计时,结束后到达下一个状态。
default my_door_a_state = "closed" # "opening" "open" "closing"
default my_door_b_state = "closed" # "opening" "open" "closing"
screen my_doors():
if my_door_a_state == "closed":
button:
background "my_door_image closed"
action SetVariable("my_door_a_state", "opening")
elif my_door_a_state == "opening":
add "my_door_image opening"
timer .5 action SetVariable("my_door_a_state", "open")
当然,如果这样写的话会有大量重复代码片段。所以,我定义了状态机 RuleSet,以及 Rule 来声明当前状态,何种条件,下一种状态,副作用比如音效等等。然后使用一个 screen 把整个 RuleSet 丢进去,自动渲染成 button 或者 timer + add 并处理 action,一切都非常完美。
然后,中间有过这样一个函数:
init python:
def apply_rule(rule: Rule, rs: RuleSet) -> bool:
# ...
return True
顾名思义,它会检查 Rule 的条件是否满足,不满足返回 False, 满足则执行,最后返回 True.
于是,中间写出了这样的东西:
screen my_screen():
# ...
timer rs.duration action Function(apply_rule, rule, rs)
然后,一切看上去都很正常,只要定义好规则,一切都是自动的。
只是,如果有两扇门,在一扇门处在动画中的时候,点击另一扇门,会导致前一扇门的动画重新播放,这很奇怪。
虽然使用自定义的 Action 修复了这个问题,但是原本的问题是如何发生的呢?
为什么手写 if elif 没有问题,为什么自定义的 Action 没有问题,只是 Function 就有问题?这个问题,看上去,就像是,第二次点击改变状态,导致整体重新渲染,所以正在播放中的动画中断重新开始。
但是,手写 if elif 并没有问题,所以我第一时间推测,这是纯渲染的问题,所以尝试更改 screen 的写法。
但是接下来发现自定义 Action 不会出现动画的闪动(甚至需要你手动 renpy.restart_interaction() 不然根本不刷新),所以推测和 Function 的机制有关。
于是,从 Function 的原理,Rule 的一致性和相等判断,查了半天,全都毫无头绪。
但是我真的非常非常非常好奇原因,于是做了大量的实验。
然后!发现!如果我随便定义一个 def my_demo_func() 把 apply_rule 包起来……哎!动画!不闪了!
区别在哪?
返回值!
给 my_demo_func 加上返回值,嗯,果然动画会闪动。
嘛,到头来,问题其实很简单。
因为所有的一切都发生在 call screen 的状态下:
label my_label:
call screen my_label_detail():
jump my_label
然后:
If the function returns a non-None value, the interaction stops and returns that value. (When called using the call screen statement, the result is placed in the _return variable.)
Instead of using a Function action, you can define your own subclass of the Action class. This lets you name the action, and determine when it should be selected and sensitive.
因为 apply_rule 会 return bool, 所以 call screen 直接结束了。因为 label 有预防意外跳出的 jump 自己的语句,所以重新进入 label. 然后因为 Ren’Py 会 diff 然后复用元素,所以画面根本并不会中断,只是当两个动画同时播放时才会闪那么一下。
这下先入为主,完全没往这个方向想。