浏览代码

Merge branch 'state-machine-improvements' into main

/main
Ciro Continisio 4 年前
当前提交
ededb7c0
共有 51 个文件被更改,包括 2458 次插入143 次删除
  1. 9
      UOP1_Project/Assets/Prefabs/Characters/PigChef.prefab
  2. 2
      UOP1_Project/Assets/Prefabs/Characters/PigChef.prefab.meta
  3. 2
      UOP1_Project/Assets/Prefabs/GameplayEssentials/SpawnSystem.prefab
  4. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/HasHitHead.asset
  5. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsGrounded.asset
  6. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsHoldingJump.asset
  7. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsMoving.asset
  8. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsSliding.asset
  9. 1
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/JumpHoldTimer.asset
  10. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/HasHitHeadConditionSO.cs
  11. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsCharacterControllerGroundedConditionSO.cs
  12. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsHoldingJumpConditionSO.cs
  13. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsMovingConditionSO.cs
  14. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsSlidingConditionSO.cs
  15. 2
      UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/TimeElapsedConditionSO.cs
  16. 19
      UOP1_Project/Assets/Scripts/StateMachine/Core/State.cs
  17. 6
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateAction.cs
  18. 57
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateCondition.cs
  19. 19
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachine.cs
  20. 18
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateTransition.cs
  21. 4
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateActionSO.cs
  22. 11
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateConditionSO.cs
  23. 24
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateSO.cs
  24. 1001
      UOP1_Project/Assets/Prefabs/Pig.prefab
  25. 7
      UOP1_Project/Assets/Prefabs/Pig.prefab.meta
  26. 99
      UOP1_Project/Assets/ScriptableObjects/Protagonist/CharacterTransitionTable.asset
  27. 8
      UOP1_Project/Assets/ScriptableObjects/Protagonist/CharacterTransitionTable.asset.meta
  28. 119
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachineDebugger.cs
  29. 11
      UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachineDebugger.cs.meta
  30. 8
      UOP1_Project/Assets/Scripts/StateMachine/Editor.meta
  31. 98
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/TransitionTableSO.cs
  32. 11
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/TransitionTableSO.cs.meta
  33. 171
      UOP1_Project/Assets/Scripts/StateMachine/Editor/AddTransitionHelper.cs
  34. 11
      UOP1_Project/Assets/Scripts/StateMachine/Editor/AddTransitionHelper.cs.meta
  35. 59
      UOP1_Project/Assets/Scripts/StateMachine/Editor/ContentStyle.cs
  36. 11
      UOP1_Project/Assets/Scripts/StateMachine/Editor/ContentStyle.cs.meta
  37. 47
      UOP1_Project/Assets/Scripts/StateMachine/Editor/SerializedTransition.cs
  38. 11
      UOP1_Project/Assets/Scripts/StateMachine/Editor/SerializedTransition.cs.meta
  39. 83
      UOP1_Project/Assets/Scripts/StateMachine/Editor/StateEditor.cs
  40. 11
      UOP1_Project/Assets/Scripts/StateMachine/Editor/StateEditor.cs.meta
  41. 11
      UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionDisplayHelper.cs.meta
  42. 392
      UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionTableEditor.cs
  43. 11
      UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionTableEditor.cs.meta
  44. 17
      UOP1_Project/Assets/Scripts/StateMachine/Editor/UOP1.StateMachine.Editor.asmdef
  45. 7
      UOP1_Project/Assets/Scripts/StateMachine/Editor/UOP1.StateMachine.Editor.asmdef.meta
  46. 121
      UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionDisplayHelper.cs
  47. 8
      UOP1_Project/Assets/ScriptableObjects/Protagonist/Transitions.meta
  48. 11
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateTransitionSO.cs.meta
  49. 68
      UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateTransitionSO.cs

9
UOP1_Project/Assets/Prefabs/Characters/PigChef.prefab


m_Script: {fileID: 11500000, guid: 1eeda163c70b3cb4ebb88ba92f608fd8, type: 3}
m_Name:
m_EditorClassIdentifier:
CurrentState:
debug: 0
_initialStateSO: {fileID: 11400000, guid: e128814ff6dbf63449bbc4dc8b6dc066, type: 2}
_transitionTableSO: {fileID: 11400000, guid: f0baa4ca2bfa24e4cb6d1efe9fa81ea3, type: 2}
_debugger:
debugTransitions: 0
appendConditionsInfo: 1
appendActionsInfo: 1
currentState:
--- !u!114 &3722006066079062647
MonoBehaviour:
m_ObjectHideFlags: 0

2
UOP1_Project/Assets/Prefabs/Characters/PigChef.prefab.meta


fileFormatVersion: 2
guid: 0fa393e1e37bc9e4e829c25a9452bcd3
guid: 062b3805bf6784e4d9c599ee60eaa002
PrefabImporter:
externalObjects: {}
userData:

2
UOP1_Project/Assets/Prefabs/GameplayEssentials/SpawnSystem.prefab


m_Name:
m_EditorClassIdentifier:
_defaultSpawnIndex: 0
_playerPrefab: {fileID: 5557640735889932260, guid: 0fa393e1e37bc9e4e829c25a9452bcd3,
_playerPrefab: {fileID: 5557640735889932260, guid: 062b3805bf6784e4d9c599ee60eaa002,
type: 3}
_cameraManager: {fileID: 0}
_spawnLocations:

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;
}

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


namespace UOP1.StateMachine
using UOP1.StateMachine.ScriptableObjects;
namespace UOP1.StateMachine
public string Name { get; internal set; }
internal StateSO _originSO;
internal StateMachine _stateMachine;
internal StateTransition[] _transitions;
internal StateAction[] _actions;

public State(
StateSO originSO,
_originSO = originSO;
_stateMachine = stateMachine;
_transitions = transitions;
_actions = 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;
}
}
}

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


