182 lines
7.0 KiB
C#
182 lines
7.0 KiB
C#
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<AttributeBinding> {
|
|
[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<string> _statePriority = new();
|
|
readonly Dictionary<AttributeBinding, object> _defaults = new();
|
|
readonly Dictionary<AttributeBinding, PropertyTweener<object>> _tweeners = new();
|
|
readonly Dictionary<string, Dictionary<AttributeBinding, object>> _runtimeStates = new();
|
|
|
|
void Awake() {
|
|
var types = new Dictionary<AttributeBinding, Type>();
|
|
foreach (var state in m_states) {
|
|
Dictionary<AttributeBinding, object> attrs;
|
|
_statePriority.Add(state.Name);
|
|
_runtimeStates.Add(state.Name, attrs = new Dictionary<AttributeBinding, object>());
|
|
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<object> getter;
|
|
Action<object> 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<object> CreateTweener(Type type, Func<object> getter, Action<object> setter) {
|
|
if (type == typeof(byte)) return new PropertyTweener<object>(getter, setter, Tweeners.Byte.Boxed());
|
|
else if (type == typeof(sbyte)) return new PropertyTweener<object>(getter, setter, Tweeners.SByte.Boxed());
|
|
else if (type == typeof(short)) return new PropertyTweener<object>(getter, setter, Tweeners.Int16.Boxed());
|
|
else if (type == typeof(ushort)) return new PropertyTweener<object>(getter, setter, Tweeners.UInt16.Boxed());
|
|
else if (type == typeof(int)) return new PropertyTweener<object>(getter, setter, Tweeners.Int32.Boxed());
|
|
else if (type == typeof(uint)) return new PropertyTweener<object>(getter, setter, Tweeners.UInt32.Boxed());
|
|
else if (type == typeof(long)) return new PropertyTweener<object>(getter, setter, Tweeners.Int64.Boxed());
|
|
else if (type == typeof(ulong)) return new PropertyTweener<object>(getter, setter, Tweeners.UInt64.Boxed());
|
|
else if (type == typeof(IntPtr)) return new PropertyTweener<object>(getter, setter, Tweeners.IntPtr.Boxed());
|
|
else if (type == typeof(UIntPtr)) return new PropertyTweener<object>(getter, setter, Tweeners.UIntPtr.Boxed());
|
|
else if (type == typeof(float)) return new PropertyTweener<object>(getter, setter, Tweeners.Float.Boxed());
|
|
else if (type == typeof(double)) return new PropertyTweener<object>(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<string> m_cState = new();
|
|
public IReadOnlyList<string> 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<string> {
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|