导入odin

This commit is contained in:
2026-01-01 23:09:08 +08:00
parent 9ceffccd39
commit 04dd4a23b2
814 changed files with 120820 additions and 87 deletions

View File

@@ -0,0 +1,2 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Obvious.Soap.Editor")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df8c14b9a36686a40883e2f8b501d998
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 793fbd4d77362324bbe755cc3c2b332c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
using System;
using UnityEngine;
namespace Obvious.Soap.Attributes
{
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class AutoTag : PropertyAttribute
{
public string Tag { get; }
public int TagIndex { get; }
public AutoTag(string tag)
{
Tag = tag;
}
public AutoTag(int tagIndex)
{
TagIndex = tagIndex;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 913f53a0ea7032745a8f5d59573865ae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using System;
using UnityEngine;
namespace Obvious.Soap.Attributes
{
[AttributeUsage(AttributeTargets.Field)]
public class BeginDisabledGroup : PropertyAttribute
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1cf2368cb3484b4287c9693b1774f000
timeCreated: 1736292126

View File

@@ -0,0 +1,10 @@
using System;
using UnityEngine;
namespace Obvious.Soap.Attributes
{
[AttributeUsage(AttributeTargets.Field)]
public class EndDisabledGroup : PropertyAttribute
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5bd2c68066b34c2b8c448d7dfc474ad3
timeCreated: 1736297896

View File

@@ -0,0 +1,19 @@
using System;
using UnityEngine;
namespace Obvious.Soap.Attributes
{
/// <summary>
/// Marks fields to be injected with a runtime-created variable.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class RuntimeInjectableAttribute : PropertyAttribute
{
public string Id { get; private set; }
public RuntimeInjectableAttribute(string id)
{
Id = id;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d7328a9d0d01164e9bc10bb2269a820
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using System;
using UnityEngine;
namespace Obvious.Soap.Attributes
{
/// <summary>
/// Show a field in the inspector if a condition is met. Hides it otherwise.
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class ShowIfAttribute : PropertyAttribute
{
public readonly string conditionFieldName;
public readonly object comparisonValue;
public ShowIfAttribute(string conditionFieldName)
{
this.conditionFieldName = conditionFieldName;
}
public ShowIfAttribute(string conditionFieldName, object comparisonValue = null)
{
this.conditionFieldName = conditionFieldName;
this.comparisonValue = comparisonValue;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 66cce3e5364ae6449b41b0223e7b9c32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
using System;
using UnityEngine;
namespace Obvious.Soap.Attributes
{
[AttributeUsage(AttributeTargets.Field)]
public class SubAssetAttribute : PropertyAttribute
{
public SubAssetAttribute()
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29ca5afb7d107004fad13a379011a7e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 293034c24d29b59468c18b0751bb42a9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,176 @@
using UnityEngine.Events;
using UnityEngine;
namespace Obvious.Soap
{
/// <summary>
/// Triggers a UnityEvent when the comparison is true
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindcomparisontounityevent")]
[AddComponentMenu("Soap/Bindings/BindComparisonToUnityEvent")]
public class BindComparisonToUnityEvent : MonoBehaviour
{
public CustomVariableType Type = CustomVariableType.None;
[SerializeField] private BoolVariable _boolVariable = null;
[SerializeField] private BoolReference _boolComparer = null;
[SerializeField] private IntVariable _intVariable = null;
[SerializeField] private IntReference _intComparer = null;
[SerializeField] private FloatVariable _floatVariable = null;
[SerializeField] private FloatReference _floatComparer = null;
[SerializeField] private StringVariable _stringVariable = null;
[SerializeField] private StringReference _stringComparer = null;
public Comparator Comparison = Comparator.EQUAL;
[SerializeField] private UnityEvent _unityEvent = null;
private void Awake()
{
Subscribe();
}
private void Start()
{
Evaluate();
}
private void Evaluate()
{
switch (Type)
{
case CustomVariableType.Bool:
Evaluate(_boolVariable.Value);
break;
case CustomVariableType.Int:
Evaluate(_intVariable.Value);
break;
case CustomVariableType.Float:
Evaluate(_floatVariable.Value);
break;
case CustomVariableType.String:
Evaluate(_stringVariable.Value);
break;
}
}
private void Evaluate(bool value)
{
if (value == _boolComparer)
_unityEvent.Invoke();
}
private void Evaluate(int value)
{
switch (Comparison)
{
case Comparator.EQUAL:
if (value == _intComparer.Value)
_unityEvent.Invoke();
break;
case Comparator.SMALLER:
if (value < _intComparer.Value)
_unityEvent.Invoke();
break;
case Comparator.BIGGER:
if (value > _intComparer.Value)
_unityEvent.Invoke();
break;
case Comparator.BIGGER_OR_EQUAL:
if (value >= _intComparer.Value)
_unityEvent.Invoke();
break;
case Comparator.SMALLER_OR_EQUAL:
if (value <= _intComparer.Value)
_unityEvent.Invoke();
break;
}
}
private void Evaluate(float value)
{
switch (Comparison)
{
case Comparator.EQUAL:
if (Mathf.Approximately(value, _floatComparer.Value))
_unityEvent.Invoke();
break;
case Comparator.SMALLER:
if (value < _floatComparer.Value)
_unityEvent.Invoke();
break;
case Comparator.BIGGER:
if (value > _floatComparer.Value)
_unityEvent.Invoke();
break;
case Comparator.BIGGER_OR_EQUAL:
if (value >= _floatComparer.Value)
_unityEvent.Invoke();
break;
case Comparator.SMALLER_OR_EQUAL:
if (value <= _floatComparer.Value)
_unityEvent.Invoke();
break;
}
}
private void Evaluate(string value)
{
if (value.Equals(_stringComparer.Value))
_unityEvent.Invoke();
}
private void Subscribe()
{
switch (Type)
{
case CustomVariableType.Bool:
_boolVariable.OnValueChanged += Evaluate;
break;
case CustomVariableType.Int:
_intVariable.OnValueChanged += Evaluate;
break;
case CustomVariableType.Float:
_floatVariable.OnValueChanged += Evaluate;
break;
case CustomVariableType.String:
_stringVariable.OnValueChanged += Evaluate;
break;
}
}
private void OnDestroy()
{
switch (Type)
{
case CustomVariableType.Bool:
_boolVariable.OnValueChanged -= Evaluate;
break;
case CustomVariableType.Int:
_intVariable.OnValueChanged -= Evaluate;
break;
case CustomVariableType.Float:
_floatVariable.OnValueChanged -= Evaluate;
break;
case CustomVariableType.String:
_stringVariable.OnValueChanged -= Evaluate;
break;
}
}
/// <summary>
/// Represents the different comparison operations
/// </summary>
public enum Comparator
{
EQUAL,
SMALLER,
BIGGER,
BIGGER_OR_EQUAL,
SMALLER_OR_EQUAL
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 974f833efdfdf284aa66e2cb2f3a3668
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
using UnityEngine;
using UnityEngine.UI;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
namespace Obvious.Soap
{
/// <summary>
/// Binds a float variable to a filling image
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindfillingimage")]
[AddComponentMenu("Soap/Bindings/BindFillingImage")]
[RequireComponent(typeof(Image))]
public class BindFillingImage : CacheComponent<Image>
{
[SerializeField] private FloatVariable _floatVariable = null;
[Tooltip("If true, the max value is taken from the bound variable's max value")]
[SerializeField] bool _useMaxValueFromVariable = false;
#if ODIN_INSPECTOR
[HideIf("_useMaxValueFromVariable")]
#endif
[SerializeField] private FloatReference _maxValue = new FloatReference(100,true);
protected override void Awake()
{
base.Awake();
_component.type = Image.Type.Filled;
Refresh(_floatVariable);
_floatVariable.OnValueChanged += Refresh;
if (_useMaxValueFromVariable)
_floatVariable.MaxReference.OnValueChanged += Refresh;
else
_maxValue.OnValueChanged += Refresh;
}
private void OnDestroy()
{
_floatVariable.OnValueChanged -= Refresh;
if (_useMaxValueFromVariable)
_floatVariable.MaxReference.OnValueChanged -= Refresh;
else
_maxValue.OnValueChanged -= Refresh;
}
private void Refresh(float currentValue)
{
var maxValue = _useMaxValueFromVariable ? _floatVariable.MaxReference.Value : _maxValue.Value;
_component.fillAmount = _floatVariable.Value / maxValue;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba1bd61ff5f85b642a175fc402fe4bc7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using UnityEngine;
using UnityEngine.UI;
namespace Obvious.Soap
{
/// <summary>
/// Binds a color variable to a graphic (works with UI and SpriteRenderer)
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindgraphiccolor")]
[AddComponentMenu("Soap/Bindings/BindGraphicColor")]
[RequireComponent(typeof(Graphic))]
public class BindGraphicColor : CacheComponent<Graphic>
{
[SerializeField] private ColorVariable _colorVariable = null;
protected override void Awake()
{
base.Awake();
Refresh(_colorVariable);
_colorVariable.OnValueChanged += Refresh;
}
private void OnDestroy()
{
_colorVariable.OnValueChanged -= Refresh;
}
private void Refresh(Color color)
{
_component.color = color;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f8ac922abe854745b373db06bca49d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using TMPro;
using UnityEngine;
namespace Obvious.Soap
{
/// <summary>
/// Binds a string variable to an input field
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindinputfield")]
[AddComponentMenu("Soap/Bindings/BindInputField")]
[RequireComponent(typeof(TMP_InputField))]
public class BindInputField : CacheComponent<TMP_InputField>
{
[SerializeField] private StringVariable _stringVariable = null;
protected override void Awake()
{
base.Awake();
_component.onValueChanged.AddListener(SetBoundVariable);
}
private void Start()
{
//Do it in start, as the variable has to be loaded first.
_component.text = _stringVariable;
}
private void OnDestroy() => _component.onValueChanged.RemoveListener(SetBoundVariable);
private void SetBoundVariable(string value) => _stringVariable.Value = value;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7403922145c9dee438fee5c369d3a330
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
using UnityEngine;
namespace Obvious.Soap
{
/// <summary>
/// Binds a color variable to a renderer
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindrenderercolor")]
[AddComponentMenu("Soap/Bindings/BindRendererColor")]
[RequireComponent(typeof(Renderer))]
public class BindRendererColor : CacheComponent<Renderer>
{
[SerializeField] private ColorVariable _colorVariable = null;
protected override void Awake()
{
base.Awake();
Refresh(_colorVariable);
_colorVariable.OnValueChanged += Refresh;
}
private void OnDestroy()
{
_colorVariable.OnValueChanged -= Refresh;
}
private void Refresh(Color color)
{
_component.material.color = color;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 848384699fb6fa6418e5bc4e5ce8a2b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,82 @@
using UnityEngine;
using UnityEngine.UI;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
namespace Obvious.Soap
{
/// <summary>
/// Binds a float variable to a slider
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindslider")]
[AddComponentMenu("Soap/Bindings/BindSlider")]
[RequireComponent(typeof(Slider))]
public class BindSlider : CacheComponent<Slider>
{
[SerializeField] private FloatVariable _floatVariable = null;
[Tooltip("If true, the max value is taken from the bound variable's max value")]
[SerializeField] private bool _useMaxValueFromVariable = false;
#if ODIN_INSPECTOR
[HideIf("_useMaxValueFromVariable")]
#endif
[SerializeField] private FloatReference _maxValue = new FloatReference(100, true);
protected override void Awake()
{
base.Awake();
OnValueChanged(_floatVariable);
_component.onValueChanged.AddListener(SetBoundVariable);
_floatVariable.OnValueChanged += OnValueChanged;
if (_useMaxValueFromVariable)
{
OnMaxValueChanged(_floatVariable.MaxReference.Value);
_floatVariable.MaxReference.OnValueChanged += OnMaxValueChanged;
}
else
{
OnMaxValueChanged(_maxValue.Value);
_maxValue.OnValueChanged += OnMaxValueChanged;
}
}
private void OnDestroy()
{
_component.onValueChanged.RemoveListener(SetBoundVariable);
_floatVariable.OnValueChanged -= OnValueChanged;
if (_useMaxValueFromVariable)
_floatVariable.MaxReference.OnValueChanged -= OnMaxValueChanged;
else
_maxValue.OnValueChanged -= OnMaxValueChanged;
}
private void OnValueChanged(float value)
{
_component.value = value;
}
private void OnMaxValueChanged(float value)
{
_component.maxValue = value;
}
private void SetBoundVariable(float value)
{
_floatVariable.Value = value;
}
private void OnValidate()
{
if (_component == null)
return;
OnValueChanged(_floatVariable.Value);
if (_useMaxValueFromVariable)
OnMaxValueChanged(_floatVariable.MaxReference.Value);
else
OnMaxValueChanged(_maxValue.Value);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 586b99df9b2cb32469408b1a47ca09d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
using UnityEngine;
using UnityEngine.UI;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
namespace Obvious.Soap
{
/// <summary>
/// Binds an int variable to a slider
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindslider")]
[AddComponentMenu("Soap/Bindings/BindSliderToInt")]
[RequireComponent(typeof(Slider))]
public class BindSliderInt : CacheComponent<Slider>
{
[SerializeField] private IntVariable _intVariable = null;
[SerializeField] private bool _useMaxValueFromVariable = false;
#if ODIN_INSPECTOR
[ShowIf("_useMaxValueFromVariable", false)]
#endif
[SerializeField] private IntReference _maxValue = new IntReference(100, true);
protected override void Awake()
{
base.Awake();
OnValueChanged(_intVariable.Value);
_component.onValueChanged.AddListener(SetBoundVariable);
_intVariable.OnValueChanged += OnValueChanged;
if (_useMaxValueFromVariable)
{
OnMaxValueChanged(_intVariable.MaxReference.Value);
_intVariable.MaxReference.OnValueChanged += OnMaxValueChanged;
}
else
{
OnMaxValueChanged(_maxValue.Value);
_maxValue.OnValueChanged += OnMaxValueChanged;
}
}
private void OnDestroy()
{
_component.onValueChanged.RemoveListener(SetBoundVariable);
_intVariable.OnValueChanged -= OnValueChanged;
if (_useMaxValueFromVariable)
_intVariable.MaxReference.OnValueChanged -= OnMaxValueChanged;
else
_maxValue.OnValueChanged -= OnMaxValueChanged;
}
private void OnValueChanged(int value)
{
_component.value = value;
}
private void OnMaxValueChanged(int value)
{
_component.maxValue = value;
}
private void SetBoundVariable(float value)
{
_intVariable.Value = Mathf.RoundToInt(value);
}
private void OnValidate()
{
if (_component == null)
return;
OnValueChanged(_intVariable.Value);
if (_useMaxValueFromVariable)
OnMaxValueChanged(_intVariable.MaxReference.Value);
else
OnMaxValueChanged(_maxValue.Value);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f637bcca09b49b4d91ff8c9f18d3b15
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,146 @@
using System;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
namespace Obvious.Soap
{
/// <summary>
/// Binds a variable to a text component
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindtext-textmeshpro")]
[AddComponentMenu("Soap/Bindings/BindText")]
[RequireComponent(typeof(Text))]
public class BindText: CacheComponent<Text>
{
public CustomVariableType Type = CustomVariableType.None;
[SerializeField] private BoolVariable _boolVariable = null;
[SerializeField] private IntVariable _intVariable = null;
[SerializeField] private FloatVariable _floatVariable = null;
[SerializeField] private StringVariable _stringVariable = null;
private readonly StringBuilder _stringBuilder = new StringBuilder();
private Action<bool> _boolValueChangedHandler;
private Action<int> _intValueChangedHandler;
private Action<float> _floatValueChangedHandler;
private Action<string> _stringValueChangedHandler;
public string Prefix = string.Empty;
public string Suffix = string.Empty;
//int specific
[Tooltip("Useful too an offset, for example for Level counts. If your level index is 0, add 1, so it displays Level : 1")]
public int Increment = 0;
[Tooltip("Clamps the value shown to a minimum and a maximum.")]
public Vector2Int MinMaxInt = new Vector2Int(int.MinValue, int.MaxValue);
//float specific
[Min(1)]
public int DecimalAmount = 2;
[Tooltip("Clamps the value shown to a minimum and a maximum.")]
public bool IsClamped = false;
public Vector2 MinMaxFloat = new Vector2(float.MinValue, float.MaxValue);
protected override void Awake()
{
base.Awake();
if (Type == CustomVariableType.None)
{
Debug.LogError("Select a type for this binding component", gameObject);
return;
}
Refresh();
Subscribe();
}
private void Refresh()
{
_stringBuilder.Clear();
_stringBuilder.Append(Prefix);
switch (Type)
{
case CustomVariableType.Bool:
_stringBuilder.Append(_boolVariable.Value ? "True" : "False");
break;
case CustomVariableType.Int:
var clampedInt = IsClamped ? Mathf.Clamp(_intVariable.Value, MinMaxInt.x, MinMaxInt.y) : _intVariable.Value;
_stringBuilder.Append(clampedInt + Increment);
break;
case CustomVariableType.Float:
double clampedFloat = IsClamped ? Mathf.Clamp(_floatVariable.Value, MinMaxFloat.x, MinMaxFloat.y) : _floatVariable.Value;
double rounded = System.Math.Round(clampedFloat, DecimalAmount);
_stringBuilder.Append(rounded);
break;
case CustomVariableType.String:
_stringBuilder.Append(_stringVariable.Value);
break;
}
_stringBuilder.Append(Suffix);
_component.text = _stringBuilder.ToString();
}
private void Subscribe()
{
switch (Type)
{
case CustomVariableType.Bool:
if (_boolVariable != null)
{
_boolValueChangedHandler = value => Refresh();
_boolVariable.OnValueChanged += _boolValueChangedHandler;
}
break;
case CustomVariableType.Int:
if (_intVariable != null)
{
_intValueChangedHandler = value => Refresh();
_intVariable.OnValueChanged += _intValueChangedHandler;
}
break;
case CustomVariableType.Float:
if (_floatVariable != null)
{
_floatValueChangedHandler = value => Refresh();
_floatVariable.OnValueChanged += _floatValueChangedHandler;
}
break;
case CustomVariableType.String:
if (_stringVariable != null)
{
_stringValueChangedHandler = value => Refresh();
_stringVariable.OnValueChanged += _stringValueChangedHandler;
}
break;
}
}
private void OnDestroy()
{
switch (Type)
{
case CustomVariableType.Bool:
if (_boolVariable != null)
_boolVariable.OnValueChanged -= _boolValueChangedHandler;
break;
case CustomVariableType.Int:
if (_intVariable != null)
_intVariable.OnValueChanged -= _intValueChangedHandler;
break;
case CustomVariableType.Float:
if (_floatVariable != null)
_floatVariable.OnValueChanged -= _floatValueChangedHandler;
break;
case CustomVariableType.String:
if (_stringVariable != null)
_stringVariable.OnValueChanged -= _stringValueChangedHandler;
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 773a31c2a4dd4924cb2d27678cf6deb4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,152 @@
using System;
using System.Text;
using UnityEngine;
using TMPro;
namespace Obvious.Soap
{
/// <summary>
/// Binds a variable to a TextMeshPro component
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindtext-textmeshpro")]
[AddComponentMenu("Soap/Bindings/BindTextMeshPro")]
[RequireComponent(typeof(TMP_Text))]
public class BindTextMeshPro : CacheComponent<TMP_Text>
{
public CustomVariableType Type = CustomVariableType.None;
[SerializeField] private BoolVariable _boolVariable = null;
[SerializeField] private IntVariable _intVariable = null;
[SerializeField] private FloatVariable _floatVariable = null;
[SerializeField] private StringVariable _stringVariable = null;
private readonly StringBuilder _stringBuilder = new StringBuilder();
private Action<bool> _boolValueChangedHandler;
private Action<int> _intValueChangedHandler;
private Action<float> _floatValueChangedHandler;
private Action<string> _stringValueChangedHandler;
[Tooltip("Displays before the value")] public string Prefix = string.Empty;
[Tooltip("Displays after the value")] public string Suffix = string.Empty;
//int specific
[Tooltip(
"Useful too an offset, for example for Level counts. If your level index is 0, add 1, so it displays Level : 1")]
public int Increment = 0;
[Tooltip("Clamps the value shown to a minimum and a maximum.")]
public Vector2Int MinMaxInt = new Vector2Int(int.MinValue, int.MaxValue);
//float specific
[Min(1)] public int DecimalAmount = 2;
[Tooltip("Clamps the value shown to a minimum and a maximum.")]
public bool IsClamped = false;
public Vector2 MinMaxFloat = new Vector2(float.MinValue, float.MaxValue);
protected override void Awake()
{
base.Awake();
if (Type == CustomVariableType.None)
{
Debug.LogError("Select a type for this binding component", gameObject);
return;
}
Refresh();
Subscribe();
}
private void Refresh()
{
_stringBuilder.Clear();
_stringBuilder.Append(Prefix);
switch (Type)
{
case CustomVariableType.Bool:
_stringBuilder.Append(_boolVariable.Value ? "True" : "False");
break;
case CustomVariableType.Int:
var clampedInt = IsClamped
? Mathf.Clamp(_intVariable.Value, MinMaxInt.x, MinMaxInt.y)
: _intVariable.Value;
_stringBuilder.Append(clampedInt + Increment);
break;
case CustomVariableType.Float:
double clampedFloat = IsClamped
? Mathf.Clamp(_floatVariable.Value, MinMaxFloat.x, MinMaxFloat.y)
: _floatVariable.Value;
double rounded = System.Math.Round(clampedFloat, DecimalAmount);
_stringBuilder.Append(rounded);
break;
case CustomVariableType.String:
_stringBuilder.Append(_stringVariable.Value);
break;
}
_stringBuilder.Append(Suffix);
_component.SetText(_stringBuilder);
}
private void Subscribe()
{
switch (Type)
{
case CustomVariableType.Bool:
if (_boolVariable != null)
{
_boolValueChangedHandler = value => Refresh();
_boolVariable.OnValueChanged += _boolValueChangedHandler;
}
break;
case CustomVariableType.Int:
if (_intVariable != null)
{
_intValueChangedHandler = value => Refresh();
_intVariable.OnValueChanged += _intValueChangedHandler;
}
break;
case CustomVariableType.Float:
if (_floatVariable != null)
{
_floatValueChangedHandler = value => Refresh();
_floatVariable.OnValueChanged += _floatValueChangedHandler;
}
break;
case CustomVariableType.String:
if (_stringVariable != null)
{
_stringValueChangedHandler = value => Refresh();
_stringVariable.OnValueChanged += _stringValueChangedHandler;
}
break;
}
}
private void OnDestroy()
{
switch (Type)
{
case CustomVariableType.Bool:
if (_boolVariable != null)
_boolVariable.OnValueChanged -= _boolValueChangedHandler;
break;
case CustomVariableType.Int:
if (_intVariable != null)
_intVariable.OnValueChanged -= _intValueChangedHandler;
break;
case CustomVariableType.Float:
if (_floatVariable != null)
_floatVariable.OnValueChanged -= _floatValueChangedHandler;
break;
case CustomVariableType.String:
if (_stringVariable != null)
_stringVariable.OnValueChanged -= _stringValueChangedHandler;
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eca49ccdbc3362d4397b0fbd0b2b9d2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using UnityEngine;
using UnityEngine.UI;
namespace Obvious.Soap
{
/// <summary>
/// Binds a bool variable to a toggle
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/scene-documentation/2_bindings/bindtoggle")]
[AddComponentMenu("Soap/Bindings/BindToggle")]
[RequireComponent(typeof(Toggle))]
public class BindToggle : CacheComponent<Toggle>
{
[SerializeField] private BoolVariable _boolVariable = null;
protected override void Awake()
{
base.Awake();
OnValueChanged(_boolVariable);
_component.onValueChanged.AddListener(SetBoundVariable);
_boolVariable.OnValueChanged += OnValueChanged;
}
private void OnDestroy()
{
_component.onValueChanged.RemoveListener(SetBoundVariable);
_boolVariable.OnValueChanged -= OnValueChanged;
}
private void OnValueChanged(bool value)
{
_component.isOn = value;
}
private void SetBoundVariable(bool value)
{
_boolVariable.Value = value;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9f6cdac9ade6d644be932ae21ca0d3c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8fafaa2b606c8174d9bbe26515992389, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using UnityEngine;
namespace Obvious.Soap
{
/// <summary>
/// a component that caches a reference to a component
/// </summary>
/// <typeparam name="T">Type</typeparam>
public class CacheComponent<T> : MonoBehaviour
{
protected T _component;
protected virtual void Awake()
{
GetReference();
}
private void Reset()
{
GetReference();
}
private void GetReference()
{
if (_component != null)
return;
_component = GetComponent<T>();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 494c134ce1af4561924d823298a6c530
timeCreated: 1652660641

View File

@@ -0,0 +1,16 @@
{
"name": "Obvious.Soap",
"references": [
"GUID:6546d7765b4165b40850b3667f981c26",
"GUID:6055be8ebefd69e48b49212b09b47b2f"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ee6baafdecd94804a8714654c4bd097f
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 42e3d307dc2ad5d4a9f46fe506aa8f66
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeBoolVariableInjector : RuntimeVariableInjector<BoolVariable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9a16f63b5569437983b30d94a8da9448
timeCreated: 1736292491

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeColorVariableInjector : RuntimeVariableInjector<ColorVariable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4309fbcdc5d9498fb9e078a7613db029
timeCreated: 1736292511

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeComponentVariableInjector : RuntimeVariableInjector<ComponentVariable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aa760b4b8fe24b5c80e72891a88a2d13
timeCreated: 1736292520

View File

@@ -0,0 +1,8 @@
namespace Obvious.Soap
{
public class RuntimeFloatVariableInjector : RuntimeVariableInjector<FloatVariable>
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 06526d2f91714f0e882b68eed50d265f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7ac24cd0440b6c941996e400d1a4fda5, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeGameObjectVariableInjector : RuntimeVariableInjector<GameObjectVariable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a06f8d87f78f41c58710679bdd408208
timeCreated: 1736292515

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeIntVariableInjector : RuntimeVariableInjector<IntVariable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 947fdc26593546c58402ba0befe7364d
timeCreated: 1736292487

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeQuaternionVariableInjector : RuntimeVariableInjector<QuaternionVariable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c9ae6e7dcdb1449cb8790e9cae8e2b86
timeCreated: 1736292507

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeStringVariableInjector : RuntimeVariableInjector<StringVariable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 638a7143be564fdf8a8a8969cdb4a0f8
timeCreated: 1736292494

View File

@@ -0,0 +1,105 @@
using System.Collections.Generic;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#else
using Obvious.Soap.Attributes;
#endif
using UnityEngine;
namespace Obvious.Soap
{
[DefaultExecutionOrder(-1)]
public class RuntimeVariableInjector<TVariable> : MonoBehaviour where TVariable : ScriptableVariableBase
{
[SerializeField] private InjectedVariableWrapper[] _runtimeVariables = null;
[SerializeField] private bool _debugLogEnabled = false;
private List<InjectedVariable<TVariable>> _runtimeInjectedVariables;
private void Awake()
{
if (_runtimeVariables == null || _runtimeVariables.Length == 0)
{
if (_debugLogEnabled)
Debug.LogWarning($"{nameof(RuntimeVariableInjector<TVariable>)}: No injected variables specified.");
return;
}
_runtimeInjectedVariables = new List<InjectedVariable<TVariable>>();
foreach (var wrapper in _runtimeVariables)
{
var injectedVariable = new InjectedVariable<TVariable>(wrapper);
if (injectedVariable.VariableTemplate != null)
{
var deepCopy = SoapRuntimeUtils.CreateCopy(injectedVariable.VariableTemplate);
injectedVariable.RuntimeVariable = deepCopy;
wrapper.RuntimeVariable = deepCopy;
}
else
{
var runtimeVariable =
SoapRuntimeUtils.CreateRuntimeInstance<TVariable>($"{gameObject.name}_{injectedVariable.Id}");
injectedVariable.RuntimeVariable = runtimeVariable;
wrapper.RuntimeVariable = runtimeVariable;
}
_runtimeInjectedVariables.Add(injectedVariable);
}
foreach (var injectedVariable in _runtimeInjectedVariables)
{
SoapRuntimeUtils.InjectInChildren(
gameObject,
injectedVariable.RuntimeVariable,
injectedVariable.Id,
_debugLogEnabled
);
}
}
}
[System.Serializable]
internal class InjectedVariableWrapper
{
[Tooltip("This Id has to match the one used in the attribute for a specific variable.")] [SerializeField]
private string _id = "";
[SerializeField]
#if ODIN_INSPECTOR
[ReadOnly]
#else
[BeginDisabledGroup]
#endif
private ScriptableVariableBase _runtimeVariable = null;
#if !ODIN_INSPECTOR
[EndDisabledGroup]
#endif
[SerializeField]
[Tooltip("If set, a deep copy of this variable will be created at runtime. " +
"\n If left null, a new instance will be created.")]
private ScriptableVariableBase _variableTemplate = null;
public string ID => _id;
public ScriptableVariableBase VariableTemplate => _variableTemplate;
public ScriptableVariableBase RuntimeVariable
{
get => _runtimeVariable;
set => _runtimeVariable = value;
}
}
internal class InjectedVariable<TVariable> where TVariable : ScriptableVariableBase
{
public string Id { get; }
public TVariable RuntimeVariable { get; set; }
public TVariable VariableTemplate { get; }
public InjectedVariable(InjectedVariableWrapper wrapper)
{
Id = wrapper.ID;
VariableTemplate = wrapper.VariableTemplate as TVariable;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c0f6cc1127645609af918671a39ce82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7ac24cd0440b6c941996e400d1a4fda5, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeVector2IntVariableInjector : RuntimeVariableInjector<Vector2IntVariable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fd97a24a827742039b6c65b0e1a9edae
timeCreated: 1736292501

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeVector2VariableInjector : RuntimeVariableInjector<Vector2Variable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 64a3fe16b7d342d5955d31d1bda23940
timeCreated: 1736292497

View File

@@ -0,0 +1,6 @@
namespace Obvious.Soap
{
public class RuntimeVector3VariableInjector : RuntimeVariableInjector<Vector3Variable>
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 671548cdcb164019869c265a53fdb9e8
timeCreated: 1736292504

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3dba46d5a730a784db12f430f28fd048
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Obvious.Soap
{
/// <summary>
/// Interface for objects that can be reset to their initial value.
/// </summary>
public interface IReset
{
void ResetValue();
#if UNITY_EDITOR
void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange);
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2f1a089b4a201e4fa8fb2d1d643b06b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
namespace Obvious.Soap
{
public abstract class ResettableScriptableObject : ScriptableObject
{
protected string _cachedJson;
#if UNITY_EDITOR
protected virtual void OnEnable()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
protected virtual void OnDisable()
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
protected virtual void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange)
{
if (playModeStateChange == PlayModeStateChange.ExitingEditMode)
{
CacheState();
}
else if (playModeStateChange == PlayModeStateChange.EnteredEditMode)
{
ResetValue();
}
}
protected virtual void CacheState()
{
_cachedJson = EditorJsonUtility.ToJson(this, prettyPrint: true);
}
[ContextMenu("Reset Value")]
public virtual void ResetValue()
{
if (!string.IsNullOrEmpty(_cachedJson))
{
EditorJsonUtility.FromJsonOverwrite(_cachedJson, this);
}
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc0665cf937e17746aab28f77136a40c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2ed6a2c37fbee034182d76727a5af815
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using UnityEngine;
namespace Obvious.Soap
{
/// <summary>
/// Interface to get objects that can be drawn in the inspector
/// </summary>
public interface IDrawObjectsInInspector
{
IReadOnlyList<Object> EditorListeners { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e498e4adf2f54a88b454f5402fb01d7f
timeCreated: 1656086814

View File

@@ -0,0 +1,31 @@
using System;
using UnityEngine;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
namespace Obvious.Soap
{
/// <summary>
/// Base classes of all ScriptableObjects in Soap
/// </summary>
#if ODIN_INSPECTOR
public abstract class ScriptableBase : SerializedScriptableObject
#else
public abstract class ScriptableBase : ScriptableObject
#endif
{
internal virtual void Reset()
{
TagIndex = 0;
Description = "";
}
[HideInInspector]
public Action RepaintRequest;
[HideInInspector]
public int TagIndex = 0;
[HideInInspector]
public string Description = "";
public virtual Type GetGenericType { get; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d4bb74215c74b0418d738ce69073159
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7ad718c424a97fa4cb2e906e0a7a9b3e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,116 @@
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Obvious.Soap
{
public abstract class ScriptableCollection : ScriptableBase, IReset
{
[Tooltip(
"Clear the collection when:\n" +
" Scene Loaded : when a scene is loaded.\n" +
" Application Start : Once, when the application starts. Modifications persist between scenes")]
[SerializeField] protected ResetType _resetOn = ResetType.SceneLoaded;
[HideInInspector] public Action Modified;
public event Action OnCleared;
public abstract int Count { get; }
public abstract bool CanBeSerialized();
private ResetType _lastResetType;
protected virtual void Awake()
{
hideFlags = HideFlags.DontUnloadUnusedAsset;
}
protected virtual void OnEnable()
{
#if UNITY_EDITOR
_lastResetType = _resetOn;
#endif
if (_resetOn == ResetType.None)
return;
Clear();
RegisterResetHooks(_resetOn);
}
protected virtual void OnDisable()
{
UnregisterResetHooks(_resetOn);
}
private void RegisterResetHooks(ResetType resetType)
{
if (resetType == ResetType.SceneLoaded)
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
#if UNITY_EDITOR
if (resetType == ResetType.ApplicationStarts || resetType == ResetType.SceneLoaded)
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
#endif
}
private void UnregisterResetHooks(ResetType resetType)
{
if (resetType == ResetType.SceneLoaded)
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
#if UNITY_EDITOR
if (resetType == ResetType.ApplicationStarts || resetType == ResetType.SceneLoaded)
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
#endif
}
protected virtual void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (mode == LoadSceneMode.Single)
{
Clear();
}
}
public virtual void Clear()
{
OnCleared?.Invoke();
Modified?.Invoke();
}
internal override void Reset()
{
base.Reset();
_resetOn = ResetType.SceneLoaded;
Clear();
}
public void ResetValue() => Clear();
#if UNITY_EDITOR
public virtual void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange)
{
if (playModeStateChange == PlayModeStateChange.EnteredEditMode ||
playModeStateChange == PlayModeStateChange.ExitingEditMode)
Clear();
}
private void OnValidate()
{
if (_lastResetType != _resetOn)
{
UnregisterResetHooks(_lastResetType);
RegisterResetHooks(_resetOn);
_lastResetType = _resetOn;
}
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0feea8ee55364f4b9683b75d66c3f37c
timeCreated: 1737327539

View File

@@ -0,0 +1,181 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Obvious.Soap
{
public abstract class ScriptableDictionary<T, V> : ScriptableDictionaryBase, IDictionary<T, V>
{
[SerializeField] protected Dictionary<T, V> _dictionary = new Dictionary<T, V>();
public override int Count => _dictionary.Count;
public bool IsReadOnly => false;
public bool IsEmpty => _dictionary.Count == 0;
/// <summary> Event raised when an item is added to the list. </summary>
public event Action<T, V> OnItemAdded;
/// <summary> Event raised when an item is removed from the list. </summary>
public event Action<T, V> OnItemRemoved;
public V this[T key]
{
get => _dictionary[key];
set
{
_dictionary[key] = value;
Modified?.Invoke();
}
}
public ICollection<T> Keys => _dictionary.Keys;
public ICollection<V> Values => _dictionary.Values;
public override Type GetGenericType => typeof(T);
/// <summary>
/// Adds an item to the dictionary.
/// Raises OnItemAdded and OnModified event.
/// </summary>
/// <param name="item"></param>
public void Add(KeyValuePair<T, V> item)
{
Add(item.Key, item.Value);
}
/// <summary>
/// Adds a key and value to the dictionary.
/// Raises OnItemAdded and OnModified event.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(T key, V value)
{
_dictionary.Add(key, value);
OnItemAdded?.Invoke(key, value);
Modified?.Invoke();
#if UNITY_EDITOR
RepaintRequest?.Invoke();
#endif
}
/// <summary>
/// Checks if the dictionary contains a key.
/// Then adds the key and value to the dictionary.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns>True if succeeded</returns>
public bool TryAdd(T key, V value)
{
if (_dictionary.ContainsKey(key))
{
return false;
}
Add(key, value);
return true;
}
/// <summary>
/// Removes an item from the dictionary.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Remove(KeyValuePair<T, V> item)
{
return Remove(item.Key);
}
/// <summary>
/// Tries to Remove an item from the dictionary using a key.
/// If Success, raises OnItemRemoved and OnModified event.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool Remove(T key)
{
if (!_dictionary.TryGetValue(key, out var value))
return false;
var removedFromList = _dictionary.Remove(key);
if (removedFromList)
{
OnItemRemoved?.Invoke(key,value);
Modified?.Invoke();
#if UNITY_EDITOR
RepaintRequest?.Invoke();
#endif
return true;
}
return false;
}
/// <summary>
/// Tries to get a value from the dictionary using a key.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool TryGetValue(T key, out V value)
{
return _dictionary.TryGetValue(key, out value);
}
public override void Clear()
{
_dictionary.Clear();
base.Clear();
#if UNITY_EDITOR
RepaintRequest?.Invoke();
#endif
}
public bool Contains(KeyValuePair<T, V> item)
{
return _dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<T, V>[] array, int arrayIndex)
{
var i = arrayIndex;
foreach (var pair in _dictionary)
{
array[i] = pair;
i++;
}
}
public bool ContainsKey(T key)
{
return _dictionary.ContainsKey(key);
}
public bool ContainsValue(V value)
{
return _dictionary.ContainsValue(value);
}
public IEnumerator<KeyValuePair<T, V>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public override bool CanBeSerialized()
{
var canKeyBeSerialized = SoapUtils.IsUnityType(typeof(T)) ||
SoapUtils.IsSerializable(typeof(T));
var canValueBeSerialized = SoapUtils.IsUnityType(typeof(V)) ||
SoapUtils.IsSerializable(typeof(V));
var canBeSerialized = canKeyBeSerialized && canValueBeSerialized;
return canBeSerialized;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cbd7c4c9626794a4e83edc0f434c235b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using UnityEngine;
namespace Obvious.Soap
{
[HelpURL("https://obvious-game.gitbook.io/soap/soap-core-assets/scriptable-dictionary")]
public abstract class ScriptableDictionaryBase : ScriptableCollection
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 574c3e833db7381459a36db968e615a0
timeCreated: 1675031072

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3226e4cc472d210439f8e9adb777ce72
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using UnityEngine;
namespace Obvious.Soap
{
[HelpURL("https://obvious-game.gitbook.io/soap/soap-core-assets/scriptable-enum")]
public abstract class ScriptableEnumBase : ScriptableBase
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a968fca847314c5285a5b1586b1ad1c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4a7850133d8e6584bb9889782f85a552
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 66c9748321f623d458ffaabf2176a2df
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,61 @@
using System.Threading;
using UnityEngine;
namespace Obvious.Soap
{
/// <summary>
/// Base class for all event listeners
/// </summary>
[HelpURL("https://obvious-game.gitbook.io/soap/soap-core-assets/event-listener")]
public abstract class EventListenerBase : MonoBehaviour
{
protected enum Binding
{
UNTIL_DESTROY,
UNTIL_DISABLE
}
[SerializeField] protected Binding _binding = Binding.UNTIL_DESTROY;
[SerializeField] protected bool _disableAfterSubscribing = false;
protected readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
protected abstract void ToggleRegistration(bool toggle);
/// <summary>
/// Returns true if the event listener contains a call to the method with the given name
/// </summary>
public abstract bool ContainsCallToMethod(string methodName);
protected virtual void Awake()
{
if (_binding == Binding.UNTIL_DESTROY)
ToggleRegistration(true);
gameObject.SetActive(!_disableAfterSubscribing);
}
protected virtual void OnEnable()
{
if (_binding == Binding.UNTIL_DISABLE)
ToggleRegistration(true);
}
protected virtual void OnDisable()
{
if (_binding == Binding.UNTIL_DISABLE)
{
ToggleRegistration(false);
_cancellationTokenSource.Cancel();
}
}
protected virtual void OnDestroy()
{
if (_binding == Binding.UNTIL_DESTROY)
{
ToggleRegistration(false);
_cancellationTokenSource.Cancel();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8f4d2d3fb5e4a33b2cc509626f7313f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f26f39c2872ef84439641da6a91f8b92, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace Obvious.Soap
{
/// <summary>
/// A listener for a ScriptableEventBool
/// </summary>
[AddComponentMenu("Soap/EventListeners/EventListenerBool")]
public class EventListenerBool : EventListenerGeneric<bool>
{
[SerializeField] private EventResponse[] _eventResponses = null;
protected override EventResponse<bool>[] EventResponses => _eventResponses;
[System.Serializable]
public class EventResponse : EventResponse<bool>
{
[SerializeField] private ScriptableEventBool _scriptableEvent = null;
public override ScriptableEvent<bool> ScriptableEvent => _scriptableEvent;
[SerializeField] private BoolUnityEvent _response = null;
public override UnityEvent<bool> Response => _response;
}
[System.Serializable]
public class BoolUnityEvent : UnityEvent<bool>
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e64750262007f204b879cd45e18965e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f26f39c2872ef84439641da6a91f8b92, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using UnityEngine;
using UnityEngine.Events;
namespace Obvious.Soap
{
/// <summary>
/// A listener for a ScriptableEventColor
/// </summary>
[AddComponentMenu("Soap/EventListeners/EventListenerColor")]
public class EventListenerColor : EventListenerGeneric<Color>
{
[SerializeField] private EventResponse[] _eventResponses = null;
protected override EventResponse<Color>[] EventResponses => _eventResponses;
[System.Serializable]
public class EventResponse : EventResponse<Color>
{
[SerializeField] private ScriptableEventColor _scriptableEvent = null;
public override ScriptableEvent<Color> ScriptableEvent => _scriptableEvent;
[SerializeField] private ColorUnityEvent _response = null;
public override UnityEvent<Color> Response => _response;
}
[System.Serializable]
public class ColorUnityEvent : UnityEvent<Color>
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d16da97870a756e4b9063178aca2c848
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f26f39c2872ef84439641da6a91f8b92, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
using UnityEngine;
using UnityEngine.Events;
namespace Obvious.Soap
{
[AddComponentMenu("Soap/EventListeners/EventListenerComponent")]
public class EventListenerComponent : EventListenerGeneric<Component>
{
[SerializeField] private EventResponse[] _eventResponses = null;
protected override EventResponse<Component>[] EventResponses => _eventResponses;
[System.Serializable]
public class EventResponse : EventResponse<Component>
{
[SerializeField] private ScriptableEventComponent _scriptableEvent = null;
public override ScriptableEvent<Component> ScriptableEvent => _scriptableEvent;
[SerializeField] private ComponentUnityEvent _response = null;
public override UnityEvent<Component> Response => _response;
}
[System.Serializable]
public class ComponentUnityEvent : UnityEvent<Component>
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 04727cc775fa20d4c821cb3c9f304468
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f26f39c2872ef84439641da6a91f8b92, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace Obvious.Soap
{
/// <summary>
/// A listener for a ScriptableEventFloat
/// </summary>
[AddComponentMenu("Soap/EventListeners/EventListenerFloat")]
public class EventListenerFloat : EventListenerGeneric<float>
{
[SerializeField] private EventResponse[] _eventResponses = null;
protected override EventResponse<float>[] EventResponses => _eventResponses;
[System.Serializable]
public class EventResponse : EventResponse<float>
{
[SerializeField] private ScriptableEventFloat _scriptableEvent = null;
public override ScriptableEvent<float> ScriptableEvent => _scriptableEvent;
[SerializeField] private FloatUnityEvent _response = null;
public override UnityEvent<float> Response => _response;
}
[System.Serializable]
public class FloatUnityEvent : UnityEvent<float>
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d0e4e54c3505e9428c41e7621def42c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f26f39c2872ef84439641da6a91f8b92, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using UnityEngine;
using UnityEngine.Events;
namespace Obvious.Soap
{
/// <summary>
/// A listener for a ScriptableEventGameObject
/// </summary>
[AddComponentMenu("Soap/EventListeners/EventListenerGameObject")]
public class EventListenerGameObject : EventListenerGeneric<GameObject>
{
[SerializeField] private EventResponse[] _eventResponses = null;
protected override EventResponse<GameObject>[] EventResponses => _eventResponses;
[System.Serializable]
public class EventResponse : EventResponse<GameObject>
{
[SerializeField] private ScriptableEventGameObject _scriptableEvent = null;
public override ScriptableEvent<GameObject> ScriptableEvent => _scriptableEvent;
[SerializeField] private GameObjectUnityEvent _response = null;
public override UnityEvent<GameObject> Response => _response;
}
[System.Serializable]
public class GameObjectUnityEvent : UnityEvent<GameObject>
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a09b9b4be86d5f4b83fa4de77c033f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f26f39c2872ef84439641da6a91f8b92, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,149 @@
using System;
using UnityEngine.Events;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using System.Collections;
namespace Obvious.Soap
{
public abstract class EventListenerGeneric<T> : EventListenerBase
{
protected virtual EventResponse<T>[] EventResponses { get; }
private readonly Dictionary<ScriptableEvent<T>, EventResponse<T>> _dictionary =
new Dictionary<ScriptableEvent<T>, EventResponse<T>>();
protected override void ToggleRegistration(bool toggle)
{
foreach (var eventResponse in EventResponses)
{
if (toggle)
{
eventResponse.ScriptableEvent.RegisterListener(this);
if (!_dictionary.ContainsKey(eventResponse.ScriptableEvent))
_dictionary.Add(eventResponse.ScriptableEvent, eventResponse);
}
else
{
eventResponse.ScriptableEvent.UnregisterListener(this);
if (_dictionary.ContainsKey(eventResponse.ScriptableEvent))
_dictionary.Remove(eventResponse.ScriptableEvent);
}
}
}
internal void OnEventRaised(ScriptableEvent<T> eventRaised, T param, bool debug = false)
{
var eventResponse = _dictionary[eventRaised];
if (eventResponse.Delay > 0)
{
if (gameObject.activeInHierarchy)
StartCoroutine(Cr_DelayInvokeResponse(eventRaised, eventResponse, param, debug));
else
DelayInvokeResponseAsync(eventRaised, eventResponse, param, debug, _cancellationTokenSource.Token);
}
else
InvokeResponse(eventRaised, eventResponse, param, debug);
}
private IEnumerator Cr_DelayInvokeResponse(ScriptableEvent<T> eventRaised, EventResponse<T> eventResponse, T param,
bool debug)
{
yield return new WaitForSeconds(eventResponse.Delay);
InvokeResponse(eventRaised, eventResponse, param, debug);
}
private async void DelayInvokeResponseAsync(ScriptableEvent<T> eventRaised, EventResponse<T> eventResponse, T param,
bool debug, CancellationToken cancellationToken)
{
try
{
await Task.Delay((int)(eventResponse.Delay * 1000), cancellationToken);
InvokeResponse(eventRaised, eventResponse, param, debug);
}
catch (TaskCanceledException)
{
}
}
private void InvokeResponse(ScriptableEvent<T> eventRaised, EventResponse<T> eventResponse, T param, bool debug)
{
eventResponse.Response?.Invoke(param);
if (debug)
Debug(eventRaised);
}
protected virtual void Debug(ScriptableEvent<T> eventRaised)
{
var response = _dictionary[eventRaised].Response;
var registeredListenerCount = response.GetPersistentEventCount();
for (var i = 0; i < registeredListenerCount; i++)
{
var sb = new StringBuilder();
sb.Append("<color=#f75369>[Event] </color>");
sb.Append(eventRaised.name);
sb.Append(" => ");
sb.Append(response.GetPersistentTarget(i).name);
sb.Append(".");
sb.Append(response.GetPersistentMethodName(i));
sb.Append("()");
UnityEngine.Debug.Log(sb.ToString(), gameObject);
}
}
public override bool ContainsCallToMethod(string methodName)
{
var containsMethod = false;
foreach (var eventResponse in EventResponses)
{
var registeredListenerCount = eventResponse.Response.GetPersistentEventCount();
for (var i = 0; i < registeredListenerCount; i++)
{
if (eventResponse.Response.GetPersistentMethodName(i) == methodName)
{
var sb = new StringBuilder();
sb.Append($"<color=#f75369>{methodName}()</color>");
sb.Append(" is called by: <color=#f75369>[Event] </color>");
sb.Append(eventResponse.ScriptableEvent.name);
UnityEngine.Debug.Log(sb.ToString(), gameObject);
containsMethod = true;
break;
}
}
}
return containsMethod;
}
[Serializable]
public class EventResponse<U> : ISerializationCallbackReceiver
{
public virtual ScriptableEvent<U> ScriptableEvent { get; }
[Tooltip("Delay in seconds before invoking the response.")]
public FloatReference Delay = new FloatReference(0, true);
public virtual UnityEvent<U> Response { get; }
[NonSerialized]
private bool _isInitialized = false;
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
// Set Delay to use local only if it hasn't been initialized yet
if (!_isInitialized && Delay.Variable == null)
{
Delay.UseLocal = true;
_isInitialized = true;
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More