Unity简单有限状态机实现

前言】 本篇来说一下关于简单有限状态机的算法实现,当然我们的几个状态会比较简单,而且本身我也是处于入门状态,所以当成一个简单的知识积累。有限状态机,顾名思义,状态是有限个的,而且状态之间是关联的,本篇写的状态机,其实是类似写游戏里面的AI机器人,就是那些游戏里面的怪啊,npc啊,本篇也是针对几个行为或者是状态,进行设计,编写相关脚本。
1.新建·一个项目,再资源商店搜索Zombie,导入敌人的模型和动画,搜索Yurowm导入玩家的模型和动画,至于场景,搜索_Barking_Dog,导入场景 Unity简单有限状态机实现_第1张图片
2.新建之后,先把这个放一边,首先我们来分析怪物状态以及过渡条件
如下图:
Unity简单有限状态机实现_第2张图片
3.根据以上简单的集中关系组合,我们来进行设计。首先新建一个文件夹,取名Animators,创建一个动画控制器,把Zombie文件夹下面的动画idle,attack,walk拉进动画控制器,设置以下关系以及新建参数。至于动画之间的过渡,idle过渡到walk和attack,就设置相应的为true就可以了,反之回到idle设置为false,而walk到attack或者是attack到walk,则设置目标为true,自身为false。这样,简单的控制器就完成了。
Unity简单有限状态机实现_第3张图片
4.分析敌人获取主角的算法,这里我采用的是获取角度获取视野的方式,当然这个不限于这种简单的方式,我这里就简单用一下这种算法。获取主角的原理如下,很简单:
Unity简单有限状态机实现_第4张图片
判断角度a即可,然后判断距离,获取是否在攻击范围之内。
5。还有一个,在移动的方式上,我选择了unity自带的AI寻路移动,所以不可避免地要在Zombie的模型上添加一个Nav Mesh Agent组件,到时候直接调用组件移动即可。把_Barking_Dog文件下的Test_Map拉进场景,然后把_Level和摄像机拉进我们自己的Scene,当然原来的摄像机和灯光要删除。检查一下_Level下面的子物体Floor_01是否都设置为静态,如果是就可以开始烘焙寻路了。
6.为Zombie添加Animator组件,把控制器赋上,然后在资源库里面找到Yurowm的模型,拉进场景里面,取名Player,设置标签为Player。
7.接下来上脚本,新建一个脚本为FsmDemo,赋给敌人,解释在脚本里面:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public enum EnemyState//状态枚举
{
Patrol,//巡逻
Attack,//攻击
Rest,//休息
Chase//追击

}
public class FsmDemo : MonoBehaviour {

    public EnemyState enemyState;
    private Transform Player;//主角

    public float EnemyRestTime = 3;//控制休息时间
    private float RestTime;//休息时间

    private Animator EnemyAnimator;//动画控制器

    public float MaxSightDistance = 10;//最大的视野范围
    public float CanAttackDistance = 1;//攻击范围

    public Transform MoveTarget;//最终要移动到的目标
    public Transform point;//路点父物体
    public List Points = new List();//路点的链表数组

    private NavMeshAgent m_navmeshment;//寻路组件

    private int index = 0;//路点索引
    public bool canmove = true;//是否可以移动
    void Start() {
        m_navmeshment = GetComponent();//获取寻路组件
        RestTime = EnemyRestTime;//初始化休息时间
        EnemyAnimator = GetComponent();//获取动画控制器
        Player = GameObject.FindWithTag("Player").transform;//获取主角
        enemyState = EnemyState.Rest;//初始化状态

        for (int i = 0; i < point.childCount; i++)//获取父物体下面的路点
        {
            Points.Add(point.GetChild(i));
        }
        MoveTarget = Points[0];//初始化移动目标
        if (m_navmeshment == null || Player == null || EnemyAnimator == null)//以防出错
        {
            Debug.LogError("未添加完整组件或者是标签");
            enabled = false;
            return;
        }
      
	}
	
