前言:
为了更清晰的认识状态机并且理清 Enemy 设计思路,所以整理了一下 Enemy 的代码设计逻辑
做了一张简单的思维图先进行一个简单的认识
干货:FMS有限状态机
状态机类似于动画器 (animator) ,动画器可以简单清晰地管理游戏角色的动画:待机、跳跃、下落、跑步……,状态机的目的也是如此,每一个角色都有不同的行为方式,当这个角色的行为方式数量极大时,就有可能出现代码处理漏掉等各种问题,而为了更方便清晰地管理每一个状态,减少维护成本,引入了状态机这个概念
如何具体设计?
设计状态机的目的就是为了优化代码,使逻辑清晰
根据我们的需求来设计
首先创建一个简单的敌人(本体)
敌人包含了他的属性,以及一些必要的行为(函数),函数的具体内容就不写出来了,可以根据需求填入
为了简化代码,这个 Enemy类也可以作为一个父类,不同的敌人都继承这个父类获取必要的属性等,如果有部分不同可以直接进行函数的重写
public class Enemy: MonoBehaviour
{
public float speed; //速度
public float maxhealth; //最大生命值
public float currenthealth; //当前生命值
public Transform pointA, pointB; //来回巡逻的两个端点
public Vector2 targetpoint; //移动目标点
public Animator anim; //获取动画器播放动画
private void Start()
{
}
private void Update()
{
}
public void MoveAction() //移动函数
{
}
public void IdleAction() //待机函数
{
anim.play("idle");//播放待机动画
}
//不同的敌人攻击方式可能不同,有的是远程攻击,有的是近战攻击,所以用关键字 virtual 写成虚方法,然后在子类直接重写
public virtual void AttackAction() //攻击函数
{
}
}
其次设计状态机
当我们从一个状态进入到另一个状态,那么就会涉及到 第一个状态的结束(这一步根据需求可有可无) , 第二个状态的开始 , 第二个状态的持续 这三个过程(直到收到切换为下一个状态的信号才跳出)
所以我们会有 这三个必要过程 来组成每个状态,但是因为每个不同的状态都会有这三个过程,这里不妨优化一下代码,让所有的状态都继承一个父类,减少冗杂繁琐的相同代码,所以创建一个抽象的基类,命名为 EnmeyBaseState
,这个类声明三个抽象函数,函数的实现由子类确定:
(利用面向对象的 抽象类 实现 多态 )
public abstract class EnemyBaseState
{
protected Enemy enemy; //创建 Enemy 用于在子类中获取项目本体方便调用本体中的函数
public abstract void EnterState(); //状态的开始
public abstract void OnUpdate(); //状态的持续
public abstract void EndState(); //状态的结束
}
抽象类以及抽象函数创建好了,函数的实现我们就只需要在子类每个状态中实现
以攻击状态为例:
攻击状态命名为 AttackState
, 并且需要继承我们的基类 EnemyBaseState
同时 必须实现 EnemyBaseState
中声明的抽象函数(如果不需要用到对应的过程我们可以不填写实现内容,但是函数必须实现)
为了方便获取项目本体并且 减少一点代码量 ,我用了构造函数获取本体
public class AttackState : EnemyBaseState
{
//用构造函数直接获取项目本体
public AttackState(Enemy object)
{
enemy = object;
}
//不需要用到的过程我实现了函数后里面没有填写任何东西
public override void EnterState()
{
}
//持续执行攻击函数
public override void OnUpdate()
{
//调用本体的攻击函数实现攻击
enmey.AttackAction();
}
//不需要用到的过程我实现了函数后里面没有填写任何东西
public override void EndState()
{
}
}
按着这个样子同样写好待机状态和巡逻状态,这里就不重复了
最后合并状态机和本体项目
本体要获取到状态机的所有状态,当状态数量太大时可能会影响我们编写代码的效率以及速度,所以我们可以使用枚举代表所有的状态,并且用字典来将枚举中的状态和状态机的状态一一对应起来
在 Enemy 类中继续添加,并且最开始执行代码时添加到字典中
public enum State //创建枚举
{
idle, patrol, attack
}
private Dictionary<State_Enum, EnemyBaseState> states = new Dictionary<State_Enum, EnemyBaseState>(); //创建字典
private void Awake()
{
states.Add(State.idle, new IdleState(this)); //添加待机状态到字典
states.Add(State.patrol, new PatrolState(this)); //添加巡逻状态到字典
states.Add(State.attack, new AttackState(this)); //添加攻击状态到字典
}
获取了状态之后我们要写一个函数用于状态的转换
之后我们就可以直接通过这个函数 传入状态参数来切换目标状态
//创建一个 EnemyBaseState 类的参数,用于控制 Enemy 当前的状态
public EnemyBaseState currentState;
public void TransitionState(State type) //切换状态函数
{
currentState.EndState(); //调用上一个状态的结束
currentState = states[type]; //切换下一个状态
currentState.EnterState(); //调用下一个状态的开始
}
public void Update()
{
currentState.OnUpdate(); //持续调用这一个状态的持续
}
最后本体的整体代码如下:
具体什么时候需要切换状态,根据自己需求设置
public class Enemy: MonoBehaviour
{
public float speed; //速度
public float maxhealth; //最大生命值
public float currenthealth; //当前生命值
public Transform pointA, pointB; //来回巡逻的两个端点
public Vector2 targetpoint; //移动目标点
public Animator anim; //获取动画器播放动画
public EnemyBaseState currentState; //当前状态
private float idletime;
public enum State //创建枚举
{
idle, patrol, attack
}
private Dictionary<State_Enum, EnemyBaseState> states = new Dictionary<State_Enum, EnemyBaseState>(); //创建字典
private void Awake()
{
states.Add(State.idle, new IdleState(this)); //添加待机状态到字典
states.Add(State.patrol, new PatrolState(this)); //添加巡逻状态到字典
states.Add(State.attack, new AttackState(this)); //添加攻击状态到字典
TransitionState(idle); //一开始直接调用idle状态
}
public void Update()
{
currentState.OnUpdate(); //持续调用这一个状态的持续
}
public void TransitionState(State type) //切换状态函数
{
currentState.EndState(); //调用上一个状态的结束
currentState = states[type]; //切换下一个状态
currentState.EnterState(); //调用下一个状态的开始
}
public void MoveAction() //移动函数
{
}
public void IdleAction() //待机函数
{
idleTime -= Time.deltaTime;
anim.play("idle");//播放待机动画
if (idleTime <= 0)
{
TransitionState(State.patrol); //待机时间结束切换巡逻状态
}
}
//不同的敌人攻击方式可能不同,有的是远程攻击,有的是近战攻击,所以用关键字 virtual 写成虚方法,然后在子类直接重写
public virtual void AttackAction() //攻击函数
{
anim.play("attack");
}
}
总结:
敌人作为一个父类 统一管理所有子类,通过状态机来进行状态的切换,状态机有一个 基类作为父类 ,管理所有的状态
敌人通过 枚举 和 字典 获取所有的状态,并且在需要的时候切换状态
每个状态都用 构造函数 来获取敌人本体,用于在切换到当前状态时获取本体的一些数据或者使用本体的函数
标签:Enemy,函数,状态,void,状态机,思路,public From: https://www.cnblogs.com/shadow-fy/p/17316527.html