【Unity基础】有限状态机简易框架(控制怪兽AI状态)

框架核心思路

StateBase类中的字典以键值对为[状态转换枚举类型][状态基类]存数据;
StateBase类中**getState(ETransition trans)**方法通过遍历字典中的枚举名字获取对应的状态类 ;

StateManager类中的listState容器存储状态类

这里实现了状态离开前的函数和下个状态开始的函数

public void doTransition(ETransition trans)
    {
        StateBase nextState= curState.getState(trans);
        if(nextState!=null)
        {
            //执行状态离开之前的函数(数据还原,一次性操作的逻辑)
            curState.doBeforeExit();
            //切换状态
            curState = nextState;
            //下个状态开始之前执行的函数(数据还原,一次性操作的逻辑)
            curState.doBeforeEnter();
        }
    }

状态的基类

声明所有的状态 和 状态转换
声明一个字典 key为枚举类型的状态转换 , Value为状态基类
子类
**addTransition(ETransition transition, StateBase state)**往字典里面存数据

两个抽象方法:action() 、 condition()

子类状态重写抽象方法实现各自的当前状态下的行为 、 进入该状态的条件
子类在重写condition()实现状态转换时 ,创建一个Monster对象 ,里面的doTranstion()方法 ,Monster类中的doTranstion()方法则是调用StateManager的doTranstion() , 这是最底层的逻辑。

两个虚方法:doBeforeExit() doBeforeEnter()
子类可以选择性实现进入状态前做的事情 、 离开当前状态前做的事情

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
/// <summary>
/// AI状态的基类
/// 1、状态名字
/// 2、状态转换
/// </summary>
/// 
public enum ETransition
{
    None,
    Idle2Follow,
    Idle2Attack,
    Idle2Flee,
    Idle2Patrol,
    Follow2Attack,
    Follow2Idle,
    Attack2Follow,
    Attack2Idle,
    Attack2Flee,
    Patrol2Attack,
    Patrol2Follow,
    Flee2Idle,
    Flee2Dead,
    Any2Dead

}
public enum EAIState
{
    Idle,//闲置
    Follow,
    Attack,
    Dead,
    Patrol,//巡逻
    Flee,//逃跑
}

public abstract class StateBase
{
    //游戏对象上的脚本组件
    protected MonsterController controller;
    //AI所在的游戏对象的Transform
    protected Transform transSelf;
    //目标所在的游戏对象的Transform
    protected Transform transTarget;


    //动画名字,使用老版本的动画
    protected string animationName;
    //AI状态
    protected EAIState stateName;
    public string Name
    {
        get
        {
            return Enum.GetName(typeof(EAIState), stateName);
        }
    }

    //转换状态
    Dictionary<ETransition, StateBase> dic = new Dictionary<ETransition, StateBase>();
    //目标和自己之间距离
    protected float distance;


    public StateBase(MonsterController controller)
    {
        this.controller = controller;
        transSelf = controller.transform;
        transTarget = GameObject.FindWithTag("Player").transform;
    }
    /// <summary>
    /// 添加数据
    /// </summary>
    /// <param name="transition">转换线</param>
    /// <param name="state">转换过去的状态</param>
    public void addTransition(ETransition transition, StateBase state)
    {
        if(!dic.ContainsKey(transition))
        {
            dic[transition] = state;
        }
    }
    public void delTransition(ETransition transition)
    {
        if (dic.ContainsKey(transition))
        {
            dic.Remove(transition);
        }
    }
    /// <summary>
    /// 查找状态
    /// </summary>
    /// <param name="trans">转换线</param>
    /// <returns></returns>
    public StateBase getState(ETransition trans)
    {
        if(dic.ContainsKey(trans))
        {
            return dic[trans];
        }
        return null;
    }
    /// <summary>
    /// 当前状态的行为
    /// </summary>
    public abstract void action();
    /// <summary>
    /// 条件转换的理由
    /// </summary>
    public virtual void reason()
    {
        distance = Vector3.Distance(transSelf.position, transTarget.position);
    }
    /// <summary>
    /// 离开当前状态之前做的事情
    /// </summary>
    public virtual void doBeforeExit() { }
    /// <summary>
    /// 进入当前状态做的事情
    /// </summary>
    public virtual void doBeforeEnter() { }
}

状态管理器StateManager

addState(StateBase state)
这个方法实现往容器listState中添加状态
声明curState用来引用当前容器里面的某一状态

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 状态管理器
/// 某一时刻只有一个状态在执行
/// 增,删,改,查
/// </summary>
public class StateManager
{
    List<StateBase> listState = new List<StateBase>();
    //当前状态的引用,引用listState容器中的某一个状态
    public StateBase curState;
    /// <summary>
    /// 往容器中添加状态
    /// </summary>
    /// <param name="state"></param>
    public void addState(StateBase state)
    {
        if (state == null) return;
        if(!listState.Contains(state))
        {
            listState.Add(state);
        }
        //把第一个添加进来的状态当成默认状态
        if (curState==null)
        {
            curState = state;
        }
    }

