先介绍下连段和连携的区别。
你打中对手了,之后进行的是连段。
对手防住了,你之后进行的是连携,也就是一般所说的压制。
(1) 连段开发
你要做连段的ai,当然你自己得会这个人物的ai。
如果你对这个人物不熟悉咋办?
最好查阅相关的资料wiki,看这人物有什么连段。有些人物的readme文件里也有写连段。
如果是原创人物,没有相关连段资料,那就得自己进行连段开发。
连段开发不是瞎摸索,是有规律的。
最主要的是要知道这个人物的取消链。
所谓“知道取消链”,用俗话说就是知道“哪招后面可以接哪招”,
不过有点微妙的不同,暂时这样理解也没啥问题。
先说下什么叫取消:
A招后用B招先“取消了A的收招硬直”,然后出B招,那叫做取消。
A招后等A自然收招,再出B招,那就不算取消。
一般性人物的取消链是:
A:普通技(单发的a,b,z啥的)-特殊技(6a,6x这种)-必杀(24a,626c这种)-超杀(2624,426426之类)
B:普通技或特殊技 - 必杀或超杀
A类型的取消链就比较长,
B类型的取消链就比较短。(普通技和特殊技,必杀和超杀之间不能相互取消)
有些系统普通技中可以按轻拳(A)-中拳(B)-重拳(C)的方式取消,那也是比较常见的abc系统。
某些系统有更特殊的取消链
比如mbac这种,可以A-B-C,也可以C-B-A。但A-B-C-B-A是不行的,前面取消用过的招不能再用。(但等C完全收招以后,再出b是可行的,因为这不算取消了)
像efz在某些必杀甚至超杀后,都可以用22c强制取消,不过要消耗RF槽。
这些看看相关系统的说明就知道了。
有不少系统,在某些技后可以用跳跃取消,那就是跳取消(jump cancel)。
有些系统某些技后可以用66之类的前dash取消,那就是前冲取消(dash cancel),空中的就是空dash cancel。
其他更多的就不多说了。
一般取消链在人物目录下的readme文件里有写,有些readme里面还直接给出几个连段,那样更好。
你也可以看cmd文件里的人用指令:
举个例子
[State -1, 623A]
Type = ChangeState
Value = 1002
TriggerAll = var(59) = 0
TriggerAll = var(50) != 101
TriggerAll = StateType != A
TriggerAll = Command = "623a"
Trigger1 = (Ctrl) || (StateNo = 100)
Trigger2 = ((StateNo = [200,204]) && (MoveContact)
Trigger3 = ((StateNo = 205) && (Time > 35) && (MoveContact))
Trigger4 = ((StateNo = 400 || StateNo = 402)) && (MoveContact)
Trigger5 = ((StateNo = 250) && (Time > 22))
Trigger6 = ((StateNo = 251) && (Time > 33))
Trigger7 = ((StateNo = 252) && (Time > 35))
Trigger8 = ((StateNo = 450) && (Time > 13))
其中trigger1是立回,trigger2以下就是取消了。
挑几个说,
trigger2表示state号1002这招,可以取消stateno为200到204的招式(也就是可以接在这些招的后面),不过有个条件是movecontact。
这说明,你200-204,这几个招,必须打到对手(被防也可以),才能取消1002。挥空就无法取消,只能等招式收招。。。
但只要是击中对手,在200-204收招之前,无论何时都可以取消1002这招。
假设204全体动作有60F(约1秒),那只要击中,那60F之内无论何时都能自由取消1002。
trigger3里面stateno = 205后有个time,说明这招打中后,不是立刻就能取消1002的,要等到35F以后才能取消。挥空同样不能取消。
在trigger5里注意没有movecontact,说明不管打中对手与否,只要time>22,都可以取消1002。这种叫做“空振取消”。
如果嫌看cmd很麻烦的话,还是自己进训练模式摸索吧。
另外说点写连段的原则
一般人物为了防止无限。总是会加各种限制的。
比如浮空时,打过几下后,对手将不再被普通技击中。(但有些超杀此时可以击中,当然超杀一般是不能无限用的)
修正低于一定值后无法命中之类。
不过有些人物还是有无限的。(有些无限虽然因为修正很难打死人,但可以用来拖时间)
格斗新手来说可能很喜欢玩无限,
但在老手眼里,无限连是很无聊的东西。
因为这就相当于把包含立回,压制,充满各种攻防转换的格斗游戏变成了单纯的一方的连招游戏。
所以请写ai的各位自重,尽量不要写无限连。
如果还是想写,也必须加个var做开关(或者与ai等级挂钩也可以),能让人自由开启或关闭无限。
发布的时候请在readme里面注明。
消耗低,难度低的10割连也请这样处理。
除非你是想做凶狂级别的人物。否则最好这么做,免得自己写出的ai被人喷。
(2)正式连段教程
A 取消式连段
通过取消形成的连段。这是连段的最基本形式。
还是来看例子吧。
首先还是看cmd里的人用指令。假设你想在某招后连623a
[State -1, 623A]
Type = ChangeState
Value = 1002
TriggerAll = var(59) = 0
TriggerAll = var(50) != 101
TriggerAll = StateType != A
TriggerAll = Command = "623a"
Trigger1 = (Ctrl) || (StateNo = 100)
Trigger2 = ((StateNo = [200,204]) && (MoveContact)
Trigger3 = ((StateNo = 205) && (Time > 35) && (MoveContact))
Trigger4 = ((StateNo = 400 || StateNo = 402)) && (MoveContact)
Trigger5 = ((StateNo = 250) && (Time > 22))
Trigger6 = ((StateNo = 251) && (Time > 33))
Trigger7 = ((StateNo = 252) && (Time > 35))
Trigger8 = StateNo = 450
这是人用指令
不看trigger1,后面的trigger告诉你,这么多招后都是可以接1002的。
但我们不需要写那么多。也不能把这些代码都原封不动照搬到ai上去。
为什么呢?
我们要对这些招具体分析
假设你写的是一个abc系统的连招,
假设上面那些trigger告诉你的是,a后面可以接这623a(1022这招),b后面也可以,c后也可以连,
2a,2b,2c以及其他某些招后也都可以
但我们不可能让a后就直接连那个623a的吧?
原因1:a后面的硬直可能连不上623a
原因2:就算能连上,伤害并非最优的。a b c 623a这样伤害更高。
所以我们只要就写c或2c后连623a就行了
然后在下面再写a后连b(或2b),b后连c(或2c)的代码。
由于我们是写连段,所以应该把movecontact改成movehit。确认命中后再接623a。
当然如果623a被防后仍然有利的话,那保持movecontact不改也可以,否则,一定要改成movehit。
不是所有招既可以用来连段,也能用来连携的。
接着注意trigger5以后。由于是空振取消,你如果不加movehit,那即使stateno = 250那招挥空了,也会取消623a,可能会造成很大的破绽。
不过由于是空振的属性,也可以这么用,250挥空时如果对手想抓住这个破绽进攻,可以取消623a,凹掉对手的攻击(当然这个623a得有无敌时间)
如果251或252后接623a没有价值(比如修正太低,没伤害,或者根本接不上),那我们只需要写空振后防止对手进攻的代码就行了。
假设450这招是个多段技,可以看出这招全程都可以取消623a。
我们当然希望要让这个多段技所有段数打满再接623a,而不是第1下就接623a,这时就不要直接写movehit
而是写一个time >= 39(time = 39也可以,不过用>=保险点) ,假设39F以后才是打满,之后再写movehit。
这种也算是延迟取消
PS:其实可以写成movehit >= 39。
本来就可以用movehit = X或[a,b]这种形式来指定这招击中后的时间。
不过个人感觉还是写time >= 39保险点。
[State -1, 623A]
Type = ChangeState
Value = 1002
TriggerAll = var(59) > 0 ;ai启动时,这样就与人用指令区别开来了。
TriggerAll = var(50) != 101 ;triggerall里除了command和ai开关以外的东西必须照抄,即使你不知道这是啥意思,这是为了防止ai做出系统不允许做出的动作。
TriggerAll = StateType != A
Trigger1 = (Ctrl || StateNo = 100) && p2movetype !=H ;加个p2movetype != H是为了防止影响某些非取消性连段。其实最好把立回指令写在别的地方。
trigger2 = stateno = 202 || stateno = 402) && movehit ;假设这2招就是c和2c的stateno,在这2招后接623a
Trigger3 = StateNo = 250 && Time > 22 && (movehit || p2movetype = A) ;要么接上,要么对手此时在进攻,对手不进攻就不取消,否则破绽大(注意movehit的时候,p2movetype必定为H,而不是A)
Trigger4 = StateNo = 251 && Time > 33 && p2movetype = A
Trigger5 = StateNo = 252 && Time > 35 && p2movetype = A ;这2招无连段价值,只需要写提防对手进攻的代码就行了
Trigger6 = StateNo = 450
trigger6 = (Time >= 39 && movehit) || p2movetype = A ;打中了当然要等这招打满再接623a,没打中的话只要对手进攻,就得立刻反击,否则来不及。
然后下面再写a接b或2b,b接c或2c的指令
[State -1, c]
type = changestate
value = 202
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype !=A
triggerall = p2statetype !=C ;假设c打不到蹲下的人
trigger1 = stateno = 201 || stateno = 401 ;接在b或者2b的后面
trigger1 = movecontact && random < 500 ;给连段加点变化,b后面可以接2c也可以接c。用movecontact表明即使b被防,取消c也没啥关系,不用确认命中。
[State -1, 2c]
type = changestate
value = 402
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype !=A
trigger1 = stateno = 201 || stateno = 401
trigger1 = movecontact ;这里不加random,就能保证在没接c的情况下一定接上2c,因为代码是自上而下读取执行的。
[State -1, b]
type = changestate
value = 201
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype !=A
triggerall = p2bodydist x < 50 ;这b比较短,必须距离比较近才能接上
trigger1 = stateno = 200 || stateno = 400 ;接在a或2a后面
trigger1 = movecontact && random < 600 ;由于伤害稍高,可以提高几率
[State -1, 2b]
type = changestate
value = 401
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype !=A
trigger1 = stateno = 200 || stateno = 400
trigger1 = movecontact
a的指令在立回指令里,a前面自然没啥东西好连的。
这样a/2a - b/2b - c/2c - 623a的一套连段就完成了
一般写连段推荐把取消链等级比较高的先写(比如超杀,必杀),等级比较低(轻拳啥的)写在后面。
但有时候必须反过来。比如想让2aaa多点几次再连b,那么就得把2a写在b前面,否则2a一次后直接连b了。
不过要加点限制,不然由于系统是自上而下执行指令,会造成一直点2a而不接b
比如这样
[state -1, c]
略
[state -1, 2a]
type = changestate
value = 400
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype !=A
trigger1 = ctrl && p2movetype !=H && !inguarddist ;立回用
trigger2 = stateno = 400 && movecontact && (p2bodydist x = [0,30]) ;后面的距离限制要用括号括起来,否则会出错。没距离限制会一直点2a不接b,直到距离不够挥空为止。
[state -1, b]
略
用跳取消空连的话也差不多,比如写一个2abc 5c jbc jbc (j是跳的意思)
前面就按地面取消那样写,5c后那个跳取消就这样。
[state -1, varset]
triggerall = var(59) > 0 && roundstate = 2
trigger1 = p2movetype = A && p2movetype = H
trigger1 = p2dist x >= 0
sysvar(1) = 1 ;这里前面教程里讲过,保证连段里必须往前跳。
[state -1, jump cancel]
type = changestate
value = 40
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype != A ;注意stateno 40是起跳瞬间,此时人还在地上。
trigger1 = stateno = 202 && movehit && p2statetype = A ;对手在空中,对手不在空中跳了会连不上
[state -1, jb]
type = changestate
value = 601
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype = A ; 这里自己在空中了
trigger1 = p2bodydist x = [0,40]
trigger1 = p2bodydist y = [-40,20] ;限定距离,别刚跳上去,对手还离得远远的就出jb了,看jb的判定自己调整
trigger1 = ctrl && p2movetype = H && p2statetype = A && (p2stateno != [120,159]) ;跳取消后处于ctrl态,对手是空中受击态,且不在防御,其实这里算是空中目押。
[state -1, jc]
type = changestate
value = 602
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype = A
trigger1 = stateno = 601 && movehit ;一般jb打中后必定能接上jc的话,就不用限制距离了
[state -1, air jump cancel]
type = changestate
value = 45 ;空中跳的state
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype = A
triggerall = var(6) <= 1 ;一般都有个限定空中跳取消次数的变量,否则会让你一直jbc jbc突破天际的- =
trigger1 = stateno = 602 && movehit
然后jb,jc就不必写了。
因为第2次jbc和第1次的条件没啥2样。
其他的取消式连段也是类似情况。多看看别人写的ai会更好的理解。
B 非取消式连段
不是取消的话,就是得等某招完全收招后再连。
分2种情况,一种是最速,一种是目押。
这里的目押和一般格斗意义上的不同,就是泛指非取消式的连段。
(一般格斗意义里严格的目押还有时间限制的意义,必须在几F内,不能早也不能晚。不过对ai来说,目押的难度不在于时间的长短。)
非取消连段里的最速也算包含在目押内,但情况比较特殊,所以分开讲。
1) 最速
就是在某一招收招后,立刻出另一招
2种做法
一种是利用animtime = 0,一种是利用prevstateno。
还是例子
[state -1, 26a]
type = changestate
value = 1000
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype != A
trigger1 = stateno = 1201 && movehit && animtime = 0
trigger2 = prevstateno = 1201 && ctrl
trigger2 = p2movetype = H && p2statetype = A
这2种都是表示在state号1201的招后最速接26a(stateno 1000)。其实2个trigger只要用1个就行了。
这2者有些微妙的区别。
trigger1表示此时动作的state号还是为1201,不过已经进行到最后1F了,但此时并非ctrl态,而且可以用movehit确认1201是否命中。
trigger2表示前面那招是1201,此时处于ctrl态,动作号已经不是1201了,一般此时动作号是0,此时不能用movehit确认命中情况了,必须用其他方法确认。
严格来说trigger1比trigger2快一点点,但trigger2是人类能够做到的,trigger1是不可能的,因为要操作角色必须在ctrl态。
不过一般2者看不出啥区别。我个人推荐最好用trigger2的形式,有时用trigger1的形式会出现点bug。
不过trigger1能确认movehit和调整连段确率,有时更方便。
PS:trigger2那种形式的也不一定是最速,因为mugen自带ai的干扰,有小概率会做出下蹲,跳跃等动作,这样会导致连段失败。
此时可以考虑用trigger1那种形式。或者对特定状态下的下蹲和跳跃做限制。
再说个prevstateno的用法
假设A接B后可以接C,但D接B后只能接E,
那在stateno = B命中的时候,可以用prevstateno判断前面那招到底是A还是D。
比如这样
[state -3]
type = changestate
value = 202
triggerall = var(59) && roundstate = 2
trigger1 = prevstateno = 200
trigger1 = stateno = 201 && movehit
[state -3]
type = changestate
value = 203
triggerall = var(59) && roundstate = 2
trigger1 = prevstateno = 205
trigger1 = stateno = 201 && movehit
2)目押
和最速的不同在于不能立刻出另一招,必须得在某些条件下才出招
比如必须让对手自由下落到某一个高度再接某招:
[state -1, b]
type = changestate
value = 201
triggerall = var(59) > 0 && roundstate = 2
triggerall = statetype != A
trigger1 = prevstateno = 1300 && ctrl
trigger1 = p2movetype = H && p2statetype = A
trigger1 = p2stateno != [120,159]
trigger1 = enemynear, vel y > 0 && enemynear, pos Y >= -80 ;前面是表示当对手下落速度大于0时(小于0就是上升),确认其处于下落阶段,后面是确定对手的绝对高度(由于statetype != A,所以可以不用p2dist y这种相对高度来测量)
这是比较基本的目押,一般要求不高就这样好了。
如果要提高精度的话,可以把速度计算进去。不过这样就很麻烦了,需要计算,下落不是恒速的还要算加速度。如果不是要求特别高不必这样。
还有些就像空连后最后控制高度再出招,使得j攻击后能连上地面普通技,这也是一种目押。
具体的看其他人写的ai吧。
一些复杂系统的连招
这里没啥实质性内容。只是吐点槽。
如bbb,北斗,ggxx,efz,墙角心等格斗的连招是很复杂的,一套没10几,20几hit下不来。
要把连招做得好一点,不是靠简单的取消就可以完成的的。
要靠各种var或fvar来控制。
有些连段,看上去好像很民工,实际上无论自己试过就知道完全不是那回事。
很多连段起跳后不能立刻接j攻击,必须压低高度目押出j攻击,为了之后连招方便。
有时为了提高伤害,jb后必须延迟取消jc,这样落地后才能接上地面招增加伤害。
而地面接招必须要计算高度,要精确目押的话还要计算重力加速度。
连超杀还得考虑修正因素,否则接了超杀没伤害反而浪费气。
还必须有var限制起跳和空dash次数。
还得考虑连段后的起攻情况
墙角心和北斗也可以看出空连时有延迟的情况。
所以实际编起ai来很复杂,很辛苦。新手的话不推荐编这种连段比较复杂的系统的ai。
实在有爱的话,请做好觉悟。最好从已经有的ai改起。
要让ai乖乖听话可并不是很容易的事情,有一个变量没搞好就会经常出状况。
这里说点原则
1 尽量用人物已经有的变量来控制连段,比如修正,hit数(用enemynear,gethitvar(hitcount)来读取)之类。
实在不行才用自己设置的变量。而且这个变量不能与已有变量冲突。
2 用自己设置的变量也最好做到用一个变量对应几个连段,而不是用多个变量对应多个连段
比如用一个fvar(20)就可以对应很多情况了
[state -3]
type = varset
trigger1 = 条件1
fv = 20
value = 0.1 ;fvar最好用这种方式定义,不要直接写fvar(20) = 0.1,否则容易出错
[state -3]
type = varset
trigger1 = 条件2
fv = 20
value = 1.5
。。。。。。
3 一般人物可以用var(0)-var(59),fvar(0)-fvar(39)共60个var,40个fvar
变量如果用完了可以定义一个helper
helper自己的var(0),fvar(0)等与本体的var不冲突。
比如这样
[State -3,ai helper]
Type = Helper
Trigger1 = NumHelper(7777) = 0
Name = "AI helper"
StateNo = 7777
ID = 7777
PosType = P1
Pos = 0, 0
Ownpal = 1
IgnoreHitPause = 1
SuperMoveTime = 99999
PauseMoveTime = 99999
SprPriority = 7
注意stateno和id不要与已有的helper冲突。
然后在ai文件里新建个
[Statedef 7777]
Anim = 9999
Ctrl = 0
再在下面定义变量即可
[State 7777,fv26]
Type = VarSet
TriggerAll = Parent,Var(59)>0
trigger1 = Parent,stateno = 1020 ;对于本体的各种数据必须加上parent前缀,是helper(7777)自己的变量或stateno就不要加前缀了
trigger1 = enemynear,movetype = H && enemynear,statetype = A
trigger1 = enemynear,stateno != [120,159]
Fv = 26
value = 1
IgnoreHitPause = 1
这里就定义了一个helper(7777)的fvar(26)
在ai里用的时候就这样写
trigger1 = helper(7777),fvar(26) = 1 ;一定不能忘记helper(7777)的前缀,否则系统会判定为本体的fvar(26)
(3)连段的测试
编了一套连段总得做测试。但直接进watch模式看ai对战,效率实在太低,不一定正好有机会能用到你编写的连段。
这里推荐一个小技巧,就是利用ai开关。
比如你要测试2abc 5c jbc jbc的空连。
前面的取消基本没啥问题,关键是看5c(立c)打中空中的敌人后,ai是不是会好好的空连。
[State -3, ai switch]
Type = VarSet
triggerall = Var(59) = 0
trigger1 = Command = "AI00" || Command = "AI01" || Command = "AI02"
trigger2 = Command = "AI03" || Command = "AI04" || Command = "AI05"
trigger3 = Command = "AI06" || Command = "AI07" || Command = "AI08"
trigger4 = Command = "AI09" || Command = "AI10" || Command = "AI11"
trigger5 = Command = "AI12" || Command = "AI13" || Command = "AI14"
trigger6 = Command = "AI15" || Command = "AI16" || Command = "AI17"
trigger7 = Command = "AI18" || Command = "AI19" || Command = "AI20"
trigger8 = Command = "AI21" || Command = "AI22" || Command = "AI23"
trigger9 = Command = "AI24" || Command = "AI25" || Command = "AI26"
trigger10 = Command = "AI27" || Command = "AI28" || Command = "AI29"
trigger11 = Command = "AI30" || Command = "AI31" || Command = "AI32"
trigger12 = Command = "AI33" || Command = "AI34" || Command = "AI35"
trigger13 = Command = "AI36" || Command = "AI37" || Command = "AI38"
trigger14 = Command = "AI39" || Command = "AI40" || Command = "AI41"
trigger15 = Command = "AI42" || Command = "AI43" || Command = "AI44"
trigger16 = Command = "AI45" || Command = "AI46" || Command = "AI47"
trigger17 = Command = "AI48" || Command = "AI49" || Command = "AI50"
trigger18 = Command = "AI51" || Command = "AI52" || Command = "AI53"
trigger19 = Command = "AI54" || Command = "AI55" || Command = "AI56"
trigger20 = Command = "AI57" || Command = "AI58" || Command = "AI59"
trigger21 = Command = "AI60"
trigger22 = stateno = 202 ;加上这条trigger,假设202就是5c的stateno
然后进入训练模式
自己连2abc 5c,当你出完5c后,ai就接管控制权了,你自己就无法继续控制了。这样就知道ai是否能顺利完成之后的空连了。
之后的预定
连携与起攻
helper与var
各种小技巧与问题等等
以上教程由net教授撰写