Skip to content

Instantly share code, notes, and snippets.

@MikeGringauz
Last active August 22, 2016 14:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MikeGringauz/4524f7bac7dca523a1f8ec471a54f858 to your computer and use it in GitHub Desktop.
Save MikeGringauz/4524f7bac7dca523a1f8ec471a54f858 to your computer and use it in GitHub Desktop.
TinyTween engine adopted to Duality 2D game engine with sample Spaceship component demo.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Duality;
using Duality.Components.Physics;
using Duality.Components.Renderers;
using Duality.Input;
using TinyTween;
namespace Duality_
{
[RequiredComponent( typeof( RigidBody ) )]
public class Spaceship : Component, ICmpInitializable , ICmpUpdatable
{
private SpriteRenderer _sprite;
private RigidBody _body;
private ColorTween _fader = new ColorTween ();
private FloatTween _scaler = new FloatTween ();
void ICmpInitializable.OnInit (InitContext context)
{
if (context != InitContext.Activate)
return;
_sprite = GameObj.GetComponent<SpriteRenderer>();
_body = GameObj.GetComponent<RigidBody>();
// Start spaceship intro animation
_fader.Start( _sprite.ColorTint, _sprite.ColorTint.WithAlpha( 255 ), 5000f, ScaleFuncs.Linear );
_scaler.Start( GameObj.Transform.Scale, GameObj.Transform.Scale + 1f, 5000f, ScaleFuncs.SineEaseInOut );
}
void ICmpInitializable.OnShutdown (ShutdownContext context)
{
}
public void OnUpdate ()
{
// Update spaceship intro animation
if (_fader.State == TweenState.Running)
{
Debug.WriteLine( $"ColorA:{GameObj.GetComponent<SpriteRenderer>().ColorTint}" );
_sprite.ColorTint = _fader.CurrentValue;
_fader.Update( Time.LastDelta );
}
if (_scaler.State == TweenState.Running)
{
Debug.WriteLine( $"Scale:{GameObj.Transform.Scale}\n" );
GameObj.Transform.Scale = _scaler.CurrentValue;
_scaler.Update( Time.LastDelta );
}
// Allow user input only after spaceship intro animation completed
if (_fader.State == TweenState.Stopped && _scaler.State == TweenState.Stopped)
{
if (DualityApp.Keyboard[Key.Left])
_body.ApplyLocalForce( -0.001f * _body.Inertia );
else if (DualityApp.Keyboard[Key.Right])
_body.ApplyLocalForce( 0.001f * _body.Inertia );
else
_body.AngularVelocity -= _body.AngularVelocity * 0.1f * Time.TimeMult;
if (DualityApp.Keyboard[Key.Up])
_body.ApplyLocalForce( Vector2.UnitY * -0.2f * _body.Mass );
else if (DualityApp.Keyboard[Key.Down])
_body.ApplyLocalForce( Vector2.UnitY * 0.2f * _body.Mass );
}
}
}
}
// TinyTween.cs
//
// Copyright (c) 2013 Nick Gravelyn
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using Duality;
using Duality.Drawing;
namespace TinyTween
{
/// <summary>
/// Takes in progress which is the percentage of the tween complete and returns
/// the interpolation value that is fed into the lerp function for the tween.
/// </summary>
/// <remarks>
/// Scale functions are used to define how the tween should occur. Examples would be linear,
/// easing in quadratic, or easing out circular. You can implement your own scale function
/// or use one of the many defined in the ScaleFuncs static class.
/// </remarks>
/// <param name="progress">The percentage of the tween complete in the range [0, 1].</param>
/// <returns>The scale value used to lerp between the tween's start and end values</returns>
public delegate float ScaleFunc (float progress);
/// <summary>
/// Standard linear interpolation function: "start + (end - start) * progress"
/// </summary>
/// <remarks>
/// In a language like C++ we wouldn't need this delegate at all. Templates in C++ would allow us
/// to simply write "start + (end - start) * progress" in the tween class and the compiler would
/// take care of enforcing that the type supported those operators. Unfortunately C#'s generics
/// are not so powerful so instead we must have the user provide the interpolation function.
///
/// Thankfully frameworks like XNA and Unity provide lerp functions on their primitive math types
/// which means that for most users there is nothing specific to do here. Additionally this file
/// provides concrete implementations of tweens for vectors, colors, and more for XNA and Unity
/// users, lessening the burden even more.
/// </remarks>
/// <typeparam name="T">The type to interpolate.</typeparam>
/// <param name="start">The starting value.</param>
/// <param name="end">The ending value.</param>
/// <param name="progress">The interpolation progress.</param>
/// <returns>The interpolated value, generally using "start + (end - start) * progress"</returns>
public delegate T LerpFunc<T> (T start, T end, float progress);
/// <summary>
/// State of an ITween object
/// </summary>
public enum TweenState
{
/// <summary>
/// The tween is running.
/// </summary>
Running,
/// <summary>
/// The tween is paused.
/// </summary>
Paused,
/// <summary>
/// The tween is stopped.
/// </summary>
Stopped
}
/// <summary>
/// The behavior to use when manually stopping a tween.
/// </summary>
public enum StopBehavior
{
/// <summary>
/// Does not change the current value.
/// </summary>
AsIs,
/// <summary>
/// Forces the tween progress to the end value.
/// </summary>
ForceComplete
}
/// <summary>
/// Interface for a tween object.
/// </summary>
public interface ITween
{
/// <summary>
/// Gets the current state of the tween.
/// </summary>
TweenState State { get; }
/// <summary>
/// Pauses the tween.
/// </summary>
void Pause ();
/// <summary>
/// Resumes the paused tween.
/// </summary>
void Resume ();
/// <summary>
/// Stops the tween.
/// </summary>
/// <param name="stopBehavior">The behavior to use to handle the stop.</param>
void Stop (StopBehavior stopBehavior);
/// <summary>
/// Updates the tween.
/// </summary>
/// <param name="elapsedTime">The elapsed time to add to the tween.</param>
void Update (float elapsedTime);
}
/// <summary>
/// Interface for a tween object that handles a specific type.
/// </summary>
/// <typeparam name="T">The type to tween.</typeparam>
public interface ITween<T> : ITween where T : struct
{
/// <summary>
/// Gets the current value of the tween.
/// </summary>
T CurrentValue { get; }
/// <summary>
/// Starts a tween.
/// </summary>
/// <param name="start">The start value.</param>
/// <param name="end">The end value.</param>
/// <param name="duration">The duration of the tween.</param>
/// <param name="scaleFunc">A function used to scale progress over time.</param>
void Start (T start, T end, float duration, ScaleFunc scaleFunc);
}
/// <summary>
/// A concrete implementation of a tween object.
/// </summary>
/// <typeparam name="T">The type to tween.</typeparam>
public class Tween<T> : ITween<T> where T : struct
{
private readonly LerpFunc<T> _lerpFunc;
private float _currentTime;
private float _duration;
private ScaleFunc _scaleFunc;
private TweenState _state;
private T _start;
private T _end;
private T _value;
/// <summary>
/// Gets the current time of the tween.
/// </summary>
public float CurrentTime { get { return _currentTime; } }
/// <summary>
/// Gets the duration of the tween.
/// </summary>
public float Duration { get { return _duration; } }
/// <summary>
/// Gets the current state of the tween.
/// </summary>
public TweenState State { get { return _state; } }
/// <summary>
/// Gets the starting value of the tween.
/// </summary>
public T StartValue { get { return _start; } }
/// <summary>
/// Gets the ending value of the tween.
/// </summary>
public T EndValue { get { return _end; } }
/// <summary>
/// Gets the current value of the tween.
/// </summary>
public T CurrentValue { get { return _value; } }
/// <summary>
/// Initializes a new Tween with a given lerp function.
/// </summary>
/// <remarks>
/// C# generics are good but not good enough. We need a delegate to know how to
/// interpolate between the start and end values for the given type.
/// </remarks>
/// <param name="lerpFunc">The interpolation function for the tween type.</param>
public Tween (LerpFunc<T> lerpFunc)
{
_lerpFunc = lerpFunc;
_state = TweenState.Stopped;
}
/// <summary>
/// Starts a tween.
/// </summary>
/// <param name="start">The start value.</param>
/// <param name="end">The end value.</param>
/// <param name="duration">The duration of the tween.</param>
/// <param name="scaleFunc">A function used to scale progress over time.</param>
public void Start (T start, T end, float duration, ScaleFunc scaleFunc)
{
if (duration <= 0f)
throw new ArgumentException( "Tween duration must be greater than 0" );
if (scaleFunc == null)
throw new ArgumentNullException( "Tween scaleFunc must be not null" );
_currentTime = 0f;
_state = TweenState.Running;
_start = start;
_end = end;
_duration = duration;
_scaleFunc = scaleFunc;
UpdateValue();
}
/// <summary>
/// Pauses the tween.
/// </summary>
public void Pause ()
{
if (_state == TweenState.Running)
_state = TweenState.Paused;
}
/// <summary>
/// Resumes the paused tween.
/// </summary>
public void Resume ()
{
if (_state == TweenState.Paused)
_state = TweenState.Running;
}
/// <summary>
/// Stops the tween.
/// </summary>
/// <param name="stopBehavior">The behavior to use to handle the stop.</param>
public void Stop (StopBehavior stopBehavior)
{
_state = TweenState.Stopped;
if (stopBehavior == StopBehavior.ForceComplete)
{
_currentTime = _duration;
UpdateValue();
}
}
/// <summary>
/// Updates the tween.
/// </summary>
/// <param name="elapsedTime">The elapsed time to add to the tween.</param>
public void Update (float elapsedTime)
{
if (_state != TweenState.Running)
return;
_currentTime += elapsedTime;
if (_currentTime >= _duration)
{
_currentTime = _duration;
_state = TweenState.Stopped;
}
UpdateValue();
}
/// <summary>
/// Helper that uses the current time, duration, and delegates to update the current value.
/// </summary>
private void UpdateValue ()
{
_value = _lerpFunc( _start, _end, _scaleFunc(_currentTime / _duration) );
}
}
/// <summary>
/// Object used to tween float values.
/// </summary>
public class FloatTween : Tween<float>
{
private static float LerpFloat (float start, float end, float progress)
{
return start + (end - start) * progress;
}
// Static readonly delegate to avoid multiple delegate allocations
private static readonly LerpFunc<float> LerpFunc = LerpFloat;
/// <summary>
/// Initializes a new FloatTween instance.
/// </summary>
public FloatTween () : base(LerpFunc) { }
}
/// <summary>
/// Object used to tween Vector2 values.
/// </summary>
public class Vector2Tween : Tween<Vector2>
{
// Static readonly delegate to avoid multiple delegate allocations
private static readonly LerpFunc<Vector2> LerpFunc = Vector2.Lerp;
/// <summary>
/// Initializes a new Vector2Tween instance.
/// </summary>
public Vector2Tween () : base(LerpFunc) { }
}
/// <summary>
/// Object used to tween Vector3 values.
/// </summary>
public class Vector3Tween : Tween<Vector3>
{
// Static readonly delegate to avoid multiple delegate allocations
private static readonly LerpFunc<Vector3> LerpFunc = Vector3.Lerp;
/// <summary>
/// Initializes a new Vector3Tween instance.
/// </summary>
public Vector3Tween () : base(LerpFunc) { }
}
/// <summary>
/// Object used to tween Vector4 values.
/// </summary>
public class Vector4Tween : Tween<Vector4>
{
// Static readonly delegate to avoid multiple delegate allocations
private static readonly LerpFunc<Vector4> LerpFunc = Vector4.Lerp;
/// <summary>
/// Initializes a new Vector4Tween instance.
/// </summary>
public Vector4Tween () : base( LerpFunc ) { }
}
/// <summary>
/// Object used to tween Color values.
/// </summary>
public class ColorTween : Tween<ColorRgba>
{
// Static readonly delegate to avoid multiple delegate allocations
private static readonly LerpFunc<ColorRgba> LerpFunc = ColorRgba.Lerp;
/// <summary>
/// Initializes a new ColorTween instance.
/// </summary>
public ColorTween () : base(LerpFunc) { }
}
/// <summary>
/// Object used to tween Quaternion values.
/// </summary>
public class QuaternionTween : Tween<Quaternion>
{
// Static readonly delegate to avoid multiple delegate allocations
private static readonly LerpFunc<Quaternion> LerpFunc = Quaternion.Slerp;
/// <summary>
/// Initializes a new QuaternionTween instance.
/// </summary>
public QuaternionTween () : base(LerpFunc) { }
}
/// <summary>
/// Defines a set of premade scale functions for use with tweens.
/// </summary>
/// <remarks>
/// To avoid excess allocations of delegates, the public members of ScaleFuncs are already
/// delegates that reference private methods.
///
/// Implementations based on http://theinstructionlimit.com/flash-style-tweeneasing-functions-in-c
/// which are based on http://www.robertpenner.com/easing/
/// </remarks>
public static class ScaleFuncs
{
/// <summary>
/// A linear progress scale function.
/// </summary>
public static readonly ScaleFunc Linear = LinearImpl;
/// <summary>
/// A quadratic (x^2) progress scale function that eases in.
/// </summary>
public static readonly ScaleFunc QuadraticEaseIn = QuadraticEaseInImpl;
/// <summary>
/// A quadratic (x^2) progress scale function that eases out.
/// </summary>
public static readonly ScaleFunc QuadraticEaseOut = QuadraticEaseOutImpl;
/// <summary>
/// A quadratic (x^2) progress scale function that eases in and out.
/// </summary>
public static readonly ScaleFunc QuadraticEaseInOut = QuadraticEaseInOutImpl;
/// <summary>
/// A cubic (x^3) progress scale function that eases in.
/// </summary>
public static readonly ScaleFunc CubicEaseIn = CubicEaseInImpl;
/// <summary>
/// A cubic (x^3) progress scale function that eases out.
/// </summary>
public static readonly ScaleFunc CubicEaseOut = CubicEaseOutImpl;
/// <summary>
/// A cubic (x^3) progress scale function that eases in and out.
/// </summary>
public static readonly ScaleFunc CubicEaseInOut = CubicEaseInOutImpl;
/// <summary>
/// A quartic (x^4) progress scale function that eases in.
/// </summary>
public static readonly ScaleFunc QuarticEaseIn = QuarticEaseInImpl;
/// <summary>
/// A quartic (x^4) progress scale function that eases out.
/// </summary>
public static readonly ScaleFunc QuarticEaseOut = QuarticEaseOutImpl;
/// <summary>
/// A quartic (x^4) progress scale function that eases in and out.
/// </summary>
public static readonly ScaleFunc QuarticEaseInOut = QuarticEaseInOutImpl;
/// <summary>
/// A quintic (x^5) progress scale function that eases in.
/// </summary>
public static readonly ScaleFunc QuinticEaseIn = QuinticEaseInImpl;
/// <summary>
/// A quintic (x^5) progress scale function that eases out.
/// </summary>
public static readonly ScaleFunc QuinticEaseOut = QuinticEaseOutImpl;
/// <summary>
/// A quintic (x^5) progress scale function that eases in and out.
/// </summary>
public static readonly ScaleFunc QuinticEaseInOut = QuinticEaseInOutImpl;
/// <summary>
/// A sinusoidal progress scale function that eases in.
/// </summary>
public static readonly ScaleFunc SineEaseIn = SineEaseInImpl;
/// <summary>
/// A sinusoidal progress scale function that eases out.
/// </summary>
public static readonly ScaleFunc SineEaseOut = SineEaseOutImpl;
/// <summary>
/// A sinusoidal progress scale function that eases in and out.
/// </summary>
public static readonly ScaleFunc SineEaseInOut = SineEaseInOutImpl;
private const float Pi = (float)Math.PI;
private const float HalfPi = Pi / 2f;
private static float LinearImpl (float progress) => progress;
private static float QuadraticEaseInImpl (float progress) => EaseInPower( progress, 2f );
private static float QuadraticEaseOutImpl (float progress) => EaseOutPower( progress, 2f );
private static float QuadraticEaseInOutImpl (float progress) => EaseInOutPower( progress, 2f );
private static float CubicEaseInImpl (float progress) => EaseInPower( progress, 3f );
private static float CubicEaseOutImpl (float progress) => EaseOutPower( progress, 3f );
private static float CubicEaseInOutImpl (float progress) => EaseInOutPower( progress, 3f );
private static float QuarticEaseInImpl (float progress) => EaseInPower( progress, 4f );
private static float QuarticEaseOutImpl (float progress) => EaseOutPower( progress, 4f );
private static float QuarticEaseInOutImpl (float progress) => EaseInOutPower( progress, 4f );
private static float QuinticEaseInImpl (float progress) => EaseInPower( progress, 5f );
private static float QuinticEaseOutImpl (float progress) => EaseOutPower( progress, 5f );
private static float QuinticEaseInOutImpl (float progress) => EaseInOutPower( progress, 5f );
private static float EaseInPower (float progress, float power)
{
return ((float) Math.Pow( progress, power ));
}
private static float EaseOutPower (float progress, float power)
{
float sign = ((int) power) % 2 == 0 ? -1f : 1f;
return sign * (((float) Math.Pow( progress - 1f, power )) + sign);
}
private static float EaseInOutPower (float progress, float power)
{
progress *= 2f;
if (progress < 1f)
{
return ((float) Math.Pow( progress, power )) / 2f;
}
else
{
float sign = ((int) power) % 2 == 0 ? -1f : 1f;
return (sign / 2f * (((float) Math.Pow( progress - 2f, power )) + sign * 2f));
}
}
private static float SineEaseInImpl (float progress)
{
return ((float) Math.Sin( progress * HalfPi - HalfPi )) + 1f;
}
private static float SineEaseOutImpl (float progress)
{
return ((float) Math.Sin( progress * HalfPi ));
}
private static float SineEaseInOutImpl (float progress)
{
return ((float) (Math.Sin( progress * Pi - HalfPi ) + 1f)) / 2f;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment