若人物无法手动操作,请尝试用千寻辅助工具关闭常时AI或者查阅人物包内的readme文件

8. 公用状态修改:移动、跳跃、受身及防御

终于真正的开始写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


你,确定要这么做吗?
正在处理中...