Add tweener and state tweener.
This commit is contained in:
95
Assets/Cryville/Common/Tweener.cs
Normal file
95
Assets/Cryville/Common/Tweener.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
|
||||
namespace Cryville.Common {
|
||||
public class PropertyTweener<T> {
|
||||
readonly Func<T> _getter;
|
||||
readonly Action<T> _setter;
|
||||
readonly Tweener<T> _tweener;
|
||||
public PropertyTweener(Func<T> getter, Action<T> setter, Tweener<T> tweener) {
|
||||
_getter = getter;
|
||||
_setter = setter;
|
||||
_tweener = tweener;
|
||||
var initialValue = getter();
|
||||
_tweener.Start(initialValue, initialValue, float.Epsilon);
|
||||
}
|
||||
public PropertyTweener<T> Start(T endValue, float duration) {
|
||||
_tweener.Start(_getter(), endValue, duration);
|
||||
return this;
|
||||
}
|
||||
public void Advance(float deltaTime) {
|
||||
if (!_tweener.Advance(deltaTime, out var value)) return;
|
||||
_setter(value);
|
||||
}
|
||||
}
|
||||
public delegate T Addition<T>(T a, T b);
|
||||
public delegate T Multiplication<T>(float k, T b);
|
||||
public delegate float EasingFunction(float t);
|
||||
public class Tweener<T> {
|
||||
readonly Addition<T> _addition;
|
||||
readonly Multiplication<T> _multiplication;
|
||||
public Tweener(Addition<T> addition, Multiplication<T> multiplication) {
|
||||
_addition = addition;
|
||||
_multiplication = multiplication;
|
||||
}
|
||||
public EasingFunction EasingFunction { get; set; } = EasingFunctions.Linear;
|
||||
public Tweener<T> With(EasingFunction easing) {
|
||||
EasingFunction = easing;
|
||||
return this;
|
||||
}
|
||||
|
||||
T _startValue = default;
|
||||
T _endValue = default;
|
||||
float _duration = float.Epsilon;
|
||||
float _time;
|
||||
bool _endOfTween;
|
||||
public Tweener<T> Start(T startValue, T endValue, float duration) {
|
||||
_startValue = startValue;
|
||||
_endValue = endValue;
|
||||
_duration = duration;
|
||||
_time = 0;
|
||||
_endOfTween = false;
|
||||
return this;
|
||||
}
|
||||
public bool Advance(float deltaTime, out T value) {
|
||||
if (_endOfTween) {
|
||||
value = _endValue;
|
||||
return false;
|
||||
}
|
||||
if (_time >= _duration) {
|
||||
value = _endValue;
|
||||
_endOfTween = true;
|
||||
return true;
|
||||
}
|
||||
_time += deltaTime;
|
||||
var ratio = EasingFunction(System.Math.Clamp(_time / _duration, 0, 1));
|
||||
value = _addition(_multiplication(1 - ratio, _startValue), _multiplication(ratio, _endValue));
|
||||
return true;
|
||||
}
|
||||
public Tweener<object> Boxed() {
|
||||
return new Tweener<object>((a, b) => _addition((T)a, (T)b), (k, v) => _multiplication(k, (T)v));
|
||||
}
|
||||
}
|
||||
public static class Tweeners {
|
||||
public static Tweener<byte> Byte => new((a, b) => (byte)(a + b), (k, v) => (byte)(k * v));
|
||||
public static Tweener<sbyte> SByte => new((a, b) => (sbyte)(a + b), (k, v) => (sbyte)(k * v));
|
||||
public static Tweener<short> Int16 => new((a, b) => (short)(a + b), (k, v) => (short)(k * v));
|
||||
public static Tweener<ushort> UInt16 => new((a, b) => (ushort)(a + b), (k, v) => (ushort)(k * v));
|
||||
public static Tweener<int> Int32 => new((a, b) => a + b, (k, v) => (int)(k * v));
|
||||
public static Tweener<uint> UInt32 => new((a, b) => a + b, (k, v) => (uint)(k * v));
|
||||
public static Tweener<long> Int64 => new((a, b) => a + b, (k, v) => (long)(k * v));
|
||||
public static Tweener<ulong> UInt64 => new((a, b) => a + b, (k, v) => (ulong)(k * v));
|
||||
public static Tweener<IntPtr> IntPtr => new((a, b) => new IntPtr((long)a + (long)b), (k, v) => new IntPtr((long)(k * (long)v)));
|
||||
public static Tweener<UIntPtr> UIntPtr => new((a, b) => new UIntPtr((ulong)a + (ulong)b), (k, v) => new UIntPtr((ulong)(k * (ulong)v)));
|
||||
public static Tweener<float> Float => new((a, b) => a + b, (k, v) => k * v);
|
||||
public static Tweener<double> Double => new((a, b) => a + b, (k, v) => k * v);
|
||||
}
|
||||
public static class EasingFunctions {
|
||||
public static float Linear(float x) => x;
|
||||
public static float InQuad(float x) => x * x;
|
||||
public static float InCubic(float x) => x * x * x;
|
||||
public static float InSine(float x) => 1 - OutSine(1 - x);
|
||||
public static float OutQuad(float x) => 1 - InQuad(1 - x);
|
||||
public static float OutCubic(float x) => 1 - InCubic(1 - x);
|
||||
public static float OutSine(float x) => MathF.Sin(x * MathF.PI / 2);
|
||||
}
|
||||
}
|
11
Assets/Cryville/Common/Tweener.cs.meta
Normal file
11
Assets/Cryville/Common/Tweener.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b4037ba4138aae47b8da984f30b4db9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
181
Assets/Cryville/Common/Unity/StateTweener.cs
Normal file
181
Assets/Cryville/Common/Unity/StateTweener.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
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 bool Equals(AttributeBinding other) {
|
||||
return Component.Equals(other.Component) && Attribute.Equals(other.Attribute);
|
||||
}
|
||||
public override bool Equals(object obj) {
|
||||
return obj is AttributeBinding && Equals((AttributeBinding)obj);
|
||||
}
|
||||
public override int GetHashCode() {
|
||||
return Component.GetHashCode() ^ Attribute.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
StateTweener[] m_children;
|
||||
|
||||
readonly List<string> _statePriority = new List<string>();
|
||||
readonly Dictionary<AttributeBinding, object> _defaults = new Dictionary<AttributeBinding, object>();
|
||||
readonly Dictionary<AttributeBinding, PropertyTweener<object>> _tweeners = new Dictionary<AttributeBinding, PropertyTweener<object>>();
|
||||
readonly Dictionary<string, Dictionary<AttributeBinding, object>> _runtimeStates = new Dictionary<string, Dictionary<AttributeBinding, object>>();
|
||||
|
||||
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 List<string>();
|
||||
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[m_cState.Count - 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Cryville/Common/Unity/StateTweener.cs.meta
Normal file
11
Assets/Cryville/Common/Unity/StateTweener.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d605791cb18d88e4fb0ab6f794361eee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user