Add tweener and state tweener.

This commit is contained in:
2023-11-10 14:51:14 +08:00
parent adf5019e2a
commit bd834cff4a
4 changed files with 298 additions and 0 deletions

View 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);
}
}

View File

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

View 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));
}
}
}
}

View File

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