using Cryville.Common.Reflection; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; namespace Cryville.Common.Unity { public class StateTweener : MonoBehaviour { [SerializeField] State[] m_states; [Serializable] struct State { [SerializeField] public string Name; [SerializeField] public AttributeState[] Attributes; } [Serializable] struct AttributeState { [SerializeField] public AttributeBinding Attribute; [SerializeField] public string Value; } [Serializable] struct AttributeBinding : IEquatable { [SerializeField] public Component Component; [SerializeField] public string Attribute; public readonly bool Equals(AttributeBinding other) { return Component.Equals(other.Component) && Attribute.Equals(other.Attribute); } public override readonly bool Equals(object obj) { return obj is AttributeBinding other && Equals(other); } public override readonly int GetHashCode() { return Component.GetHashCode() ^ Attribute.GetHashCode(); } } [SerializeField] StateTweener[] m_children; readonly List _statePriority = new(); readonly Dictionary _defaults = new(); readonly Dictionary> _tweeners = new(); readonly Dictionary> _runtimeStates = new(); void Awake() { var types = new Dictionary(); foreach (var state in m_states) { Dictionary attrs; _statePriority.Add(state.Name); _runtimeStates.Add(state.Name, attrs = new Dictionary()); foreach (var attr in state.Attributes) { var binding = attr.Attribute; if (!types.TryGetValue(binding, out var type)) { var comp = binding.Component; var ctype = comp.GetType(); var path = binding.Attribute.Split('.'); var propName = path[0]; var prop = ctype.GetMember(propName, MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Single(); var gvd = FieldLikeHelper.GetGetValueDelegate(prop); var svd = FieldLikeHelper.GetSetValueDelegate(prop); Func getter; Action setter; if (path.Length == 1) { type = FieldLikeHelper.GetMemberType(prop); getter = () => gvd(comp); setter = v => svd(comp, v); } else if (path.Length == 2) { var propType = FieldLikeHelper.GetMemberType(prop); var subPropName = path[1]; var subProp = propType.GetMember(subPropName, MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Single(); type = FieldLikeHelper.GetMemberType(subProp); var gsvd = FieldLikeHelper.GetGetValueDelegate(subProp); getter = () => gsvd(gvd(comp)); var ssvd = FieldLikeHelper.GetSetValueDelegate(subProp); setter = v => { object obj = gvd(comp); ssvd(obj, v); svd(comp, obj); }; } else { throw new FormatException("Invalid attribute path."); } _tweeners.Add(binding, CreateTweener(type, getter, setter)); _defaults.Add(binding, getter()); types.Add(binding, type); } attrs.Add(binding, Convert.ChangeType(attr.Value, type)); } } foreach (var def in _defaults) { foreach (var state in _runtimeStates) { if (!state.Value.ContainsKey(def.Key)) { state.Value.Add(def.Key, def.Value); } } } _spcmp = new StatePriorityComparer(this); } PropertyTweener CreateTweener(Type type, Func getter, Action setter) { if (type == typeof(byte)) return new PropertyTweener(getter, setter, Tweeners.Byte.Boxed()); else if (type == typeof(sbyte)) return new PropertyTweener(getter, setter, Tweeners.SByte.Boxed()); else if (type == typeof(short)) return new PropertyTweener(getter, setter, Tweeners.Int16.Boxed()); else if (type == typeof(ushort)) return new PropertyTweener(getter, setter, Tweeners.UInt16.Boxed()); else if (type == typeof(int)) return new PropertyTweener(getter, setter, Tweeners.Int32.Boxed()); else if (type == typeof(uint)) return new PropertyTweener(getter, setter, Tweeners.UInt32.Boxed()); else if (type == typeof(long)) return new PropertyTweener(getter, setter, Tweeners.Int64.Boxed()); else if (type == typeof(ulong)) return new PropertyTweener(getter, setter, Tweeners.UInt64.Boxed()); else if (type == typeof(IntPtr)) return new PropertyTweener(getter, setter, Tweeners.IntPtr.Boxed()); else if (type == typeof(UIntPtr)) return new PropertyTweener(getter, setter, Tweeners.UIntPtr.Boxed()); else if (type == typeof(float)) return new PropertyTweener(getter, setter, Tweeners.Float.Boxed()); else if (type == typeof(double)) return new PropertyTweener(getter, setter, Tweeners.Double.Boxed()); else throw new NotSupportedException("Property type not supported."); } void Update() { foreach (var tweener in _tweeners) tweener.Value.Advance(Time.deltaTime); } readonly List m_cState = new(); public IReadOnlyList CurrentState => m_cState; public void ClearState(float transitionDuration = float.Epsilon) { foreach (var child in m_children) child.ClearState(transitionDuration); m_cState.Clear(); foreach (var tweener in _tweeners) { tweener.Value.Start(_defaults[tweener.Key], transitionDuration); } } public void EnterState(string state, float transitionDuration = float.Epsilon) { if (_runtimeStates.TryGetValue(state, out var rs)) { int index = m_cState.BinarySearch(state, _spcmp); if (index < 0) { index = ~index; m_cState.Insert(index, state); } if (index == m_cState.Count - 1) { foreach (var s in rs) { _tweeners[s.Key].Start(s.Value, transitionDuration); } } } foreach (var child in m_children) child.EnterState(state, transitionDuration); } public void ExitState(string state, float transitionDuration = float.Epsilon) { foreach (var child in m_children) child.ExitState(state, transitionDuration); if (!_runtimeStates.ContainsKey(state)) return; int index = m_cState.BinarySearch(state, _spcmp); if (index < 0) return; m_cState.RemoveAt(index); if (index < m_cState.Count) return; var attrs = m_cState.Count == 0 ? _defaults : _runtimeStates[m_cState[^1]]; foreach (var tweener in _tweeners) { tweener.Value.Start(attrs[tweener.Key], transitionDuration); } } StatePriorityComparer _spcmp; class StatePriorityComparer : IComparer { readonly StateTweener _self; public StatePriorityComparer(StateTweener self) { _self = self; } public int Compare(string x, string y) { return _self._statePriority.IndexOf(x).CompareTo(_self._statePriority.IndexOf(y)); } } } }