简单的状态机 [Simple State Machine]
启动和运行AI的最快速,最简单的方法。 SimpleStateMachine是一个Component子类,它允许你将枚举设置为其通用约束,并且它将使用该枚举来控制状态机。 枚举值每个映射到一个状态,并且可以有可选的enter / tick / exit方法。 这些方法的命名约定最好用一个例子显示:
enum SomeEnum
{
Walking,
Idle
}
public class YourClass : SimpleStateMachine<SomeEnum>()
{
void onAddedToEntity()
{
initialState = SomeEnum.Idle;
}
void Walking_Enter() {}
void Walking_Tick() {}
void Walking_Exit() {}
void Idle_Enter() {}
void Idle_Tick() {}
void Idle_Exit() {}
}
状态机 [State Machine]
下一步是StateMachine,它实现“状态为对象”模式。 StateMachine为每个状态使用单独的类,因此它是更复杂系统的更好选择。
我们开始使用StateMachine进入上下文的概念。 在编码中,context只是用于满足泛型约束的类。 在List <string>
中,字符串将是context类,列表操作的类。 使用所有其他AI解决方案,您可以指定context类。 它可能是您的Enemy类,Player类或包含与AI相关的任何信息的辅助对象(例如Player,敌人列表,导航信息等)。
这是一个显示用法的简单示例(为简洁省略了State子类):
// 创建一个状态机,它将使用SomeClass类型的对象作为焦点,初始状态为PatrollingState
var machine = new SKStateMachine<SomeClass>( someClass, new PatrollingState() );
// 我们现在可以添加任何其他状态
machine.addState( new AttackState() );
machine.addState( new ChaseState() );
// 通常在更新对象时调用此方法
machine.update( Time.deltaTime );
// 改变状态。 状态机将自动创建并缓存该类的实例(在本例中为ChasingState)
machine.changeState<ChasingState>();
了解完以上的基本知识,就开始实现我们的FSM代码。
具有枚举约束的简单状态机。 使用此功能时必须遵循一些规则:
- 在调用update之前必须设置initialState(使用构造函数或onAddedToEntity)
- 如果在子类中实现更新,则必须调用base.update()
注意:如果您使用枚举作为约束,您可以通过执行Core Emitter为其枚举执行的操作来避免Mono中的分配/装箱
:将IEqualityComparer传递给构造函数。
public class SimpleStateMachine<TEnum> : Component, IUpdatable where TEnum : struct, IComparable, IFormattable
{
class StateMethodCache
{
public Action enterState;
public Action tick;
public Action exitState;
}
protected float elapsedTimeInState = 0f;
protected TEnum previousState;
Dictionary<TEnum,StateMethodCache> _stateCache;
StateMethodCache _stateMethods;
TEnum _currentState;
protected TEnum currentState
{
get
{
return _currentState;
}
set
{
// 不要改变到现在的状态
if( _stateCache.Comparer.Equals( _currentState, value ) )
return;
// 交换 previous/current
previousState = _currentState;
_currentState = value;
// 退出状态,获取下一个缓存状态方法,然后进入该状态
if( _stateMethods.exitState != null )
_stateMethods.exitState();
elapsedTimeInState = 0f;
_stateMethods = _stateCache[_currentState];
if( _stateMethods.enterState != null )
_stateMethods.enterState();
}
}
protected TEnum initialState
{
set
{
_currentState = value;
_stateMethods = _stateCache[_currentState];
if( _stateMethods.enterState != null )
_stateMethods.enterState();
}
}
public SimpleStateMachine( IEqualityComparer<TEnum> customComparer = null )
{
_stateCache = new Dictionary<TEnum,StateMethodCache>( customComparer );
// 缓存我们所有的状态方法
var enumValues = (TEnum[])Enum.GetValues( typeof( TEnum ) );
foreach( var e in enumValues )
configureAndCacheState( e );
}
public virtual void update()
{
elapsedTimeInState += Time.deltaTime;
if( _stateMethods.tick != null )
_stateMethods.tick();
}
void configureAndCacheState( TEnum stateEnum )
{
var stateName = stateEnum.ToString();
var state = new StateMethodCache();
state.enterState = getDelegateForMethod( stateName + "_Enter" );
state.tick = getDelegateForMethod( stateName + "_Tick" );
state.exitState = getDelegateForMethod( stateName + "_Exit" );
_stateCache[stateEnum] = state;
}
Action getDelegateForMethod( string methodName )
{
var methodInfo = ReflectionUtils.getMethodInfo( this, methodName );
if( methodInfo != null )
return ReflectionUtils.createDelegate<Action>( this, methodInfo );
return null;
}
}
public abstract class State<T>
{
protected StateMachine<T> _machine;
protected T _context;
public void setMachineAndContext( StateMachine<T> machine, T context )
{
_machine = machine;
_context = context;
onInitialized();
}
/// <summary>
/// 在设置状态机和context后直接调用,允许状态进行任何所需的设置
/// </summary>
public virtual void onInitialized()
{}
/// <summary>
/// 当状态变为活动状态时调用
/// </summary>
public virtual void begin()
{}
/// <summary>
/// 在更新之前调用,允许状态最后一次机会改变状态
/// </summary>
public virtual void reason()
{}
/// <summary>
/// 每个帧调用此状态为活动状态
/// </summary>
public abstract void update( float deltaTime );
/// <summary>
/// 当此状态不再是活动状态时调用
/// </summary>
public virtual void end()
{}
}
public class StateMachine<T>
{
public event Action onStateChanged;
public State<T> currentState { get { return _currentState; } }
public State<T> previousState;
public float elapsedTimeInState = 0f;
protected State<T> _currentState;
protected T _context;
Dictionary<Type, State<T>> _states = new Dictionary<Type, State<T>>();
public StateMachine( T context, State<T> initialState )
{
_context = context;
// 设置我们的初始状态
addState( initialState );
_currentState = initialState;
_currentState.begin();
}
/// <summary>
/// 将状态添加到状态机
/// </summary>
public void addState( State<T> state )
{
state.setMachineAndContext( this, _context );
_states[state.GetType()] = state;
}
/// <summary>
/// 使用提供的增量时间来tick状态机
/// </summary>
public virtual void update( float deltaTime )
{
elapsedTimeInState += deltaTime;
_currentState.reason();
_currentState.update( deltaTime );
}
/// <summary>
/// 改变当前状态
/// </summary>
public R changeState<R>() where R : State<T>
{
// 避免改变到相同的状态
var newType = typeof( R );
if( _currentState.GetType() == newType )
return _currentState as R;
// 如果我们有一个currentState,则只调用end
if( _currentState != null )
_currentState.end();
Assert.isTrue( _states.ContainsKey( newType ), "{0}: 状态{1}不存在。 您是否通过调用addState添加它?", GetType(), newType );
// 交换状态和执行begin
elapsedTimeInState = 0f;
previousState = _currentState;
_currentState = _states[newType];
_currentState.begin();
// 如果我们有一个监听器,则触发状态已更改的事件
if( onStateChanged != null )
onStateChanged();
return _currentState as R;
}
}