说一下这个给女朋友讲设计模式 之 状态模式

今天晚上跑步太晚了,回去已经11:30了

她不喜欢跑步,只喜欢和我压马路,如果我和她说:今晚出去转转吧,她就很开心的跟上来.

如果我换衣服换鞋,手机放桌上,是的.我出去跑步不带手机.她就朝这瞄一眼,然后自己抱着毛绒玩具,打开ipad,看<人世间>了.也有的时候看<鸡毛飞上天>,她不喜欢大部分脑残国产剧,只看几个口碑不错的.

回来了之后,洗完澡,再把餐桌收拾一下.切点水果,热杯牛奶给她.已经快12点了.

敲门,进她房间之后,看她已经洗完澡,刷过牙了,躺着了,夜灯柔柔的亮着,满天星.我问她,水果和牛奶还吃吗?

她说:不了,我想睡觉了.说完就盖好被子,然后又说,把灯关一下.

于是,我就把水果和牛奶拿到我的房间, 想了想,还是到书房吧,好久没做游戏了.

打开电脑,准备思索一番敲代码的时候,她抱着公仔走来.流氓兔,是的,她巨喜欢流氓兔

还记得第一次见她的时候,她兴高采烈的和我介绍,这是眼镜盒上的,这是包包上的,

然后好像在自言自语的说:迷死它了迷死它了.

她往我腿上一坐,装作很困的样子,然后圈主我的脖子,此时,流氓兔的兔毛一直在刺激我的鼻子,我好想打喷嚏.

我拍拍她的背,摸了摸她的头发,她也转头看向电脑,看到了电脑上一个AI模型,她问:这个是什么?

我回答说,这是个AI,我正在想用什么方法去控制它

她又问:都有什么方法可以控制它?这个好丑啊.

我哈哈一笑,说:一般的,在游戏开发中,用于驱动AI的只有两种常用的,一种是状态机,一种就是行为树.状态机呢,就是在合适的时候,去转换AI的状态,行为树呢就是上面许多”叶子”节点,通过执行不同的节点来达到驱动AI.

她说:哦,那你打算要用哪一种?

我说:这个要看后面的复杂程度,如果这个AI的功能单一,状态机就完全够用,如果后续功能越来越多,维护起来的话状态机肯定没有行为树方便的.现在打算先用状态机.

她说:状态机不是复杂了就不好维护了吗?为什么还要用它呀

我说:在开发的时候,并不是觉得哪个厉害就用哪个,而是哪个更适合就用哪个,目前我想到的AI没什么功能,所以先用状态机

她问:现在有那几个功能啊?

我说:有Idle , Chase ,Attack, Move 这几个写在代码里就是这样的:

1//状态枚举
2public enum ENUM_AI_State
3{
4    Idle = 0,
5    Chase, //追击敌人
6    Attack, //攻击
7    Move   //移动
8}

她疑惑的问我,你是不是少写了下面的等于啊?

因为看起来下面的几种状态应该是给个值的,比如:Chase = 1

我笑着答:不用,枚举类型它会根据上面的值自动为下面的变量赋值的,自动加1.

她又问:就是根据这几个状态,去执行不同的AI行为吗?

我说:是的,而且因为有许多不同AI的情况下,我们需要…

写父类吧.她开心的说

我回答她:哈哈,是的

真开心,她永远都能知道我是怎么想的. 

我飞快的写下一个状态机抽象父类,因为在这个里面不需要父类具体实现方法.所有的方法都要子类去实现.

 1public abstract class ICharacter 
 2{
 3    //默认状态
 4    protected ENUM_AI_State m_AiState = ENUM_AI_State.Idle;
 5    //移动相关
 6    protected const float MOVE_CHECK_DIST = 1.5f;
 7    protected bool m_bOnMove = false;
 8    //是否有攻击目标位置
 9    protected bool m_bSetAttackPostion = false;
10    protected Vector3 m_AttackPostion;
11
12    //追击的对象
13    protected bool m_BoChase = false;
14    protected ICharacter m_ChaseTarget = null;
15    protected const float CHASE_CHECK_DIST = 2.0f;
16
17    //攻击的对象 因为是AI 对战,所以攻击的目标也是AI
18    protected ICharacter m_AttackTarget = null;
19
20    //更新
21    public abstract void UpdateAI(List< ICharacter >Targets);
22
23    //...
24}

她问:就是根据枚举里面的变化去切换吗?

