「单刀双掷开关」触发模型
「单刀双掷开关」触发模型
注
本文探讨的「单刀双掷开关」模型系笔者实现抢电厂的触发逻辑之后,得出的一个名字。相比起「状态机」,前者更精确地描述了抢电厂的实际情况。
实际上根据触发的逻辑原理,我们的确可以把战役流程当成 C 程序来写。 那么,有意识地运用程序设计中的数学、物理、乃至专门的设计模型,或许能为你的任务流程增添些许彩头。
I. 单刀双掷开关(SPDT)
单刀双掷开关(SPDT)是电路中的一个开关元件,在电路图中通常这么表示:
可以看到,初始态它是断开的。开关既可以拨到 b 路,也可以拨到 c 路,以此实现「挡位切换」。 但无论怎么拨,在单刀双掷开关分管的电路内,至多只有一条分路是连通的。换言之,只要有分路保持连通,另一路必然断开。
II. 争夺电厂的行为逻辑
一开始,地图上的电厂是中立所属的。我们设玩家总是首先占领电厂,那么敌军势必要想办法反占。 于是整件事就演变成了:电厂在玩家与敌军之间来回易主,每次占领都会切换到对应阵营的设置、后续流程。
那么用 SPDT 模型套上去,容易发现:
- 初始态(断路):电厂归中立,不属于玩家和敌军任意一方
- b 路(假设玩家):被玩家占领后,任务计数递增,触发敌方反占增援波次
- c 路(假设敌军):被反占后,任务计数递减,(暂时)不再有增援波次
那么至此事情便明了了——争抢电厂这件事本质上就是小孩子在乱摁风扇不同档位的按钮玩。(
当然,回归正题,这里有一个前置条件:玩家总是抢先占领电厂。这么假设的原因是,我希望整个抢电厂的流程能够循环下去。
不妨逆推一下,假设敌军抢先,那么他们肯定要出兵,阻止我们反占;而当我们成功反占之后,敌军为了「收复」仍然需要出兵。 那么实际上,无论谁的肉人进了电厂,这个出兵的波次都必然要触发。
这种两条分路都需要的设计并不适合 SPDT,与「逻辑或」结合实现又会把触发复杂化(本来触发组件已经够啰嗦了)。
III. SPDT 模型的触发设计
具体地以某一个电厂为例。当玩家占领之后,电厂的计数累增,触发敌军的反占波次。玩家这一路的逻辑可用如下伪代码表示:
if (CapturedBy(Player)) // 事件:1 - 进入事件(玩家)
{
Phobos_VarEdit( // 行为:501 - 编辑变量(Phobos)
varidx, // 变量的下标。既可传 局部变量 也可传 全局变量。
1, // 变量的运算符,1 代表累加(+=)
1, // 累加的数值。三个参数合起来就是 varidx += 1。
false // 该变量是否为 全局变量
);
EnableTrigger(enemySpawn); // 行为 53 - 允许触发
ForceTrigger(enemySpawn); // 行为 22 - 强制触发(必须在 允许触发 之后)
}
为了表述方便,这里借用 C# 的 lambda 语法来指代敌军的刷兵反占波次:
var enemySpawn = () => {
if (
RandomTimeElapsed(191) // 事件:51 - 随机流逝时间(范围是入参的 50% 到 150%)
&& HouseWithoutBuilding(idxCAPOWR) // 事件:57 -(触发所属方)建筑不再存在
) {
SpawnTeamType(...); // 行为 7 - 援军小队
}
};
我们说过,SPDT 开关中,但凡有一路输出连通,另一路就断开。那么同样地,用相反的逻辑去实现敌军这一路:
if (CapturedBy(Enemy))
{
Phobos_VarEdit(
varidx,
2, // 2 代表累减(-=)
1, // 即 varidx -= 1。
false
);
DisableTrigger(enemySpawn); // 行为 54 - 禁止触发
}
主体部分完成之后,我们需要实现 SPDT 开关的电路特性:玩家线禁止自己、允许敌军线;同样敌军线也禁止自己,允许玩家线。 由于玩家线、敌军线、敌军刷兵波次均为重复触发,SPDT 的电路特性正好可以防止上述触发意外执行多次。
最后,由于假设玩家抢先,这里需要对敌军线勾上「禁止触发」;玩家线则无需禁止,自然等待玩家的肉人进入中立电厂即可。 此外,刷兵的触发也需要禁止,以免过早地刷出来。
IV. 补充:多路控制实现随机选取
除了单一的刷兵之外,你也可以结合 Phobos 的扩展局部变量做多路控制:
// enemySpawn
if (
RandomTimeElapsed(191) // 事件:51 - 随机流逝时间(范围是入参的 50% 到 150%)
&& HouseWithoutBuilding(idxCAPOWR) // 事件:57 -(触发所属方)建筑不再存在
&& switchVarIdx == 0 // 可以用原版的事件 37,或者 Phobos 的事件 502
) {
Phobos_GenRandInt(switchVarIdx, 1, 2048, false); // 行为:502 - 给变量赋随机数(含最值)
}
// enemySpawn 1. paradrop
if (switchVarIdx > 0 && switchVarIdx <= 1024) { // Phobos 事件 500 与 504
SpawnTeamType(...);
switchVarIdx = 0;
}
// enemySpawn 2. ground
if (switchVarIdx > 1024 && switchVarIdx <= 2048) { // 即 1024 < switchVarIdx <= 2048.
SpawnTeamType(...);
switchVarIdx = 0;
}
当然纯 Ares 可能就得借助随机流逝时间了,而那种方式没办法做到精确控制。
V. 后记
这玩意其实在原版「尤里的复仇」盟军 03 中已经现有框架了。我不过结合新的平台功能再做扩展,完善这个模型罢了。毕竟我这个人喜欢整活。
不单单是物理,像「状态机」原本也是个数学模型,用在程序设计里倒是也有,但用在触发实现的应该不多。 总而言之,还是那句话,触发这玩意很考验逻辑思维。而通过不断地解构需求、得出思路,最终用「实现」去达成目的,这正是做触发与敲代码的乐趣。
当然,写下这篇笔记一是为了整理自己的认识,免得久而不用日渐生疏,如过眼云烟;二嘛,正所谓「抛砖引玉」。 我自知自己学艺不精,玩的都是别人早就搞出来的东西,止增笑耳。这次整理也算是个契机,让我主动了解别的程序设计模型,也算是给我长长见识。
Thanks for reading.