前言
状态机与行为树是两大热门AI实现方式,这里简单贴出我的实现,仅作为参考,简单讲解原理,多的就不做解释,其他的AI实现方式可以使用GOAP或ML_Agent
状态机
状态机由三部分组成:状态、转换、条件。
状态表示对象当前的行为模式,如待机、追击、攻击。每个状态有进入、持续、退出三个阶段,用于处理动画切换、物理参数变更等副作用。
转换定义状态间的迁移路径。不同于硬编码的if-else,转换与状态解耦,形成有向图结构。运行期从当前状态出发,遍历所有出边,检查条件决定是否迁移。
条件是转换的守卫,返回布尔值。将条件抽象为独立对象,可实现复用(如"距离小于X"被多个转换共用)与组合(与、或、非)。
数据驱动体现在:状态机资产(SO)描述图结构,运行实例持有当前状态与上下文数据。同一资产可被多个敌人共享,各自独立推进。
//状态
public abstract class BaseState : ScriptableObject
{
public StateType Type;
public virtual void Enter(BaseStateValue value){}
public virtual void Exit(BaseStateValue value){}
public virtual void OnUpdate(BaseStateValue value) {}
}
//条件
public abstract class BaseCondition :ScriptableObject
{
public abstract bool CanTranslate(BaseStateValue value);
}
//状态机
public abstract class BaseStateMachine : MonoBehaviour
{
[SerializeField]
protected BaseStateMachineAsset _stateMachineAsset;
protected BaseState _currentState;
public BaseStateValue _currentStateValue;
protected virtual void Start()
{
CreateStateValue();
Initialize();
}
protected void Update()
{
_currentState.OnUpdate(_currentStateValue);
var list = _stateMachineAsset.GetTranslations(_currentState.Type);
if (list == null) return;
foreach (var t in list)
{
if(t.Condition.CanTranslate(_currentStateValue))
{
_currentState.Exit(_currentStateValue);
_currentState = t.To;
_currentState.Enter(_currentStateValue);
break;
}
}
}
protected void FixedUpdate()
{
if(_currentState is IPhysicsState physics)
{
physics.OnFixedUpdate(_currentStateValue);
}
}
protected abstract void CreateStateValue();
protected void Initialize()
{
if (_stateMachineAsset == null)
{
Debug.LogError("资源为空");
}
if(_stateMachineAsset.InitialState==null)
{
Debug.LogError("初始状态为空");
}
else
{
_currentState = _stateMachineAsset.InitialState;
_currentState.Enter(_currentStateValue);
}
}
public BaseStateValue GetStateValue()=> _currentStateValue;
}
//状态机配置
public class BaseStateMachineAsset : ScriptableObject
{
public List<BaseTranslation> Translations;
public BaseState InitialState;
private Dictionary<StateType, List<BaseTranslation>> _map;
public void OnValidate()
{
if(Translations != null&&Translations.Count>0)
{
BuildMap();
}
}
public IReadOnlyList<BaseTranslation> GetTranslations(StateType type)
{
if(_map == null)
{
return null;
}
if(_map.TryGetValue(type,out var res))
{
return res.AsReadOnly();
}
return null;
}
private void BuildMap()
{
_map = new Dictionary<StateType, List<BaseTranslation>>();
foreach (var translation in Translations)
{
if(!_map.ContainsKey(translation.From.Type))
{
_map[translation.From.Type] = new List<BaseTranslation>();
}
_map[translation.From.Type].Add(translation);
}
foreach (var translations in _map.Values)
{
translations.Sort((a,b)=>b.Weight.CompareTo(a.Weight));
}
}
}
//上下文(数据容器)
public class BaseContext
{
public GameObject Self;
public bool IsHurt;
public bool IsDead;
public BaseContext(GameObject gameObject)
{
Self = gameObject;
IsHurt = false;
IsDead = false;
}
}
//转换
public abstract class BaseTranslation : ScriptableObject
{
public BaseState From;
public BaseState To;
public BaseCondition Condition;
public int Weight; //权重
}
行为树
行为树采用树形结构组织行为,节点按功能分为三类:
组合节点控制子节点执行方式。Sequence顺序执行,子节点失败则中断;Selector选择执行,子节点成功则中断。这种隐式优先级避免了状态机中显式转换条件的膨胀。
修饰节点包装单个子节点,修改其返回值或执行逻辑。如Invert反转结果,Repeat循环执行,Cooldown限制频率。修饰器可嵌套,实现复杂控制流而不新增节点类型。
叶节点执行具体逻辑,包括条件判断(Condition)与动作执行(Action)。条件节点不改变世界状态,仅返回评估结果;动作节点可能持续多帧,返回Running表示未完成。
行为树每帧从根节点开始深度遍历,根据节点返回值决定遍历路径。这种"重新评估"机制天然支持反应式行为:高优先级条件(如受击)插入树前端,立即打断当前行为。
由于行为树的优势是可视化,而可视化需要用到GraphView编辑窗口,这里只贴出基础实现,项目中使用需要借助插件或者升级到Unity6
//返回节点状态
public enum NodeState
{
Success,
Failure,
Running
}
//基础节点
public abstract class BTNode
{
public string Name;
public abstract NodeState Execute();
}
//叶子节点
public abstract class ActionNode : BTNode
{
protected Func<NodeState> _action;
protected ActionNode(string name,Func<NodeState> act)
{
Name = name;
_action = act;
}
public override NodeState Execute()
{
return _action();
}
public abstract class CompositeNode : BTNode
{
protected List<BTNode> _children=new List<BTNode>();
protected int currentIndex = 0;
public void AddChild(BTNode child)
{
_children.Add(child);
}
}
}
public class Selector : CompositeNode
{
public override NodeState Execute()
{
for (int i = 0; i < _children.Count; i++)
{
var state = _children[i].Execute();
if(state==NodeState.Success
||state==NodeState.Running)
{
return state;
}
}
return NodeState.Failure;
}
}
public class Sequence : CompositeNode
{
public override NodeState Execute()
{
for (int i = 0; i < _children.Count; i++)
{
var state = _children[i].Execute();
if(state==NodeState.Failure||
state==NodeState.Running)
{
return state;
}
}
return NodeState.Success;
}
}
// 修饰器基类
public abstract class DecoratorNode : BTNode
{
protected BTNode _child;
public void SetChild(BTNode child) => _child = child;
}
// 反转结果
public class Inverter : DecoratorNode
{
public override NodeState Execute()
{
var state = _child.Execute();
return state switch
{
NodeState.Success => NodeState.Failure,
NodeState.Failure => NodeState.Success,
_ => NodeState.Running
};
}
}
// 重复执行N次
public class Repeater : DecoratorNode
{
private int _repeatCount;
private int _currentCount;
public Repeater(int count)
{
_repeatCount = count;
_currentCount = 0;
}
public override NodeState Execute()
{
while (_currentCount < _repeatCount)
{
var state = _child.Execute();
if (state == NodeState.Running) return NodeState.Running;
_currentCount++;
}
_currentCount = 0;
return NodeState.Success;
}
}
// 冷却时间
public class Cooldown : DecoratorNode
{
private float _duration;
private float _lastExecuteTime = float.MinValue;
public Cooldown(float duration) => _duration = duration;
public override NodeState Execute()
{
if (Time.time - _lastExecuteTime < _duration)
return NodeState.Failure;
var state = _child.Execute();
if (state != NodeState.Running)
_lastExecuteTime = Time.time;
return state;
}
}
// 行为树运行器
public class BehaviorTreeRunner : MonoBehaviour
{
[SerializeField] private BTNode _root;
void Update() => _root?.Execute();
public void SetRoot(BTNode root) => _root = root;
}
结语
有什么不明白的欢迎在评论区讨论,年关将近,本文有些仓促