CL游戏引擎 - AI模块 - FSM [完整实现]

简单的状态机 [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;
	}

}

猜你喜欢

转载自blog.csdn.net/u011488756/article/details/85163630
FSM