213 lines
9.1 KiB
C#
213 lines
9.1 KiB
C#
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
|
|
|
using System;
|
|
using UnityEngine;
|
|
|
|
namespace Animancer.FSM
|
|
{
|
|
public partial class StateMachine<TState>
|
|
{
|
|
/// <summary>
|
|
/// A simple system that can <see cref="InputBuffer{TStateMachine}.Buffer"/> a state then try to enter it every
|
|
/// time <see cref="InputBuffer{TStateMachine}.Update(float)"/> is called until the
|
|
/// <see cref="InputBuffer{TStateMachine}.TimeOut"/> expires.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <strong>Documentation:</strong>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm/utilities#input-buffers">
|
|
/// Input Buffers</see>
|
|
/// <para></para>
|
|
/// See <see cref="StateMachine{TState}.InputBuffer{TStateMachine}"/> for example usage.
|
|
/// </remarks>
|
|
///
|
|
/// https://kybernetik.com.au/animancer/api/Animancer.FSM/InputBuffer
|
|
///
|
|
public class InputBuffer : InputBuffer<StateMachine<TState>>
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Creates a new <see cref="InputBuffer"/>.</summary>
|
|
public InputBuffer() { }
|
|
|
|
/// <summary>Creates a new <see cref="InputBuffer"/> for the specified `stateMachine`.</summary>
|
|
public InputBuffer(StateMachine<TState> stateMachine) : base(stateMachine) { }
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
|
|
/// <summary>
|
|
/// A simple system that can <see cref="Buffer"/> a state then try to enter it every time
|
|
/// <see cref="Update(float)"/> is called until the <see cref="TimeOut"/> expires.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <strong>Documentation:</strong>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm/utilities#input-buffers">
|
|
/// Input Buffers</see>
|
|
/// <para></para>
|
|
/// <strong>Example:</strong><code>
|
|
/// public StateMachine<CharacterState> stateMachine;// Initialized elsewhere.
|
|
///
|
|
/// [SerializeField] private CharacterState _Attack;
|
|
/// [SerializeField] private float _AttackInputTimeOut = 0.5f;
|
|
///
|
|
/// private StateMachine<CharacterState>.InputBuffer _InputBuffer;
|
|
///
|
|
/// private void Awake()
|
|
/// {
|
|
/// // Initialize the buffer.
|
|
/// _InputBuffer = new StateMachine<CharacterState>.InputBuffer(stateMachine);
|
|
/// }
|
|
///
|
|
/// private void Update()
|
|
/// {
|
|
/// // When input is detected, buffer the desired state.
|
|
/// if (Input.GetButtonDown("Fire1"))// Left Click by default.
|
|
/// {
|
|
/// _InputBuffer.Buffer(_Attack, _AttackInputTimeOut);
|
|
/// }
|
|
///
|
|
/// // At the end of the frame, Update the buffer so it tries to enter the buffered state.
|
|
/// // After the time out, it will clear itself so Update does nothing until something else is buffered.
|
|
/// _InputBuffer.Update();
|
|
/// }
|
|
/// </code></remarks>
|
|
///
|
|
/// https://kybernetik.com.au/animancer/api/Animancer.FSM/InputBuffer_1
|
|
///
|
|
public class InputBuffer<TStateMachine> where TStateMachine : StateMachine<TState>
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
private TStateMachine _StateMachine;
|
|
private Action _ForceDefaultState;
|
|
|
|
/// <summary>The <see cref="StateMachine{TState}"/> this buffer is feeding input to.</summary>
|
|
public TStateMachine StateMachine
|
|
{
|
|
get => _StateMachine;
|
|
set
|
|
{
|
|
if (_StateMachine is WithDefault withDefault)
|
|
withDefault.ForceSetDefaultState = _ForceDefaultState;
|
|
|
|
_StateMachine = value;
|
|
|
|
TryRegisterForceSetDefaultState();
|
|
Clear();
|
|
}
|
|
}
|
|
|
|
private void TryRegisterForceSetDefaultState()
|
|
{
|
|
if (_StateMachine is WithDefault withDefault)
|
|
{
|
|
_ForceDefaultState = withDefault.ForceSetDefaultState;
|
|
withDefault.ForceSetDefaultState = TryEnterStateOrForceDefault;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The <typeparamref name="TState"/> this buffer is currently attempting to enter.</summary>
|
|
public TState State { get; set; }
|
|
|
|
/// <summary>The amount of time left before the <see cref="State"/> is cleared.</summary>
|
|
public float TimeOut { get; set; }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Is this buffer currently trying to enter a <see cref="State"/>?</summary>
|
|
public bool IsActive => State != null;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Creates a new <see cref="InputBuffer{TStateMachine}"/>.</summary>
|
|
public InputBuffer() { }
|
|
|
|
/// <summary>Creates a new <see cref="InputBuffer{TStateMachine}"/> for the specified `stateMachine`.</summary>
|
|
public InputBuffer(TStateMachine stateMachine)
|
|
{
|
|
_StateMachine = stateMachine;
|
|
TryRegisterForceSetDefaultState();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Sets the <see cref="State"/> and <see cref="TimeOut"/>.</summary>
|
|
/// <remarks>Doesn't actually attempt to enter the state until <see cref="Update(float)"/> is called.</remarks>
|
|
public void Buffer(TState state, float timeOut)
|
|
{
|
|
State = state;
|
|
TimeOut = timeOut;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Attempts to enter the <see cref="State"/> and returns true if successful.</summary>
|
|
protected virtual bool TryEnterState()
|
|
=> StateMachine.TryResetState(State);
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Calls <see cref="TryEnterState"/>. If it fails, then <see cref="WithDefault.ForceSetDefaultState"/>.
|
|
/// </summary>
|
|
public void TryEnterStateOrForceDefault()
|
|
{
|
|
if (IsActive &&
|
|
TryEnterState())
|
|
return;
|
|
|
|
_ForceDefaultState();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Calls <see cref="Update(float)"/> using <see cref="Time.deltaTime"/>.</summary>
|
|
/// <remarks>This method should be called at the end of a frame after any calls to <see cref="Buffer"/>.</remarks>
|
|
public bool Update()
|
|
=> Update(Time.deltaTime);
|
|
|
|
/// <summary>
|
|
/// Attempts to enter the <see cref="State"/> if there is one and returns true if successful. Otherwise the
|
|
/// <see cref="TimeOut"/> is decreased by `deltaTime` and <see cref="Clear"/> is called if it reaches 0.
|
|
/// </summary>
|
|
/// <remarks>This method should be called at the end of a frame after any calls to <see cref="Buffer"/>.</remarks>
|
|
public bool Update(float deltaTime)
|
|
{
|
|
if (IsActive)
|
|
{
|
|
if (TryEnterState())
|
|
{
|
|
Clear();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
TimeOut -= deltaTime;
|
|
|
|
if (TimeOut < 0)
|
|
Clear();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Clears this buffer so it stops trying to enter the <see cref="State"/>.</summary>
|
|
public virtual void Clear()
|
|
{
|
|
State = null;
|
|
TimeOut = default;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
}
|
|
|