namespace UOP1.StateMachine
using UOP1.StateMachine.ScriptableObjects;
namespace UOP1.StateMachine
{
/// <summary>
/// An object representing an action.

internal StateActionSO _originSO;
/// <summary>
/// Called every frame the <see cref="StateMachine"/> is in a <see cref="State"/> with this <see cref="StateAction"/>.
/// </summary>

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


namespace UOP1.StateMachine
using UnityEngine;
using UOP1.StateMachine.ScriptableObjects;
namespace UOP1.StateMachine
{
/// <summary>
/// Class that represents a conditional statement.

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 StateConditionSO _originSO;
internal readonly StateMachine _stateMachine;
public StateCondition(Condition condition, bool expectedResult)
public StateCondition(StateConditionSO originSO, StateMachine stateMachine, Condition condition, bool expectedResult)
_originSO = originSO;
_stateMachine = stateMachine;
_condition._originSO = originSO;
public bool IsMet => _condition.Statement() == _expectedResult;
public bool IsMet()
{
bool statement = _condition.GetStatement();
bool isMet = statement == _expectedResult;
#if UNITY_EDITOR
_stateMachine._debugger.TransitionConditionResult(_originSO.name, statement, isMet);
#endif
return isMet;
}
}
}

19
UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachine.cs


{
public class StateMachine : MonoBehaviour
{
[Tooltip("Set the initial state of this StateMachine")]
[SerializeField] private ScriptableObjects.TransitionTableSO _transitionTableSO = default;
public string CurrentState;
public bool debug;
[Space]
[SerializeField]
internal StateMachineDebugger _debugger = default;
[Tooltip("Set the initial state of this StateMachine")]
[SerializeField] private ScriptableObjects.StateSO _initialStateSO = null;
private readonly Dictionary<Type, Component> _cachedComponents = new Dictionary<Type, Component>();
private State _currentState;

_currentState = _initialStateSO.GetState(this);
_currentState = _transitionTableSO.GetInitialState(this);
CurrentState = _currentState.Name;
_debugger.Awake(this, _currentState._originSO.name);
#endif
}

_currentState.OnStateExit();
_currentState = transitionState;
_currentState.OnStateEnter();
#if UNITY_EDITOR
if (debug)
Debug.Log($"{name} entering state {_currentState.Name}");
CurrentState = _currentState.Name;
#endif
}
}
}

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


private bool ShouldTransition()
{
#if UNITY_EDITOR
_targetState._stateMachine._debugger.TransitionEvaluationBegin(_targetState._originSO.name);
#endif
_conditions[idx].IsMet :
_results[i] && _conditions[idx].IsMet;
_conditions[idx].IsMet() :
_results[i] && _conditions[idx].IsMet();
#if UNITY_EDITOR
_targetState._stateMachine._debugger.TransitionEvaluationEnd(ret, _targetState._actions);
#endif
}
internal void ClearConditionsCache()
{
for (int i = 0; i < _conditions.Length; i++)
_conditions[i]._condition.ClearStatementCache();
}
}
}

4
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))

createdInstances.Add(this, action);
action._originSO = this;
action.Awake(stateMachine);
return action;
}

11
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>
return new StateCondition((Condition)cond, expectedResult);
return new StateCondition(this, stateMachine, (Condition)cond, expectedResult);
var condition = new StateCondition(CreateCondition(), expectedResult);
var condition = new StateCondition(this, stateMachine, CreateCondition(), expectedResult);
createdInstances.Add(this, condition._condition);
condition._condition.Awake(stateMachine);
return condition;

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


public class StateSO : ScriptableObject
{
[SerializeField] private StateActionSO[] _actions = null;
[SerializeField] private StateTransitionSO[] _transitions = null;
public State GetState(StateMachine stateMachine)
{
return GetState(stateMachine, new Dictionary<ScriptableObject, object>());
}
/// <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))

createdInstances.Add(this, state);
state.Name = name;
state._originSO = this;
state._transitions = GetTransitions(_transitions, stateMachine, createdInstances);
state._transitions = new StateTransition[0];
}
private static StateTransition[] GetTransitions(StateTransitionSO[] scriptableTransitions,
StateMachine stateMachine, Dictionary<ScriptableObject, object> createdInstances)
{
int count = scriptableTransitions.Length;
var transitions = new StateTransition[count];
for (int i = 0; i < count; i++)
transitions[i] = scriptableTransitions[i].GetTransition(stateMachine, createdInstances);
return transitions;
}
private static StateAction[] GetActions(StateActionSO[] scriptableActions,

1001
UOP1_Project/Assets/Prefabs/Pig.prefab
文件差异内容过多而无法显示
查看文件

7
UOP1_Project/Assets/Prefabs/Pig.prefab.meta


fileFormatVersion: 2
guid: 0fa393e1e37bc9e4e829c25a9452bcd3
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

99
UOP1_Project/Assets/ScriptableObjects/Protagonist/CharacterTransitionTable.asset


%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 872cbaa965d1f6e4e98365d74e2060df, type: 3}
m_Name: CharacterTransitionTable
m_EditorClassIdentifier:
_transitions:
- FromState: {fileID: 11400000, guid: e128814ff6dbf63449bbc4dc8b6dc066, type: 2}
ToState: {fileID: 11400000, guid: 027d32476800b3543b2f5446a59054c8, type: 2}
Conditions:
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: a79b812272ab8314aa305b39f9a2740a, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: 0aa906b71b1b4504d950f5ee8e1f06e8, type: 2}
ToState: {fileID: 11400000, guid: c57ed1dc57a890943b2648ca8a581075, type: 2}
Conditions:
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: bf6a2f4642738ac4f8e84bcdf1cb5f93, type: 2}
Operator: 1
- ExpectedResult: 1
Condition: {fileID: 11400000, guid: 620f4efd93744084a8ac9ba272f093dc, type: 2}
Operator: 1
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: 41dbff2af0b6f7141a32bb464309ba69, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: c57ed1dc57a890943b2648ca8a581075, type: 2}
ToState: {fileID: 11400000, guid: 027d32476800b3543b2f5446a59054c8, type: 2}
Conditions:
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: c7258bdb2558a214f916c8b5b3c0fa1f, type: 2}
Operator: 0
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: a79b812272ab8314aa305b39f9a2740a, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: c57ed1dc57a890943b2648ca8a581075, type: 2}
ToState: {fileID: 11400000, guid: e128814ff6dbf63449bbc4dc8b6dc066, type: 2}
Conditions:
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: c7258bdb2558a214f916c8b5b3c0fa1f, type: 2}
Operator: 0
- ExpectedResult: 1
Condition: {fileID: 11400000, guid: a79b812272ab8314aa305b39f9a2740a, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: 027d32476800b3543b2f5446a59054c8, type: 2}
ToState: {fileID: 11400000, guid: e128814ff6dbf63449bbc4dc8b6dc066, type: 2}
Conditions:
- ExpectedResult: 1
Condition: {fileID: 11400000, guid: a79b812272ab8314aa305b39f9a2740a, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: 027d32476800b3543b2f5446a59054c8, type: 2}
ToState: {fileID: 11400000, guid: 0aa906b71b1b4504d950f5ee8e1f06e8, type: 2}
Conditions:
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: 620f4efd93744084a8ac9ba272f093dc, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: 027d32476800b3543b2f5446a59054c8, type: 2}
ToState: {fileID: 11400000, guid: 4c2c0dc4f7ee62b4d95b20ac3dce281e, type: 2}
Conditions:
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: c5090b215e4f1594f9d1ef6680cd15bb, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: 027d32476800b3543b2f5446a59054c8, type: 2}
ToState: {fileID: 11400000, guid: c57ed1dc57a890943b2648ca8a581075, type: 2}
Conditions:
- ExpectedResult: 1
Condition: {fileID: 11400000, guid: c7258bdb2558a214f916c8b5b3c0fa1f, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: e128814ff6dbf63449bbc4dc8b6dc066, type: 2}
ToState: {fileID: 11400000, guid: 4c2c0dc4f7ee62b4d95b20ac3dce281e, type: 2}
Conditions:
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: c5090b215e4f1594f9d1ef6680cd15bb, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: e128814ff6dbf63449bbc4dc8b6dc066, type: 2}
ToState: {fileID: 11400000, guid: 0aa906b71b1b4504d950f5ee8e1f06e8, type: 2}
Conditions:
- ExpectedResult: 0
Condition: {fileID: 11400000, guid: 620f4efd93744084a8ac9ba272f093dc, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: e128814ff6dbf63449bbc4dc8b6dc066, type: 2}
ToState: {fileID: 11400000, guid: c57ed1dc57a890943b2648ca8a581075, type: 2}
Conditions:
- ExpectedResult: 1
Condition: {fileID: 11400000, guid: c7258bdb2558a214f916c8b5b3c0fa1f, type: 2}
Operator: 0
- FromState: {fileID: 11400000, guid: 4c2c0dc4f7ee62b4d95b20ac3dce281e, type: 2}
ToState: {fileID: 11400000, guid: e128814ff6dbf63449bbc4dc8b6dc066, type: 2}
Conditions:
- ExpectedResult: 1
Condition: {fileID: 11400000, guid: c5090b215e4f1594f9d1ef6680cd15bb, type: 2}
Operator: 0