    public void setDefaultState(StateBase state)
    {
        if(state!=null)
        {
            curState = state;
            curState.doBeforeEnter();
        }
       
    }

    public void deleState(StateBase state)
    {
        if (state == null) return;
        if (!listState.Contains(state))
        {
            listState.Remove(state);
        }
    }
    /// <summary>
    /// 切换状态
    /// </summary>
    /// <param name="trans">切换线</param>
    public void doTransition(ETransition trans)
    {
        StateBase nextState= curState.getState(trans);
        if(nextState!=null)
        {
            //执行状态离开之前的函数(数据还原,一次性操作的逻辑)
            curState.doBeforeExit();
            //切换状态
            curState = nextState;
            //下个状态开始之前执行的函数(数据还原,一次性操作的逻辑)
            curState.doBeforeEnter();
        }
    }

    public void OnUpdate()
    {
        if(curState!=null)
        {
            Debug.Log(curState.Name);
            curState.action();
            curState.condition();
        }
    }
}

怪物控制类:

这是一个Mono类 , 把这个类挂载到游戏物体上运行时脚本才开始跑, 所以在start()函数中运行makeFSM() , 初始化状态管理器类中的字典的数据 , 想要添加状态时只要往里面添加对应状态的addTranstion()方法就可以。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class MonsterController : MonoBehaviour
{
    //随机点的距离
    public float randomDistance = 5.0f;
    public float fleeDistance = 5.0f;//逃跑距离
    public float speed=2.0f;
    public float speedAngle=360.0f;
    public float attackDistance = 2.0f;
    public float followDistance = 6.0f;
    public float hp = 100;
    //CharacterController cc;
    NavMeshAgent agent;
    Animation anim;
    StateManager stateMng;

    public string[] animAttackNames =
    {
        "Attack",
        "Attack Paw"
    };
    public Vector3 RandomPos
    {
        get
        {
            Vector3 dir= Random.onUnitSphere;
            dir.y = 0;
            dir.Normalize();
            Vector3 targetPos = transform.position + dir * randomDistance;
            GameObject go= GameObject.CreatePrimitive(PrimitiveType.Sphere);
            go.transform.position = targetPos;
            go.transform.localScale = Vector3.one * 0.1f;
            agent.SetDestination(targetPos);
            return targetPos;
            //NavMeshHit hit;
            //if(agent.SamplePathPosition(1 << LayerMask.NameToLayer("Map"), 20, out hit))
            //{
            //    return hit.position;
            //}

            //return  Vector3.zero;
        }
    }
    public void doTransition(ETransition trans)
    {
        stateMng.doTransition(trans);
    }
    public void playAnimation(string name)
    {
        //可以执行到动画最后
        anim.Play(name);
        //动画之间的过渡(不会执行到上一个动作的最后)
        //anim.CrossFade(name);
    }

    public Vector3 getRandomPos()
    {
        return Vector3.zero;
    }
    public void stopMove()
    {
        agent.isStopped = true;
    }
    public void move(Vector3 pos)
    {
        agent.isStopped = false;
        agent.SetDestination(pos);
    }
    public void playAttack()
    {
        int index = Random.Range(0, animAttackNames.Length);
        print("playAttack:"+ animAttackNames[index]);
        anim.Stop();
        anim.Play(animAttackNames[index]);
    }
    
    public void rotateToTarget(Transform target)
    {

    }
    // Start is called before the first frame update
    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        anim = GetComponent<Animation>();
        agent.speed = speed;
        agent.angularSpeed = speedAngle;
        makeFSM();
    }
    void makeFSM()
    {
        //生成AI状态管理器
        stateMng = new StateManager();
        //生成所有的AI状态
        Idle idle = new Idle(this);
        Attack attack = new Attack(this);
        Follow follow = new Follow(this);
        Patrol patrol = new Patrol(this);
        Flee flee = new Flee(this);
        Dead dead = new Dead(this);
        stateMng.addState(idle);
        stateMng.addState(attack);
        stateMng.addState(follow);
        stateMng.addState(patrol);
        stateMng.addState(flee);
        stateMng.addState(dead);
        stateMng.setDefaultState(patrol);
        //每一个状态的转换条件
        idle.addTransition(ETransition.Idle2Attack, attack);
        idle.addTransition(ETransition.Idle2Follow, follow);
        idle.addTransition(ETransition.Idle2Flee, flee);
        idle.addTransition(ETransition.Any2Dead, dead);
        idle.addTransition(ETransition.Idle2Patrol, patrol);

        follow.addTransition(ETransition.Follow2Attack, attack);
        follow.addTransition(ETransition.Follow2Idle, idle);
        follow.addTransition(ETransition.Any2Dead, dead);


        attack.addTransition(ETransition.Attack2Follow, follow);
        attack.addTransition(ETransition.Attack2Idle, idle);
        attack.addTransition(ETransition.Attack2Flee, flee);
        attack.addTransition(ETransition.Any2Dead, dead);

        flee.addTransition(ETransition.Flee2Idle, idle);
        flee.addTransition(ETransition.Flee2Dead, dead);
        flee.addTransition(ETransition.Any2Dead, dead);
        
        patrol.addTransition(ETransition.Patrol2Attack, attack);
        patrol.addTransition(ETransition.Patrol2Follow, follow);
    }
    // Update is called once per frame
    void Update()
    {
        if(stateMng!=null)
        {
            stateMng.OnUpdate();
        }
    }
}

