浏览代码

Added caching to the conditions for better performance (#164)

Condition results are now cached so they are only evaluated once per frame, regardless of how many times they are being checked in the state machine logic.
Caching is made by each instance of the state machine and the condition respective scriptable object.
Added a boolean value to enable/disable this feature on each Condition Scriptable Object individually.

Extra internal changes:
The abstract function Condition.Statement() changed from public to protected so it can only be used inside the internal Condition.GetStatement().
Added documentation regarding all Get methods for the abstract Scriptable Objects to clarify their behaviors.
Removed StateTransitionSO since it was replaced by TransitionTableSO
Changed public TransitionTableSO.GetInitialState() to internal.
Changed a few SO references to their specialized classes.
/main
GitHub 4 年前
当前提交
b5b57142
共有 23 个文件被更改,包括 86 次插入99 次删除
  1. 2
      UOP1_Project/Assets/Prefabs/Pig.prefab
  2. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/HasHitHead.asset
  3. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsGrounded.asset
  4. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsHoldingJump.asset
  5. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsMoving.asset
  6. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsSliding.asset
  7. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/JumpHoldTimer.asset
  8. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/HasHitHeadConditionSO.cs
  9. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsCharacterControllerGroundedConditionSO.cs
  10. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsHoldingJumpConditionSO.cs
  11. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsMovingConditionSO.cs
  12. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsSlidingConditionSO.cs
  13. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/TimeElapsedConditionSO.cs
  14. 16
      UOP1_Project/Assets/Scripts/StateMachine/Core/State.cs
  15. 4
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateAction.cs
  16. 42
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateCondition.cs
  17. 6
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateTransition.cs
  18. 3
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateActionSO.cs
  19. 7
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateConditionSO.cs
  20. 3
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateSO.cs
  21. 5
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/TransitionTableSO.cs
  22. 11
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateTransitionSO.cs.meta
  23. 68
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateTransitionSO.cs

2
UOP1_Project/Assets/Prefabs/Pig.prefab


m_EditorClassIdentifier:
_transitionTableSO: {fileID: 11400000, guid: f0baa4ca2bfa24e4cb6d1efe9fa81ea3, type: 2}
_debugger:
debugTransitions: 1
debugTransitions: 0
appendConditionsInfo: 1
appendActionsInfo: 1
currentState:

1
UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/HasHitHead.asset


m_Script: {fileID: 11500000, guid: 07e6889b70ab561458e46326d9c2a88b, type: 3}
m_Name: HasHitHead
m_EditorClassIdentifier:
cacheResult: 1

1
UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsGrounded.asset


m_Script: {fileID: 11500000, guid: 953dea50bde69be4fb0df0c3a49eef08, type: 3}
m_Name: IsGrounded
m_EditorClassIdentifier:
cacheResult: 1

1
UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsHoldingJump.asset


m_Script: {fileID: 11500000, guid: aea20a61197822e49aaa66318da39267, type: 3}
m_Name: IsHoldingJump
m_EditorClassIdentifier:
cacheResult: 1

1
UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsMoving.asset


m_Script: {fileID: 11500000, guid: fc2d5ccac974a0d41a92a30245aa08eb, type: 3}
m_Name: IsMoving
m_EditorClassIdentifier: Assembly-CSharp::StartMovingConditionSO
cacheResult: 1
_treshold: 0.02

1
UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsSliding.asset


m_Script: {fileID: 11500000, guid: 9352ebfadd39e754e86b2aa1daffdca0, type: 3}
m_Name: IsSliding
m_EditorClassIdentifier:
cacheResult: 1

1
UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/JumpHoldTimer.asset


m_Script: {fileID: 11500000, guid: 8139f9f328ed1144690b7a9ffca2d5b4, type: 3}
m_Name: JumpHoldTimer
m_EditorClassIdentifier:
cacheResult: 1
_timerLength: 0.4

2
UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/HasHitHeadConditionSO.cs


_characterController = stateMachine.GetComponent<CharacterController>();
}
public override bool Statement()
protected override bool Statement()
{
bool isMovingUpwards = _characterScript.movementVector.y > 0f;
if (isMovingUpwards)

2
UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsCharacterControllerGroundedConditionSO.cs


_characterController = stateMachine.GetComponent<CharacterController>();
}
public override bool Statement() => _characterController.isGrounded;
protected override bool Statement() => _characterController.isGrounded;
}