	// Update is called once per frame
	void Update () {

        DealWithEnemyState();
        //Debug.DrawLine(transform.position, transform.forward, Color.red);
        //Debug.DrawLine(transform.position, Player.position - transform.position, Color.red);
	}
    private void DealWithEnemyState()//处理状态的方法
    {
        switch (enemyState)
        {
            case EnemyState.Patrol://巡逻
            
                OnPatrolState();
                ChangePatrolToOtherState();

                break;
            case EnemyState.Attack://攻击
                OnAttackState();
                ChangeAttackToOtherState();

                break;
            case EnemyState.Rest://休息
                OnRestState();
                ChangeRestToOtherState();
                break;
            case EnemyState.Chase://追击
                OnChaseState();
                ChangeChaseToOtherState();
                break;
            default:
                break;
        }
    }
    private void OnRestState()//处理休息
    {
        RestTime -= Time.deltaTime;//休息时间倒计时
        EnemyAnimator.SetBool("walk", false);
        EnemyAnimator.SetBool("attack", false);//播放动画
        m_navmeshment.speed = 0;//移动速度设置为0
      
    }
    private void OnAttackState()//处理攻击
    {
        
        m_navmeshment.speed = 0;//速度设置为0
        EnemyAnimator.SetBool("attack", true);
        EnemyAnimator.SetBool("walk",false);//播放动画
        
    }
    private void OnChaseState()//处理追击
    {
      
        EnemyAnimator.SetBool("walk", true);
        EnemyAnimator.SetBool("attack", false);//播放动画
        MoveTarget = Player;//设置目标位Player
        m_navmeshment.speed = 0.1f;//设置移动速度
        MoveToTarget(MoveTarget);//开始移动追击
    }

 
    private void OnPatrolState()//处理巡逻
    {
        //移动到目标点,进行位移
        m_navmeshment.speed = 0.1f;
       
        if (canmove)
        {
            MoveToTarget(MoveTarget);
        }
        EnemyAnimator.SetBool("walk", true);
        EnemyAnimator.SetBool("attack", false);
        //播放移动动画
    }
    private void ChangeRestToOtherState()//休息状态到别的状态
    {
        
        if (IsOnSightOfEnemy())//在视野范围内
        {
            MoveTarget = Player;   //设置目标MoveToTarget
            print("在视野内");
            if (IsOnAttackDistance())//在攻击范围内
            {
                print("在攻击范围内");
                enemyState = EnemyState.Attack;//改变状态
            }
            else
            {
                print("不在攻击范围内");
                enemyState = EnemyState.Chase;
            }
        }
        else//不在视野范围内
        //判断倒计时,且目标点还是巡逻点
        {
            //设置目标巡逻点
            if (RestTime <= 0)//倒计时结束
            {
                RestTime = EnemyRestTime;//重置倒计时时间
                canmove = true;//可以移动
                if (index == Points.Count - 1)//循环路点
                {
                    index = 0;
                }
                else
                {
                    index++;
                }
                MoveTarget = Points[index];
                enemyState = EnemyState.Patrol;//改变状态

            }
            else//倒计时未结束,不能移动
            {
                canmove = false;
            }
        }
    }
    private void ChangeAttackToOtherState()
    {
        //时刻监视状态
        if (IsOnSightOfEnemy())
        {
            MoveTarget = Player;   //设置目标MoveToTarget
            print("在视野内");
            if (IsOnAttackDistance())
            {
                print("在攻击范围内");
                enemyState = EnemyState.Attack;
            }
            else
            {
                print("不在攻击范围内");
                enemyState = EnemyState.Chase;
            }
        }
        else
        {
            enemyState = EnemyState.Patrol;//不在视野内,也就是说主角逃走了,设置为巡逻
        }
        //判断血量
        //判断目标
    }//攻击到别的状态
    private void ChangeChaseToOtherState()
    {
        if (IsOnSightOfEnemy())//在视野内
        {
            MoveTarget = Player;   //设置目标MoveToTarget
            print("在视野内");
            if (IsOnAttackDistance())
            {
                print("在攻击范围内");
                enemyState = EnemyState.Attack;
            }
            else
            {
                print("不在攻击范围内");
                enemyState = EnemyState.Chase;
            }
        }
        else
        {
            MoveTarget = Points[index];
            enemyState = EnemyState.Patrol;

        }
    }//追击到别的状态
    private void ChangePatrolToOtherState()
    {
        //监测状态
        if (IsOnSightOfEnemy())
        {
            MoveTarget = Player;   //设置目标MoveToTarget
            print("在视野内");
            if (IsOnAttackDistance())
            {
                print("在攻击范围内");
                enemyState = EnemyState.Attack;
            }
            else
            {
                print("不在攻击范围内");
                enemyState = EnemyState.Chase;
            }
        }
        else
        {
        
            float dis = Vector3.Distance(transform.position, MoveTarget.position);
            if (dis < 0.5f)
            {
                enemyState = EnemyState.Rest;
            }
        }
        //如果不在视野内,目标还是巡逻点
    }//巡逻到别的状态
    private bool IsOnSightOfEnemy()//是否在可视范围内
    {
        bool onsight=false;
        float dis = Vector3.Distance(Player.position, transform.position);
        float angle = Vector3.Angle(transform.forward, Player.position - transform.position);
        if (dis < MaxSightDistance)
        {
            if (angle < 70)
            {
                onsight = true;
            }
        }
        return onsight;
          
      
    }
    private bool IsOnAttackDistance()//判断是否在攻击范围内的方法
    {
        if (IsOnSightOfEnemy())
        {
            float dis = Vector3.Distance(Player.position, transform.position);
            if (dis < CanAttackDistance)
            {
                return true;
            }
        }
        return false;
    }
    private void MoveToTarget(Transform target)//移动的方法
    {
        m_navmeshment.SetDestination(target.transform.position);
    }

}

8.回到Unity中,设置巡逻路点,我这里设置3个点,其实设置多少个都可以,算法里会进行循环执行,如下图
Unity简单有限状态机实现_第5张图片
9.把父路点赋给FsmDemo脚本里面的point,调整Zombie的位置,运行。主角在这里没有进行脚本控制,所以你可以在Scene面板中移动主角进行测试。
10.总结:其实总结下来,这种简单的有限状态机实现起来很简单,但是遇到复杂一点的状态机,就要考虑优化的问题了。所以说,学习的时候要有很强的逻辑性,也要研究一下自己的实现方式,是用什么模式实现,毕竟如果真是做项目,需要团队合作,这一点还是要考虑的。

你可能感兴趣的:(Unity开发,游戏开发)