8
UOP1_Project/Assets/ScriptableObjects/Protagonist/CharacterTransitionTable.asset.meta


fileFormatVersion: 2
guid: f0baa4ca2bfa24e4cb6d1efe9fa81ea3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

119
UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachineDebugger.cs


using System;
using System.Text;
using UnityEngine;
namespace UOP1.StateMachine
{
/// <summary>
/// Class specialized in debugging the state transitions, should only be used while in editor mode.
/// </summary>
[Serializable]
internal class StateMachineDebugger
{
[SerializeField]
[Tooltip("Issues a debug log when a state transition is triggered")]
internal bool debugTransitions = false;
[SerializeField]
[Tooltip("List all conditions evaluated, the result is read: ConditionName == BooleanResult [PassedTest]")]
internal bool appendConditionsInfo = true;
[SerializeField]
[Tooltip("List all actions activated by the new State")]
internal bool appendActionsInfo = true;
[SerializeField]
[Tooltip("The current State name [Readonly]")]
internal string currentState;
private StateMachine _stateMachine;
private StringBuilder _logBuilder;
private string _targetState = string.Empty;
private const string CHECK_MARK = "\u2714";
private const string UNCHECK_MARK = "\u2718";
private const string THICK_ARROW = "\u279C";
private const string SHARP_ARROW = "\u27A4";
/// <summary>
/// Must be called together with <c>StateMachine.Awake()</c>
/// </summary>
internal void Awake(StateMachine stateMachine, string initialState)
{
_stateMachine = stateMachine;
_logBuilder = new StringBuilder();
currentState = initialState;
}
internal void TransitionEvaluationBegin(string targetState)
{
_targetState = targetState;
if (!debugTransitions)
return;
_logBuilder.Clear();
_logBuilder.AppendLine($"{_stateMachine.gameObject.name} state changed");
_logBuilder.AppendLine($"{currentState} {SHARP_ARROW} {_targetState}");
if (appendConditionsInfo)
{
_logBuilder.AppendLine();
_logBuilder.AppendLine($"Transition Conditions:");
}
}
internal void TransitionConditionResult(string conditionName, bool result, bool isMet)
{
if (!debugTransitions || _logBuilder.Length == 0 || !appendConditionsInfo)
return;
_logBuilder.Append($" {THICK_ARROW} {conditionName} == {result}");
if (isMet)
_logBuilder.AppendLine($" [{CHECK_MARK}]");
else
_logBuilder.AppendLine($" [{UNCHECK_MARK}]");
}
internal void TransitionEvaluationEnd(bool passed, StateAction[] actions)
{
if (passed)
currentState = _targetState;
if (!debugTransitions || _logBuilder.Length == 0)
return;
if (passed)
{
LogActions(actions);
PrintDebugLog();
}
_logBuilder.Clear();
}
private void LogActions(StateAction[] actions)
{
if (!appendActionsInfo)
return;
_logBuilder.AppendLine();
_logBuilder.AppendLine("State Actions:");
foreach (StateAction action in actions)
{
_logBuilder.AppendLine($" {THICK_ARROW} {action._originSO.name}");
}
}
private void PrintDebugLog()
{
_logBuilder.AppendLine();
_logBuilder.Append("--------------------------------");
Debug.Log(_logBuilder.ToString());
}
}
}

11
UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachineDebugger.cs.meta


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

8
UOP1_Project/Assets/Scripts/StateMachine/Editor.meta


