《Unity3D高级编程之进阶主程》第五章,3D模型与动画(三) - 状态机

如何用状态机模拟人物行为动作。

===

什么是状态机?状态机有两种,一种是有限状态机,一种是无限状态机。有限状态机运用的地方比较多,而无限状态机由于状态是无限的所以也有无限多的映射因此只是理论上的模型,我们主要来讲讲有限状态机的使用。

有限状态机可以简单描述为,实例本身有很多种状态,实例从一种状态切换到另一种状态的动作就是状态机转换,而转换是有条件的,这个转换条件就是状态机之间的连线。

打个比方,人有三个状态:健康,感冒,康复中。触发的条件有淋雨(t1),吃药(t2),打针(t3),休息(t4)。状态机的连接图可以是这样:

	健康-(t4休息)->健康;

	健康-(t1淋雨)->感冒;

	感冒-(t3打针)->健康;

	感冒-(t2吃药)->康复中;

	康复中-(t4休息)->健康

状态在不同的条件下跳转到不同状态中去,每个状态要转移到其他状态都必须有他们之间的连线条件,而且不一定状态与状态之间有连线,因为有可能是不允许转换的,例如‘健康’就不允许转换到‘康复’。

状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:

现态:是指当前所处的状态。

条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。

动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

游戏中人物行为动作中使用状态机

游戏项目中状态机的关键是事件机制和控制状态的控制类。状态机的数量和作用都会因系统的不同而不同,触发条件也各异的,唯有事件机制和控制类是状态机的不变的功能。

事件机制使得状态在切换时,在进入状态和退出状态时触发了进入事件和退出事件,这个是状态启动运作和停止运作的关键点。

当状态在满足转换条件时,在即将退出状态前向当前状态发起退出事件,告诉当前状态机你将停止运行,停止运行前需要处理什么逻辑请赶快处理,等待退出逻辑处理完毕后,再向新状态发起进入事件,告诉新状态你将要开始运作,运作前的有什么逻辑或者准备工作请尽快处理。这样每次状态的切换都能合理的告诉当前状态和将要切换的状态进行事件的调用处理。

状态机在游戏项目中哪些地方会使用到呢?

所有能够构成独立状态的系统或者功能都能使用状态机来表现,我们来举例看看:

1,场景切换。

场景是独立的,而且只能有一个场景展示在游戏中,因此场景的切换可以用状态机来表示。例如当前为登录场景,点击登录后切换到游戏场景,这时需要把登录场景的UI销毁,UI的销毁工作是登录场景状态在退出时触发的退出事件中做的事。同样的,在之后进入到游戏场景状态时,要先把游戏场景的UI创建出来,这个操作是游戏场景状态在触发启动事件时要做的事。

2,人物行为状态切换。

人物一般只能有一个动作状态,比如攻击状态,比如防守状态,比如死亡状态,又比如人物跑步状态,这些行为都只能用单独的一个状态来表示,但也有人物边跑步边吃东西的时候,这时我们也会有几种方式去实现,比如我们把人物跑步且吃东西另外创建一个新的状态来运行,也可以把跑步状态里加一个吃东西的参数,让跑步这个状态机来运行不一样的跑步动作。

3,宝箱,机关等具有多动画的元素都可以构成独立的状态。

可以把宝箱或机关的每个动画都看成一个状态,比如打开状态的宝箱和关闭状态的宝箱,以及打开时的机关状态以及关闭时的机关状态。

4, AI。

用状态机来做AI是比较常见到的方式,每个AI状态都可以看成一个独立运行的状态。比如AI状态中的激怒状态,一般来说怪物在此状态中会不断向周围的敌人发起攻击,如果20秒后恢复到平静状态后就不再攻击则认为AI状态由激怒状态转换到了空闲状态,又比如AI巡逻状态,在某个点周围或者按一定路线进行走动,如果5米内发现敌人就会激活条件转换到AI激怒状态。