我说:是的,但是目前我们还不能这么做,因为直接去实现接口方法的话,必然要通过一系列的判断.稍微好点的方法就是switch()了.所以,我们还需要写一个角色的父类,实现抽象类里面的方法,然后再供给子类用,她又问:写起来是什么样的,很多吗?

我说,我给你写一个看看吧:

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

public class ISoldier : ICharacter
{
    public override void UpdateAI(List<ICharacter> Targets)
    {
        switch (m_AiState)
        {
            case ENUM_AI_State.Idle:
                break;
            case ENUM_AI_State.Chase:
                break;
            case ENUM_AI_State.Attack:
                break;
            case ENUM_AI_State.Move:
                break;
            default:
                break;
        }

    }
}

她问:难道就是这样就好了吗?

我回答:当然不是,当AI在Idle模式的时候,需要在里面判断一些特定的条件,到其他3个状态,比如,它看到了敌人,需要在Idle的分支中,先把状态改为Chase,然后要追到敌人的时候,状态再改为Attack.

唔,她皱皱眉头,说道:这样岂不是状态越多,里面就越乱了.

我说,是的,还需要再优化一下.

她问:又要重构?

我说:是的,一般的,有点重构癖好的,都是拿switch改类,就是每个状态都改成一个类.并且为它们写一个总的父类.

她问:你也要这样吗?

我说:不是,虽然我很想,在这之前,我们还需要一个能感知到状态切换的一个类,也就是说,我们需要一个状态的拥有者,通过它 来切换状态.

她有点迷惑,说:好绕啊

于是我和她说:那我们做个简单的好不好

她点点头.

我说,我们先写一个接口吧,这个接口就执行AI的控制:

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

public interface IState
{
    void Execute(AIController ac);
}

还有一个总控制器FSM:

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

public class FSM : MonoBehaviour
{
    public IState currentState { get; private set; }
    public bool isCanMoveToCamera;
    public bool isCanMoveToMission;
    public bool isCanMoveToExcut;
    public AIController ac;
    // Use this for initialization
    void Start()
    {

    }//End of Start 

    public void ChangeState(IState newState)
    {
        currentState = newState;
    }
    // Update is called once per frame
    void Update()
    {
        if (currentState != null && ac != null)
        {
            currentState.Execute(ac);
        }
    }//End of Update
}

我对她说,这次简单点,就做3个模式,移动到相机,移动到任务点,和执行任务

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

public class _MoveCameraState : IState {
    public void Execute (AIController ac)
    {
        if (ac.fsm.isCanMoveToCamera) {
            ac.MoveToCamera ();
        } else {
            ac .fsm .ChangeState (new _MoveMissionState());
        }
    }
}

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

public class _MoveMissionState : IState
{
    public void Execute(AIController ac)
    {
        if (ac.fsm.isCanMoveToMission)
        {
            ac.MoveToExcut();
        }
        else
        {
            ac.fsm.ChangeState(new _MoveExcutState());
        }
    }
}

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

public class _MoveExcutState : IState
{
    public void Execute(AIController ac)
    {
        if (ac.fsm.isCanMoveToExcut)
        {
            ac.MoveToExcut();
        }
        else
        {
            ac.fsm.ChangeState(new _MoveCameraState());
        }
    }
}

她问:这几个这么像,不能写成一个类扩展吗?

我哈哈大笑,当然不行啦,这是单独的状态,虽然我们这次写的是简单的.但是也不能这么做啊,没有意义的.

再把AIController写完

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

public class AIController : MonoBehaviour
{
    public FSM fsm;
    // Use this for initialization
    void Start()
    {
        fsm = GetComponent<FSM>();
        fsm.ac = this;
        //初始化一个默认的状态机
        fsm.ChangeState(new _MoveCameraState());
    }//End of Start 
    public void MoveToCamera()
    {
        Debug.Log("移动到相机");
    }
    public void MoveToMission()
    {
        Debug.Log("移动到任务点");
    }
    public void MoveToExcut()
    {
        Debug.Log("移动到执行点");
    } 
}

然后把FSM与AIContrller挂载在物体上,通过修改bool值来达到状态切换:

然后我运行一下,点击修改了几次bool值:

我说这样就行啦!

她唔的应了一声,这孩子,确实是困了,她八爪鱼一样趴在我的身上.我把她送到她的房间,放好再盖上被子,对她说一声晚安.

…END…

正文完