使用面向对象的思想写有限状态机

使用面向对象的思想写有限状态机

好久好久没更新了,现在俺又回来了嘻嘻。
——————————————————————分割线————————————————————————————
今天要讲的呢,是通过使用面向对象的思想,写一个简单的有限状态机。说起面向对象,相信大多数人都不陌生,虽然在一开始学这种思想的时候会有点难,而且因为抽象思维很重,所以一开始也不太敢去真的使用这种思想,但是我觉得,其实只要慢慢多去写代码,多去理解使用类,接口等的意义,后面学起来还是可以很容易上手的,而且会有一种,万物皆可面向对象的感觉。
至于什么是对象这种有点哲学的问题我就不回答了,但是有一点,就是时刻要问自己什么是“类”,什么是“接口”。我记得我曾经在一次实习的时候,他们的技术总监问了我一个问题——什么是类??然后我回答了一个——Class呗。反正现在想起来怪怪的,因为之前对面向对象这种思想不是很通透,学的不够深入,能有这种回答也是正常。但是在C#等一些编程语言中,类的确就是class。
在这里的话,我觉得可以这样理解,类就是一些具有相同性质的事务的总称。比如在我们开发游戏的时候,为角色写攻击的代码,角色可能有不同的技能或者说攻击方式,但是这些技能和攻击方式都是会造成伤害或者是会消耗魔法值,这就是他们的一些共性,然后我们可能就会写攻击类,或者是技能类,要用的时候只要调用或者继承。
好了,废话不多说,我们开始进行我们的有限状态机的开发吧。这里的有限状态机我们可以引用unity维基百科的一个案例来进行操作和学习。原地址:unity有限状态机
首先打开unity,新建两个脚本,一个取名为FSMState,一个取名为FSMSystem,然后打开两个脚本。
在这里插入图片描述
好,建好了两个脚本,先问一个问题:什么是有限状态机???
————————————————————10秒钟冥想————————————————————————————
冥想结束。有限状态机,顾名思义,就是拥有有限个状态的机器,FSM的全称是Finite State Machine。那么根据有限状态机的意思,我们便可以推测出,状态个数永远会大于等于1个,且小于n。而且所有状态都是某个事物身上发生的,状态之间都是有类似的共性。于此,我们便有FSMstate这个类文件,它主要是所有状态的一个抽象化,并不是具体的状态,**但它有状态需要的方法或者条件。**而FSMSystem则是管理所有状态的一个管理器,也就是状态管理器系统。理解了这两个的意义,对写代码会有很大的帮助。
————————————————————分割线————————————————————————————————
现在呢,我们通过为一个NPC怪物写巡逻状态机让它动起来。在unity商店下载一个怪物的资源包:Dragon the Terror Bringer and Dragon Boar,然后导入,选择到一个做好的龙的预制体,把动画组件去掉,改名为NpcDragon,然后调整摄像机位置,这个龙就是我们要为它写状态的对象。
在这里插入图片描述
场景布置好了,我们再回到它的一个动画目录里,看一下它有哪些动画
在这里插入图片描述
好像动画还挺多的,不过没事,我们先画一个简单的图:
在这里插入图片描述
根据上面的图,其实我们发现,主要的三种状态无非是攻击,移动,静止,然后再添加其他的一些别的状态。但是这些状态之间的转换是有条件的,比如攻击状态,必须要在攻击范围之内且主角是活着的才能攻击。根据这样的一种构思,我们开始写FSMState的脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 状态类
/// </summary>
public enum Transition//状态所对应的一个转换条件,也可以理解为判断是否可以进入该状态的一个条件
{
NullTransition=0,
LostPlayer,//失去玩家目标,继续巡逻
Rest,//进行休息,对应静止待命
Getplayer,//发现目标,进入攻击状态
}
public enum StateID//四个主要状态的ID,我在这里进行了简化处理
{
NullStateID=0,
Patrol,//巡逻
Idle,//静止状态
Attack,//处于攻击状态,即发现了目标准备进行攻击

}
public abstract class FSMState//抽象类
{
    protected FSMSystem fsm;//定义一个状态管理器
    //定义一个字典,用来保存状态对应的条件和状态本身
    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    //封装状态ID
    protected StateID stateID;
    public StateID ID { get { return stateID; } }

    public FSMState(FSMSystem fsm)//构造函数初始化,绑定状态管理器
    {
        this.fsm = fsm;
    }

    public void AddTransition(Transition trans, StateID id)//进行初始化状态,同时把该状态绑定到状态机中
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("该状态条件为空");
            return;
        }
        if (id == StateID.NullStateID)
        {
            Debug.LogError("该状态为空");
            return;
        }
        if (map.ContainsKey(trans))
        {
            Debug.LogError("已经实例化一次该状态,不能再次添加");
            return;
        }

        map.Add(trans, id);//如果该状态完整且拥有对应的条件,则进行添加
        if (fsm != null)//这一部可以精简操作,比官方的更方便一点
        {
            //Debug.Log(this);
            fsm.AddState(this);
        }
    }
    public void DeleteTransition(Transition trans)//删除状态
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("该状态为空");
            return;
        }
        if (map.ContainsKey(trans))
        {
            map.Remove(trans);
            return;
        }
    }
    public StateID GetOutputState(Transition trans)//根据状态所对应的条件,来获取对应的状态ID
    {
        foreach (FSMState state in fsm.states)//遍历管理器的所有状态
        {
            if (state.map.ContainsKey(trans))//判断状态汇中是否含有该字典的键值
            {
                return state.map[trans];
            }

        }
        return StateID.NullStateID;
    }
    public virtual void DoBeforeEntering() { }//进入下一状态时进行的操作
    public virtual void DoBeforeLeaving() { }//离开当前状态时可以进行的操作

    public abstract void Act(GameObject player, GameObject npc);//具体的状态实际操作
    public abstract void Reason(GameObject player, GameObject npc);//根据条件改变,更改状态
}

然后是FSMSystem的脚本:

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

public class FSMSystem 
{
    public List<FSMState> states;//保存所要管理的状态的链表

    private FSMState currentState;
    public FSMState CurrentState { get { return currentState; } }//当前运行的状态

    private StateID currentStateID;
    public StateID CurrentStateID { get { return currentStateID; } }//当前状态的ID
    public FSMSystem()//实例化
    {
        states = new List<FSMState>();
    }
    public void AddState(FSMState s)//进行添加状态的函数
    {
        if (s == null)
        {
            Debug.LogError("状态为空");
        }
        if (states.Count == 0)//初始化当前状态
        {
            states.Add(s);
            currentState = s;
            currentStateID = s.ID;
            return;
        }
        foreach (FSMState state in states)
        {
            if (state.ID == s.ID)
            {
                Debug.LogError("已存在该状态的类");
                return;
            }
        }
        states.Add(s);
    }
    public void DeleteState(StateID id)
    {
        if (id == StateID.NullStateID)
        {
            Debug.LogError("不存在空状态,无法删除");
            return;

        }
        foreach (FSMState state in states)
        {
            if (state.ID == id)
            {
                states.Remove(state);
                return;
            }
        }
    }//删除状态
    public void PerformTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("该状态条件为空");
            return;
        }
        StateID id = currentState.GetOutputState(trans);//获得条件对应的状态ID
        if (id == StateID.NullStateID)
        {
            Debug.LogError("不存在该状态");
            return;
        }
        currentStateID = id;
        foreach (FSMState state in states)
        {
            if (state.ID == currentStateID)
            {
                currentState.DoBeforeLeaving();
                currentState = state;
                currentState.DoBeforeEntering();
                break;
            }
        }

    }//根据状态条件更改状态
    public void Update(GameObject player, GameObject npc)//具体的执行
    {
        currentState.Act(player, npc);
        currentState.Reason(player, npc);
    }

}
	

然后新建四个脚本,三个为状态脚本,一个是要挂到龙身上的脚本。
在这里插入图片描述
然后分别打开这些脚本,我们先进行简单的测试,看一下状态机能不能驱动,脚本如下:
Attack脚本:

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

public class Attack : FSMState//继承抽象类
{
    public Attack(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Attack;//初始化该状态的ID
    }
    public override void Act(GameObject player, GameObject npc)//操作行为
    {
        Debug.Log("攻击中");
    }

    public override void Reason(GameObject player, GameObject npc)//改变状态的方法
    {
        if (Input.GetKeyDown(KeyCode.Q))//当按下Q键时,改变状态为巡逻
        {
            fsm.PerformTransition(Transition.LostPlayer);//根据改变状态的条件,设置当前状态
        }
        if (Input.GetKeyDown(KeyCode.W))
        {
            fsm.PerformTransition(Transition.Rest);
        }
      
    }

    
}

Idle脚本:

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

public class Idle : FSMState
{
    public Idle(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Idle;
    }
    public override void Act(GameObject player, GameObject npc)
    {
        Debug.Log("静止中");
    }

    public override void Reason(GameObject player, GameObject npc)
    {
     
        if (Input.GetKeyDown(KeyCode.E))
        {
            fsm.PerformTransition(Transition.Getplayer);
        }
        if (Input.GetKeyDown(KeyCode.Q))
        {
            fsm.PerformTransition(Transition.LostPlayer);
        }
    }

   
}

Patrol脚本:

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

public class Patrol : FSMState
{
    public Patrol(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Patrol;
    }
    public override void Act(GameObject player, GameObject npc)
    {
        Debug.Log("巡逻中");
    }

    public override void Reason(GameObject player, GameObject npc)
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            fsm.PerformTransition(Transition.Rest);
        }
        if (Input.GetKeyDown(KeyCode.E))
        {
            fsm.PerformTransition(Transition.Getplayer);
        }
    }

  
}

然后打开Dragon脚本:

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

public class Dragon : MonoBehaviour
{
    FSMSystem fsm;
    void Start()
    {
        fsm = new FSMSystem();
        Attack a = new Attack(fsm);
        a.AddTransition(Transition.Getplayer, StateID.Attack);
        Idle i = new Idle(fsm);
        i.AddTransition(Transition.Rest, StateID.Idle);
        Patrol p = new Patrol(fsm);
        p.AddTransition(Transition.LostPlayer, StateID.Patrol);

    }

    // Update is called once per frame
    void Update()
    {
        fsm.Update(this.gameObject,this.gameObject);
    }
}

上面的代码是用来测试的,不是最终的状态机,具体的实现功能还没有实现,不过此时我们已经可以运行游戏试一下:
当我们按下QWE键时,便看到状态之间的切换,说明状态类和状态管理器已经可以运行,然后下一节我们来具体完善。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ToDoNothing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值