Unity引擎2D游戏开发,有限状态机&抽象类多态
状态机与抽象类
观察如下代码:
public class AttackFinish : StateMachineBehaviour
{
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<PlayerController>().isAttack = true;
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
//override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
//
//}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<PlayerController>().isAttack = false;
}
// OnStateMove is called right after Animator.OnAnimatorMove()
//override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that processes and affects root motion
//}
// OnStateIK is called right after Animator.OnAnimatorIK()
//override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that sets up animation IK (inverse kinematics)
//}
}
这继承的就是一个状态机,在此类实现了父类的部分方法。当在不同的状态时,执行着不同的流程。比如,当状态进入时OnStateEnter(),当状态正在持续刷新时OnStateUpdate(),当状态退出时OnStateExit()等等
接着,查看继承的父类StateMachineBehaviour
:
(一部分)
public abstract class StateMachineBehaviour : ScriptableObject
{
public virtual void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
}
public virtual void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
}
public virtual void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
}
public virtual void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
}
public virtual void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
}
}
可以看到这个状态机类,是一个抽象方法(abstract class),特征是内部只有方法的声明,没有方法体
创建一个状态机
在Scripts的Enemy文件夹下面创建一个名为BaseState
的C#脚本,作为一个基本的状态机抽象类
在内部创建四个状态方法
public abstract class BaseState
{
public abstract void OnEnter();
public abstract void LogicUpdate();
public abstract void PhysicsUpdate();
public abstract void OnExit();
}
状态进入OnEnter(),逻辑更新LogicUpdate(),物理更新PhysicsUpdate(),状态退出OnExit()
再在Enemy文件夹下面创建一个BoarPatrolState
的C#脚本,继承上面所创建的BaseState
public class BoarPatrolState : BaseState
{
public override void LogicUpdate()
{
throw new System.NotImplementedException();
}
public override void OnEnter()
{
throw new System.NotImplementedException();
}
public override void OnExit()
{
throw new System.NotImplementedException();
}
public override void PhysicsUpdate()
{
throw new System.NotImplementedException();
}
}
在Enemy代码中引用创建的状态机
在Enemy
中创建三个变量
// 当前状态
private BaseState currentState;
// 巡逻状态
protected BaseState patrolState;
// 追击状态
protected BaseState chaseState;
创建一个OnEnable()
方法,为对象启用时执行某个状态
private void OnEnable()
{
currentState = patrolState;
currentState.OnEnter();
}
在Update()
方法中添加一行代码,调用逻辑状态LogicUpdate()
currentState.LogicUpdate();
在FixedUpdate()
方法中添加一行代码,调用物理状态PhysicsUpdate()
currentState.PhysicsUpdate();
创建一个OnDisable()
方法,在对象销毁时执行某种状态
private void OnDisable()
{
currentState.OnExit();
}
这样就做好了敌人对象在创建完成后的一系列状态。敌人若发现了玩家,会进入追击状态,则可以在BoarPatrolState类中的LogicUpdate()更改状态为chaseState,紧接着执行相关一系列的方法
有限状态机,在一定的条件下,只会执行一种状态。例如追击只会执行追击状态,巡逻只会执行巡逻状态,它执行的状态是有限的,并不会执行多种状态。
实现敌人的有限状态机
先清空Boar类中的Move()
方法,稍后会在创建的BoarPatrolState类中的PhysicsUpdate()
进行编写
public class Boar : Enemy
{
}
需要知道是什么敌人在执行状态(会有多个敌人的种类),因此在BaseState
中创建一个全局变量,用于标记敌人的种类
protected Enemy currentEnemy;
并在OnEnter()
方法上,添加一个形参。这样也能够在调用的位置传入敌人种类
public abstract void OnEnter(Enemy enemy);
在Enemy的OnEnable()
方法中,调用OnEnter()
的位置传入this
当前敌人
private void OnEnable()
{
currentState = patrolState;
currentState.OnEnter(this);
}
在BoarPatrolState中的OnEnter()方法中,将传入的enemy赋值给currentEnemy,以便后续对其进行逻辑编写
public override void OnEnter(Enemy enemy)
{
currentEnemy = enemy;
}
将Enemy中Update()方法内部撞墙等待的代码移植到BoarPatrolState类LogicUpdate()方法中
public override void LogicUpdate()
{
if (!currentEnemy.physicsCheck.isGround || (currentEnemy.physicsCheck.touchLeftWall && currentEnemy.faceDirect.x < 0) || (currentEnemy.physicsCheck.touchRightWall && currentEnemy.faceDirect.x > 0))
{
currentEnemy.wait = true;
currentEnemy.animator.SetBool("walk", false);
} else
{
currentEnemy.animator.SetBool("walk", true);
}
}
会有无法找到变量的问题,在各自变量前,添加“currentEnemy.
”即可正确引用。还需要注意,这些变量的访问修饰符需要改为public。与此同时,这些变量会显示在Unity的Inspector窗口。如果不需要显示,在变量前添加“[HideInInspector]
”,即:
[HideInInspector] public Animator animator;
[HideInInspector] public PhysicsCheck physicsCheck;
现在需要进入其他的状态要怎么进入呢?比如在Enemy中创建的三个全局变量的状态?
准备在Boar类中重写Enemy中的Awake()方法。在此之前,先将Enemy
中Awake()
方法修改为protected virtual
protected virtual void Awake()
{
rb = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
physicsCheck = GetComponent<PhysicsCheck>();
waitingTimeCounter = waitingTime;
currentSpeed = normalSpeed;
}
接着Boar中重写Awake()方法,并将patrolState引用为BoarPatralState()
protected override void Awake()
{
base.Awake();
patrolState = new BoarPatrolState();
}
现在patrolState引用为BoarPatrolState对象,那么Enemy内OnEnable()中的OnEnter就可以正常执行野猪敌人的状态
将Update()中TimeCounter()移动至currentState.LogicUpdate()下方,因为LogicUpdate()中执行了撞墙等待,所以在此之后开始计时
private void Update()
{
// 实时方向
faceDirect = new Vector3(-transform.localScale.x, 0, 0);
currentState.LogicUpdate();
TimeCounter();
}
在OnExit()方法,也设置walk的动画为false
public override void OnExit()
{
currentEnemy.animator.SetBool("walk", false);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?