终于真正的开始写AI了。从第8章开始到第13章,都是关于AI的编写。
从这章开始我们正式接触人物里面的代码。代码比较长可能比较难理解,建议一段一段的读,不要被长度给吓到,多壮观的建筑都
是一砖一瓦搭成的,无论多长的代码也是如此,把每段代码逐个击破合起来就是你想要的答案。
首先我们先从各个人物几乎都有的状态开始入手,也就是公用状态。
=========================================================================
8.1公用状态与Stcommon简介
=========================================================================
---------------------------------------------------------------------------
公用状态是一些各个人物几乎都有的状态,而这些状态的状态号基本上也是固定的。
例如站姿0,蹲姿10~12,行走20,起跳40,跑/前冲100,后撤105,防御120~155,倒地5110,起身5120等等。
Stcommon是一个存放公用状态的特殊st文件。
如果其他st文件没有重复定义Stcommon里面的状态,人物将会使用Stcommon里面定义的状态。
Mugen在data文件夹里面附有默认的common1.cns,如果人物没有自带Stcommon文件,那么人物会调用这个common1.cns。
因此,如果人物自带Stcommon文件,那么我们就直接对那个文件进行修改;如果人物没有自带Stcommon文件的话,就要在人物文件
夹内自己新建/复制一个Stcommon文件,并把Def文件内的Stcommon路径改成你文件的路径。
打开common1.cns后我们可以发现,其实Stcommon和一般的st文件没有太多不同。
里面很多Sctrl其实是不需要管的,如第5章所说,我们只需要看会被command影响的某些Sctrl。
一般来说这些涉及的内容包括:移动(行走、跑、前冲、后撤、跳跃)、受身及防御。
=========================================================================
8.2行走
=========================================================================
---------------------------------------------------------------------------
行走是格斗游戏最为常见的移动方式之一。
虽然移动速度比较慢,但是全程都是属于可控状态,可以随时应对各种情况,是最安全的移动方式。
默认的common1.cns中代码如下:
;---------------------------------------------------------------------------
; Walk
[Statedef 20]
type = S
physics = S
sprpriority = 0
[State 20, 1]
type = VelSet
trigger1 = command = "holdfwd"
x = const(velocity.walk.fwd.x)
[State 20, 2]
type = VelSet
trigger1 = command = "holdback"
x = const(velocity.walk.back.x)
[State 20, 3]
type = ChangeAnim
triggerall = vel x > 0
trigger1 = Anim != 20 && Anim != 5
trigger2 = Anim = 5 && AnimTime = 0
value = 20
[State 20, 4]
type = ChangeAnim
triggerall = vel x < 0
trigger1 = Anim != 21 && Anim != 5
trigger2 = Anim = 5 && AnimTime = 0
value = 21
如代码所示,默认的行走分为向前走和向后走,这两个方向的行走用的是同一个状态号20。
这个状态会根据手操按键的不同(也就是前和后)而切换为对应的动画(动画20和21)和速度。
然而这样一个状态包含两个不同属性的动作的情况是相当不利于AI编写的。
因为这样不但要在Statedef -1/-3里面加入对应的trigger使其动起来,还要在stcommon文件里面区分前后两种情况,
分开两个地方去管理一类型动作。这么做相当麻烦还容易出错,所以我们要对其进行一定的调整。
比较常用的方法是新建一个(或者一些)状态来区分不同的动作,以后写状态切换的时候就会简单不少。
实例如下:
;下面两段放到[Statedef 20]附近
;---------------------------------------------------------------------------
; Walk B 向后走
[Statedef 19]
type = S
physics = S
sprpriority = 0
[State 20, 2]
type = VelSet
trigger1 = 1
x = const(velocity.walk.back.x)
[State 20, 4]
type = ChangeAnim
trigger1 = Anim != 21 && Anim != 5
trigger2 = Anim = 5 && AnimTime = 0
value = 21
;---------------------------------------------------------------------------
; Walk F 向前走
[Statedef 21]
type = S
physics = S
sprpriority = 0
[State 20, 1]
type = VelSet
trigger1 = 1
x = const(velocity.walk.fwd.x)
[State 20, 3]
type = ChangeAnim
trigger1 = Anim != 20 && Anim != 5
trigger2 = Anim = 5 && AnimTime = 0
value = 20
之后写上让人物行走的Changestate就可以了:
;下面一段放到[Statedef -1]或[Statedef -3]下面
;---------------------------------------------------------------------------
[State -1, 向前走]
type = ChangeState
value = 21
Triggerall = Var(59) > 0;AI开启时
Triggerall = Roundstate = 2;在格斗阶段
Triggerall = Statetype != A;不在空中
Triggerall = Frontedgebodydist >= 10;不在画面边缘
Triggerall = Stateno != 20 && Stateno != 100;不是步行状态和跑步状态
Triggerall = ctrl;可控时
Trigger1 = P2bodydist X > 135 && Random < P2bodydist X * 2 - 250;Trigger1~4根据距离不同分段改变概率
Trigger2 = P2bodydist X > 100 && Random < P2bodydist X
Trigger3 = P2bodydist X > 50 && Random < 100
Trigger4 = P2bodydist X > 0 && Random < 25
;---------------------------------------------------------------------------
[State -1, 向后走]
type = ChangeState
value = 19
Triggerall = Var(59) > 0
Triggerall = Roundstate = 2
Triggerall = Statetype != A
Triggerall = Backedgebodydist >= 10
Triggerall = !inguarddist;非防御距离,因为防御距离内按后会变成防御而不是向后走
Triggerall = ctrl
Trigger1 = P2bodydist X < 75 && Random > 950 + P2bodydist X / 2;近距离低几率触发
之后人物就会根据不同的距离以不同的概率随机前后行走了。
=========================================================================
8.3跑/前冲/后撤
=========================================================================
---------------------------------------------------------------------------
跑/前冲也是格斗游戏非常常见的移动方式。
虽然移动速度比较快,但是跟行走对比起来相对不灵活,有一定的风险。
然而较快的速度令它在抢硬直、连段目押等需要快速移动的情况大展身手。
默认的common1.cns中代码如下:
;---------------------------------------------------------------------------
; Run forward
[Statedef 100]
type = S
physics = S
anim = 100
sprpriority = 1
[State 100, 1]
type = VelSet
trigger1 = 1
x = const(velocity.run.fwd.x)
[State 100, 2] ;Prevent run from canceling into walk
type = AssertSpecial
trigger1 = 1
flag = NoWalk
[State 100, 3] ;Prevent from turning
type = AssertSpecial
trigger1 = 1
flag = NoAutoTurn
[State 100, 4]
type = ChangeState
trigger1 = command != "holdfwd"
value = 0
如代码所示,默认的是跑,是可控的匀速前进,并且可以随时停下。
然而,很多格斗游戏里面的跑并不是这么简单的,通常是由起步、匀速运动、停止之类的部分组成。
因为起步和停止都是有一定硬直的动作,如果在较短的距离开始跑,对方一旦使用攻击,你将没法停下来进行防御。
和行走对比起来,这类型的跑使用起来需要考虑更多问题。
虽然跑并没有行走那样有两个方向的烦恼,但是跑的启动和停止是分开控制的,并不是像行走那样不需要变为State 0的Sctrl。
所以除了启动我们还要改写Statedef 100下的停止条件:
;---------------------------------------------------------------------------
; Run forward
[Statedef 100]
...
[State 100, 4]
type = ChangeState
trigger1 = Var(59) = 0;AI未开启时
trigger1 = command != "holdfwd";和原来一样接受手操指令
trigger2 = Var(59) > 0
trigger2 = EnemyNear,movetype != H || (EnemyNear,movetype = H && p2dist x < 0);对方不在受击状态或者对方在受击状态但跑过头了
trigger2 = p2bodydist x <= (30+random/40) || Frontedgebodydist <= 10;比较近的距离或者画面边缘
trigger3 = Var(59) > 0
trigger3 = Roundstate != 2;防止非战斗阶段时一直跑根本停不下来
value = 0
之后写上让人物跑的Changestate就可以了:
;下面一段放到[Statedef -1]或[Statedef -3]下面
;---------------------------------------------------------------------------
[State -1, 前跑]
type = ChangeState
value = 100
Triggerall = Var(59) > 0
Triggerall = Roundstate = 2
Triggerall = Statetype != A
Triggerall = Frontedgebodydist >= 20;避免在画面边缘开始跑
Triggerall = ctrl
Trigger1 = P2bodydist X > 200 && Random < P2bodydist X*5 - 720;Trigger1~4根据距离不同分段改变概率
Trigger2 = P2bodydist X > 135 && Random < P2bodydist X*2 - 120
Trigger3 = P2bodydist X > 90 && Random < P2bodydist X*1.75 - 90
Trigger4 = (P2bodydist X = [45,90]) && Random < P2bodydist X *1.2 - 45
---------------------------------------------------------------------------
虽然Mugen内默认的状态100是跑,但一些游戏当中双击前方向键(66)并不一定是跑,还有可能是前冲(冲刺/Dash)。
前冲很有可能是长时间硬直的向前移动,若乱使用可以说破绽百出,使用时比起跑要更小心,否则就是给对方送硬直。
后撤默认使用的是状态105,是双击后方向键(44)所发出来的。
在Mugen中,后撤默认为空中状态,也就是启动的瞬间就在空中,所以可以利用这个特性躲避投技。
而在不少格斗游戏作品当中,后撤会有短暂无敌的性能,我们也可以利用这性能对对方攻击进行闪避。
因为默认并没有前冲,所以在此只给出后撤的例子:
;下面一段放到[Statedef -1]或[Statedef -3]下面
;---------------------------------------------------------------------------
[State -1, 后撤]
type = ChangeState
value = 105
Triggerall = Var(59) > 0
Triggerall = Roundstate = 2
Triggerall = Statetype != A
Triggerall = (Random < Var(59)*200) || (Var(59) > 3)
Triggerall = ctrl
Trigger1 = EnemyNear,Movetype = A && EnemyNear,Stateno >= 200;对方攻击状态
Trigger1 = facing != EnemyNear,facing;双方面对面
Trigger1 = EnemyNear,hitdefattr = SC,AT;对手攻击带投技判定
Trigger1 = P2bodydist X = [-20,100];一定距离内
=========================================================================
8.4跳跃
=========================================================================
---------------------------------------------------------------------------
跳跃同样是格斗游戏常见的移动方式。
因为跳跃能令攻击方式多样化(上下择和落地投等)而且有着相当高的回报,在手操里有着很重要的地位。
同时还可以躲避投技和远处飞行道具,并获得先机。
但是先不说mugen当中空防并不能防御下段攻击和一大堆无敌升龙,通常来说人物在空中只能按照一定的轨迹移动,
而且多数格斗游戏中人物在地面的性能比空中要好,跳跃也需要好好考虑位置和时机才能在AI战里面使用。
默认的common1.cns中代码如下:
;---------------------------------------------------------------------------
; Jump Start
[Statedef 40]
type = S
physics = S
anim = 40
ctrl = 0
sprpriority = 1
facep2 = 1
[State 40, 1]
type = VarSet
trigger1 = Time = 0
sysvar(1) = 0
[State 40, 2]
type = VarSet
trigger1 = command = "holdfwd"
sysvar(1) = 1
[State 40, 3]
type = VarSet
trigger1 = command = "holdback"
sysvar(1) = -1
[State 40, 4]
type = VelSet
trigger1 = AnimTime = 0
x = ifelse(sysvar(1)=0, const(velocity.jump.neu.x), ifelse(sysvar(1)=1, const(velocity.jump.fwd.x), const(velocity.jump.back.x)))
y = const(velocity.jump.y)
[State 40, 5]
type = VelSet
trigger1 = AnimTime = 0
trigger1 = prevstateno = 100 ;RUN_FWD
trigger1 = sysvar(1) = 1
x = const(velocity.runjump.fwd.x)
[State 40, 6]
type = ChangeState
trigger1 = AnimTime = 0
value = 50
ctrl = 1
如代码所示,和行走类似,默认的跳跃分为前跳、后跳、原地跳和跑跳,用的是同一个状态号40。
也是通过按键不同区分开不同的方向和速度。
而在某些有大小跳的作品里面,跳跃的分类会更多。
所以我们会采用和行走一样的办法来处理。
实例如下:
;---------------------------------------------------------------------------
;Statedef 39后跳 41前跳 42原地跳
[Statedef 39]
type = S
physics = S
anim = 40
ctrl = 0
sprpriority = 1
[State 40, 1]
type = VarSet
trigger1 = 1
sysvar(1) = -1
[State 40, 4]
type = VelSet
trigger1 = AnimTime = 0
x = const(velocity.jump.back.x)
y = const(velocity.jump.y)
[State 40, 6]
type = ChangeState
trigger1 = AnimTime = 0
value = 50
ctrl = 1
;---------------------------------------------------------------------------
[Statedef 41]
type = S
physics = S
anim = 40
ctrl = 0
sprpriority = 1
[State 40, 1]
type = VarSet
trigger1 = 1
sysvar(1) = 1
[State 40, 4]
type = VelSet
trigger1 = AnimTime = 0
x = const(velocity.jump.fwd.x)
y = const(velocity.jump.y)
[State 40, 5]
type = VelSet
trigger1 = AnimTime = 0
trigger1 = prevstateno = 100 ;RUN_FWD
trigger1 = sysvar(1) = 1
x = const(velocity.runjump.fwd.x)
[State 40, 6]
type = ChangeState
trigger1 = AnimTime = 0
value = 50
ctrl = 1
;---------------------------------------------------------------------------
[Statedef 42]
type = S
physics = S
anim = 40
ctrl = 0
sprpriority = 1
[State 40, 1]
type = VarSet
trigger1 = 1
sysvar(1) = 0
[State 40, 4]
type = VelSet
trigger1 = AnimTime = 0
x = const(velocity.jump.neu.x)
y = const(velocity.jump.y)
[State 40, 6]
type = ChangeState
trigger1 = AnimTime = 0
value = 50
ctrl = 1
===========================================================================
8.5受身
===========================================================================
---------------------------------------------------------------------------
受身是指受击身体恢复,通常是空中或者低空受击硬直后,或者倒地时可以更快的恢复成为可控状态。
更快的恢复可控状态可以用于躲避对方的伪连(指中途可被受身回避的连段),或者更容易作出反击。
默认的common1.cns中代码如下(此处因为WinMugen版本写的比较简单容易理解,我用WinMugen版本的代码进行讲解):
;---------------------------------------------------------------------------
; HITA_FALL (knocked up, falling)
[Statedef 5050]
type = A
movetype= H
physics = N
...
[State 5050, 4] ;Recover near ground
type = ChangeState
triggerall = Vel Y > 0
triggerall = Pos Y >= -20
triggerall = alive
triggerall = CanRecover
trigger1 = Command = "recovery"
value = 5200 ;HITFALL_RECOVER
[State 5050, 5]; Recover in mid air
type = ChangeState
triggerall = Vel Y > -1
triggerall = alive
triggerall = CanRecover
trigger1 = Command = "recovery"
value = 5210 ;HITFALL_AIRRECOVER
...
---------------------------------------------------------------------------
; HIT_FALLRECOVER (still falling)
[Statedef 5200]
type = A
movetype= H
physics = N
[State 5200, 1] ;Change anim if done with transition
type = ChangeAnim
trigger1 = Anim = 5035
trigger1 = AnimTime = 0
value = 5050
[State 5200, 2]
type = VelAdd
trigger1 = 1
y = GetHitVar(yaccel)
[State 5200, 3]
type = SelfState
trigger1 = Vel Y > 0
trigger1 = Pos Y >= 10
value = 5201
;---------------------------------------------------------------------------
; HIT_FALLRECOVER (on the ground)
[Statedef 5201]
type = A
movetype= H
physics = A
anim = 5200
[State 5201, 1] ;Turn if not facing opponent
type = Turn
trigger1 = Time = 0
trigger1 = p2dist X < -5
[State 5201, 2]
type = VelSet
trigger1 = Time = 0
x = -.15
y = -3.5
[State 5201, 3]
type = PosSet
trigger1 = Time = 0
y = 0
[State 5201, 4]
type = NotHitBy
trigger1 = 1
value = SCA
time = 1
[State 5201, 5] ;Blink white
type = PalFX
trigger1 = Time = 0
time = 3
add = 128,128,128 ;256,256,256
[State 5201, 6]
type = GameMakeAnim
trigger1 = Time = 1
value = 60
pos = 0, 0
under = 1
;---------------------------------------------------------------------------
; HIT_AIRFALLRECOVER
[Statedef 5210]
type = A
movetype= I
physics = N
anim = 5210
ctrl = 0
[State 5210, 1] ;Blink white
type = PalFX
trigger1 = Time = 0
time = 3
add = 128,128,128 ;256,256,256
[State 5210, 1]
type = PosFreeze
trigger1 = Time = 0
value = 4
[State 5210, 2] ;Turn if not facing opponent
type = Turn
trigger1 = Time = 0
trigger1 = p2dist X < -20
[State 5210, 1]
type = VelMul
trigger1 = Time = 4
x = .8
y = .8
[State 5210, 1]
type = VelAdd
trigger1 = Time = 4
y = -4.5
[State 5210, 1]
type = VelMul
trigger1 = Time = 4
trigger1 = Vel Y > 0
y = .5
[State 5210, 1]
type = VelAdd
trigger1 = Time = 4
trigger1 = Vel Y > -3
y = -2
[State 5210, 1]
type = VelAdd
trigger1 = Time = 4
trigger1 = Vel Y > -2
y = -1
[State 5210, 2] ;Go up
type = VelAdd
trigger1 = Time = 4
trigger1 = Command = "holdup"
y = -2
[State 5210, 2] ;Go down
type = VelAdd
trigger1 = Time = 4
trigger1 = Command = "holddown"
y = 1.5
[State 5210, 2] ;Go fwd
type = VelMul
trigger1 = Time = 4
trigger1 = Command = "holdfwd"
x = 1
[State 5210, 2] ;Go back
type = VelAdd
trigger1 = Time = 4
trigger1 = Command = "holdback"
x = -1
[State 5210, 3]
type = NotHitBy
trigger1 = Time = 0
value = SCA
time = 15
[State 5210, 4]
type = CtrlSet
trigger1 = Time = 20
value = 1
[State 5210, 5]
type = VelAdd;Gravity
trigger1 = Time >= 4
y = .35
[State 5210, 5] ;Land on ground
type = ChangeState
trigger1 = Vel Y > 0
trigger1 = Pos Y >= 0
value = 52 ;JUMP_LAND
ctrl = 1
如代码所示,Mugen当中默认有两种受身,分别是高空受身和低空受身,分别是状态5210和5200。
其发动条件是在掉落状态5050当中处于可恢复状态时,速度和高度符合条件即可。
高空受身5210是受身后20f恢复可控,而低空受身5200是进入预备状态到达低空高度转变为状态5201。
值得一提的是,高空受身有15f的无敌,而低空受身在到达低空高度直到落地前的状态5201有全程无敌。
如果可以在对方伪连时合理利用高空受身这15f的无敌和低空受身状态5201的全程无敌躲开对方的攻击判定,
甚至之后在对方攻击硬直中反击命中对方的话,可以说是相当大的收益。
然而受身其实也是有一定的风险的。高空受身在15f无敌以后有5f的硬直,而低空受身不可控,不可切换成防御状态,
落地也有硬直,乱受身的话很容易被写了受身狩(也就是抓受身)抓住机会。
另外有些AI也会用低硬直的招式骗对方手受身,一旦对方上当形成受身狩就是一套额外的连段。
因为我们暂时还没有学出招记录等技术,没法特别精确地用受身躲避对方伪连,
所以要注意受身的几率不要太高,以免被受身狩循环致死。
实例如下:
;---------------------------------------------------------------------------
; HITA_FALL (knocked up, falling)
[Statedef 5050]
type = A
movetype= H
physics = N
...
[State 5050, 4] ;Recover near ground
type = ChangeState
triggerall = Vel Y > 0
triggerall = Pos Y >= -20
triggerall = alive
triggerall = CanRecover
trigger1 = Var(59) = 0;当手操时由手操命令控制
trigger1 = Command = "recovery"
trigger2 = Var(59) > 0
trigger2 = EnemyNear,Movetype = H || EnemyNear,Statetype = L;对方倒地或者受击
value = 5200 ;HITFALL_RECOVER
[State 5050, 5]; Recover in mid air
type = ChangeState
triggerall = Vel Y > -1
triggerall = alive
triggerall = CanRecover
trigger1 = Var(59) = 0;当手操时由手操命令控制
trigger1 = Command = "recovery"
trigger2 = Var(59) > 0
trigger2 = EnemyNear,Movetype = H || EnemyNear,Statetype = L
Trigger3 = Var(59) > 0
Trigger3 = EnemyNear,Movetype = A && EnemyNear,Stateno >= 200;对方攻击状态
Trigger3 = EnemyNear,AnimTime <= -Random/40+5;对方有一定硬直时间
Trigger3 = Random < 100;低几率触发
value = 5210 ;HITFALL_AIRRECOVER
...
;---------------------------------------------------------------------------
; HIT_AIRFALLRECOVER
[Statedef 5210]
type = A
movetype= I
physics = N
anim = 5210
ctrl = 0
...
;具体方向控制看喜好
[State 5210, 2] ;Go up
type = VelAdd
triggerall = Time = 4
trigger1 = Var(59) = 0
trigger1 = Command = "holdup"
y = -2
[State 5210, 2] ;Go down
type = VelAdd
triggerall = Time = 4
trigger1 = Var(59) = 0
trigger1 = Command = "holddown"
trigger2 = Var(59) > 0
y = 1.5
[State 5210, 2] ;Go fwd
type = VelMul
triggerall = Time = 4
trigger1 = Var(59) = 0
trigger1 = Command = "holdfwd"
trigger2 = Var(59) > 0
x = 1
[State 5210, 2] ;Go back
type = VelAdd
triggerall = Time = 4
trigger1 = Var(59) = 0
trigger1 = Command = "holdback"
x = -1
=========================================================================
8.6防御
=========================================================================
---------------------------------------------------------------------------
格斗有攻就有守,防御不必多说是格斗游戏里面很重要的一环。
防御对于攻击来说算相对简单,因为无论你怎么攻击,防御就三种:站防、蹲防、空防(有些游戏甚至没有空防)。
除了投技不可防御、破防技等特殊情况,攻击判定(打击技和飞行道具)有分为上中下三段,可以被对应的防御防住。
(PS:格斗游戏中,上段站、蹲防均可,中段不可蹲防,下段不可站防。但是Mugen当中,上段(guardflag = H)不可蹲防,
中段(guardflag = M = HL)站、蹲防均可,下段(guardflag = L)不可站防,另外guardflag = A决定是否能空防。
在本教程里统一用格斗游戏的说法,即中段不可蹲防,下段不可站防)
默认的common1.cns中代码如下:
;---------------------------------------------------------------------------
; GUARD (start)
[Statedef 120]
type = U ;Leave state type unchanged
physics = U ;Leave physics unchanged
[State 120, 1]
type = ChangeAnim
trigger1 = Time = 0
value = 120 + (statetype = C) + (statetype = A)*2;这种写法用一段代码完成了三段代码的工作,值得学习
[State 120, 2]
type = StateTypeSet
trigger1 = Time = 0 && statetype = S
physics = S
[State 120, 3]
type = StateTypeSet
trigger1 = Time = 0 && statetype = C
physics = C
[State 120, 4]
type = StateTypeSet
trigger1 = Time = 0 && statetype = A
physics = A
[State 120, Hi to Lo]
type = StateTypeSet
trigger1 = statetype = S && command = "holddown"
statetype = C
physics = C
[State 120, Lo to Hi]
type = StateTypeSet
trigger1 = statetype = C && command != "holddown"
statetype = S
physics = S
[State 120, 5]
type = ChangeState
trigger1 = AnimTime = 0
value = 130 + (statetype = C) + (statetype = A)*2
[State 120, Stop Guarding]
type = ChangeState
trigger1 = command != "holdback"
trigger2 = !inguarddist
value = 140
;---------------------------------------------------------------------------
; STAND GUARD (guarding)
[Statedef 130]
type = S
physics = S
[State 130, 1]
type = ChangeAnim
trigger1 = Anim != 130
value = 130
[State 130, Hi to Lo]
type = ChangeState
trigger1 = command = "holddown"
value = 131
[State 130, Stop Guarding]
type = ChangeState
trigger1 = command != "holdback"
trigger2 = !inguarddist
value = 140
;---------------------------------------------------------------------------
; CROUCH GUARD (guarding)
[Statedef 131]
type = C
physics = C
[State 131, 1]
type = ChangeAnim
trigger1 = Anim != 131
value = 131
[State 131, Lo to Hi]
type = ChangeState
trigger1 = command != "holddown"
value = 130
[State 131, Stop Guarding]
type = ChangeState
trigger1 = command != "holdback"
trigger2 = !inguarddist
value = 140
;---------------------------------------------------------------------------
; AIR GUARD (guarding)
[Statedef 132]
type = A
physics = N
[State 132, 1]
type = ChangeAnim
trigger1 = Anim != 132
value = 132
[State 132, 2]
type = VelAdd
trigger1 = 1
y = Const(movement.yaccel)
[State 132, 3]
type = VarSet
trigger1 = 1
sysvar(0) = (pos y >= 0) && (vel y > 0)
[State 132, 4]
type = VelSet
trigger1 = sysvar(0)
y = 0
[State 132, 5]
type = PosSet
trigger1 = sysvar(0)
y = 0
[State 132, 6]
type = ChangeState
trigger1 = sysvar(0)
trigger1 = command = "holdback"
trigger1 = inguarddist
value = 130
[State 132, 7]
type = ChangeState
trigger1 = sysvar(0)
value = 52
[State 132, Stop Guarding]
type = ChangeState
trigger1 = command != "holdback"
trigger2 = !inguarddist
value = 140
;---------------------------------------------------------------------------
; GUARD (end)
[Statedef 140]
type = U ;Leave state type unchanged
physics = U ;Leave physics unchanged
ctrl = 1
[State 140, 1]
type = ChangeAnim
trigger1 = Time = 0
value = 140 + (statetype = C) + (statetype = A)*2
[State 140, 2]
type = StateTypeSet
trigger1 = Time = 0 && statetype = S
physics = S
[State 140, 3]
type = StateTypeSet
trigger1 = Time = 0 && statetype = C
physics = C
[State 140, 4]
type = StateTypeSet
trigger1 = Time = 0 && statetype = A
physics = A
[State 140, Hi to Lo]
type = StateTypeSet
trigger1 = statetype = S && command = "holddown"
statetype = C
physics = C
[State 140, Lo to Hi]
type = StateTypeSet
trigger1 = statetype = C && command != "holddown"
statetype = S
physics = S
;[State 140, 5] ;Implemented within engine
;type = ChangeState
;trigger1 = AnimTime = 0
;value = (statetype = C)*11 + (statetype = A)*51
;---------------------------------------------------------------------------
; SGUARDHIT (shaking)
[Statedef 150]
type = S
movetype= H
physics = N
velset = 0,0
[State 150, 1]
type = ChangeAnim
trigger1 = 1
value = 150
[State 150, 2]
type = ChangeState
trigger1 = HitShakeOver
value = 151 + 2*(command = "holddown")
[State 150, Hi to Lo]
type = StateTypeSet
trigger1 = statetype = S && command = "holddown"
statetype = C
physics = C
[State 150, Lo to Hi]
type = StateTypeSet
trigger1 = statetype = C && command != "holddown"
statetype = S
physics = S
[State 150, 3]
type = ForceFeedback
trigger1 = time = 0
waveform = square
time = 3
;---------------------------------------------------------------------------
; SGUARDHIT2 (knocked back)
[Statedef 151]
type = S
movetype= H
physics = S
anim = 150
[State 151, 1]
type = HitVelSet
trigger1 = Time = 0
x = 1
[State 151, 2]
type = VelSet
trigger1 = Time = GetHitVar(slidetime)
trigger2 = HitOver
x = 0
[State 151, 3]
type = CtrlSet
trigger1 = Time = GetHitVar(ctrltime)
value = 1
[State 151, Hi to Lo]
type = StateTypeSet
trigger1 = statetype = S && command = "holddown"
statetype = C
physics = C
[State 151, Lo to Hi]
type = StateTypeSet
trigger1 = statetype = C && command != "holddown"
statetype = S
physics = S
[State 151, 4]
type = ChangeState
trigger1 = HitOver
value = 130
ctrl = 1
;---------------------------------------------------------------------------
; CGUARDHIT (shaking)
[Statedef 152]
type = C
movetype= H
physics = N
velset = 0,0
[State 152, 1]
type = ChangeAnim
trigger1 = 1
value = 151
[State 152, 3]
type = ChangeState
trigger1 = HitShakeOver
value = 151 + 2*(command = "holddown")
[State 152, Hi to Lo]
type = StateTypeSet
trigger1 = statetype = S && command = "holddown"
statetype = C
physics = C
[State 152, Lo to Hi]
type = StateTypeSet
trigger1 = statetype = C && command != "holddown"
statetype = S
physics = S
[State 152, 4]
type = ForceFeedback
trigger1 = time = 0
waveform = square
time = 4
;---------------------------------------------------------------------------
; CGUARDHIT2 (knocked back)
[Statedef 153]
type = C
movetype= H
physics = C
anim = 151
[State 153, 1]
type = HitVelSet
trigger1 = Time = 0
x = 1
[State 153, 2]
type = VelSet
trigger1 = Time = GetHitVar(slidetime)
trigger2 = HitOver
x = 0
[State 153, 3]
type = CtrlSet
trigger1 = Time = GetHitVar(ctrltime)
value = 1
[State 153, Hi to Lo]
type = StateTypeSet
trigger1 = statetype = S && command = "holddown"
statetype = C
physics = C
[State 153, Lo to Hi]
type = StateTypeSet
trigger1 = statetype = C && command != "holddown"
statetype = S
physics = S
[State 153, 4]
type = ChangeState
trigger1 = HitOver
value = 131
ctrl = 1
;---------------------------------------------------------------------------
; AGUARDHIT (shaking)
[Statedef 154]
type = A
movetype= H
physics = N
velset = 0,0
[State 154, 1]
type = ChangeAnim
trigger1 = 1
value = 152
[State 154, 2]
type = ChangeState
trigger1 = HitShakeOver
value = 155 ;AGUARDHIT2
[State 154, 3]
type = ForceFeedback
trigger1 = time = 0
waveform = square
time = 4
;---------------------------------------------------------------------------
; AGUARDHIT2 (knocked away)
[Statedef 155]
type = A
movetype= H
physics = N
anim = 152
[State 155, 1]
type = HitVelSet
trigger1 = Time = 0
x = 1
y = 1
[State 155, 2]
type = VelAdd
trigger1 = 1
y = Const(movement.yaccel)
[State 155, 3]
type = CtrlSet
trigger1 = Time = GetHitVar(ctrltime)
value = 1
[State 155, 4]
type = VarSet
trigger1 = 1
sysvar(0) = (pos y >= 0) && (vel y > 0)
[State 155, 5]
type = VelSet
trigger1 = sysvar(0)
y = 0
[State 155, 6]
type = PosSet
trigger1 = sysvar(0)
y = 0
[State 155, 6]
type = ChangeState
trigger1 = sysvar(0)
trigger1 = command = "holdback"
trigger1 = inguarddist
value = 130
[State 155, 7]
type = ChangeState
trigger1 = sysvar(0)
value = 52
防御状态默认占据了状态号[120,155],中间根据防御不同阶段又细分了好几个状态。
120是防御准备状态,也就是按下后方向键人物进入的动作,只用于转变为防御未受击状态;
[130,132]是防御未受击状态,也就是做好了防御动作但是尚未被攻击的阶段,[130,132]分别对应站、蹲、空;
140是防御结束状态,也就是防御准备状态、未受击状态或者受击状态结束时松开后方向键进入的状态,只用于离开防御状态;
[150,155]是防御受击状态,防御准备状态或防御未受击状态受到攻击时进入的状态,[150,151]、[152,153]、[154,155]分别对应
站、蹲、空,偶数为受击震动,奇数为受击滑动。在受击硬直后若还按着后方向键进入防御未受击状态,否则进入防御结束状态。
防御状态的切换是已经写好了的,我们需要管的地方只有手操指令的切换。
而这些切换无非控制的其实是站/蹲防的切换(空防你只有防与不放不用想太多)和停止防御状态两种。
通常来说,站姿攻击多为上段和中段,蹲姿攻击多为上段和下段,空中攻击多为上段和中段。
所以在地面上对站姿攻击和空中攻击我们用站防应对,对蹲姿攻击我们用蹲防应对,对方停止攻击的时候我们停止防御就行了。
实例如下:
;---------------------------------------------------------------------------
; GUARD (start)
[Statedef 120]
type = U ;Leave state type unchanged
physics = U ;Leave physics unchanged
...
[State 120, Hi to Lo]
type = StateTypeSet
triggerall = statetype = S;当我方是站姿
trigger1 = !Var(59) && command = "holddown"
trigger2 = Var(59) && EnemyNear,Statetype = C;而对方是蹲姿的时候
statetype = C;变为蹲姿
physics = C
[State 120, Lo to Hi]
type = StateTypeSet
triggerall = statetype = C;当我方是蹲姿
trigger1 = !Var(59) && command != "holddown"
trigger2 = Var(59) && EnemyNear,Statetype = S;而对方是站姿的时候
statetype = S;变为站姿
physics = S
[State 120, 5]
type = ChangeState
trigger1 = AnimTime = 0
value = 130 + (statetype = C) + (statetype = A)*2
[State 120, Stop Guarding]
type = ChangeState
trigger1 = !Var(59) && command != "holdback"
trigger2 = !inguarddist
value = 140
;---------------------------------------------------------------------------
; STAND GUARD (guarding)
[Statedef 130]
type = S
physics = S
[State 130, 1]
type = ChangeAnim
trigger1 = Anim != 130
value = 130
[State 130, Hi to Lo]
type = ChangeState
trigger1 = !Var(59) && command = "holddown"
trigger2 = Var(59) && EnemyNear,Statetype = C
value = 131
[State 130, Stop Guarding]
type = ChangeState
trigger1 = !Var(59) && command != "holdback"
trigger2 = !inguarddist
value = 140
;因为下面的站/蹲姿切换都是类似的代码,以上面的当做参考足矣,下略
......
之后还有启动防御的Changestate:
;下面一段放到[Statedef -1]或[Statedef -3]下面
;---------------------------------------------------------------------------
[State -1, 防御]
type = ChangeState
value = 120
Triggerall = Var(59) > 0
Triggerall = Roundstate = 2
Triggerall = Statetype != A
Triggerall = ctrl
Triggerall = !(EnemyNear(Var(54)),hitdefattr = sca,at);对方不是投技判定
Trigger1 = Inguarddist ;在防御范围内
完成这段的修改,你也限制了人物攻击的话,你的人物已经算是铁壁了。
什么?你说铁壁这么简单?没错,只要不主动攻击卖硬直,铁壁就是这么简单。
===========================================================================
8.7限制隐性手操指令切换状态
===========================================================================
---------------------------------------------------------------------------
到这里我们已经把带有command的Sctrl修改完了,但是这章内容尚未完结。
还记得笔者在第5章提及的内容吗:除了带有command的Sctrl以外,Mugen里面还存在内置的隐性的手操指令切换状态,
例如下蹲(Stateno 10)、行走(Stateno 20)和跳跃(Stateno 40)等。
我们虽然修改了这几个动作里面的带command的内容,但是我们还没有对它们的触发进行限制。
只要人物处于可控状态,只要内置的隐性的用手操指令刚好被触发,这几个动作依然会不受控制的动起来。
所以除了上面的内容,我们还要限制隐性手操指令切换状态。
---------------------------------------------------------------------------
因为人物要处于可控状态才会切换状态为下蹲、行走和跳跃等状态,所以首先我们可以把一些可控的状态改成不可控。
在Statedef开始的地方,如果没有标注说是否可控,将会继承上一个状态的可控与否。
例如从可控的站姿切换到行走,行走并没有标注是否可控,行走也将变成可控。
分析之前的代码可知,Mugen默认的行走和跑都可以从可控状态进入,所以这两个状态都可能会被隐性的手操指令切换状态。
于是我们可以进行修改,实例如下:
;因为我们已经采用了新的行走状态State 19/21,所以我们可以不用修改State 20
;---------------------------------------------------------------------------
; Walk B 向后走
[Statedef 19]
type = S
physics = S
sprpriority = 0
ctrl = 0;不可控
...
;---------------------------------------------------------------------------
; Walk F 向前走
[Statedef 21]
type = S
physics = S
sprpriority = 0
ctrl = 0;不可控
;而State 100,我们可以像行走那样添加新的状态,设置为不可控,或者是像下面这么修改
;---------------------------------------------------------------------------
; Run forward
[Statedef 100]
...
[State 100, AI Ctrlset];添加不可控设置
type = Ctrlset
trigger1 = Var(59) > 0;当AI开关打开的时候
value = 0;不可控
通过这样的修改,在行走和跑步的时候就不会被手操指令影响了,但缺点就是不可控状态可能会混淆对方AI,
我们在写AI的时候也要注意这种写法可能对我们带来的不良影响。
另外,由于原来可控的状态现在变成了不可控,所以其他Sctrl里原来triggerX = ctrl的条件不作相应调整的话人物就变得不会动
了,因此我们还需要调整这些trigger,令他们可以在这些原来是可控的状态里跟原来一样切换状态。例如:
;Stand Light Punch
[State -1, Stand Light Punch]
type = ChangeState
value = 200
trigger1 = Var(59) > 0
trigger1 = statetype = S
trigger1 = ctrl
改为
;Stand Light Punch
[State -1, Stand Light Punch]
type = ChangeState
value = 200
trigger1 = Var(59) > 0
trigger1 = statetype = S
trigger1 = ctrl || (Stateno = [19,21]) || Stateno = 100;这样就算不可控,行走和跑时都可以切换状态了
---------------------------------------------------------------------------
然而修改了行走和跑步依然还是不够的,站姿等可控的情况下依然会乱动。
所以为了对应大多数的可控情况,我们还需要一段泛用的限制代码:
;下面一段放到[Statedef -1]或[Statedef -3]下面
;---------------------------------------------------------------------------
[State -1, 地面动作限制]
type = ChangeState
value = 0;强行拉回站姿
Triggerall = Var(59) > 0
Triggerall = Statetype != A
Triggerall = Roundstate = 2
Trigger1 = Stateno = 10;下蹲
Trigger2 = Stateno = 20;行走
Trigger3 = Stateno = 40;跳
ctrl = 1
虽然有部分魔改的成分(笑),但是这种是比较常用而且实在的限制乱动的写法了。
---------------------------------------------------------------------------
至此,公用状态算是基本完成了。
随着以后不同目的的章节的探讨,我们还会继续给这些公用状态添加一些trigger,令它们在适当的时候触发。
不过那是之后的事了,本章只讲这么多。
===========================================================================
课后作业:
1.
格斗游戏中,什么段攻击可以击破蹲防?什么段攻击可以击破站防?
2.
为什么说章节8.7的这段代码有部分魔改的成分?
[State -1, 地面动作限制]
type = ChangeState
value = 0
Triggerall = Var(59) > 0
Triggerall = Statetype != A
Triggerall = Roundstate = 2
Trigger1 = Stateno = 10
Trigger2 = Stateno = 20
Trigger3 = Stateno = 40
ctrl = 1