以上都是状态机在游戏项目各个逻辑模块中的运用,下面我们重点来介绍下人物行为的状态机结构,我们先按常见的需求把人物行为动作划分一下:

	1,	休息状态。原地不动,并且重复做一个休息的动画。

	2,	攻击状态。播放攻击动画,并且对目标或前方进行攻击。

	3,	技能状态。技能稍微复杂点,因为每个技能都不一样,所以技能状态里面的逻辑可以由不同的技能类来实现。比如建个技能基类class SkillBase,里面有几个统一的接口,然后子类对基类进行继承后,细化技能的细节。

	4,	防御状态。播放防御动画,当受到攻击时,不切换受伤状态。

	5,	受伤状态。播放受伤动画,完毕后自动进入休息状态。

	6,	行走与跑步状态。播放行走或跑步动画,并根据操作输入移动方向。

	7,	跳跃状态。播放跳跃动画,并上下移动人物进行跳跃,下落时底部受到碰撞就进入休息状态。

	8,	死亡状态。播放死亡动画,并且不再受到任何指令而转入任何状态。

这里我们对每个状态编写一些伪代码来描述状态机在人物行为中的运作,如下:


Class BaseState
{
	Public virtual void OnEnter(){}
	Public virtual void OnExit(){}
	Public virtual void Update(){}
}

Class IdleState
{
	Public override void OnEnter()
	{
		role.playAnimation(“idle”,loop);
	}
}

Class HurtState
{
	Public override void OnEnter()
	{
		role.playAnimation(“hurt”,Once);
	}
	
	Public override void Update()
	{
		If(!Role.IsPlayingAnimation(“hurt”))
		{
			HurtFinish();
		}
	}

	Public override void OnExit()
	{
		GotoIdleState();
	}

}

Class AttackState
{
	Public override void OnEnter()
	{
		Role.playAnimation(“attack”,once);
	}

	Public override void Update()
	{
		If(!Role.IsPlayingAnimation(“attack”))
		{
			AttackStateFinish();
		}
	}

	Public override void OnExit()
	{
		GotoIdleState();
	}
}

Class MoveState
{
	Public override void OnEnter()
	{
		Role.playAnimation(“walk”,loop);
	}

	Public override void Update()
	{
		Move();
	}

	Public override void OnExit()
	{
		GotoIdleState();
	}
}

上述代码表达了对状态机中的状态的描述,其中空闲状态,在没有收到任何指令时只是循环播放Idle动画其他什么都不做。攻击状态,进入状态时播放攻击动画,并在动画结束后返回到Idle状态并等待指令。移动状态在进入时播放walk动画并移动,当移动行为结束时就退出,在退出时进入空闲状态。

状态机中除了事件接口对状态起了关键的作用外,控制状态的控制类也是关键,它其实就是状态的管理类,用于管理状态的入口和出口。

我们也同样用伪代码的形式来描述状态管理类是如何运作的,以下是伪代码:


class RoleStateController
{
	private IdleState idleState;
	private MoveState moveState;
	private AttackState attackState;
	private HurtState hurtState;

	private BaseState currentState;
	
	public void OnHurt()
	{
		ReduceHP();
		If(currentState != hurtState)
		{
			ChangeToHurtState();
		}
	}

	public void InputAttack()
	{
		If(currentState  == hurtState) return;	//受伤状态下不可攻击
		If(currentState == attackState) return;	//攻击状态还没结束时不可重新开始攻击
		ChangeToAttackState();
	}

	Public void InputMove(){}
}

在状态机控制类中,存储了各个状态,并且提供了输入的接口,这种输入的事件是由状态机外部提供的,比如发起攻击,比如受到伤害,比如向前向后移动等等,在外部需要状态机触发状态改变时,发起了对状态机的输入事件。这就是状态机控制类需要做的事,简单来说就是存储状态,并提供输入事件接口。

更大规模的状态机可能还需要稍微改进下,比如业务逻辑都会转移到状态内去做,状态之间的转换则更为简单。如果状态很多则需要用数组的形式来存储,因为那样才能时内存连续提高CPU内存读取效率。

有了状态机的控制类就有了对状态机的管理,就可以对各种逻辑的控制,比如硬值,受伤中不可行走和攻击,攻击中不可移动等,这些当状态在切换时的条件限制,是阻挡还是通过,该切换什么状态,都可以由控制类来决定的,它是状态机系统的大脑。

· 书籍著作, Unity3D, 前端技术

感谢您的耐心阅读

Thanks for your reading

  • 版权申明

    本文为博主原创文章,未经允许不得转载:

    《Unity3D高级编程之进阶主程》第五章,3D模型与动画(三) - 状态机

    Copyright attention

    Please don't reprint without authorize.

  • 微信公众号,文章同步推送,致力于分享一个资深程序员在北上广深拼搏中对世界的理解

    QQ交流群: 777859752 (高级程序书友会)