fileFormatVersion: 2
guid: 3596ae92061fc0d4d9cd18d55258b236
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UOP1.StateMachine.ScriptableObjects
{
[CreateAssetMenu(fileName = "New Transition Table", menuName = "State Machines/Transition Table")]
public class TransitionTableSO : ScriptableObject
{
[SerializeField] private TransitionItem[] _transitions = default;
/// <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>();
var createdInstances = new Dictionary<ScriptableObject, object>();
var fromStates = _transitions.GroupBy(transition => transition.FromState);
foreach (var fromState in fromStates)
{
if (fromState.Key == null)
throw new ArgumentNullException(nameof(fromState.Key), $"TransitionTable: {name}");
var state = fromState.Key.GetState(stateMachine, createdInstances);
states.Add(state);
transitions.Clear();
foreach (var transitionItem in fromState)
{
if (transitionItem.ToState == null)
throw new ArgumentNullException(nameof(transitionItem.ToState), $"TransitionTable: {name}, From State: {fromState.Key.name}");
var toState = transitionItem.ToState.GetState(stateMachine, createdInstances);
ProcessConditionUsages(stateMachine, transitionItem.Conditions, createdInstances, out var conditions, out var resultGroups);
transitions.Add(new StateTransition(toState, conditions, resultGroups));
}
state._transitions = transitions.ToArray();
}
return states.Count > 0 ? states[0]
: throw new InvalidOperationException($"TransitionTable {name} is empty.");
}
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++)
{
int idx = resultGroupsList.Count;
resultGroupsList.Add(1);
while (i < count - 1 && conditionUsages[i].Operator == Operator.And)
{
i++;
resultGroupsList[idx]++;
}
}
resultGroups = resultGroupsList.ToArray();
}
[Serializable]
public struct TransitionItem
{
public StateSO FromState;
public StateSO ToState;
public ConditionUsage[] Conditions;
}
[Serializable]
public struct ConditionUsage
{
public Result ExpectedResult;
public StateConditionSO Condition;
public Operator Operator;
}
public enum Result { True, False }
public enum Operator { And, Or }
}
}

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


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

171
UOP1_Project/Assets/Scripts/StateMachine/Editor/AddTransitionHelper.cs


using System;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using UOP1.StateMachine.ScriptableObjects;
using static UnityEditor.EditorGUILayout;
namespace UOP1.StateMachine.Editor
{
internal class AddTransitionHelper : IDisposable
{
internal SerializedTransition SerializedTransition { get; }
private readonly SerializedObject _transition;
private readonly ReorderableList _list;
private readonly TransitionTableEditor _editor;
private bool _toggle = false;
internal AddTransitionHelper(TransitionTableEditor editor)
{
_editor = editor;
_transition = new SerializedObject(ScriptableObject.CreateInstance<TransitionItemSO>());
SerializedTransition = new SerializedTransition(_transition.FindProperty("Item"));
_list = new ReorderableList(_transition, SerializedTransition.Conditions);
SetupConditionsList(_list);
}
internal void Display()
{
// Display add button only if not already adding a transition
if (!_toggle)
{
if (GUILayout.Button(EditorGUIUtility.IconContent("Toolbar Plus"), GUILayout.Width(35)))
{
_toggle = true;
SerializedTransition.ClearProperties();
}
if (!_toggle)
return;
}
var rect = BeginVertical();
rect.x += 45;
rect.width -= 40;
EditorGUI.DrawRect(rect, ContentStyle.LightGray);
Separator();
// State Fields
BeginHorizontal();
{
Space(50, false);
StatePropField("From", SerializedTransition.FromState);
Space(10, false);
StatePropField("To", SerializedTransition.ToState);
}
EndHorizontal();
// Conditions List
BeginHorizontal();
{
Space(50, false);
BeginVertical();
_list.DoLayoutList();
EndVertical();
}
EndHorizontal();
Separator();
// Add and cancel buttons
BeginHorizontal();
{
Space(50, false);
if (GUILayout.Button("Add Transition"))
{
if (SerializedTransition.FromState.objectReferenceValue == null)
Debug.LogException(new ArgumentNullException("FromState"));
else if (SerializedTransition.ToState.objectReferenceValue == null)
Debug.LogException(new ArgumentNullException("ToState"));
else if (SerializedTransition.FromState.objectReferenceValue == SerializedTransition.ToState.objectReferenceValue)
Debug.LogException(new InvalidOperationException("FromState and ToState are the same."));
else
{
_editor.AddTransition(SerializedTransition);
_toggle = false;
}
}
else if (GUILayout.Button("Cancel"))
{
_toggle = false;
}
}
EndHorizontal();
EndVertical();
void StatePropField(string label, SerializedProperty prop)
{
BeginVertical();
LabelField(label);
BeginHorizontal();
Space(20, false);
PropertyField(prop, GUIContent.none, GUILayout.MaxWidth(180));
EndHorizontal();
EndVertical();
}
}
public void Dispose()
{
UnityEngine.Object.DestroyImmediate(_transition.targetObject);
_transition.Dispose();
GC.SuppressFinalize(this);
}
private static void SetupConditionsList(ReorderableList reorderableList)
{
reorderableList.elementHeight *= 2.3f;
reorderableList.drawHeaderCallback += rect => GUI.Label(rect, "Conditions");
reorderableList.onAddCallback += list =>
{
int count = list.count;
list.serializedProperty.InsertArrayElementAtIndex(count);
var prop = list.serializedProperty.GetArrayElementAtIndex(count);
prop.FindPropertyRelative("Condition").objectReferenceValue = null;
prop.FindPropertyRelative("ExpectedResult").enumValueIndex = 0;
prop.FindPropertyRelative("Operator").enumValueIndex = 0;
};
reorderableList.drawElementCallback += (Rect rect, int index, bool isActive, bool isFocused) =>
{
var prop = reorderableList.serializedProperty.GetArrayElementAtIndex(index);
rect = new Rect(rect.x, rect.y + 2.5f, rect.width, EditorGUIUtility.singleLineHeight);
var condition = prop.FindPropertyRelative("Condition");
if (condition.objectReferenceValue != null)
{
string label = condition.objectReferenceValue.name;
GUI.Label(rect, "If");
GUI.Label(new Rect(rect.x + 20, rect.y, rect.width, rect.height), label, EditorStyles.boldLabel);
EditorGUI.PropertyField(new Rect(rect.x + rect.width - 180, rect.y, 20, rect.height), condition, GUIContent.none);
}
else
{
EditorGUI.PropertyField(new Rect(rect.x, rect.y, 150, rect.height), condition, GUIContent.none);
}
EditorGUI.LabelField(new Rect(rect.x + rect.width - 120, rect.y, 20, rect.height), "Is");
EditorGUI.PropertyField(new Rect(rect.x + rect.width - 60, rect.y, 60, rect.height), prop.FindPropertyRelative("ExpectedResult"), GUIContent.none);
EditorGUI.PropertyField(new Rect(rect.x + 20, rect.y + EditorGUIUtility.singleLineHeight + 5, 60, rect.height), prop.FindPropertyRelative("Operator"), GUIContent.none);
};
reorderableList.onChangedCallback += list => reorderableList.serializedProperty.serializedObject.ApplyModifiedProperties();
reorderableList.drawElementBackgroundCallback += (Rect rect, int index, bool isActive, bool isFocused) =>
{
if (isFocused)
EditorGUI.DrawRect(rect, ContentStyle.Focused);
if (index % 2 != 0)
EditorGUI.DrawRect(rect, ContentStyle.ZebraDark);
else
EditorGUI.DrawRect(rect, ContentStyle.ZebraLight);
};
}
// SO to serialize a TransitionItem
internal class TransitionItemSO : ScriptableObject
{
public TransitionTableSO.TransitionItem Item = default;
}
}
}

