状态机游戏AI设计

Posted KNGG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了状态机游戏AI设计相关的知识,希望对你有一定的参考价值。

身为一个日常躺平的大学生,寒假入手了无主之地3,开始了刷刷刷之旅,但出于对游戏的兴趣,我想讨论一下无主之地敌人的AI设计,通常AI设计都会符合遵循一定的规律,行为是由逻辑的可拿代码实现的,就拿无主之地3中最基础的那棒子追着玩家打的狂热者而言,当玩家进入狂热者攻击范围时,他会追上玩家并且挥动棒子攻击,于是我们可以根据他的行为写出下面的代码.

void Update()

    if(CanAttack())
    
        Attack();//在进入可攻击范围内进行
    
    else
    
        Dash();//在小于攻击范围内追击
    

这就简易直观的还原了狂热者的行为,用图像展示如下:

这就是不是有状态机那味了,那我们就可以把这两个方法Attack,Dash抽象为两种状态,我们刚刚的代码就是让他在狂热者的两个状态切换,切换仅满足一定的条件即可,你说 欸?这不就是状态机吗?我拿unity连线也是这,我想说这就是状态机没错,状态机本身就是这么简单.接下来我们就谈谈状态机.

状态机(Finite State Machine).

状态机其实跟上面的代码的作用一样,当然你用switch,case也可以做出来,但状态机比较规范方便罢了,后期也方便扩展,这里我介绍一个我寒假新学的一种架构,确实有点蠢,但也是最方便理解的,理解了这个在平时运用啊什么的可以灵活变化一下,进行简化或者复杂化.

状态机主要由以下几部分组成:

我们可以参考Unity的官方文档,写一个敌人的状态机为例子

1.枚举所有状态:StateType

public enum EnemyStateID

    NullState,
    ChaseState,
    AttackState,
    IdleState........

2.转换标明:Transition

public enum EnemyTransition

    NullTansition = 0,
    SeeSoldier,
    NoSoldier,
    LowHealth.....

用来标明什么情况下进行状态转换,也可以展示当前状态.

3.状态机:FSMSystem

public class EnemyFSMSystem

    private List<IEnemyState> mStates;

    private IEnemyState mCurrentState;

    public IEnemyState currentState
    
        get  return mCurrentState; 
    
    public void AddState(params IEnemyState[] states)
    
        foreach (IEnemyState s in states)
        
            AddState(s);
        
    
    public void AddState(IEnemyState state)
    
        if (state == null)
        
            Debug.LogError("state can not be null");
            return;
        
        if (mStates.Count == 0)
        
            mStates.Add(state);
            mCurrentState = state;
            mCurrentState.DoBeforeEntering();
            return;
        
        foreach (IEnemyState s in mStates)
        
            if (s.stateID == state.stateID)
            
                Debug.LogError("要添加的状态ID" + s.stateID + "已经添加");
                return;
            
        
        mStates.Add(state);
    
    public void DeleteState(EnemyStateID Id)
    
        if (Id == EnemyStateID.NullState)
        
            Debug.LogError("要删除的状态ID为空" + Id);
            return;
        
        foreach (IEnemyState s in mStates)
        
            if (s.stateID == Id)
            
                mStates.Remove(s);
                return;
            
        
        Debug.LogError("要删除的ID不存在于集合中:" + Id);
    
    public void PerfromTransition(EnemyTransition trans)
    
        if (trans == EnemyTransition.NullTansition)
        
            Debug.LogError("要执行的转换条件为空:" + trans);
            return;
        
        EnemyStateID NextstateID = mCurrentState.GetOutPutState(trans);
        if (NextstateID == EnemyStateID.NullState)
        
            Debug.LogError("在转换条件" + trans + "下没有对应的转换状态");
            return;
        
        foreach (IEnemyState s in mStates)
        
            if (s.stateID == NextstateID)
            
                mCurrentState.DoBeforeLeaving();
                mCurrentState = s;
                mCurrentState.DoBeforeEntering();
                return;
            
        
    

FSMSystem是状态机的心脏,当然你也可以献上心脏(滑稽),在例子里我们写了一个储存状态的列表,与下面的类组合,再通过调用下面的PerfromTransition函数通过条件进行状态之间的转化,从而确保能转换到我想要的状态,有点状态模式超级升级版那味了对吧,我感觉状态机就是状态模式演化来的(狗头保命个人观点). 通过上述操作就可以通过标识转换条件进行转换,而不是哪个状态,实现了一点逻辑和状态的分离.

4.具体状态 

public abstract class IEnemyState : MonoBehaviour

    protected ICharacter mCharacter;//一个角色管理类 可以理解为一个游戏物体GameObject Enemy.
    protected Dictionary<EnemyTransition, EnemyStateID> mMap = new Dictionary<EnemyTransition, EnemyStateID>();
    protected EnemyStateID mStateID;
    public EnemyStateID stateID  get  return mStateID;  
    protected EnemyFSMSystem mFSM;

    public IEnemyState(EnemyFSMSystem fsm, ICharacter character)//初始化
    
        mFSM = fsm;
        mCharacter = character;
    
    public void AddTransition(EnemyTransition trans, EnemyStateID id)
    
        //将trans,id加入到mMap这个字典,用来标明某个transition
        //进行转换和判别
        if (trans == EnemyTransition.NullTansition)
        
            Debug.LogError("EnemyState Error:Trans can not be NULL");
            return;
        
        if (id == EnemyStateID.NullState)
        
            Debug.LogError("EnemyState Error:StateID can not be NULL");
            return;
        
        if (mMap.ContainsKey(trans))
        
            Debug.LogError("EnemyState Error:" + trans + "Already added");
            return;
        
        mMap.Add(trans, id);
    
    public void DeleteTransition(EnemyTransition trans)
    
        if (mMap.ContainsKey(trans) == false)
        
            Debug.LogError("删除转换条件的时候,转换条件不存在:[" + trans + "]不存在");
            return;
        
        mMap.Remove(trans);
    
    public EnemyStateID GetOutPutState(EnemyTransition trans)
    
        if (mMap.ContainsKey(trans) == false)
        
            return EnemyStateID.NullState;
        
        else
        
            return mMap[trans];
        
    
    public virtual void DoBeforeEntering()  
    public virtual void DoBeforeLeaving()  

    public abstract void Reason(List<ICharacter> targets);
    public abstract void Act(List<ICharacter> targets);

这个就是最后留给具体状态继承的类了,通过字典mMap可以添加对应的键值对,来进行状态的载入,删除和展示.

具体应用

我这里留下了几个虚函数和抽象函数,我们可以在其中比如预载一些状态配对的动作,玩家坐标,动画等,就不需要再连接每一个的动画模型等东西了.比如行为动作的不同 在具体的某个具体状态的具体实现来写就可以.

以上是关于状态机游戏AI设计的主要内容,如果未能解决你的问题,请参考以下文章

游戏状态机的设计与实现

使用行为树(Behavior Tree)实现游戏AI

游戏设计模式——状态机模式

FSM(状态机)HFSM(分层状态机)BT(行为树)的区别

游戏设计模式之三 状态模式有限状态机 & Unity版本实现

游戏设计模式之三 状态模式有限状态机 & Unity版本实现