2
UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsHoldingJumpConditionSO.cs


_characterScript = stateMachine.GetComponent<Character>();
}
public override bool Statement() => _characterScript.jumpInput;
protected override bool Statement() => _characterScript.jumpInput;
}

2
UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsMovingConditionSO.cs


_treshold = treshold;
}
public override bool Statement()
protected override bool Statement()
{
Vector3 movementVector = _characterScript.movementInput;
movementVector.y = 0f;

2
UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsSlidingConditionSO.cs


_characterScript = stateMachine.GetComponent<Character>();
}
public override bool Statement()
protected override bool Statement()
{
if (_characterScript.lastHit == null)
return false;

2
UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/TimeElapsedConditionSO.cs


_timerLength = timerLength;
}
public override bool Statement() => Time.time >= _startTime + _timerLength;
protected override bool Statement() => Time.time >= _startTime + _timerLength;
}

16
UOP1_Project/Assets/Scripts/StateMachine/Core/State.cs


using UnityEngine;
using UOP1.StateMachine.ScriptableObjects;
internal ScriptableObject _originSO;
internal StateSO _originSO;
internal StateMachine _stateMachine;
internal StateTransition[] _transitions;
internal StateAction[] _actions;

public State(
ScriptableObject originSO,
StateSO originSO,
StateMachine stateMachine,
StateTransition[] transitions,
StateAction[] actions)

public bool TryGetTransition(out State state)
{
state = null;
return true;
break;
state = null;
return false;
for (int i = 0; i < _transitions.Length; i++)
_transitions[i].ClearConditionsCache();
return state != null;
}
}
}

4
UOP1_Project/Assets/Scripts/StateMachine/Core/StateAction.cs


