Ciro Continisio
4 年前
当前提交
ededb7c0
共有 51 个文件被更改,包括 2458 次插入 和 143 次删除
-
9UOP1_Project/Assets/Prefabs/Characters/PigChef.prefab
-
2UOP1_Project/Assets/Prefabs/Characters/PigChef.prefab.meta
-
2UOP1_Project/Assets/Prefabs/GameplayEssentials/SpawnSystem.prefab
-
1UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/HasHitHead.asset
-
1UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsGrounded.asset
-
1UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsHoldingJump.asset
-
1UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsMoving.asset
-
1UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/IsSliding.asset
-
1UOP1_Project/Assets/ScriptableObjects/Protagonist/Conditions/JumpHoldTimer.asset
-
2UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/HasHitHeadConditionSO.cs
-
2UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsCharacterControllerGroundedConditionSO.cs
-
2UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsHoldingJumpConditionSO.cs
-
2UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsMovingConditionSO.cs
-
2UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/IsSlidingConditionSO.cs
-
2UOP1_Project/Assets/Scripts/Characters/StateMachine/Conditions/TimeElapsedConditionSO.cs
-
19UOP1_Project/Assets/Scripts/StateMachine/Core/State.cs
-
6UOP1_Project/Assets/Scripts/StateMachine/Core/StateAction.cs
-
57UOP1_Project/Assets/Scripts/StateMachine/Core/StateCondition.cs
-
19UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachine.cs
-
18UOP1_Project/Assets/Scripts/StateMachine/Core/StateTransition.cs
-
4UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateActionSO.cs
-
11UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateConditionSO.cs
-
24UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateSO.cs
-
1001UOP1_Project/Assets/Prefabs/Pig.prefab
-
7UOP1_Project/Assets/Prefabs/Pig.prefab.meta
-
99UOP1_Project/Assets/ScriptableObjects/Protagonist/CharacterTransitionTable.asset
-
8UOP1_Project/Assets/ScriptableObjects/Protagonist/CharacterTransitionTable.asset.meta
-
119UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachineDebugger.cs
-
11UOP1_Project/Assets/Scripts/StateMachine/Core/StateMachineDebugger.cs.meta
-
8UOP1_Project/Assets/Scripts/StateMachine/Editor.meta
-
98UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/TransitionTableSO.cs
-
11UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/TransitionTableSO.cs.meta
-
171UOP1_Project/Assets/Scripts/StateMachine/Editor/AddTransitionHelper.cs
-
11UOP1_Project/Assets/Scripts/StateMachine/Editor/AddTransitionHelper.cs.meta
-
59UOP1_Project/Assets/Scripts/StateMachine/Editor/ContentStyle.cs
-
11UOP1_Project/Assets/Scripts/StateMachine/Editor/ContentStyle.cs.meta
-
47UOP1_Project/Assets/Scripts/StateMachine/Editor/SerializedTransition.cs
-
11UOP1_Project/Assets/Scripts/StateMachine/Editor/SerializedTransition.cs.meta
-
83UOP1_Project/Assets/Scripts/StateMachine/Editor/StateEditor.cs
-
11UOP1_Project/Assets/Scripts/StateMachine/Editor/StateEditor.cs.meta
-
11UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionDisplayHelper.cs.meta
-
392UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionTableEditor.cs
-
11UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionTableEditor.cs.meta
-
17UOP1_Project/Assets/Scripts/StateMachine/Editor/UOP1.StateMachine.Editor.asmdef
-
7UOP1_Project/Assets/Scripts/StateMachine/Editor/UOP1.StateMachine.Editor.asmdef.meta
-
121UOP1_Project/Assets/Scripts/StateMachine/Editor/TransitionDisplayHelper.cs
-
8UOP1_Project/Assets/ScriptableObjects/Protagonist/Transitions.meta
-
11UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateTransitionSO.cs.meta
-
68UOP1_Project/Assets/Scripts/StateMachine/ScriptableObjects/StateTransitionSO.cs
|
|||
fileFormatVersion: 2 |
|||
guid: 0fa393e1e37bc9e4e829c25a9452bcd3 |
|||
guid: 062b3805bf6784e4d9c599ee60eaa002 |
|||
PrefabImporter: |
|||
externalObjects: {} |
|||
userData: |
1001
UOP1_Project/Assets/Prefabs/Pig.prefab
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
fileFormatVersion: 2 |
|||
guid: 0fa393e1e37bc9e4e829c25a9452bcd3 |
|||
PrefabImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
%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 |
|
|||
fileFormatVersion: 2 |
|||
guid: f0baa4ca2bfa24e4cb6d1efe9fa81ea3 |
|||
NativeFormatImporter: |
|||
externalObjects: {} |
|||
mainObjectFileID: 0 |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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()); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 4fe968fa3a5601843b52a0cb3b94c2d9 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 3596ae92061fc0d4d9cd18d55258b236 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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 } |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 872cbaa965d1f6e4e98365d74e2060df |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 00a48f7c82ea3bd46a36c3f64d759837 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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, |
|||
}; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f3ccde57d7ba058488ea2c2a47ce27a9 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 6441ed07e21d7e4429c67fddfc22d061 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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); |
|||
}; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 2aa6909dd9f17914ab9fcb1dd55e71a5 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 3ec49868c2508ae4199e341bcbba3675 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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]); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 596d4a27cc6cd7f478e7912eb0488033 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"name": "UOP1.StateMachine.Editor", |
|||
"references": [ |
|||
"GUID:784148ec08432ab4a974dd364656dae9" |
|||
], |
|||
"includePlatforms": [ |
|||
"Editor" |
|||
], |
|||
"excludePlatforms": [], |
|||
"allowUnsafeCode": false, |
|||
"overrideReferences": false, |
|||
"precompiledReferences": [], |
|||
"autoReferenced": true, |
|||
"defineConstraints": [], |
|||
"versionDefines": [], |
|||
"noEngineReferences": false |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 8c0a18d9d0551b541815cb03f6f62fb4 |
|||
AssemblyDefinitionImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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); |
|||
}; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7273e93cd813a8947b64537b38daed30 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 9e1893bf30b6f554a9d80007ee336691 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
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 } |
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue