unity开发中的命令模式与有限状态机综合应用

在游戏开发领域,命令模式和有限状态机是两种常见的设计模式,它们分别用于处理请求的封装和对象状态的管理。本文将介绍如何在 Unity 开发中将命令模式与有限状态机相结合,从而实现敌人 AI 的功能。

命令模式

封装

“命令模式”既然叫做了命令,那肯定有一个 key 用于标识命令,还要有对应命令的功能。常用的 key 有两种:字符串 string 和枚举 enum。字符串的优点在于灵活,而枚举则比较死板(同时这也是它的优点,至少能知道 key 在什么范围)。这里使用字符串作为 key 来标识命令。所有的命令基本上都有四个功能(有的可能不需要):初始化、执行、退出、撤销。那么可以将这四个功能提取出来,封装成接口。使用泛型作为函数参数可以灵活运用在多个场景中,并且命令可以拿到命令发出者本身的属性。

public interface ICommand<T>
{
    void Init(T obj);
    void Execute(T obj);
    void Exit(T obj);
    void UoDo(T obj);
}

具体的命令,具体实现。

public class Move : ICommand<PlayerCharacterController>
{
    public void Init(PlayerCharacterController obj) => obj.animator.SetBool("isMove", true);
    
    public void Execute(PlayerCharacterController obj) => obj.animator.SetBool("isMove", true);
    
    public void Exit(PlayerCharacterController obj) => obj.animator.SetBool("isMove", false);
    
    public void UoDo(PlayerCharacterController obj){}
}

为保证命令可以跨类使用,这里可以将对命令的管理封装成一个公共管理类出来。

public class CmdManager<T>
{
    public ICommand<T> curCmd
    {
        get; private set;
    }
    private static Dictionary<string, ICommand<T>> cmds = new Dictionary<string, ICommand<T>>();
    public void AddCmd(string name, ICommand<T> cmd)
    {
        if (cmds.ContainsKey(name))
        {
            Debug.LogWarning($"索引{name}已存在");
        }
        else
        {
            cmds.Add(name, cmd);
        }
    }
    public void ReplaceCmd(string name, ICommand<T> cmd)
    {
        if (cmds.ContainsKey(name))
        {
            cmds[name] = cmd;
        }
        else AddCmd(name, cmd);
    }
    public void RemoveCmd(string name)
    {
        if (cmds.ContainsKey(name))
        {
            cmds.Remove(name);
        }
        else Debug.LogWarning($"索引命令<{name}>并不存在");
    }
    public void SwitchCmd(string name)
    {
        if (cmds.ContainsKey(name))
        {
            curCmd = cmds[name];
        }
        else Debug.LogWarning($"索引命令<{name}>并不存在");
    }
}

你也可以添加一个栈用于存储使用过的命令,实现回溯操作,这里就不过多的介绍了。

使用

既然已经介绍了命令的封装,那肯定要说说命令的使用了。下面以一个简单的代码来介绍如何使用命令。

public class PlayerCharacter : MonoBehaviour
{
    private Animator anim;
    public int curJumpNum = 2;
    public CmdManager<PlayerCharacter> cmr = new CmdManager<PlayerCharacter>();
    public ICommand<PlayerCharacter> state
    {
        get => cmr.curCmd;
        set => state = value;
    }
    public class PlayerJump : ICommand<PlayerCharacter>
    {
        public void Init(PlayerCharacter obj){}
        public void Execute(PlayerCharacter obj)
        {
            if (obj.curJumpNum <= 0) return;
            obj.anim.Play("Jump");
            obj.curJumpNum--;
        }

        public void Exit(PlayerCharacter obj) => obj.anim.SetBool("isJump", false);
       
        public void UoDo(PlayerCharacter obj){}
 }
    void Start()
    {
        anim.GetComponent<Animator>();
        cmr.AddCmd("jump", new PlayerJump());
    }
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            cmr.SwitchCmd("jump");
            curCmd.Execute(this);
        }
    }
}

当按下空格键时,角色会跳起,并且具有二段跳功能。在命令中可以获取命令发出者的变量,因此你可以将一些判断逻辑写到命令中去。

有限状态机

想一想,是不是每个状态都有三个过程:开始,更新,结束(没有撤销)。这与命令是不是很像?如果你能想到这点,那么你基本上已经掌握了命令模式。既然状态机和命令很像,可以封装一个状态基类继承命令接口,就可以使用之前命令模式封装好的功能了。

public abstract class StateBase<T> : ICommand<T>
{
    public void Init(T obj) => OnBegin(obj);

    public void Execute(T obj) => OnUpdate(obj);

    public void Exit(T obj) => OnEnd(obj);
    
    public void UoDo(T obj) { }
    public abstract void OnBegin(T obj);
    public abstract void OnUpdate(T obj);
    public abstract void OnEnd(T obj);
}

每次实现状态只要继承 StateBase 就可以了,并且可以使用 CmdManager 来管理状态(当然,也可以直接使用命令来实现状态,但这每次实现都会多一个函数不是吗)。
ps:由于篇幅有限,只能介绍这么多了,如果有更好的方法,欢迎评论。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值