11
UOP1_Project/Assets/Scripts/StateMachine/Editor/AddTransitionHelper.cs.meta


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

59
UOP1_Project/Assets/Scripts/StateMachine/Editor/ContentStyle.cs


using UnityEditor;
using UnityEngine;
namespace UOP1.StateMachine.Editor
{
internal static class ContentStyle
{
internal static Color DarkGray { get; private set; }
internal static Color LightGray { get; private set; }
internal static Color Focused { get; private set; }
internal static Color ZebraDark { get; private set; }
internal static Color ZebraLight { get; private set; }
internal static RectOffset Padding { get; private set; }
internal static RectOffset LeftPadding { get; private set; }
internal static RectOffset Margin { get; private set; }
internal static GUIStyle BoldCentered { get; private set; }
internal static GUIStyle StateListStyle { get; private set; }
internal static GUIStyle WithPadding { get; private set; }
internal static GUIStyle WithPaddingAndMargins { get; private set; }
private static bool _initialised = false;
[InitializeOnLoadMethod]
internal static void Initialize()
{
if (_initialised)
return;
_initialised = true;
DarkGray = EditorGUIUtility.isProSkin ? new Color(0.283f, 0.283f, 0.283f) : new Color(0.7f, 0.7f, 0.7f);
LightGray = EditorGUIUtility.isProSkin ? new Color(0.33f, 0.33f, 0.33f) : new Color(0.8f, 0.8f, 0.8f);
ZebraDark = new Color(0.1f, 0.5f, 0.9f, 0.1f);
ZebraLight = new Color(0.8f, 0.8f, 0.9f, 0.1f);
Focused = new Color(0.5f, 0.5f, 0.5f, 0.5f);
Padding = new RectOffset(5, 5, 5, 5);
LeftPadding = new RectOffset(10, 0, 0, 0);
Margin = new RectOffset(8, 8, 8, 8);
WithPadding = new GUIStyle { padding = Padding };
WithPaddingAndMargins = new GUIStyle { padding = Padding, margin = Margin };
//Prepare a modification of the GUIStyleState to feed into the GUIStyle, for the text colour
GUIStyleState guiStyleStateNormal = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector).label.normal;
//bright text for Professional skin, dark text for Personal skin
guiStyleStateNormal.textColor = EditorGUIUtility.isProSkin ? new Color(.85f, .85f, .85f) : new Color(0.337f, 0.337f, 0.337f);
BoldCentered = new GUIStyle { fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleCenter };
StateListStyle = new GUIStyle
{
alignment = TextAnchor.MiddleLeft,
padding = LeftPadding,
fontStyle = FontStyle.Bold,
fontSize = 12,
margin = Margin,
normal = guiStyleStateNormal,
};
}
}
}

11
UOP1_Project/Assets/Scripts/StateMachine/Editor/ContentStyle.cs.meta


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

47
UOP1_Project/Assets/Scripts/StateMachine/Editor/SerializedTransition.cs


using UnityEditor;
namespace UOP1.StateMachine.Editor
{
internal readonly struct SerializedTransition
{
internal readonly SerializedProperty Transition;
internal readonly SerializedProperty FromState;
internal readonly SerializedProperty ToState;
internal readonly SerializedProperty Conditions;
internal readonly int Index;
internal SerializedTransition(SerializedProperty transition)
{
Transition = transition;
FromState = Transition.FindPropertyRelative("FromState");
ToState = Transition.FindPropertyRelative("ToState");
Conditions = Transition.FindPropertyRelative("Conditions");
Index = -1;
}
internal SerializedTransition(SerializedObject transitionTable, int index)
{
Transition = transitionTable.FindProperty("_transitions").GetArrayElementAtIndex(index);
FromState = Transition.FindPropertyRelative("FromState");
ToState = Transition.FindPropertyRelative("ToState");
Conditions = Transition.FindPropertyRelative("Conditions");
Index = index;
}
internal SerializedTransition(SerializedProperty transition, int index)
{
Transition = transition.GetArrayElementAtIndex(index);
FromState = Transition.FindPropertyRelative("FromState");
ToState = Transition.FindPropertyRelative("ToState");
Conditions = Transition.FindPropertyRelative("Conditions");
Index = index;
}
internal void ClearProperties()
{
FromState.objectReferenceValue = null;
ToState.objectReferenceValue = null;
Conditions.ClearArray();
}
}
}

11
UOP1_Project/Assets/Scripts/StateMachine/Editor/SerializedTransition.cs.meta


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

83
UOP1_Project/Assets/Scripts/StateMachine/Editor/StateEditor.cs


