diff --git a/Examples/Assets/InfinityWaves/Enemy/EnemyStateMachine.asset b/Examples/Assets/InfinityWaves/Enemy/EnemyStateMachine.asset index 43e9b11b..a8406ece 100644 --- a/Examples/Assets/InfinityWaves/Enemy/EnemyStateMachine.asset +++ b/Examples/Assets/InfinityWaves/Enemy/EnemyStateMachine.asset @@ -22,70 +22,72 @@ MonoBehaviour: _transitionStarted: {fileID: 0} _completeCurrentTransition: {fileID: 0} _states: - - _id: - _usage: 0 - _value: CHASING - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _cooldown: - _usage: 0 - _value: 0 - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _subMachine: {fileID: 0} - - _id: - _usage: 0 - _value: ATTACKING - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _cooldown: - _usage: 0 - _value: 2 - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _subMachine: {fileID: 0} + _list: + - _id: + _usage: 0 + _value: CHASING + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _cooldown: + _usage: 0 + _value: 0 + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _subMachine: {fileID: 0} + - _id: + _usage: 0 + _value: ATTACKING + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _cooldown: + _usage: 0 + _value: 2 + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _subMachine: {fileID: 0} _transitions: - - _fromState: - _usage: 0 - _value: ATTACKING - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _toState: - _usage: 0 - _value: CHASING - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _command: - _usage: 0 - _value: CHASE - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _testCondition: {fileID: 0} - _raiseEventToCompleteTransition: 0 - - _fromState: - _usage: 0 - _value: CHASING - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _toState: - _usage: 0 - _value: ATTACKING - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _command: - _usage: 0 - _value: ATTACK - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _testCondition: {fileID: 0} - _raiseEventToCompleteTransition: 0 + _list: + - _fromState: + _usage: 0 + _value: CHASING + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _toState: + _usage: 0 + _value: ATTACKING + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _command: + _usage: 0 + _value: ATTACK + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _testCondition: {fileID: 0} + _raiseEventToCompleteTransition: 0 + - _fromState: + _usage: 0 + _value: ATTACKING + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _toState: + _usage: 0 + _value: CHASING + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _command: + _usage: 0 + _value: CHASE + _constant: {fileID: 0} + _variable: {fileID: 0} + _variableInstancer: {fileID: 0} + _testCondition: {fileID: 0} + _raiseEventToCompleteTransition: 0 diff --git a/Examples/Assets/InfinityWaves/GlobalState/GameStateMachine.asset b/Examples/Assets/InfinityWaves/GlobalState/GameStateMachine.asset index d33133ad..de63646e 100644 --- a/Examples/Assets/InfinityWaves/GlobalState/GameStateMachine.asset +++ b/Examples/Assets/InfinityWaves/GlobalState/GameStateMachine.asset @@ -13,59 +13,15 @@ MonoBehaviour: m_Name: GameStateMachine m_EditorClassIdentifier: _developerDescription: - _value: InGame - _initialValue: InGame - _oldValue: InGame + _value: + _initialValue: + _oldValue: Changed: {fileID: 11400000, guid: 3979f8535cda044809f98bde2acbadcd, type: 2} ChangedWithHistory: {fileID: 0} _preChangeTransformers: [] _transitionStarted: {fileID: 0} _completeCurrentTransition: {fileID: 0} _states: - - _id: - _usage: 0 - _value: InGame - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _cooldown: - _usage: 0 - _value: 0 - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _subMachine: {fileID: 0} - - _id: - _usage: 0 - _value: GameOver - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _cooldown: - _usage: 0 - _value: 0 - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _subMachine: {fileID: 0} + _list: [] _transitions: - - _fromState: - _usage: 0 - _value: InGame - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _toState: - _usage: 0 - _value: GameOver - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _command: - _usage: 0 - _value: SetGameOver - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} - _testCondition: {fileID: 0} - _raiseEventToCompleteTransition: 0 + _list: [] diff --git a/Examples/Assets/InfinityWaves/InfinityWaves.unity b/Examples/Assets/InfinityWaves/InfinityWaves.unity index e25ca5fa..06f08fe1 100644 --- a/Examples/Assets/InfinityWaves/InfinityWaves.unity +++ b/Examples/Assets/InfinityWaves/InfinityWaves.unity @@ -1226,88 +1226,6 @@ MonoBehaviour: _constant: {fileID: 0} _variable: {fileID: 0} _variableInstancer: {fileID: 0} ---- !u!1 &1740680898 stripped -GameObject: - m_CorrespondingSourceObject: {fileID: 2421445419809145576, guid: 1326bc3a5a491414d936c2cbaaf52c53, - type: 3} - m_PrefabInstance: {fileID: 2421445420392131626} - m_PrefabAsset: {fileID: 0} ---- !u!114 &1740680904 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 2421445419809145570, guid: 1326bc3a5a491414d936c2cbaaf52c53, - type: 3} - m_PrefabInstance: {fileID: 2421445420392131626} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1740680898} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f4c23750724dd48beb6147120ab31180, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!114 &1740680906 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: -2978108162882953924, guid: 1326bc3a5a491414d936c2cbaaf52c53, - type: 3} - m_PrefabInstance: {fileID: 2421445420392131626} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1740680898} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: adacd19b642ba4bd9866612e37971b00, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!114 &1740680909 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1740680898} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6a57cc968d2764ec0bc2de2bd308d451, type: 3} - m_Name: - m_EditorClassIdentifier: - _delay: - _usage: 0 - _value: -1 - _constant: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 0} ---- !u!114 &1740680910 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1740680898} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 050ff37b5550f4b7e9bdf8b985aff484, type: 3} - m_Name: - m_EditorClassIdentifier: - _developerDescription: - _unityEventResponse: - m_PersistentCalls: - m_Calls: - - m_Target: {fileID: 1740680909} - m_MethodName: DestroyImmediate - m_Mode: 1 - m_Arguments: - m_ObjectArgument: {fileID: 0} - m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine - m_IntArgument: 0 - m_FloatArgument: 0 - m_StringArgument: - m_BoolArgument: 0 - m_CallState: 2 - _actionResponses: [] - _eventReference: - _usage: 3 - _event: {fileID: 0} - _eventInstancer: {fileID: 0} - _variable: {fileID: 0} - _variableInstancer: {fileID: 1740680906} --- !u!1001 &2421445420392131626 PrefabInstance: m_ObjectHideFlags: 0 @@ -1315,16 +1233,6 @@ PrefabInstance: m_Modification: m_TransformParent: {fileID: 0} m_Modifications: - - target: {fileID: 2421445419809145569, guid: 1326bc3a5a491414d936c2cbaaf52c53, - type: 3} - propertyPath: _enemyState._fsmInstancer - value: - objectReference: {fileID: 1740680904} - - target: {fileID: 2421445419809145569, guid: 1326bc3a5a491414d936c2cbaaf52c53, - type: 3} - propertyPath: _enemyState._usage - value: 1 - objectReference: {fileID: 0} - target: {fileID: 2421445419809145576, guid: 1326bc3a5a491414d936c2cbaaf52c53, type: 3} propertyPath: m_Name @@ -1385,11 +1293,6 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 2421445419809145581, guid: 1326bc3a5a491414d936c2cbaaf52c53, - type: 3} - propertyPath: _enemyState._fsmInstancer - value: - objectReference: {fileID: 1740680904} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 1326bc3a5a491414d936c2cbaaf52c53, type: 3} --- !u!1001 &7203441229205008813 diff --git a/Packages/BaseAtoms/Editor/Drawers/Collections/AtomBaseVariableListDrawer.cs b/Packages/BaseAtoms/Editor/Drawers/Collections/AtomListDrawer.cs similarity index 83% rename from Packages/BaseAtoms/Editor/Drawers/Collections/AtomBaseVariableListDrawer.cs rename to Packages/BaseAtoms/Editor/Drawers/Collections/AtomListDrawer.cs index 4235aae4..f4cb1ffe 100644 --- a/Packages/BaseAtoms/Editor/Drawers/Collections/AtomBaseVariableListDrawer.cs +++ b/Packages/BaseAtoms/Editor/Drawers/Collections/AtomListDrawer.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityAtoms.Editor; @@ -9,8 +7,9 @@ namespace UnityAtoms.BaseAtoms.Editor /// /// A custom property drawer for AtomBaseVariableList. /// + [CustomPropertyDrawer(typeof(AtomListAttribute))] [CustomPropertyDrawer(typeof(AtomBaseVariableList))] - public class AtomBaseVariableListDrawer : PropertyDrawer + public class AtomListDrawer : PropertyDrawer { static int INDEX_LABEL_WIDTH = 16; static int BUTTON_WIDTH = 24; @@ -24,8 +23,10 @@ namespace UnityAtoms.BaseAtoms.Editor public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + AtomListAttribute atomListAttr = attribute as AtomListAttribute; + var propertyHeight = EditorGUIUtility.singleLineHeight + LINE_BOTTOM_MARGIN + DRAWER_MARGIN * 2f; - var listProperty = property.FindPropertyRelative(SERIALIZED_LIST_PROPNAME); + var listProperty = property.FindPropertyRelative(atomListAttr != null && !string.IsNullOrWhiteSpace(atomListAttr.ChildPropName) ? atomListAttr.ChildPropName : SERIALIZED_LIST_PROPNAME); var length = listProperty.arraySize; for (var i = 0; i < length; ++i) @@ -40,6 +41,8 @@ namespace UnityAtoms.BaseAtoms.Editor public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + AtomListAttribute atomListAttr = attribute as AtomListAttribute; + label = EditorGUI.BeginProperty(position, label, property); var proColor = new Color(83f / 255f, 83f / 255f, 83f / 255f); var basicColor = new Color(174f / 255f, 174f / 255f, 174f / 255f); @@ -49,7 +52,7 @@ namespace UnityAtoms.BaseAtoms.Editor EditorGUI.indentLevel = 0; EditorGUI.BeginChangeCheck(); - var listArrayProperty = property.FindPropertyRelative(SERIALIZED_LIST_PROPNAME); + var listArrayProperty = property.FindPropertyRelative(atomListAttr != null && !string.IsNullOrWhiteSpace(atomListAttr.ChildPropName) ? atomListAttr.ChildPropName : SERIALIZED_LIST_PROPNAME); var restRect = new Rect(); var initialPosition = new Rect(position); @@ -59,7 +62,7 @@ namespace UnityAtoms.BaseAtoms.Editor var labelPosition = IMGUIUtils.SnipRectH(initialPosition, initialPosition.width - BUTTON_WIDTH, out restRect); labelPosition.height = EditorGUIUtility.singleLineHeight + LINE_BOTTOM_MARGIN; - EditorGUI.PrefixLabel(initialPosition, new GUIContent(LIST_LABEL_NAME)); + EditorGUI.PrefixLabel(initialPosition, new GUIContent(atomListAttr != null ? (atomListAttr.Label ?? label.text) : LIST_LABEL_NAME)); var addButtonPosition = IMGUIUtils.SnipRectH(restRect, restRect.width, out restRect); addButtonPosition.height = EditorGUIUtility.singleLineHeight; @@ -83,7 +86,7 @@ namespace UnityAtoms.BaseAtoms.Editor EditorGUI.PrefixLabel(indexLabelPos, new GUIContent(i.ToString())); var itemPos = IMGUIUtils.SnipRectH(restRect, linePosition.width - BUTTON_WIDTH - INDEX_LABEL_WIDTH - GUTTER * 2, out restRect, GUTTER); - EditorGUI.PropertyField(itemPos, itemProp, GUIContent.none, false); + EditorGUI.PropertyField(itemPos, itemProp, GUIContent.none, atomListAttr != null ? atomListAttr.IncludeChildrenForItems : false); var removeButtonPosition = new Rect(restRect); removeButtonPosition.height = EditorGUIUtility.singleLineHeight; @@ -93,7 +96,7 @@ namespace UnityAtoms.BaseAtoms.Editor indexToDelete = i; } - linePosition.y += EditorGUIUtility.singleLineHeight + LINE_BOTTOM_MARGIN; + linePosition.y += EditorGUI.GetPropertyHeight(itemProp) + LINE_BOTTOM_MARGIN; } if (insertIndex != -1) @@ -101,6 +104,8 @@ namespace UnityAtoms.BaseAtoms.Editor if (listArrayProperty != null) { listArrayProperty.InsertArrayElementAtIndex(insertIndex); + var newProp = listArrayProperty.GetArrayElementAtIndex(insertIndex); + newProp.isExpanded = true; } } diff --git a/Packages/BaseAtoms/Editor/Drawers/Collections/AtomBaseVariableListDrawer.cs.meta b/Packages/BaseAtoms/Editor/Drawers/Collections/AtomListDrawer.cs.meta similarity index 83% rename from Packages/BaseAtoms/Editor/Drawers/Collections/AtomBaseVariableListDrawer.cs.meta rename to Packages/BaseAtoms/Editor/Drawers/Collections/AtomListDrawer.cs.meta index e83ecafc..6faae403 100644 --- a/Packages/BaseAtoms/Editor/Drawers/Collections/AtomBaseVariableListDrawer.cs.meta +++ b/Packages/BaseAtoms/Editor/Drawers/Collections/AtomListDrawer.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 01e2d4c866bf1461f99e3ffd89dc8882 +guid: d0d49408cf0e24375a9b017fc4210a94 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/BaseAtoms/Runtime/AtomListWrapper.meta b/Packages/BaseAtoms/Runtime/AtomListWrapper.meta new file mode 100644 index 00000000..31d26376 --- /dev/null +++ b/Packages/BaseAtoms/Runtime/AtomListWrapper.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 93604d20e8e8f4a7da3af79e4303a48f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/BaseAtoms/Runtime/AtomListWrapper/AtomListWrapper.cs b/Packages/BaseAtoms/Runtime/AtomListWrapper/AtomListWrapper.cs new file mode 100644 index 00000000..4ea127a6 --- /dev/null +++ b/Packages/BaseAtoms/Runtime/AtomListWrapper/AtomListWrapper.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UnityAtoms.BaseAtoms +{ + /// + /// Needed in order to create a property drawer for a List / Array. See this for more info: https://answers.unity.com/questions/605875/custompropertydrawer-for-array-types-in-43.html + /// + /// Type used in list. + public abstract class AtomListWrapper + { + public List List { get => _list; } + + [SerializeField] + private List _list; + } +} \ No newline at end of file diff --git a/Packages/BaseAtoms/Runtime/AtomListWrapper/AtomListWrapper.cs.meta b/Packages/BaseAtoms/Runtime/AtomListWrapper/AtomListWrapper.cs.meta new file mode 100644 index 00000000..d96627d0 --- /dev/null +++ b/Packages/BaseAtoms/Runtime/AtomListWrapper/AtomListWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cbf386e8976ba4aeebf2c157c511166f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/BaseAtoms/Runtime/Attributes.meta b/Packages/BaseAtoms/Runtime/Attributes.meta new file mode 100644 index 00000000..2e9bcb96 --- /dev/null +++ b/Packages/BaseAtoms/Runtime/Attributes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ed96ad5d334a847438fd3f164b941c3f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/BaseAtoms/Runtime/Attributes/AtomListAttribute.cs b/Packages/BaseAtoms/Runtime/Attributes/AtomListAttribute.cs new file mode 100644 index 00000000..3ace5914 --- /dev/null +++ b/Packages/BaseAtoms/Runtime/Attributes/AtomListAttribute.cs @@ -0,0 +1,18 @@ +using UnityEngine; + +namespace UnityAtoms.BaseAtoms +{ + public class AtomListAttribute : PropertyAttribute + { + public string Label { get; set; } + public string ChildPropName { get; set; } + public bool IncludeChildrenForItems { get; set; } + + public AtomListAttribute(string label = null, string childPropName = "_list", bool includeChildrenForItems = true) + { + Label = label; + ChildPropName = childPropName; + IncludeChildrenForItems = includeChildrenForItems; + } + } +} \ No newline at end of file diff --git a/Packages/BaseAtoms/Runtime/Attributes/AtomListAttribute.cs.meta b/Packages/BaseAtoms/Runtime/Attributes/AtomListAttribute.cs.meta new file mode 100644 index 00000000..3549f341 --- /dev/null +++ b/Packages/BaseAtoms/Runtime/Attributes/AtomListAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c9cdeac56b0004a4c88add9ed1f3d6a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Core/Editor/Editors/Variables/AtomVariableEditor.cs b/Packages/Core/Editor/Editors/Variables/AtomVariableEditor.cs index b1e0839c..006926d7 100644 --- a/Packages/Core/Editor/Editors/Variables/AtomVariableEditor.cs +++ b/Packages/Core/Editor/Editors/Variables/AtomVariableEditor.cs @@ -67,7 +67,7 @@ namespace UnityAtoms.Editor GUILayout.Space(2); if (GUILayout.Button("Raise", GUILayout.Width(raiseButtonWidth), GUILayout.Height(EditorGUIUtility.singleLineHeight))) { - evt.GetType().GetMethod("Raise", BindingFlags.Public | BindingFlags.Instance)?.Invoke(evt, new[] { atomTarget.BaseValue }); + evt.GetType().GetMethod("RaiseEditor", BindingFlags.Public | BindingFlags.Instance)?.Invoke(evt, new[] { atomTarget.BaseValue }); } } @@ -85,7 +85,7 @@ namespace UnityAtoms.Editor { var oldValueProp = serializedObject.FindProperty("_oldValue"); object oldValue = oldValueProp.GetPropertyValue(); - evt.GetType().GetMethod("Raise", BindingFlags.Public | BindingFlags.Instance)?.Invoke(evt, new[] { (object)(new P() { Item1 = (T)atomTarget.BaseValue, Item2 = (T)oldValue }) }); + evt.GetType().GetMethod("RaiseEditor", BindingFlags.Public | BindingFlags.Instance)?.Invoke(evt, new[] { (object)(new P() { Item1 = (T)atomTarget.BaseValue, Item2 = (T)oldValue }) }); } } diff --git a/Packages/Core/Runtime/Events/AtomEvent.cs b/Packages/Core/Runtime/Events/AtomEvent.cs index 1ea9f3e7..b9e21b36 100644 --- a/Packages/Core/Runtime/Events/AtomEvent.cs +++ b/Packages/Core/Runtime/Events/AtomEvent.cs @@ -66,6 +66,12 @@ namespace UnityAtoms AddToReplayBuffer(item); } + /// + /// Used in editor scipts since Raise is ambigious when using reflection to get method. + /// + /// + public void RaiseEditor(T item) => Raise(item); + /// /// Register handler to be called when the Event triggers. /// diff --git a/Packages/Core/Runtime/Extensions/IListExtensions.cs b/Packages/Core/Runtime/Extensions/IListExtensions.cs index 9efe0dd4..51f0e5b0 100644 --- a/Packages/Core/Runtime/Extensions/IListExtensions.cs +++ b/Packages/Core/Runtime/Extensions/IListExtensions.cs @@ -16,6 +16,12 @@ namespace UnityAtoms return default(T); } + public static bool Exists(this IList list, Func func) + { + var first = list.First(func); + return first != null ? true : false; + } + public static GameObject GetOrInstantiate(this IList list, UnityEngine.Object prefab, Vector3 position, Quaternion quaternion, Func condition) { var component = list.First(condition); diff --git a/Packages/FSM/Editor/Editors/FiniteStateMachineEditor.cs b/Packages/FSM/Editor/Editors/FiniteStateMachineEditor.cs new file mode 100644 index 00000000..06446fda --- /dev/null +++ b/Packages/FSM/Editor/Editors/FiniteStateMachineEditor.cs @@ -0,0 +1,81 @@ +using System; +using System.Reflection; +using UnityEditor; +using UnityEngine; +using UnityAtoms.Editor; +using UnityAtoms.BaseAtoms; + +namespace UnityAtoms.FSM.Editor +{ + /// + /// Custom property drawer for type `FiniteStateMachine`. + /// + [CustomEditor(typeof(FiniteStateMachine))] + public sealed class FiniteStateMachineEditor : UnityEditor.Editor + { + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.PropertyField(serializedObject.FindProperty("_developerDescription")); + EditorGUILayout.Space(); + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.PropertyField(serializedObject.FindProperty("_initialValue"), true); + EditorGUI.EndDisabledGroup(); + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.PropertyField(serializedObject.FindProperty("_value"), true); + EditorGUI.EndDisabledGroup(); + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.PropertyField(serializedObject.FindProperty("_oldValue"), true); + EditorGUI.EndDisabledGroup(); + + const int raiseButtonWidth = 52; + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.PropertyField(serializedObject.FindProperty("Changed")); + var changed = serializedObject.FindProperty("Changed").objectReferenceValue; + if (changed != null && changed is AtomEventBase evt && target is AtomBaseVariable atomTarget) + { + GUILayout.Space(2); + if (GUILayout.Button("Raise", GUILayout.Width(raiseButtonWidth), GUILayout.Height(EditorGUIUtility.singleLineHeight))) + { + evt.GetType().GetMethod("RaiseEditor", BindingFlags.Public | BindingFlags.Instance)?.Invoke(evt, new[] { atomTarget.BaseValue }); + } + } + + } + + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.PropertyField(serializedObject.FindProperty("ChangedWithHistory")); + var changedWithHistory = serializedObject.FindProperty("ChangedWithHistory").objectReferenceValue; + if (changedWithHistory != null && changedWithHistory is AtomEventBase evt && target is AtomBaseVariable atomTarget) + { + + GUILayout.Space(2); + if (GUILayout.Button("Raise", GUILayout.Width(raiseButtonWidth), GUILayout.Height(EditorGUIUtility.singleLineHeight))) + { + var oldValueProp = serializedObject.FindProperty("_oldValue"); + object oldValue = oldValueProp.GetPropertyValue(); + evt.GetType().GetMethod("RaiseEditor", BindingFlags.Public | BindingFlags.Instance)?.Invoke(evt, new[] { (object)(new StringPair() { Item1 = (string)atomTarget.BaseValue, Item2 = (string)oldValue }) }); + } + } + + } + + var transitionStartedProp = serializedObject.FindProperty("_transitionStarted"); + EditorGUILayout.PropertyField(transitionStartedProp, new GUIContent() { tooltip = "Event raised when a transition is started.", text = transitionStartedProp.displayName }, true); + + var completeCurrentTransitionProp = serializedObject.FindProperty("_completeCurrentTransition"); + EditorGUILayout.PropertyField(completeCurrentTransitionProp, new GUIContent() { tooltip = "A Bool Event that is passed along in the Transition Started event (an event that is required when using this event). The transition needs also to be marked with 'Raise Event To Complete Transition in order to use this event.'", text = completeCurrentTransitionProp.displayName }, true); + + EditorGUILayout.PropertyField(serializedObject.FindProperty("_states"), true); + EditorGUILayout.PropertyField(serializedObject.FindProperty("_transitions"), true); + + serializedObject.ApplyModifiedProperties(); + } + } +} diff --git a/Packages/FSM/Editor/Editors/FiniteStateMachineEditor.cs.meta b/Packages/FSM/Editor/Editors/FiniteStateMachineEditor.cs.meta new file mode 100644 index 00000000..3ab5cdc1 --- /dev/null +++ b/Packages/FSM/Editor/Editors/FiniteStateMachineEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c9dfec58f1704690b369ef9580bb07e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/FSM/Runtime/FiniteStateMachine/FSMStateListWrapper.cs b/Packages/FSM/Runtime/FiniteStateMachine/FSMStateListWrapper.cs new file mode 100644 index 00000000..f7605893 --- /dev/null +++ b/Packages/FSM/Runtime/FiniteStateMachine/FSMStateListWrapper.cs @@ -0,0 +1,8 @@ +using System; +using UnityAtoms.BaseAtoms; + +namespace UnityAtoms.FSM +{ + [Serializable] + public class FSMStateListWrapper : AtomListWrapper { } +} \ No newline at end of file diff --git a/Packages/FSM/Runtime/FiniteStateMachine/FSMStateListWrapper.cs.meta b/Packages/FSM/Runtime/FiniteStateMachine/FSMStateListWrapper.cs.meta new file mode 100644 index 00000000..32a3703e --- /dev/null +++ b/Packages/FSM/Runtime/FiniteStateMachine/FSMStateListWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2aa0cb2bd8023434aab0a911accf16e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/FSM/Runtime/FiniteStateMachine/FiniteStateMachine.cs b/Packages/FSM/Runtime/FiniteStateMachine/FiniteStateMachine.cs index 79f20498..5f9a5215 100644 --- a/Packages/FSM/Runtime/FiniteStateMachine/FiniteStateMachine.cs +++ b/Packages/FSM/Runtime/FiniteStateMachine/FiniteStateMachine.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using UnityEngine; using UnityAtoms.BaseAtoms; +using UnityAtoms; namespace UnityAtoms.FSM { @@ -18,20 +18,19 @@ namespace UnityAtoms.FSM get { var state = GetState(_value); - return state.SubMachine != null ? state.SubMachine.Value : _value; + return state != null && state.SubMachine != null ? state.SubMachine.Value : _value; } set => Dispatch(value); } public FSMTransitionDataEvent TransitionStarted { get => _transitionStarted; set => _transitionStarted = value; } public BoolEvent CompleteCurrentTransition { get => _completeCurrentTransition; set => _completeCurrentTransition = value; } - public override string InitialValue { get => _states != null && _states.Count > 0 ? _states[0].Id : ""; } + public override string InitialValue { get => _states?.List != null && _states.List.Count > 0 ? _states.List[0].Id : ""; } /// /// Gets a boolean value indicating if the state machine is currently transitioning. /// public bool IsTransitioning { get => _currentTransition != null; } - [SerializeField] private FSMTransitionDataEvent _transitionStarted = default(FSMTransitionDataEvent); @@ -39,10 +38,12 @@ namespace UnityAtoms.FSM private BoolEvent _completeCurrentTransition = default(BoolEvent); [SerializeField] - private List _states; + [AtomList] + private FSMStateListWrapper _states; [SerializeField] - private List _transitions; + [AtomList] + private TransitionListWrapper _transitions; private bool _isUpdatingState = false; private Transition _currentTransition = null; @@ -165,12 +166,12 @@ namespace UnityAtoms.FSM public override void Reset(bool shouldTriggerEvents = false) { - // TODO: Validate transitions and states + Validate(); // Set all timers to the same as the cooldown - for (var i = 0; i < _states.Count; ++i) + for (var i = 0; i < _states.List.Count; ++i) { - _states[i].Timer = _states[i].Cooldown; + _states.List[i].Timer = _states.List[i].Cooldown; } if (!_resetOnNextTransitionCompleted && !IsTransitioning) @@ -238,6 +239,24 @@ namespace UnityAtoms.FSM protected override bool ValueEquals(string other) => false; // Always trigger events even if changing to the same state as previous + + + private void Validate() + { + for (var i = 0; i < _transitions.List.Count; ++i) + { + var transition = _transitions.List[i]; + if (!_states.List.Exists((s) => s.Id == transition.FromState)) + { + Debug.LogError($"Transition with From State {transition.FromState} can't be found in the defined states."); + } + if (!_states.List.Exists((s) => s.Id == transition.ToState)) + { + Debug.LogError($"Transition with To State {transition.ToState} can't be found in the defined states."); + } + } + } + private void EndCurrentTransition() { if (_resetOnNextTransitionCompleted) @@ -268,11 +287,11 @@ namespace UnityAtoms.FSM private void ResetAllSubMachines() { - for (var i = 0; i < _states.Count; ++i) + for (var i = 0; i < _states.List.Count; ++i) { - if (_states[i].SubMachine != null) + if (_states.List[i].SubMachine != null) { - _states[i].SubMachine.Reset(); + _states.List[i].SubMachine.Reset(); } } } @@ -286,9 +305,9 @@ namespace UnityAtoms.FSM { // Update timers and call OnStateCooldown handlers if applicable var currentValue = Value; - for (var i = 0; i < _states.Count; ++i) + for (var i = 0; i < _states.List.Count; ++i) { - var state = _states[i]; + var state = _states.List[i]; if (state.Cooldown > 0f) { var isCurrent = state.Id == currentValue; @@ -336,9 +355,9 @@ namespace UnityAtoms.FSM { Transition ret = null; - for (var i = 0; i < _transitions.Count; ++i) + for (var i = 0; i < _transitions.List.Count; ++i) { - var transition = _transitions[i]; + var transition = _transitions.List[i]; if (command == transition.Command && _currentFlatValue == transition.FromState) { return transition; @@ -350,15 +369,15 @@ namespace UnityAtoms.FSM private FSMState GetState(string id) { - for (var i = 0; i < _states.Count; ++i) + for (var i = 0; i < _states.List.Count; ++i) { - if (_states[i].Id == id) + if (_states.List[i].Id == id) { - return _states[i]; + return _states.List[i]; } } - throw new System.Exception($"State with id {id} could not be found."); + return null; } } } \ No newline at end of file diff --git a/Packages/FSM/Runtime/Transition/TransitionListWrapper.cs b/Packages/FSM/Runtime/Transition/TransitionListWrapper.cs new file mode 100644 index 00000000..9fad5bb3 --- /dev/null +++ b/Packages/FSM/Runtime/Transition/TransitionListWrapper.cs @@ -0,0 +1,8 @@ +using System; +using UnityAtoms.BaseAtoms; + +namespace UnityAtoms.FSM +{ + [Serializable] + public class TransitionListWrapper : AtomListWrapper { } +} \ No newline at end of file diff --git a/Packages/FSM/Runtime/Transition/TransitionListWrapper.cs.meta b/Packages/FSM/Runtime/Transition/TransitionListWrapper.cs.meta new file mode 100644 index 00000000..477a54e5 --- /dev/null +++ b/Packages/FSM/Runtime/Transition/TransitionListWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a7a3f96d68ef4e3d9637bb1f4634554 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: