目录
状态机是我们的所有状态的管理器,对于一个状态机,它包含了:
现态(当前的状态),次态(要转换的状态),条件(转换到次态的条件),动作(?)
创建
根据上述,我们知道我们得创建一个动画状态机去管理动画状态,然后还需要一个转换条件去管理条件.当然这些都是我们抽象出来的,那我们想使用他们就得有一个载体,因此我们选择用一个组件去管理这两个类.
进入正题,我们先创建一个组件吧.啥都不用写,继承我们的ActorComponent,
然后我们进入我们状态机的抽象类创建,我们选择继承U类,一个干净的类
接着我们创建一个我们的条件管理类,也是继承U类
创建完成.
得到这样的三个资源
属性声明
State
对于我们的状态机管理类,如果我们使用2d的动画,那么我们肯定需要一个我们的当前的状态.于是我们加载一个2D的动画资源,对于3D就加载3D动画 .
我们还需管理我们的状态转换条件.
为什么用TArray呢?
这是因为我们的次态是不唯一的呀,类比人来说,我们当前在休息,我们起来后想去做的事情不唯一,我们可能吃饭,可能散步,可能打游戏,这都是我们的次态,那我们转到这些状态的条件都是一样的吗?那肯定不行啊,如果一样了,那我们岂不是可以同时进行多个事件,对我们人类来说这是不正常的,你会扭曲的!对于我们的动画状态也是一样的,我们当前只能有一个状态,我们转换到不同的状态的条件也是不一样的,因此用容器的方式储存我们的转换条件.
然后我们还需要一个我们当前的状态的名字,就像你自己一样,存在世界上也需要一个名字用来区分. .
同时我们还需要一个Master,这个Master是我们状态依附的Actor.
然后我们再写两个函数, 这两个函数是用来加载我们的state资源的
定义
然后我们定义一个函数用来和外界通信,返回我们状态机当前管控的状态
这样,我们状态机基本的属性就构建完成了.
Condition
然后来到我们的Condition(转换规则)
也是先构建我们规则管理中需要的属性
首先我们要拿一个我们的宿主
然后我们再创建一个我们通过规则成立要转换的状态.
然后我们创建一个当前规则的名字
然后创建一个函数,这个函数是用来返回我们要改变的下一个状态
同时我们还要创建一个函数,这个函数就是我们的转换规则
设置成虚函数,让我们的子类能够重写这个规则.
定义:
这个定义是通过函数指针来调用的,写成这样的形式是因为我们不知道你想要的转换规则具体是什么,为了保证我们双方可以正常通信,我们就以函数指针的方式来判断我们的条件是否成立
对于函数指针,这么创建
这里封装两层.是为了把这个函数指针作为一个参数变量使用,就像基本类型那样.
然后在构造函数中初始化为空
对于这个函数指针的调用我们放在后面讲解
接下来我们继续我们当前的这个转换规则函数,
只要我们的这个ConditionPoiter成立,那么就调用这个函数,并且返回它所返回的值,否则我们就返回空
好了,我们规则基本属性也构建完成,然后就是我们的组件了.
Components
对于我们这个组件,他是一个实例的状态机组件,通过管控规则来进行状态的转换,
让我们的状态机工作起来.那首先我们就要组装模拟一个我们的状态机类
先是我们有一个当前状态
然后是我们次态的集合
实现状态机
后面的逻辑我们先留一下,回到我们的状态机类
我们之前在Condition中定义了一个转换规则,那么在哪里调用这个转换规则呢
自然是在状态机中,我们定义一个函数.用来返回我们的下一个状态或空指针
这个函数就是遍历我们的转换规则,如果成立就返回我们的下一个状态,要不就返回空
那么我们的这个函数在哪里调用呢?而且何时调用呢?
回到我们的Components
我们重写一个父类的函数TickComponent
TickComponent是一个对当前组件一直Tick的函数,我们把我们的逻辑写在这里来不断的管控我们的状态
不断调用我们的Update函数,判断我们的状态是否产生改变.
这就是我们组件最重要的功能.
回到我们的状态机类,通过观察上述代码,我们发现我们多了3个函数(公有的),而且是状态机类的,这三个函数就是我们的状态机中状态的3个回调通知.进入状态通知,离开状态通知,以及动作通知.这3个通知暂时在这里没有实现.这是为了以后如果我们想对我们的现态进行一些操作,就可以直接写这3个函数,比如我们写一个进入
进入的时候我们判断我们的Master和我们的Master是否有sprite,如果不写这个if,就是不安全的.为了安全考虑我们还是要进行一下检查,如果通过,我们就设置我们当前状态的动画.
那么接下来我们就要生产我们不同的动画然后让他们产生交互了.
工厂函数
state
在我们的状态机类中,我们写一个模板工厂函数.用来生产不同的动画状态
在这个模板函数中,我们要提供一个状态的名字,动画资源的路径,以及我们要把这个状态加载的Actor
实现这个函数
解读:先new一个T类对象,然后对我们之前的属性赋值,不过由于我们的属性是受保护的,所以我们就写几个公有函数可以与我们的属性通信.
AddCondition是用来添加我们容器元素的,也就是当前状态的次态.后面会有详细解释
condition
同理,我们的转换规则也是可以用工厂函数产生,然后进入我们的状态机工作.也就是加入到上面的容器中.
在我们的转换规则类中.我们定义一个模板函数
也是先new一个新规则,然后我们传入我们当前规则所需要的属性: 规则名称,所属的master(必须与我们状态机类的master一致),要转化的下一个状态,以及一个转换规则函数.
这里我们看到了我们之前定义的函数指针,他就是把我们的函数作为一个变量传出去,我们只维护这个函数指针,你把你的规则写在我们约定好的函数定义规则所属的自己定义的函数中,这样就可以完成正常通信.
下面的四个函数则是与本地的受保护的变量通信
最后返回我们的模板.
上面两个函数定义成模板的原因是为了可以便利使用,我们要这么想,我们的规则和状态时不唯一的,我们有可能会基于我们这个类产生子类,这样,我们就不用再自己写这个生产函数,只需提供模板参数就可以了.大大便利了我们日后的编程.
这样我们转换规则和状态机就基本构建完成了,让我们使用一下
Component
回到我们的组件,我们的组件要使用肯定得先初始化一下吧
我们创建一个初始化的函数,并且给外界暴露一个是否初始化的bool变量
初始化时,我们传进去一个初始状态,然后进入我们的状态,并且将我们的binit设置为true,表示我已初始化成功,你可以使用我了.
又见到这个函数了,初始化完成后,我们是不是可以给我们当前的状态添加他可以转化的次态了?当然是的,于是我们就可以给我们的容器赋值了.
使用状态机
现在我们的状态机就做完了,那我们可以使用状态机了.
在哪里使用呢?
自然是我们的Character身上啊
回到我们创建的Character! 我们创建一个我们自己的状态机组件Component
然后重写一个父类PossessedBy函数
这个函数是父类的一个函数,作用是当我们的Pawn被控制时,调用的逻辑
先把我们的组件创建出来
然后我们重写这个函数.函数有点长,我们慢慢看.
首先加载我们的状态,举个实例,我们当前项目时SuperMarrio,现在有3个状态,Walk,Jump,idle
那么我们就加载这三个状态.然后将这三个状态的转换规则也实现一下.
然后实现walk到idle,idle到walk的转换规则.
对于 walk和idle到jump状态,或者jump到walk和idle状态,我们用两种方法,第一种就如图所示.分别实现.比较繁琐.
所有的条件都创建完毕后我们要把这些变量加载到我们的状态机中.
也就是加载我们不同状态到下一个状态自己的转换规则的容器中,然后把我们这3个状态加载到我们的组件中去管理. 然后初始化一下我们的组件的初始状态,这样我们的状态机就可以正常工作了,他会不断的启动tickcomponent,然后调用状态中的updatestate.判断condition中的check(转换规则)是否成立,从而转换不同的状态.
回到这里,我们写一下第二种jump的转换规则.
先把规则加载进来
然后加到自己所属的状态中的次态集合中
,剩下的操作同第一种方法一样.把状态加到组件中,并设置初始化状态
然后我们重写一个父类的函数,用来判断我们的规则是否成立.
这个函数是OnMovementModeChanged
我们构建一个受保护的bool变量以及一个规则函数IsJump()
对于我们这个重载函数,第一个参数是移动的上一个状态,第二个是上一个自定义的模式.
那么对于跳跃,我们的上一个状态是行走,并且当前状态是掉落,我们就可以跳.也就是我们在地面上才能跳
对于返回,我们上一个状态时掉落,并且当前状态时飞行的时候,我们就不能跳,必须重新把状态重置回去才能跳.