--- /dev/null
+# JurassicTween
+
+> Your scientists were so preoccupied with whether or not they could (tween like that) they didn’t stop to think if they should.
+
+🚨 This is a preview only. I advise against shipping this. 🚨
+
+## What?
+
+JurassicTween is a Unity3D tweening library designed for:
+
+* Tweening values on `Component` objects...
+* ...in a very simple way...
+* ...favoring ease of prototyping over performance (for now).
+
+## How?
+
+Add JurassicTween to a project. Making a tween is super easy, here's how you would make an object randomly move, rotate and scale onscreen:
+
+```
+using JurassicTween;
+using UnityEngine;
+
+public class MoveMe : MonoBehaviour {
+ void Update() {
+ if (gameObject.IsTweening() == false) {
+ using (transform.Tween()) {
+ transform.position = Random.insideUnitSphere * 10.0f;
+ transform.rotation = Quaternion.Euler(0, 0, Random.Range(0.0f, 360.0f));
+ transform.localScale = Vector3.one * Random.Range(0.1f, 2.0f);
+ }
+ // using (anyOtherComponent.Tween()) {
+ // These chain together, and your own MonoBehavior components should also work...
+ // }
+ }
+ }
+}
+```
+
+Basically, the tween breaks down as follows:
+
+* Mark a game component as requiring a tween using `Component.Tween()`
+* Set the fields on the component that you want to target
+* The tween will then be set up to be from the current values to the target values you set.
+
+The tweens themselves are performed on a component, and you can check if a game object currently has any active tweens.
+
+**VERY IMPORTANT:** Not everything will work correctly, especially Unity's native components. (Transform is an exception, we've fixed that.)
+
+## Roadmap
+
+This is a preview release only. There are bugs. Lots of things won't work.
+
+Currently the following features are supported:
+
+* Some things tween
+* You can give a duration of tween
+* The curve of the tweens can be set, and you can use an AnimationCurve to design your own.
+
+If this was useful to people and there was a sustainable way to develop it, we could add:
+
+* More API features
+* Better animation system and/or better integration into the Unity systems
+* Performance!
+
+If you like this then please let me know on Twitter, find me at http://twitter.com/btsherratt/.
+
+# Licence
+
+Currently this is licenced for evaluation use in your non-commercial projects only, and all rights are currently reserved and retained by my company SKFX Ltd.. If there is interest in taking this forward then this will be changed, but I would like to decide slowly about what makes most sense for licensing here.
\ No newline at end of file
--- /dev/null
+//
+// ComponentStateCaptor.cs
+// JurassicTween
+//
+// Created by Benjamin Sherratt on 04/05/2021.
+// Copyright © 2021 SKFX Ltd. All rights reserved.
+//
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace JurassicTween.Internal {
+ public struct ComponentState {
+ public float[] values;
+
+ public ComponentState(float[] values) {
+ this.values = values;
+ }
+ }
+ public struct ComponentStateDelta {
+ public struct Range {
+ public float start;
+ public float end;
+
+ public Range(float start, float end) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ public Dictionary<string, Range> rangeByAnimationKey;
+
+ public ComponentStateDelta(Dictionary<string, Range> rangeByAnimationKey) {
+ this.rangeByAnimationKey = rangeByAnimationKey;
+ }
+ }
+
+ public interface IComponentStateGetter {
+ ComponentState GetComponentState(Component component);
+ }
+
+ public interface IComponentStateDeltaGenerator {
+ ComponentStateDelta GenerateComponentStateDelta(ComponentState startState, ComponentState endState);
+ }
+
+ public interface IComponentStateCaptor : IComponentStateGetter, IComponentStateDeltaGenerator {
+ }
+
+ public static class ComponentStateCaptor {
+ static Dictionary<Type, IComponentStateCaptor> stateCaptorByType;
+
+ public static IComponentStateCaptor GetStateCaptor(this Component component) {
+ Type componentType = component.GetType();
+
+ if (stateCaptorByType == null) {
+ stateCaptorByType = new Dictionary<Type, IComponentStateCaptor>();
+ CreateUnityInternalCaptors();
+ }
+
+ IComponentStateCaptor stateCaptor;
+ if (stateCaptorByType.ContainsKey(componentType)) {
+ stateCaptor = stateCaptorByType[componentType];
+ } else {
+ stateCaptor = new GenericComponentStateCaptor(componentType);
+ stateCaptorByType[componentType] = stateCaptor;
+ }
+
+ return stateCaptor;
+ }
+
+ static void CreateUnityInternalCaptors() {
+ // Add the custom captors for Unity internals...
+ stateCaptorByType[typeof(Transform)] = new TransformComponentStateCaptor();
+ }
+ }
+}
--- /dev/null
+//
+// Context.cs
+// JurassicTween
+//
+// Created by Benjamin Sherratt on 04/05/2021.
+// Copyright © 2021 SKFX Ltd. All rights reserved.
+//
+
+using System;
+using UnityEngine;
+
+namespace JurassicTween.Internal {
+ class Context : IDisposable {
+ private Component component;
+ private IComponentStateCaptor componentStateCaptor;
+ private ComponentState startState;
+ private float time;
+ private TweenParameters tweenParameters;
+
+ public Context(Component component, float time, TweenParameters tweenParameters) {
+ this.component = component;
+ componentStateCaptor = component.GetStateCaptor();
+ startState = componentStateCaptor.GetComponentState(component);
+
+ this.time = time;
+ this.tweenParameters = tweenParameters;
+ }
+
+ public void Dispose() {
+ ComponentState endState = componentStateCaptor.GetComponentState(component);
+
+ ComponentStateDelta delta = componentStateCaptor.GenerateComponentStateDelta(startState, endState);
+
+ Animation animation = component.gameObject.GetComponent<Animation>();
+ if (animation == null) {
+ animation = component.gameObject.AddComponent<Animation>();
+ }
+
+ string currentClipName = "jurassicTween";
+ AnimationClip animationClip = animation.GetClip(currentClipName);
+ if (animationClip == null) {
+ animationClip = new AnimationClip();
+ animationClip.legacy = true;
+ animationClip.name = currentClipName;
+ animation.AddClip(animationClip, animationClip.name);
+ }
+
+ foreach (string key in delta.rangeByAnimationKey.Keys) {
+ ComponentStateDelta.Range range = delta.rangeByAnimationKey[key];
+
+ AnimationCurve curve;
+ switch (tweenParameters.interpolation) {
+ case TweenParameters.Interpolation.Linear:
+ curve = AnimationCurve.Linear(0, range.start, time, range.end);
+ break;
+
+ case TweenParameters.Interpolation.EaseInOut:
+ curve = AnimationCurve.EaseInOut(0, range.start, time, range.end);
+ break;
+
+ case TweenParameters.Interpolation.CustomCurve:
+ curve = new AnimationCurve();
+ foreach (Keyframe referenceKeyframe in tweenParameters.customCurve.keys) {
+ Keyframe keyframe = new Keyframe(
+ Mathf.Lerp(0.0f, time, referenceKeyframe.time),
+ Mathf.Lerp(range.start, range.end, referenceKeyframe.value),
+ referenceKeyframe.inTangent,
+ referenceKeyframe.outTangent,
+ referenceKeyframe.inWeight,
+ referenceKeyframe.outWeight);
+ curve.AddKey(keyframe);
+ }
+ break;
+
+ default:
+ throw new Exception("We shouldn't be here");
+ }
+
+ animationClip.SetCurve("", component.GetType(), key, curve);
+ }
+
+ animation.Play(animationClip.name);
+ }
+ }
+}
--- /dev/null
+//
+// EngineComponentStateCaptor.cs
+// JurassicTween
+//
+// Created by Benjamin Sherratt on 04/05/2021.
+// Copyright © 2021 SKFX Ltd. All rights reserved.
+//
+
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace JurassicTween.Internal {
+ public class TransformComponentStateCaptor : IComponentStateCaptor {
+ static string[] animationKeys = {
+ "localPosition.x",
+ "localPosition.y",
+ "localPosition.z",
+ "localRotation.x",
+ "localRotation.y",
+ "localRotation.z",
+ "localRotation.w",
+ "localScale.x",
+ "localScale.y",
+ "localScale.z",
+ };
+
+ public ComponentState GetComponentState(Component component) {
+ Transform transform = (Transform)component;
+
+ float[] stateValues = new float[10];
+ stateValues[0] = transform.localPosition.x;
+ stateValues[1] = transform.localPosition.y;
+ stateValues[2] = transform.localPosition.z;
+ stateValues[3] = transform.localRotation.x;
+ stateValues[4] = transform.localRotation.y;
+ stateValues[5] = transform.localRotation.z;
+ stateValues[6] = transform.localRotation.w;
+ stateValues[7] = transform.localScale.x;
+ stateValues[8] = transform.localScale.y;
+ stateValues[9] = transform.localScale.z;
+
+ ComponentState state = new ComponentState(stateValues);
+ return state;
+ }
+
+ public ComponentStateDelta GenerateComponentStateDelta(ComponentState startState, ComponentState endState) {
+ Dictionary<string, ComponentStateDelta.Range> rangeByAnimationKey = new Dictionary<string, ComponentStateDelta.Range>();
+
+ for (uint i = 0; i < animationKeys.Length; ++i) {
+ if (startState.values[i] != endState.values[i]) {
+ rangeByAnimationKey[animationKeys[i]] = new ComponentStateDelta.Range(startState.values[i], endState.values[i]);
+ }
+ }
+
+ ComponentStateDelta delta = new ComponentStateDelta(rangeByAnimationKey);
+ return delta;
+ }
+ }
+}
--- /dev/null
+//
+// GenericComponentStateCaptor.cs
+// JurassicTween
+//
+// Created by Benjamin Sherratt on 04/05/2021.
+// Copyright © 2021 SKFX Ltd. All rights reserved.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using UnityEngine;
+
+namespace JurassicTween.Internal {
+ public class GenericComponentStateCaptor : IComponentStateCaptor {
+ struct FieldCaptureInfo {
+ public string animationKeyPath;
+ public MemberInfo[] memberInfoHierarchy;
+
+ public FieldCaptureInfo(string animationKeyPath, MemberInfo[] memberInfoHierarchy) {
+ this.animationKeyPath = animationKeyPath;
+ this.memberInfoHierarchy = memberInfoHierarchy;
+ }
+ }
+
+ static FieldCaptureInfo[] QueryCaptureFields(Type type) {
+ List<FieldCaptureInfo> fieldCaptureInfo = new List<FieldCaptureInfo>();
+ List<string> keyComponents = new List<string>();
+ List<MemberInfo> memberInfoHierarchy = new List<MemberInfo>();
+ QueryCaptureFields(fieldCaptureInfo, type, keyComponents, memberInfoHierarchy);
+ return fieldCaptureInfo.ToArray();
+ }
+
+ static void QueryCaptureFields(List<FieldCaptureInfo> fieldCaptureInfo, Type type, List<string> keyComponents, List<MemberInfo> memberInfoHierarchy) {
+ FieldInfo[] allFieldInfo = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ foreach (FieldInfo fieldInfo in allFieldInfo) {
+ if (fieldInfo.IsPublic || fieldInfo.GetCustomAttribute<SerializeField>() != null) {
+ keyComponents.Add(fieldInfo.Name);
+ memberInfoHierarchy.Add(fieldInfo);
+
+ Type fieldType = fieldInfo.FieldType;
+ if (fieldType.IsAssignableFrom(typeof(float))) {
+ string animationKey = string.Join(".", keyComponents);
+ FieldCaptureInfo info = new FieldCaptureInfo(animationKey, memberInfoHierarchy.ToArray());
+ fieldCaptureInfo.Add(info);
+ } else {
+ QueryCaptureFields(fieldCaptureInfo, fieldType, keyComponents, memberInfoHierarchy);
+ }
+
+ memberInfoHierarchy.RemoveAt(memberInfoHierarchy.Count - 1);
+ keyComponents.RemoveAt(keyComponents.Count - 1);
+ }
+ }
+ }
+
+ Type componentType;
+ FieldCaptureInfo[] fieldCaptureInformation;
+
+ public GenericComponentStateCaptor(Type componentType) {
+ this.componentType = componentType;
+ fieldCaptureInformation = QueryCaptureFields(componentType);
+ }
+
+ public ComponentState GetComponentState(Component component) {
+ float[] stateValues = new float[fieldCaptureInformation.Length];
+
+ for (uint i = 0; i < fieldCaptureInformation.Length; ++i) {
+ ref FieldCaptureInfo fieldCaptureInfo = ref fieldCaptureInformation[i];
+ object instanceScope = component;
+ foreach (MemberInfo memberInfo in fieldCaptureInfo.memberInfoHierarchy) {
+ if ((memberInfo.MemberType & MemberTypes.Field) > 0) {
+ instanceScope = ((FieldInfo)memberInfo).GetValue(instanceScope);
+ } else if ((memberInfo.MemberType & MemberTypes.Property) > 0) {
+ instanceScope = ((PropertyInfo)memberInfo).GetValue(instanceScope);
+ } else {
+ throw new Exception("We shouldn't be here...");
+ }
+ }
+
+ float value = (float)instanceScope;
+ stateValues[i] = value;
+ }
+
+ ComponentState state = new ComponentState(stateValues);
+ return state;
+ }
+
+ public ComponentStateDelta GenerateComponentStateDelta(ComponentState startState, ComponentState endState) {
+ Dictionary<string, ComponentStateDelta.Range> rangeByAnimationKey = new Dictionary<string, ComponentStateDelta.Range>();
+ for (uint i = 0; i < fieldCaptureInformation.Length; ++i) {
+ float startValue = startState.values[i];
+ float endValue = endState.values[i];
+ if (startValue != endValue) {
+ ref FieldCaptureInfo fieldCaptureInfo = ref fieldCaptureInformation[i];
+ rangeByAnimationKey[fieldCaptureInfo.animationKeyPath] = new ComponentStateDelta.Range(startValue, endValue);
+ }
+ }
+
+ ComponentStateDelta delta = new ComponentStateDelta(rangeByAnimationKey);
+ return delta;
+ }
+ }
+}
--- /dev/null
+//
+// Tween.cs
+// JurassicTween
+//
+// Created by Benjamin Sherratt on 04/05/2021.
+// Copyright © 2021 SKFX Ltd. All rights reserved.
+//\r
+\r
+using JurassicTween.Internal;\r
+using System;\r
+using UnityEngine;
+
+namespace JurassicTween {
+ public static class JurassicTween {
+ public static IDisposable Tween(this Component component, float time = 1.0f) {
+ return component.Tween(time, TweenParameters.linear);
+ }
+
+ public static IDisposable Tween(this Component component, float time, TweenParameters tweenParameters) {
+ Context context = new Context(component, time, tweenParameters);
+ return context;
+ }
+
+ public static bool IsTweening(this GameObject gameObject) {
+ Animation animation = gameObject.GetComponent<Animation>();
+ bool tweening = ((animation != null) && animation.isPlaying);
+ return tweening;
+ }
+ }
+}
--- /dev/null
+//
+// TweenParameters.cs
+// JurassicTween
+//
+// Created by Benjamin Sherratt on 04/05/2021.
+// Copyright © 2021 SKFX Ltd. All rights reserved.
+//
+
+using UnityEngine;
+
+namespace JurassicTween {
+ public struct TweenParameters {
+ public static readonly TweenParameters linear = new TweenParameters(Interpolation.Linear);
+ public static readonly TweenParameters easeInOut = new TweenParameters(Interpolation.EaseInOut);
+
+ public enum Interpolation {
+ Linear,
+ EaseInOut,
+ CustomCurve,
+ }
+
+ public Interpolation interpolation;
+ public AnimationCurve customCurve;
+
+ public TweenParameters(AnimationCurve customCurve) {
+ interpolation = Interpolation.CustomCurve;
+ this.customCurve = customCurve;
+ }
+
+ TweenParameters(Interpolation interpolation) {
+ this.interpolation = interpolation;
+ this.customCurve = null;
+ }
+ }
+}