“触发执行顺序”的版本间的差异
(创建页面,内容为“前言 本文面向的读者为已经有一定制图基础的星际1地图作者,并非从零开始教你制图。读者需要具有的最低基础是能够成功…”) |
(增加了关于ElapsedTime()的详解) |
||
(未显示2个用户的5个中间版本) | |||
第1行: | 第1行: | ||
本文需要的辅助软件为星际1的官方推荐地图编辑器 - [[SCMD|ScmDraft2]](简称scmd) | |||
每轮扫触发时,触发的运行逻辑: | |||
[[文件:TrigOrder 01.png|1266x1266像素]] | |||
运行单个触发的逻辑: | |||
[[文件:TrigOrder 02.png|1296x1296像素]] | |||
【第一章】星际争霸1游戏的时间系统: | 【第一章】星际争霸1游戏的时间系统: | ||
第84行: | 第81行: | ||
...... | ...... | ||
注意,scmd编辑器的触发器中的wait()动作会改变扫触发的时间间隔。相邻两轮扫触发的时间间隔可为[2fr,31fr]间的任意整数(通过并联wait可将上限强行拖到39fr,再加个倒计时器甚至可再拖到40fr),都可以通过wait来调整。运用串联并联叠加wait(0)而得到的加速触发系统中,每轮扫触发的时间间隔均为2fr(0.125游戏秒),之后的wait篇会详细介绍。正是由于这种加速触发的存在,我们才把无wait语句的触发系统叫做普通触发。 | 注意,scmd编辑器的触发器中的wait()动作会改变扫触发的时间间隔。相邻两轮扫触发的时间间隔可为[2fr,31fr]间的任意整数(通过并联wait可将上限强行拖到39fr,再加个倒计时器甚至可再拖到40fr),都可以通过wait来调整。运用串联并联叠加wait(0)而得到的加速触发系统中,每轮扫触发的时间间隔均为2fr(0.125游戏秒),之后的wait篇会详细介绍。正是由于这种加速触发的存在,我们才把无wait语句的触发系统叫做普通触发。 | ||
第93行: | 第86行: | ||
参考资料 | 参考资料 | ||
http://www.staredit.net/wiki/index.php?title=Hyper_Triggers | |||
http://www.staredit.net/wiki/index.php?title=Wait_blocks | |||
注:以上资料中说每轮扫触发的间隔为2游戏秒(32fr),这是错的。正确的应该是1.9375游戏秒,即31fr。资料的其他内容没有问题。 | 注:以上资料中说每轮扫触发的间隔为2游戏秒(32fr),这是错的。正确的应该是1.9375游戏秒,即31fr。资料的其他内容没有问题。 | ||
注意,条件 ElapsedTime(Exactly/AtMost/AtLeast, x) 中的x是游戏时间的整数部分。比如: | |||
条件 ElapsedTime(Exactly, 5) 翻译为人类语言是“当前游戏时间(游戏秒)的整数部分为5”,因此(在一个不含任何wait语句触发的地图中)该条件会在第4轮扫触发时被判定为“真”,因为第4轮扫触发时的游戏时间为5.9375游戏秒,整数部分为5 | |||
条件 ElapsedTime(Exactly, 6) 将永远不会被判定为真,因为第6游戏秒不会发生任何扫触发(第4轮扫触发时的游戏时间整数部分为5秒,而第5轮扫触发的游戏时间整数部分为7)。同理,ElapsedTime(Exactly, 8) 也将永远不会被判定为真。 | |||
同理,条件CountdownTimer(Exactly/AtMost/AtLeast, x)中的x也是指屏幕上方倒计时的整数部分。 | |||
所以,我们在写有关时间判定的条件时,永远不要用Exactly,要用AtLeast或者AtMost | |||
【第二章】一轮遍历检查内,触发的执行顺序: | 【第二章】一轮遍历检查内,触发的执行顺序: | ||
第124行: | 第128行: | ||
[例2-1]: | [例2-1]: | ||
<source lang="lua"> | |||
Trigger("Player 1", "Player 3", "All players"){ | Trigger("Player 1", "Player 3", "All players"){ | ||
第148行: | 第155行: | ||
} | } | ||
</source> | |||
假设开始游戏后玩家总数(含电脑)为8,那么这两个触发实际上就是13个小触发。分解后为: | 假设开始游戏后玩家总数(含电脑)为8,那么这两个触发实际上就是13个小触发。分解后为: | ||
<source lang="lua"> | |||
player1: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] | player1: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] | ||
第176行: | 第187行: | ||
player8: [Always()]?:[Set Switch("Switch1", toggle)] | player8: [Always()]?:[Set Switch("Switch1", toggle)] | ||
</source> | |||
根据触发的执行顺序对这13个触发进行排序并编号:(注意:由于本例中触发的condition和action里面都没有current player,所以在排序之后,触发的执行对象就不重要了,触发的执行对象仅被用来决定触发顺序) | 根据触发的执行顺序对这13个触发进行排序并编号:(注意:由于本例中触发的condition和action里面都没有current player,所以在排序之后,触发的执行对象就不重要了,触发的执行对象仅被用来决定触发顺序) | ||
<source lang="lua"> | |||
(01) player1: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] | (01) player1: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] | ||
第204行: | 第219行: | ||
(13) player8: [Always()]?:[Set Switch("Switch1", toggle)] | (13) player8: [Always()]?:[Set Switch("Switch1", toggle)] | ||
</source> | |||
那么,第一轮遍历检查触发时间为游戏时间开始后的0.125游戏秒。我们来走一遍: | 那么,第一轮遍历检查触发时间为游戏时间开始后的0.125游戏秒。我们来走一遍: |
2023年4月10日 (一) 22:16的最新版本
本文需要的辅助软件为星际1的官方推荐地图编辑器 - ScmDraft2(简称scmd)
每轮扫触发时,触发的运行逻辑:
运行单个触发的逻辑:
【第一章】星际争霸1游戏的时间系统:
logical step是星际游戏时间系统的最小单位,又称frame(游戏帧),下文简称fr。如1fr代表1 logical step,即1游戏帧。
游戏帧(时间单位)是跟游戏时间紧密结合的,有以下亘古不变的恒等式:
1 logical step = 1fr = 1/16游戏秒 = 0.0625游戏秒
游戏时间就是地图编辑里面触发的Elapsed time,流逝速度等于游戏内位于顶端的倒计时时间的流逝速度。
上述等式与游戏速度、现实世界时间均无关。
此外,在无网络延迟的条件下,1游戏帧在不同游戏速度下对应不同的现实时间(都是精确值,无近似):
1fr = 0.167地球秒 (Slowest游戏速度)
1fr = 0.111地球秒 (Slower游戏速度)
1fr = 0.083地球秒 (Slow游戏速度)
1fr = 0.067地球秒 (Normal游戏速度)
1fr = 0.056地球秒 (Fast游戏速度)
1fr = 0.048地球秒 (Faster游戏速度)
1fr = 0.042地球秒 (Fastest游戏速度)
由以上可以算得,fastest游戏速度下:1游戏秒=16fr=0.672地球秒。1地球秒= 125/84 游戏秒 ≈ 1.488游戏秒。
我们经常听到别人说,现实中的1秒等于游戏中的1.5秒,这个说法其实是不准确的。
本文中出现的诸如t=2fr, t=1游戏秒之类的字眼中,t的意思就是时间(time),t=2fr就是代表一个时刻,是一个时间点。
普通触发每轮扫触发的间隔为31fr = 1.9375游戏秒,与游戏速度无关。
重要的事情说三遍:
间隔为31fr,即1.9375游戏秒!不是2游戏秒!
间隔为31fr,即1.9375游戏秒!不是2游戏秒!
间隔为31fr,即1.9375游戏秒!不是2游戏秒!
在fastest游戏速度下,这个间隔为1.302地球秒
注:本文可能会出现“扫触发”、“遍历检查触发”等词汇,都是表达同一个意思。
这里要注意,游戏开始的一瞬间(即0秒时不会检查触发),第一轮检查触发的时间是t=2fr=0.125游戏秒。
之后每轮检查触发的时间间隔均为1.9375游戏秒(普通触发)。即,普通触发检查触发的时间点为:
t= 2fr = 0.125 游戏秒
t= 33fr = 2.0625 游戏秒
t= 64fr = 4 游戏秒
t= 95fr = 5.9375 游戏秒
t=126fr = 7.875 游戏秒
t=157fr = 9.8125 游戏秒
t=188fr = 11.75 游戏秒
t=219fr = 13.6875游戏秒
t=250fr = 15.625 游戏秒
......
注意,scmd编辑器的触发器中的wait()动作会改变扫触发的时间间隔。相邻两轮扫触发的时间间隔可为[2fr,31fr]间的任意整数(通过并联wait可将上限强行拖到39fr,再加个倒计时器甚至可再拖到40fr),都可以通过wait来调整。运用串联并联叠加wait(0)而得到的加速触发系统中,每轮扫触发的时间间隔均为2fr(0.125游戏秒),之后的wait篇会详细介绍。正是由于这种加速触发的存在,我们才把无wait语句的触发系统叫做普通触发。
参考资料
http://www.staredit.net/wiki/index.php?title=Hyper_Triggers
http://www.staredit.net/wiki/index.php?title=Wait_blocks
注:以上资料中说每轮扫触发的间隔为2游戏秒(32fr),这是错的。正确的应该是1.9375游戏秒,即31fr。资料的其他内容没有问题。
注意,条件 ElapsedTime(Exactly/AtMost/AtLeast, x) 中的x是游戏时间的整数部分。比如:
条件 ElapsedTime(Exactly, 5) 翻译为人类语言是“当前游戏时间(游戏秒)的整数部分为5”,因此(在一个不含任何wait语句触发的地图中)该条件会在第4轮扫触发时被判定为“真”,因为第4轮扫触发时的游戏时间为5.9375游戏秒,整数部分为5
条件 ElapsedTime(Exactly, 6) 将永远不会被判定为真,因为第6游戏秒不会发生任何扫触发(第4轮扫触发时的游戏时间整数部分为5秒,而第5轮扫触发的游戏时间整数部分为7)。同理,ElapsedTime(Exactly, 8) 也将永远不会被判定为真。
同理,条件CountdownTimer(Exactly/AtMost/AtLeast, x)中的x也是指屏幕上方倒计时的整数部分。
所以,我们在写有关时间判定的条件时,永远不要用Exactly,要用AtLeast或者AtMost
【第二章】一轮遍历检查内,触发的执行顺序:
众所周知,一个触发的由执行对象、条件(Conditions)、动作(Actions)三部分构成,相当于编程中的if-then从句。
每隔一定的时间遍历检查一轮触发(或称扫触发),这个时间间隔为31fr=1.9375游戏秒。注意,本章中所有例子里,都没有任何触发存在wait()。wait会导致遍历检查的时间间隔产生变化,以后会有详解。
每轮遍历检查触发的规则为:
每个[多于一个执行对象]的触发(如对象是force 1触发,对象是all players的触发),都要被分解为若干个子触发,每个子触发都有唯一一个执行对象,即每个子触发的执行对象只能是player1或2或3或4或5或6或7或8。如一个All players的触发,假设游戏玩家总数(含电脑)为8,那么它就要被分解为针对player1,针对player2......针对player8这样8个子触发,每个子触发的conditions和actions都完全相同。如果一个触发的执行对象仅为1个player,那么它本身也可以算作一个子触发。注意,在scmd编辑器中,一个触发的对象若同时勾上了player1和AllPlayers,那么它就要拆成9个子触发而不是8个,其中有两个针对player1的子触发(在其他编辑器如eudEditor2中,是拆成8个。这个取决于编辑器)。然后,遍历检查时,先检查执行对象为player1的所有子触发,检查顺序为触发创建的顺序,所有针对player1的子触发都检查完之后,再检查所有对于player2的子触发,以此类推,最后检查player8的触发,边检查边执行。整个遍历检查时间极其迅速,1.9375游戏秒的时间内一定足够遍历检查一遍所有触发。我估计在大多数情况下0.01秒内就可以扫完一轮触发,除非你触发写得太多太多而且cpu太差运算速度跟不上。
判断是否执行触发:
首先,系统检查这个触发的执行对象是否存在于游戏中。如果不存在,则无视这条触发。如果存在,会检查触发的“条件列表”,以判断该触发是否满足执行条件。
“触发满足执行条件”的定义:该触发的条件列表里的所有条件都满足
“触发不满足执行条件”的定义:该触发的条件列表里有至少一条不满足
满足条件的触发就会被执行,即执行列表里的每个内容都会被瞬间依次执行(含有wait语句的触发为特例,之后介绍)
如果此触发非preserved(循环触发),则被执行一次后丢弃,之后的遍历检查都无视此触发;如果此触发的动作列表里面的任何位置有preserved语句,则这条触发为preserve触发(循环触发),则每轮扫触发都会检查这个触发是否执行(注:含有wait语句的触发可能会使得其在本轮扫触发时处于“正在执行”的待机状态,这时系统在本轮不会理会这个触发)。
不满足条件的触发就不执行,等待下一轮遍历检查时再判断是否执行。
[例2-1]:
Trigger("Player 1", "Player 3", "All players"){ Conditions: Switch("Switch1", Set); Actions: Set Resources("Player 1", Add, 1, ore); } Trigger("Player 3", "Player 6", "Player 8"){ Conditions: Always(); Actions: Set Switch("Switch1", toggle); }
假设开始游戏后玩家总数(含电脑)为8,那么这两个触发实际上就是13个小触发。分解后为:
player1: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player3: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player1: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player2: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player3: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player4: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player5: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player6: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player7: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player8: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] player3: [Always()]?:[Set Switch("Switch1", toggle)] player6: [Always()]?:[Set Switch("Switch1", toggle)] player8: [Always()]?:[Set Switch("Switch1", toggle)]
根据触发的执行顺序对这13个触发进行排序并编号:(注意:由于本例中触发的condition和action里面都没有current player,所以在排序之后,触发的执行对象就不重要了,触发的执行对象仅被用来决定触发顺序)
(01) player1: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (02) player1: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (03) player2: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (04) player3: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (05) player3: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (06) player3: [Always()]?:[Set Switch("Switch1", toggle)] (07) player4: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (08) player5: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (09) player6: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (10) player6: [Always()]?:[Set Switch("Switch1", toggle)] (11) player7: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (12) player8: [Switch("Switch1", Set)]?:[Set Resources("Player 1", Add, 1, ore)] (13) player8: [Always()]?:[Set Switch("Switch1", toggle)]
那么,第一轮遍历检查触发时间为游戏时间开始后的0.125游戏秒。我们来走一遍:
(01)不满足条件,不执行,因为所有switch默认为关闭状态(cleared)
(02)不满足条件,不执行,因为所有switch默认为关(cleared)
(03)不满足条件,不执行
(04)不满足条件,不执行
(05)不满足条件,不执行
(06)满足条件,所以将Switch1打开(set)。执行完后丢弃此触发,下轮无视。
(07)满足条件,给player1加1个水晶矿。丢弃。
(08)满足条件,给player1加1个水晶矿。丢弃。
(09)满足条件,给player1加1个水晶矿。丢弃。
(10)满足条件,将Switch1关闭(clear)。丢弃。
(11)不满足条件,因为Switch1是关闭状态
(12)不满足条件,因为Switch1是关闭状态
(13)满足条件,将Switch1打开(set)。丢弃
这里再次强调,以上(01)至(13)都是在t=2fr这一瞬间完成的。
那么第一轮遍历检查之后,player1共有3块钱水晶矿。第二轮遍历检查时:
(01)满足条件,给player1加1个水晶矿。丢弃。
(02)满足条件,给player1加1个水晶矿。丢弃。
(03)满足条件,给player1加1个水晶矿。丢弃。
(04)满足条件,给player1加1个水晶矿。丢弃。
(05)满足条件,给player1加1个水晶矿。丢弃。
(11)满足条件,给player1加1个水晶矿。丢弃。
(12)满足条件,给player1加1个水晶矿。丢弃。
第二轮遍历检查总共给player1加了7块水晶矿,此时player1共有10块钱水晶矿。
所以在游戏中的效果就是,player1在一开局0.125秒后水晶矿变成3,然后过1.9375秒游戏时间之后水晶矿变成10。
在这里顺便说一下什么是Switch(开关)。Switch是一个环境变量。如果你学过编程,那么它就是一个Boolean variable(布尔变量)。一个Switch可以有两种状态,set(开启状态)和cleared(关闭状态),注意哦,这两个是形容词,所以switch作为一个触发的条件时只有这两个选项。默认为cleared状态。我们可以通过触发中的动作来改变开关的状态,我们可以:set(打开)一个switch,也可以clear(关上)一个switch,也可以toggle(扳动),也可以randomize(随机化),注意哦,这些都是动词,所以switch作为一个触发的动作时有更多的选项。