⬆️麦扣老师教程中的设计模式 - 有限状态机&抽象类多态笔记
类的继承
子类可以使用父类的变量和函数方法,也可以通过override重写父类中的方法。
状态机
包含了状态进入、状态持续中、状态退出等需要执行的内容。
教程之前写的HurtAnimation就是继承自StateMachineBehavior状态机,这就是一个抽象类。
抽象类
- 使用Abstract声明。
- 里面所有的方法都是(virtual)虚的方法,可以被重写。
- 抽象类中的方法只需要声明,不写实现。必须在继承抽象类的类中实现。
教程中举了个披萨的例子:
- 父类继承:我要做披萨,每个披萨都要用写好的饼皮,然后往上加配料。
- 抽象类:规定你必须要有饼皮,但不限制饼皮的实现,用什么材料,是厚皮还是薄皮等。
有限状态机
一个物体在一段时间、一定条件下只执行一个状态,不会同时有其他状态影响。比如,巡逻状态下,不会有追击状态同时执行。
抽象类状态机的写法
不继承M ono Behavior(也就是不能挂在unity场景物体中)
使用Abstract声明。内部定义必须要使用的函数方法(是abstract类型)
public abstract class BaseState
{
public abstract void OnEnter();
public abstract void LogicUpdate();
public abstract void PhysicUpdate();
public abstract void OnExit();
}
- OnEnter() -状态进入时的要执行的。
- LogicUpdate()-逻辑判断。将放在Enemy的update中,持续进行所有的逻辑判断。
- PhysicUpdate() -fixupdate中做物理逻辑判断。
- OnExit() -退出状态。
为什么StateMachine Behavior中用virtual,此处却用abstrat?
因为StateMachine Behavior继承了scriptable object?
创建野猪巡逻的逻辑BoarPatrolState,继承BaseState抽象类
必须实现抽象类中的方法!
在需要调用状态机的代码文件中创建BaseState类型的变量
protected BaseState currentState;
protected BaseState ChaseState;
protected BaseState patrolState;
private void OnEnable()
{
currentState = patrolState;
currentState.OnEnter();
}
private void Update()
{
currentState.LogicUpdate();
}
private void FixedUpdate()
{
currentState.PhysicUpdate();
}
private void OnDisable()
{
currentState.OnExit();
}
有限状态机-巡逻状态PatrolState
状态的进入
在野猪自己的awake中,为patrolState赋值,new一个野猪巡逻状态机
- Enemy.cs
protected virtual void Awake()
- Boar.cs
public class Boar : Enemy
{
protected override void Awake()
{
base.Awake();
patrolState = new BoarPatrolState();
}
}
巡逻进行时
物理检测撞墙、到悬崖等待后转身
在当前的状态中调用对应的Enemy代码中的方法
在BaseState中创建Enemy类型的变量currentEnemy来保存当前对应的Enemy即可。对应的实现也要进行修改。
- BaseState.cs
protected Enemy currentEnemy;
public abstract void OnEnter(Enemy enemy);
- BoarPatrolState.cs
public override void OnEnter(Enemy enemy)
{
currentEnemy = enemy;
}
- Enemy.cs
private void OnEnable()
{
currentState = patrolState;
currentState.OnEnter(this);
}
原本的代码移动到状态机中。如果要使用Enemy中的变量,需要修改访问权限为public,再通过currentEnemy变量实现调用。不想在inspector中暴露的变量,在定义最前面标注[HideInInspector]。
- BaseState.cs
public override void LogicUpdate()
{
//发现player之后切换到chase状态
//撞墙或到达悬崖进入等待,停止播放walk动画
if (!currentEnemy.physicCheck.isGround ||(currentEnemy.physicCheck.touchLeftWall && currentEnemy.faceDir.x < 0) || (currentEnemy.physicCheck.touchRightWall && currentEnemy.faceDir.x > 0))
{
currentEnemy.wait = true;
currentEnemy.anim.SetBool("walk", false);
}
else
{
currentEnemy.anim.SetBool("walk", true);
}
}
我都这样写了为什么猪还会掉落悬崖!
因为Enemy.cs的fixupdate中,持续执行了Move()。那怎么加判断让他别动了?
重新说明一下逻辑:在LogicUpdate中,检测碰到悬崖、墙壁之后先设置进入等待,不播放walk动画(walk=false),等待时间结束后转身。所以,设置等待(wait=true)时,也不能执行Move()。
private void FixedUpdate()
{
if (!isHurt && !isDead && !wait)
{
Move();
}
currentState.PhysicUpdate();
}
状态的退出
- BoarPatrolState.cs
public override void OnExit()
{
currentEnemy.anim.SetBool("walk", false);
}
我的猪为什么每次启动都要等两秒?
发现每次启动时,wait都会置为true。
那么找到将wait置为true的那一段,也就是在判断时,猪不在地上/撞墙。。所以。。是猪在scene编辑窗口中的位置不在地面上。。所以在编辑器里把野猪的isGround勾选上就好了