以上框架差不多了 只剩下在各个状态中重写抽象方法和虚方法

Attack类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Attack : StateBase
{
    float attackPerTime;//攻击频率
    float curTime;
    float timer = 0;


    public Attack(MonsterController controller):base(controller)
    {
        animationName = "Attack";
        stateName = EAIState.Attack;
    }
    public override void doBeforeEnter()
    {
        controller.stopMove();
        controller.playAttack();
    }
    public override void action()
    {
        //有条件的播放攻击动画
        //curTime -= Time.deltaTime;
        //if(curTime<=0)
        //{
        //    controller.playAnimation(animationName);
        //    curTime = attackPerTime;
        //}

        transSelf.LookAt(transTarget);

    }

    public override void condition()
    {
        base.condition();
        if (controller.hp <= 0)
        {
            controller.doTransition(ETransition.Any2Dead);
        }
        else if (controller.hp<=30)
        {
            //有一定的几率逃跑
            controller.doTransition(ETransition.Attack2Flee);
            return;
        }
        if (distance > 2.0f&&distance<6.0)//追寻范围
        {
            //切换攻击状态
            controller.doTransition(ETransition.Attack2Follow);
        }
        else if (distance > 6.0f)//idle状态
        {
            controller.doTransition(ETransition.Attack2Idle);
           
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Unity中的有限状态机(FSM)是一种流程控制方法,它将一个对象或一个系统划分为多个状态,并且根据一定的条件和规则来决定何时转换到另一个状态。FSM在游戏开发中非常常见,因为游戏中有许多对象都需要根据不同的条件和规则进行状态转换,例如玩家角色、敌人AI、动画控制等。 在Unity中,可以通过编写脚本来实现有限状态机。常见的实现方式是使用枚举类型来定义不同的状态,并在脚本中编写转换条件和规则。例如,以下是一个简单的有限状态机脚本示例: ``` public enum PlayerState { Idle, Walk, Run, Jump, Attack } public class Player : MonoBehaviour { private PlayerState currentState; void Start() { currentState = PlayerState.Idle; } void Update() { switch(currentState) { case PlayerState.Idle: if(Input.GetKeyDown(KeyCode.Space)) { currentState = PlayerState.Jump; } else if(Input.GetAxis("Horizontal") != 0) { currentState = PlayerState.Walk; } break; case PlayerState.Walk: if(Input.GetKeyDown(KeyCode.Space)) { currentState = PlayerState.Jump; } else if(Input.GetAxis("Horizontal") == 0) { currentState = PlayerState.Idle; } else if(Input.GetKey(KeyCode.LeftShift)) { currentState = PlayerState.Run; } break; case PlayerState.Run: if(Input.GetKeyDown(KeyCode.Space)) { currentState = PlayerState.Jump; } else if(Input.GetAxis("Horizontal") == 0) { currentState = PlayerState.Idle; } else if(!Input.GetKey(KeyCode.LeftShift)) { currentState = PlayerState.Walk; } break; case PlayerState.Jump: if(transform.position.y < 0) { currentState = PlayerState.Idle; } break; } } } ``` 在这个例子中,定义了五种不同的玩家状态:Idle、Walk、Run、Jump和Attack。在Start方法中,将当前状态设置为Idle。在Update方法中,根据当前状态不同的转换条件和规则,决定何时转换到另一个状态。例如,如果当前状态为Idle,并且玩家按下了空格键,就会转换到Jump状态。如果当前状态为Walk,并且玩家没有按下左Shift键,就会转换到Idle状态。 通过这种方式,可以轻松实现复杂的状态转换逻辑,从而使游戏对象在不同的状态下具有不同的行为和动画效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值