今天抽出一点空闲时间给大家讲解一下AI的设计实现思路的常见方法,学习游戏中那些复杂的AI行为是如何实现的。
为什么需要这些设计?
使用行为树和有限状态机的意义在于它们提供了灵活且结构化的方式来控制游戏AI的行为逻辑。当然最重要的在于其传统switch case/if else对于AI行为的分层解耦性差,及其容易导致后期一个脚本中大量的代码,不利于维护和其他人阅读,修bug也非常困难。因此需要使用良好的设计模式解决这一问题。
有限状态机
(1)概念
是一种基于状态的决策系统,它通过不同的状态和状态之间的转换来管理角色的行为。每个状态定义了一组特定的行为,当满足一定条件时,角色会从一个状态转换到另一个状态。状态机的主要优势在于它的简单性和可预测性,非常适合处理明确的状态变化场景。
当然太复杂的概念看着脑壳痛,所有这段话是个什么意思呢?简单来说,状态机就是保存了一组AI可能会执行的方法,不同状态机保存不同的方法,通常情况下AI会一直执行这个方法体,直到,当某个状态(if条件)被满足后,AI就会切换状态,改变执行的方法然后继续执行新方法体中的内容。
按照图来讲就像下面那样:
默认AI是在移动这个状态中执行行为,如果发现了玩家或一定时间后就会转移到当前的状态,然后继续这个状态执行下去直到其它状态的条件被满足后。
(2)实现
以下是最基础的有限状态机的实现代码,具体状态机的实现方式是不止这一种的,我提供只不过是一个比较简单的泛用型状态机。
/// <summary>
/// 有限状态机接口,运行构造简单的有限状态机
/// </summary>
/// <typeparam name="T">泛型参数,约束MonoBehaviour</typeparam>
public interface StateNode<T> where T : MonoBehaviour
{
/// 到达条件
public Func<bool> State { get; set; }
/// 可转换状态表
public List<StateNode<T>> StateList { get; set; }
/// <summary>
/// 进入一次时执行
/// </summary>
public void EnterState(T t);
/// <summary>
/// 每帧更新执行
/// </summary>
public void UpdateState(T t);
/// <summary>
/// 退出时执行
/// </summary>
public void ExitState(T t);
}
/// <summary>
/// 有限状态机控制器
/// </summary>
/// <typeparam name="T">泛型参数,约束MonoBehaviour</typeparam>
public class StateController<T> : RootNode where T : MonoBehaviour
{
public Func<bool> Condition { get; set; }
/// 当前的状态节点
protected StateNode<T> CurState;
//保存的参数
protected readonly T t;
//构造方法
public StateController(T mono,StateNode<T> StartState, Func<bool> condition)
{
CurState = StartState;
Condition = condition ?? (() => true);
t = mono;
}
/// <summary>
/// 执行当前状态机并判断是否满足切换条件
/// </summary>
public v