using UnityEngine;
using UOP1.StateMachine.ScriptableObjects;
namespace UOP1.StateMachine
{

public abstract class StateAction : IStateComponent
{
internal ScriptableObject _originSO;
internal StateActionSO _originSO;
/// <summary>
/// Called every frame the <see cref="StateMachine"/> is in a <see cref="State"/> with this <see cref="StateAction"/>.

42
UOP1_Project/Assets/Scripts/StateMachine/Core/StateCondition.cs


using UnityEngine;
using UOP1.StateMachine.ScriptableObjects;
namespace UOP1.StateMachine
{

public abstract class Condition : IStateComponent
{
private bool _isCached = false;
private bool _cachedStatement = default;
internal StateConditionSO _originSO;
public abstract bool Statement();
protected abstract bool Statement();
/// <summary>
/// Wrap the <see cref="Statement"/> so it can be cached.
/// </summary>
internal bool GetStatement()
{
bool returnValue;
if (_originSO.cacheResult && _isCached)
returnValue = _cachedStatement;
else
{
returnValue = Statement();
if (_originSO.cacheResult)
{
_isCached = true;
_cachedStatement = returnValue;
}
}
return returnValue;
}
internal void ClearStatementCache()
{
_isCached = false;
}
/// <summary>
/// Awake is called when creating a new instance. Use this method to cache the components needed for the condition.

/// </summary>
public readonly struct StateCondition
{
internal readonly ScriptableObject _originSO;
internal readonly StateConditionSO _originSO;
public StateCondition(ScriptableObject originSO, StateMachine stateMachine, Condition condition, bool expectedResult)
public StateCondition(StateConditionSO originSO, StateMachine stateMachine, Condition condition, bool expectedResult)
_condition._originSO = originSO;
bool statement = _condition.Statement();
bool statement = _condition.GetStatement();
bool isMet = statement == _expectedResult;
#if UNITY_EDITOR

6
UOP1_Project/Assets/Scripts/StateMachine/Core/StateTransition.cs


return ret;
}
internal void ClearConditionsCache()
{
for (int i = 0; i < _conditions.Length; i++)
_conditions[i]._condition.ClearStatementCache();
}
}
}

3
UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateActionSO.cs


{
public abstract class StateActionSO : ScriptableObject
{
/// <summary>
/// Will create a new custom <see cref="StateAction"/> or return an existing one inside <paramref name="createdInstances"/>
/// </summary>
internal StateAction GetAction(StateMachine stateMachine, Dictionary<ScriptableObject, object> createdInstances)
{
if (createdInstances.TryGetValue(this, out var obj))

7
UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateConditionSO.cs


{
public abstract class StateConditionSO : ScriptableObject
{
[SerializeField]
[Tooltip("The condition will only be evaluated once each frame and cached for subsequent uses.\r\n\r\nThe caching is based on each instance of the State Machine and of this Scriptable Object.")]
internal bool cacheResult = true;
/// <summary>
/// Will create a new custom <see cref="Condition"/> or use an existing one inside <paramref name="createdInstances"/>.
/// </summary>
internal StateCondition GetCondition(StateMachine stateMachine, bool expectedResult, Dictionary<ScriptableObject, object> createdInstances)
{
if (createdInstances.TryGetValue(this, out var cond))

3
UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateSO.cs


{
[SerializeField] private StateActionSO[] _actions = null;
/// <summary>
/// Will create a new state or return an existing one inside <paramref name="createdInstances"/>.
/// </summary>
internal State GetState(StateMachine stateMachine, Dictionary<ScriptableObject, object> createdInstances)
{
if (createdInstances.TryGetValue(this, out var obj))

5
UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/TransitionTableSO.cs


{
[SerializeField] private TransitionItem[] _transitions = default;
public State GetInitialState(StateMachine stateMachine)
/// <summary>
/// Will get the initial state and instantiate all subsequent states, transitions, actions and conditions.
/// </summary>
internal State GetInitialState(StateMachine stateMachine)
{
var states = new List<State>();
var transitions = new List<StateTransition>();

11
UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateTransitionSO.cs.meta


fileFormatVersion: 2
guid: 9e1893bf30b6f554a9d80007ee336691
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

68
UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateTransitionSO.cs


using System;
using System.Collections.Generic;
using UnityEngine;
namespace UOP1.StateMachine.ScriptableObjects
{
[CreateAssetMenu(fileName = "New Transition", menuName = "State Machines/Transition")]
public class StateTransitionSO : ScriptableObject
{
[SerializeField] private StateSO _targetState = null;
[SerializeField] private ConditionUsage[] _conditions = default;
internal StateTransition GetTransition(StateMachine stateMachine, Dictionary<ScriptableObject, object> createdInstances)
{
if (createdInstances.TryGetValue(this, out var obj))
return (StateTransition)obj;
var transition = new StateTransition();
createdInstances.Add(this, transition);
var state = _targetState.GetState(stateMachine, createdInstances);
ProcessConditionUsages(stateMachine, _conditions, createdInstances, out var conditions, out var resultGroups);
transition.Init(state, conditions, resultGroups);
return transition;
}
private static void ProcessConditionUsages(
StateMachine stateMachine,
ConditionUsage[] conditionUsages,
Dictionary<ScriptableObject, object> createdInstances,
out StateCondition[] conditions,
out int[] resultGroups)
{
int count = conditionUsages.Length;
conditions = new StateCondition[count];
for (int i = 0; i < count; i++)
conditions[i] = conditionUsages[i].Condition.GetCondition(
stateMachine, conditionUsages[i].ExpectedResult == Result.True, createdInstances);
List<int> resultGroupsList = new List<int>();
for (int i = 0; i < count; i++)
{
resultGroupsList.Add(1);
int idx = resultGroupsList.Count - 1;
while (i < count - 1 && conditionUsages[i].Operator == Operator.And)
{
i++;
resultGroupsList[idx]++;
}
}
resultGroups = resultGroupsList.ToArray();
}
[Serializable]
public struct ConditionUsage
{
public Result ExpectedResult;
public StateConditionSO Condition;
public Operator Operator;
}
public enum Result { True, False }
public enum Operator { And, Or }
}
}
正在加载...
取消
保存