using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using UOP1.StateMachine.ScriptableObjects;
namespace UOP1.StateMachine.Editor
{
[CustomEditor(typeof(StateSO))]
public class StateEditor : UnityEditor.Editor
{
private ReorderableList _list;
private SerializedProperty _actions;
private void OnEnable()
{
Undo.undoRedoPerformed += DoUndo;
_actions = serializedObject.FindProperty("_actions");
_list = new ReorderableList(serializedObject, _actions, true, true, true, true);
SetupActionsList(_list);
}
private void OnDisable()
{
Undo.undoRedoPerformed -= DoUndo;
}
public override void OnInspectorGUI()
{
_list.DoLayoutList();
}
private void DoUndo()
{
serializedObject.UpdateIfRequiredOrScript();
}
private static void SetupActionsList(ReorderableList reorderableList)
{
reorderableList.elementHeight *= 1.5f;
reorderableList.drawHeaderCallback += rect => GUI.Label(rect, "Actions");
reorderableList.onAddCallback += list =>
{
int count = list.count;
list.serializedProperty.InsertArrayElementAtIndex(count);
var prop = list.serializedProperty.GetArrayElementAtIndex(count);
prop.objectReferenceValue = null;
};
reorderableList.drawElementCallback += (Rect rect, int index, bool isActive, bool isFocused) =>
{
var r = rect;
r.height = EditorGUIUtility.singleLineHeight;
r.y += 5;
r.x += 5;
var prop = reorderableList.serializedProperty.GetArrayElementAtIndex(index);
if (prop.objectReferenceValue != null)
{
var label = prop.objectReferenceValue.name;
r.width = 35;
EditorGUI.PropertyField(r, prop, GUIContent.none);
r.width = rect.width - 50;
r.x += 42;
GUI.Label(r, label, EditorStyles.boldLabel);
}
else
EditorGUI.PropertyField(r, prop, GUIContent.none);
};
reorderableList.onChangedCallback += list => list.serializedProperty.serializedObject.ApplyModifiedProperties();
reorderableList.drawElementBackgroundCallback += (Rect rect, int index, bool isActive, bool isFocused) =>
{
if (isFocused)
EditorGUI.DrawRect(rect, ContentStyle.Focused);
if (index % 2 != 0)
EditorGUI.DrawRect(rect, ContentStyle.ZebraDark);
else
EditorGUI.DrawRect(rect, ContentStyle.ZebraLight);
};
}
}
}

11
UOP1_Project/Assets/Scripts/StateMachine/Editor/StateEditor.cs.meta


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

11
UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionDisplayHelper.cs.meta


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

392
UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionTableEditor.cs


using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UOP1.StateMachine.ScriptableObjects;
using static UnityEditor.EditorGUILayout;
using Object = UnityEngine.Object;
namespace UOP1.StateMachine.Editor
{
[CustomEditor(typeof(TransitionTableSO))]
internal class TransitionTableEditor : UnityEditor.Editor
{
// Property with all the transitions.
private SerializedProperty _transitions;
// _fromStates and _transitionsByFromStates form an Object->Transitions dictionary.
private List<Object> _fromStates;
private List<List<TransitionDisplayHelper>> _transitionsByFromStates;
// _toggles for the opened states. Only one should be active at a time.
private bool[] _toggles;
// Helper class to add new transitions.
private AddTransitionHelper _addTransitionHelper;
// Editor to display the StateSO inspector.
private UnityEditor.Editor _cachedStateEditor;
private bool _displayStateEditor;
private void OnEnable()
{
_addTransitionHelper = new AddTransitionHelper(this);
Undo.undoRedoPerformed += ResetIfRequired;
Reset();
}
private void OnDisable()
{
Undo.undoRedoPerformed -= ResetIfRequired;
_addTransitionHelper?.Dispose();
}
/// <summary>
/// Method to fully reset the editor. Used whenever adding, removing and reordering transitions.
/// </summary>
internal void Reset()
{
_transitions = serializedObject.FindProperty("_transitions");
GroupByFromState();
_toggles = new bool[_fromStates.Count];
}
public override void OnInspectorGUI()
{
if (!_displayStateEditor)
TransitionTableGUI();
else
StateEditorGUI();
}
private void StateEditorGUI()
{
Separator();
// Back button
if (GUILayout.Button(EditorGUIUtility.IconContent("scrollleft"), GUILayout.Width(35), GUILayout.Height(20)))
_displayStateEditor = false;
Separator();
EditorGUILayout.HelpBox("Edit the Actions that a State performs per frame. The order represent the order of execution.", MessageType.Info);
Separator();
// State name
EditorGUILayout.LabelField(_cachedStateEditor.target.name, EditorStyles.boldLabel);
Separator();
_cachedStateEditor.OnInspectorGUI();
}
private void TransitionTableGUI()
{
Separator();
EditorGUILayout.HelpBox("Click on any State's name to see the Transitions it contains, or click the Pencil/Wrench icon to see its Actions.", MessageType.Info);
Separator();
serializedObject.UpdateIfRequiredOrScript();
// For each fromState
for (int i = 0; i < _fromStates.Count; i++)
{
EditorGUI.DrawRect(BeginVertical(ContentStyle.WithPaddingAndMargins), ContentStyle.LightGray);
var transitions = _transitionsByFromStates[i];
// State Header
BeginHorizontal();
{
BeginVertical();
string label = transitions[0].SerializedTransition.FromState.objectReferenceValue.name;
if (i == 0)
label += " (Initial State)";
// State toggle
_toggles[i] = BeginFoldoutHeaderGroup(
foldout: _toggles[i],
content: label,
style: ContentStyle.StateListStyle);
Separator();
EndVertical();
// State Header Buttons
{
bool Button(string icon) => GUILayout.Button(EditorGUIUtility.IconContent(icon), GUILayout.Width(35), GUILayout.Height(20));
// Switch to state editor
if (Button("SceneViewTools"))
{
if (_cachedStateEditor == null)
_cachedStateEditor = CreateEditor(transitions[0].SerializedTransition.FromState.objectReferenceValue, typeof(StateEditor));
else
CreateCachedEditor(transitions[0].SerializedTransition.FromState.objectReferenceValue, typeof(StateEditor), ref _cachedStateEditor);
_displayStateEditor = true;
return;
}
// Move state up
if (Button("scrollup"))
{
if (ReorderState(i, true))
return;
}
// Move state down
if (Button("scrolldown"))
{
if (ReorderState(i, false))
return;
}
}
}
EndHorizontal();
// If state is open
if (_toggles[i])
{
DisableAllStateTogglesExcept(i);
EditorGUI.BeginChangeCheck();
// Display all the transitions in the state
foreach (var transition in transitions)
{
if (transition.Display())
return;
Separator();
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
EndFoldoutHeaderGroup();
EndVertical();
//GUILayout.HorizontalSlider(0, 0, 0);
Separator();
}
var rect = BeginHorizontal();
Space(rect.width - 55);
// Display add transition button
_addTransitionHelper.Display();
EndHorizontal();
}
/// <summary>
/// Move a state up or down
/// </summary>
/// <param name="index">Index of the state in _fromStates</param>
/// <param name="up">Moving up(true) or down(true)</param>
/// <returns>True if changes were made and returning is required. Otherwise false.</returns>
internal bool ReorderState(int index, bool up)
{
if ((up && index == 0) || (!up && index == _fromStates.Count - 1))
return false;
// Moving a state up is easier than moving it down. So when moving a state down, we instead move the next state up.
MoveStateUp(up ? index : index + 1);
return true;
}
private void MoveStateUp(int index)
{
var transitions = _transitionsByFromStates[index];
int transitionIndex = transitions[0].SerializedTransition.Index;
int targetIndex = _transitionsByFromStates[index - 1][0].SerializedTransition.Index;
_transitions.MoveArrayElement(transitionIndex, targetIndex);
serializedObject.ApplyModifiedProperties();
Reset();
}
/// <summary>
/// Add a new transition. If a transition with the same from and to states is found,
/// the conditions in the new transition are added to it.
/// </summary>
/// <param name="source">Source Transition</param>
internal void AddTransition(SerializedTransition source)
{
SerializedTransition transition;
if (TryGetExistingTransition(source.FromState, source.ToState, out int fromIndex, out int toIndex))
{
transition = _transitionsByFromStates[fromIndex][toIndex].SerializedTransition;
}
else
{
int count = _transitions.arraySize;
_transitions.InsertArrayElementAtIndex(count);
transition = new SerializedTransition(_transitions.GetArrayElementAtIndex(count));
transition.ClearProperties();
transition.FromState.objectReferenceValue = source.FromState.objectReferenceValue;
transition.ToState.objectReferenceValue = source.ToState.objectReferenceValue;
}
CopyConditions(transition.Conditions, source.Conditions);
serializedObject.ApplyModifiedProperties();
Reset();
_toggles[fromIndex >= 0 ? fromIndex : _toggles.Length - 1] = true;
}
/// <summary>
/// Move a transition up or down
/// </summary>
/// <param name="serializedTransition">The transition to move</param>
/// <param name="up">Move up(true) or down(false)</param>
/// <returns>True if changes were made and returning is required. Otherwise false.</returns>
internal bool ReorderTransition(SerializedTransition serializedTransition, bool up)
{
int targetIndex = -1;
int fromId = serializedTransition.FromState.objectReferenceInstanceIDValue;
SerializedTransition st;
for (int i = 0; i < _transitions.arraySize; i++)
{
if (up && i >= serializedTransition.Index)
break;
if (!up && i <= serializedTransition.Index)
continue;
st = new SerializedTransition(_transitions, i);
if (st.FromState.objectReferenceInstanceIDValue != fromId)
continue;
targetIndex = i;
if (!up)
break;
}
if (targetIndex == -1)
return false;
_transitions.MoveArrayElement(serializedTransition.Index, targetIndex);
serializedObject.ApplyModifiedProperties();
Reset();
_toggles[
_fromStates.IndexOf(
_transitions.GetArrayElementAtIndex(targetIndex)
.FindPropertyRelative("FromState")
.objectReferenceValue)] = true;
return true;
}
/// <summary>
/// Remove a transition by index.
/// </summary>
/// <param name="index">Index of the transition in the transition table</param>
internal void RemoveTransition(int index)
{
var state = _transitions.GetArrayElementAtIndex(index).FindPropertyRelative("FromState").objectReferenceValue;
_transitions.DeleteArrayElementAtIndex(index);
bool toggle = DoToggleAndSort(state, index);
serializedObject.ApplyModifiedProperties();
Reset();
if (toggle)
{
int i = _fromStates.IndexOf(state);
if (i >= 0)
_toggles[i] = true;
}
}
private bool DoToggleAndSort(Object state, int index)
{
bool ret = false;
for (int i = 0; i < _transitions.arraySize; i++)
{
if (_transitions.GetArrayElementAtIndex(i).FindPropertyRelative("FromState").objectReferenceValue == state)
{
ret = true;
if (i > index)
{
_transitions.MoveArrayElement(i, index);
break;
}
}
}
return ret;
}
private void DisableAllStateTogglesExcept(int index)
{
for (int i = 0; i < _toggles.Length; i++)
if (i != index)
_toggles[i] = false;
}
private void CopyConditions(SerializedProperty copyTo, SerializedProperty copyFrom)
{
for (int i = 0, j = copyTo.arraySize; i < copyFrom.arraySize; i++, j++)
{
copyTo.InsertArrayElementAtIndex(j);
var cond = copyTo.GetArrayElementAtIndex(j);
var srcCond = copyFrom.GetArrayElementAtIndex(i);
cond.FindPropertyRelative("ExpectedResult").enumValueIndex = srcCond.FindPropertyRelative("ExpectedResult").enumValueIndex;
cond.FindPropertyRelative("Operator").enumValueIndex = srcCond.FindPropertyRelative("Operator").enumValueIndex;
cond.FindPropertyRelative("Condition").objectReferenceValue = srcCond.FindPropertyRelative("Condition").objectReferenceValue;
}
}
private bool TryGetExistingTransition(SerializedProperty from, SerializedProperty to, out int fromIndex, out int toIndex)
{
fromIndex = _fromStates.IndexOf(from.objectReferenceValue);
if (fromIndex < 0)
{
toIndex = -1;
return false;
}
toIndex = _transitionsByFromStates[fromIndex].FindIndex(
transitionHelper => transitionHelper.SerializedTransition.ToState.objectReferenceValue == to.objectReferenceValue);
return toIndex >= 0;
}
private void ResetIfRequired()
{
if (serializedObject.UpdateIfRequiredOrScript())
Reset();
}
private void GroupByFromState()
{
var groupedTransitions = new Dictionary<Object, List<TransitionDisplayHelper>>();
int count = _transitions.arraySize;
for (int i = 0; i < count; i++)
{
var serializedTransition = new SerializedTransition(_transitions, i);
if (serializedTransition.FromState.objectReferenceValue == null)
{
Debug.LogError("Transition with invalid state found in table " + serializedObject.targetObject.name + ", deleting...");
_transitions.DeleteArrayElementAtIndex(i);
serializedObject.ApplyModifiedProperties();
Reset();
return;
}
if (serializedTransition.FromState.objectReferenceValue == null)
{
Debug.LogError("Transition with invalid state found in table " + serializedObject.targetObject.name + ", deleting...");
_transitions.DeleteArrayElementAtIndex(i);
serializedObject.ApplyModifiedProperties();
Reset();
return;
}
if (!groupedTransitions.TryGetValue(serializedTransition.FromState.objectReferenceValue, out var groupedProps))
{
groupedProps = new List<TransitionDisplayHelper>();
groupedTransitions.Add(serializedTransition.FromState.objectReferenceValue, groupedProps);
}
groupedProps.Add(new TransitionDisplayHelper(serializedTransition, this));
}
_fromStates = groupedTransitions.Keys.Distinct().ToList();
_transitionsByFromStates = new List<List<TransitionDisplayHelper>>();
foreach (var fromState in _fromStates)
_transitionsByFromStates.Add(groupedTransitions[fromState]);
}
}
}

11
UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionTableEditor.cs.meta


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

17
UOP1_Project/Assets/Scripts/StateMachine/Editor/UOP1.StateMachine.Editor.asmdef


{
"name": "UOP1.StateMachine.Editor",
"references": [
"GUID:784148ec08432ab4a974dd364656dae9"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

7
UOP1_Project/Assets/Scripts/StateMachine/Editor/UOP1.StateMachine.Editor.asmdef.meta


fileFormatVersion: 2
guid: 8c0a18d9d0551b541815cb03f6f62fb4
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

121
UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionDisplayHelper.cs


using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace UOP1.StateMachine.Editor
{
internal class TransitionDisplayHelper
{
internal SerializedTransition SerializedTransition { get; }
private readonly ReorderableList _reorderableList;
private readonly TransitionTableEditor _editor;
internal TransitionDisplayHelper(SerializedTransition serializedTransition, TransitionTableEditor editor)
{
SerializedTransition = serializedTransition;
_reorderableList = new ReorderableList(SerializedTransition.Transition.serializedObject, SerializedTransition.Conditions, true, false, true, true);
SetupConditionsList(_reorderableList);
_editor = editor;
}
internal bool Display()
{
// Transition Header
EditorGUI.DrawRect(EditorGUILayout.BeginHorizontal(GUILayout.Height(24)), ContentStyle.DarkGray);
{
// Target state
EditorGUILayout.Space(3f, false);
EditorGUILayout.LabelField("To", GUILayout.Width(20));
EditorGUILayout.LabelField(SerializedTransition.ToState.objectReferenceValue.name, EditorStyles.boldLabel);
// TODO: Fix the space in between the labels above and the buttons below
// Right now the buttons disappear to the right if the Inspector is made too narrow
// Move transition up
if (GUILayout.Button(EditorGUIUtility.IconContent("scrollup"), GUILayout.Width(30), GUILayout.Height(18)))
{
if (_editor.ReorderTransition(SerializedTransition, true))
return true;
}
// Move transition down
if (GUILayout.Button(EditorGUIUtility.IconContent("scrolldown"), GUILayout.Width(30), GUILayout.Height(18)))
{
if (_editor.ReorderTransition(SerializedTransition, false))
return true;
}
// Remove transition
if (GUILayout.Button(EditorGUIUtility.IconContent("Toolbar Minus"), GUILayout.Width(30), GUILayout.Height(18)))
{
_editor.RemoveTransition(SerializedTransition.Index);
return true;
}
}
EditorGUILayout.EndHorizontal();
// Conditions
_reorderableList.DoLayoutList();
return false;
}
private static void SetupConditionsList(ReorderableList reorderableList)
{
reorderableList.elementHeight *= 2.3f;
reorderableList.headerHeight = 1f;
reorderableList.onAddCallback += list =>
{
int count = list.count;
list.serializedProperty.InsertArrayElementAtIndex(count);
var prop = list.serializedProperty.GetArrayElementAtIndex(count);
prop.FindPropertyRelative("Condition").objectReferenceValue = null;
prop.FindPropertyRelative("ExpectedResult").enumValueIndex = 0;
prop.FindPropertyRelative("Operator").enumValueIndex = 0;
};
reorderableList.drawElementCallback += (Rect rect, int index, bool isActive, bool isFocused) =>
{
var prop = reorderableList.serializedProperty.GetArrayElementAtIndex(index);
rect = new Rect(rect.x, rect.y + 2.5f, rect.width, EditorGUIUtility.singleLineHeight);
var condition = prop.FindPropertyRelative("Condition");
// Draw the picker for the Condition SO
if (condition.objectReferenceValue != null)
{
string label = condition.objectReferenceValue.name;
GUI.Label(rect, "If");
var r = rect;
r.x += 20;
r.width = 35;
EditorGUI.PropertyField(r, condition, GUIContent.none);
r.x += 40;
r.width = rect.width - 120;
GUI.Label(r, label, EditorStyles.boldLabel);
}
else
{
EditorGUI.PropertyField(new Rect(rect.x, rect.y, 150, rect.height), condition, GUIContent.none);
}
// Draw the boolean value expected by the condition (i.e. "Is True", "Is False")
EditorGUI.LabelField(new Rect(rect.x + rect.width - 80, rect.y, 20, rect.height), "Is");
EditorGUI.PropertyField(new Rect(rect.x + rect.width - 60, rect.y, 60, rect.height), prop.FindPropertyRelative("ExpectedResult"), GUIContent.none);
// Only display the logic condition if there's another one after this
if (index < reorderableList.count - 1)
EditorGUI.PropertyField(new Rect(rect.x + 20, rect.y + EditorGUIUtility.singleLineHeight + 5, 60, rect.height), prop.FindPropertyRelative("Operator"), GUIContent.none);
};
reorderableList.onChangedCallback += list => list.serializedProperty.serializedObject.ApplyModifiedProperties();
reorderableList.drawElementBackgroundCallback += (Rect rect, int index, bool isActive, bool isFocused) =>
{
if (isFocused)
EditorGUI.DrawRect(rect, ContentStyle.Focused);
if (index % 2 != 0)
EditorGUI.DrawRect(rect, ContentStyle.ZebraDark);
else
EditorGUI.DrawRect(rect, ContentStyle.ZebraLight);
};
}
}
}

8
UOP1_Project/Assets/ScriptableObjects/Protagonist/Transitions.meta


fileFormatVersion: 2
guid: 7273e93cd813a8947b64537b38daed30
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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 }
}
}
正在加载...
取消
保存