Search Unity

Movement / Time - Take complete control of both movement and time.

Discussion in 'Assets and Asset Store' started by Trinary, Jan 5, 2016.

  1. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395

    Movement over Time allows you to hook into any object and run any value attached to that object through a sequence of changes over time. All you have to do is define the effect and then call Movement.Run.

    Movement over Time can change the position, size, color, or rotation of any object. It works on UI objects like buttons, sliders, or panels, as well as game objects like balls, characters, or doorways. It can hook into and change the public variables of shaders, camera effects, animations, arbitrary scripts, or behind the scene data objects.

    If you can access a variable from your script then it can be run through a sequence using Movement.Run.


    With Movement over Time you have full control over the flow of time on a per-effect basis. Slowing, pausing, reversing, or looping time are not only all possible, Movement over Time makes them easy!


    Examples

    When you want to change a value on a script over time whenever a particular button is pressed.

    When you want to move a GUI element across the screen, then make it double in size, and then make it spin cartwheels and turn 20% transparent.

    When you want to apply a constant push to an item in your world for 10 seconds, then let it taper off, then do nothing for a random amount of time, before starting the effect over from the beginning.

    When you finally find the perfect shader that does exactly what you want it to do, but then you realize that you really need a fade effect between your existing shader and this new one.

    Now you can do it, often using a single line of code!

    Advantages of Movement/Time over "Tween" engines:


    - "Tween" engines are limited to objects or data types that the author has built hooks for, while movement effects work on all objects and all common data types.
    - "Tween" engines use easing functions, which require you to match edges between effects, while movement effects blends edges effortlessly using inertia and elasticity.
    - Running a movement effect is fully separate from creation, which allows you to recombine them in any way you choose.
    - Movement effects can be applied to anything, even to other movement effects!

    Imagine the possibilities..

    Works with Unity Free and Pro.

    Asset store link.
     
    Last edited: Jan 20, 2016
    theANMATOR2b likes this.
  2. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I've been able to test on desktop and WebGL and have found no issues but I want someone to confirm. I am offering a free voucher to anyone who is willing to test for compatibility with iOS, Android, and other platforms.

    I am also looking for anyone who is willing to do independent performance evaluations. In theory MoT should run lerps about 10% faster than unity's built in lerp and about 50-70% faster than the built in slerp. I would also be very interested to know how the performance compares to other plugins on the asset store. I am offering a free voucher to anyone who is willing to investigate and compare performance.

    Reply if interested and I'll PM you a code.
     
  3. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    A new version is coming out soon:

    v 1.0.2

    Removed calls to Mathf.Abs() from inside all critical loops. For some reason this particular operation performs extremely slowly. The same effect can be achieved with the ternary operator.
    Removed the cause of the small hiccups that were occurring between effects in a sequence.
    Removed an unnecessary file.
    Demo 3 was not clamping the camera angle.
    Improved the handling for all functions in the case where both inertia and elasticity are defined.
     
    Last edited: Jun 26, 2016
  4. unsubstantiation

    unsubstantiation

    Joined:
    Nov 21, 2014
    Posts:
    6
    I'd be happy to test it on android, do you mind if i pair the testing with google cardboard?
     
  5. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Two people have PMd me already. I would be willing to issue a third voucher but it would need to be to someone who has a project deploying on iOS with IL2CPP.
     
  6. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I finally got my webhost back up!

    Here is demo scene #3 on WebGL. It's a great demonstration of swarming.

    I've made the entire scene using 6 movement effects. Movement / Time makes it super easy to define an entire environment in a whole new way: Using movement!
     
  7. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    V 1.1 is out!

    • Removed an unnecessary file.
    • Demo 3 was not clamping the camera angle.
    • Improved the handling for all functions in the case where both inertia and elasticity are defined.
    • Removed calls to Mathf.Abs() from inside all critical loops. For some reason this particular operation performs extremely slowly. The same effect can be achieved with the ternary operator.
    • Removed the cause of the small hiccups that were occurring between effects in a sequence.
    • Profiler hooks and closure detection code was being optimized out by the compiler. This has been corrected.
    • Added an explicit conversion from Effect to Sequence.
     
    Last edited: Jun 26, 2016
  8. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    It took a week, but I think the website is in pretty good shape now. I would say that the manual section of the site is about half way to where I would like it to be. I'll keep adding sections to it. If anyone has any questions or other input then this is a great place for them.
     
  9. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    v1.1.1

    - Changed SequenceInstance.SequenceTimescale to SequenceInstance.Timescale.
    - Fixed a bug where Timescale wasn't being reset to 1 when SequenceInstances were recycled.
     
    Last edited: Jun 26, 2016
  10. S_Darkwell

    S_Darkwell

    Joined:
    Oct 20, 2013
    Posts:
    320
    @Trinary:

    Good morning!

    I recently purchased your product and am looking to replace my existing LeanTween code.

    I've already replaced my Co-routines, which was quick and easy, but am trying to wrap my head around the best way to implement an action similar to the following LeanTween line:

    Code (CSharp):
    1. LeanTween.moveX(avatarObject, xPosition, 0.2f).setDelay(delay).setEase(LeanTweenType.easeOutSine).setOnComplete(MoveCompleteNotify);
    The line above pauses for a duration of "delay". Once the pause has finished, it moves "avatarObject" to an x-coordinate of xPosition over a time of 0.2f. Movement begins immediately, but end with a Sine-based easing algorithm. Once complete, it runs the function "MoveCompleteNotify".

    One additional concern I had is that from what I read, most forms of easing are handled by inertia and elasticity settings. How would one be able to move objects to very specific positions with these? The demos make deterministic end-points seem implausible.

    Thank you so much in advance for your response!
    - S.
     
    Trinary likes this.
  11. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Thanks for purchasing Darkwell! Here's how I would set up the effect you described:

    Code (CSharp):
    1.  
    2. Effect<GameObject, float> moveAvatarEffect = new Effect<GameObject, float>()
    3. {
    4.   Duration = 0.2f,
    5.   RetrieveEnd = (avatar) => xPosition,
    6.   OnUpdate = (avatar, value) => avatar.transform.localPosition =
    7.     new Vector3(value, avatar.transform.localPosition.y,
    8.     avatar.transform.localPosition.z),
    9.   RunEffectUntilValue =
    10.     (curVal, startVal, endVal) => Mathf.Abs(endVal - curVal) < 0.1f,
    11.   OnDone = avatar => { MoveCompleteNotify(); }
    12. };
    13.  
    14. Sequence<GameObject, float> moveAvatarSequence = new Sequence<GameObject, float>()
    15. {
    16.   Reference = avatarObject,
    17.   Inertia = 0.3f,
    18.   Effects = new [] { moveAvatarEffect }
    19. };
    20.  
    21. Timing.CallDelayed(delay, () => Movement.Run(moveAvatarSequence));
    22.  
    There are more compact ways to define effects, but this way is very easy to modify. Once you have the effect defined you don't have to redefine it for each thing that you want to move, you can just retarget the Reference variable.

    You make a good case for easing effects: They would be a little easier to define for the simple cases than Inertia is and produce a more deterministic stopping place. Now that you've made a good case for them I can easily add them to the next version.
     
    S_Darkwell likes this.
  12. S_Darkwell

    S_Darkwell

    Joined:
    Oct 20, 2013
    Posts:
    320
    Thank you for the response!

    So, I've tried the code that you posted above, and nothing moves, but MoveCompleteNotify IS called successfully.
    Perhaps the issue is in my attempt to slightly simplify the original code that I posed in hopes of making it more clear. Let me post my code verbatim and see if we can make this work.

    The actual code in my project is as thus:

    Code (CSharp):
    1.  
    2. private void UpdateAvatar(int id, float delay = 0)
    3. {
    4.   LeanTween.moveX(players[id].avatarObject, players[id].position, 0.2f).setDelay(delay).setEase(LeanTweenType.easeInOutSine).setOnComplete(MoveCompleteNotify);
    5. }
    6.  
    "players" is an array of custom Player class objects. The player class, among other variables, contains "avatarObject", which is a GameObject that visibly represents the player, and "position", which is an integer that represents the tile upon which each player is currently positioned.

    Once a Player class' "position" variable has been updated by an outside script, that script calls "UpdateAvatar()" to update its visual representation. As each tile is currently 1 meter in size, the path begins at x=0, and the path moves along the X axis, a Player's position integer variable can also double as its physical Vector3 X coordinate.

    With that explanation, my implementation of your code is as follows:
    Code (CSharp):
    1. private void UpdateAvatar(int id, float delay = 0)
    2. {
    3.     Effect<GameObject, float> moveAvatarEffect = new Effect<GameObject, float>()
    4.     {
    5.         Duration = 0.2f,
    6.         RetrieveEnd = (avatar) => players[id].position,
    7.         OnUpdate = (avatar, value) => avatar.transform.localPosition = new Vector3(value, avatar.transform.localPosition.y, avatar.transform.localPosition.z),
    8.         RunEffectUntilValue = (curVal, startVal, endVal) => Mathf.Abs(endVal - curVal) < 0.1f,
    9.         OnDone = avatar => { MoveCompleteNotify(); }
    10.     };
    11.  
    12.     Sequence<GameObject, float> moveAvatarSequence = new Sequence<GameObject, float>()
    13.     {
    14.         Reference = players[id].avatarObject,
    15.         Inertia = 0.3f,
    16.         Effects = new[] { moveAvatarEffect }
    17.     };
    18.  
    19.     Timing.CallDelayed(delay, () => Movement.Run(moveAvatarSequence));
    20. }
    Given those details, is there a reason why the "avatarObject" isn't moving?

    Thank you so much again in advance!
    - S.[/QUOTE]
     
  13. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Three things:
    1. I seem to have forgotten the line that initializes your object's starting position to the current location. So you need to add this to the sequence: RetrieveSequenceStart = avatar => avatar.transform.position.x
    2. I think you might be using world position rather than local position. In that case you need to change the OnUpdate function: OnUpdate = (avatar, value) => avatar.transform.position = new Vector3(value, avatar.transform.position.y, avatar.transform.position.z),
    3. I think for such small units you may need to drop the Inertia to something like 0.1f, or maybe even lower.
    4. If #2 and #3 doesn't fix the issue then I would take a look at caching the position to a local variable. I don't think you should need to, but if you're still having trouble then store players[id].position to a float and use RetrieveEnd = (avatar) => storedPosition,

    I realize it's a lot of setup. I'm going to work on that a bit. MoT Extensions.cs contains an MoTWorldSpaceMoveTo that sets up the effect in one line, but right now it won't let you define Inertia. I'll change it soon so that you can just add inertia to what you have if you don't want to do all this boilerplate setup.
     
    Last edited: Feb 27, 2016
  14. S_Darkwell

    S_Darkwell

    Joined:
    Oct 20, 2013
    Posts:
    320
    Thank you! I tried implementing it as you provided, as well as caching the players[id].position variable as an int with a reduced inertia (and even no intertia). Still no luck.

    Here is my code:
    Code (CSharp):
    1.  
    2.   var pos = (float)players[id].position;
    3.  
    4.   Effect<GameObject, float> moveAvatarEffect = new Effect<GameObject, float>()
    5.   {
    6.   Duration = 0.2f,
    7.   RetrieveEnd = (avatar) => pos,
    8.   OnUpdate = (avatar, value) => avatar.transform.position = new Vector3(value, avatar.transform.position.y, avatar.transform.position.z),
    9.   RunEffectUntilValue = (curVal, startVal, endVal) => Mathf.Abs(endVal - curVal) < 0.1f,
    10.   OnDone = avatar => { MoveCompleteNotify(); }
    11.   };
    12.  
    13.   Sequence<GameObject, float> moveAvatarSequence = new Sequence<GameObject, float>()
    14.   {
    15.   RetrieveSequenceStart = avatar => avatar.transform.position.x,
    16.   Reference = players[id].avatarObject,
    17.   Effects = new[] { moveAvatarEffect }
    18.   };
    19.  
    20.   Timing.CallDelayed(delay, () => Movement.Run(moveAvatarSequence));
    21.  
    What did work is the MoTWorldSpaceMoveTo:
    Code (CSharp):
    1. players[id].avatarObject.transform.MoTWorldSpaceMoveTo(0.2f, new Vector3(players[id].position, players[id].avatarObject.transform.position.y, players[id].avatarObject.transform.position.z));
    As mentioned, the MoTWorldSpaceMoveTo doesn't support inertia. Does it also not support a function call on finish, or is that a feature I'm overlooking?

    I'd still love to get the above larger block of code to function, because I'm trying to wrap my head around your system. I suspect the challenge is that for all of the coding I've done, I've rarely used lambda expressions. I always associated them with Linq functions, which I avoided for performance reasons. I'll do some research on lambda expressions over the weekend in hopes that it might clarify your approach for me.

    In the meantime, if we can achieve working results for the larger section of code, I can use that as a working base from which to experiment.

    Thank you again so much. Be well!
    - S.
     
  15. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok. Today the answer seems obvious: I flipped the sign on RunEffectUntilValue. It should be greater than: RunEffectUntilValue = (curVal, startVal, endVal) => Mathf.Abs(endVal - curVal) > 0.01f

    (with the less than sign it was returning false immediately and then quitting the effect before the first frame.)
     
    Last edited: Feb 27, 2016
    S_Darkwell likes this.
  16. S_Darkwell

    S_Darkwell

    Joined:
    Oct 20, 2013
    Posts:
    320
    Thank you!

    So, it's definitely moving now. As for the time it takes for the piece to slow at the end of its path, I was seeking something very brief (in the order of perhaps 0.05s). Even using Mathf.Epsilon in the Inertia parameter, it takes much longer than I was seeking. I assume this value is clamped internally.

    Is there a way around this limitation?

    Thank you again!
    - S.
     
  17. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    :cool: I like the idea behind Epsilon, but I suspect it would produce rounding errors when divided. Have you tried 0.0001f?

    As a temporary fix if .0001 doesn't work ,you could multiply your position by 100 in RetrieveEnd and then divide it by 100 in OnUpdate. I should have the easing functions that you're used to in the next update, so you'll be able to just drop an easing function on top of CalculatePercent.

    Inertia is clamped between 0 and 1.
     
  18. S_Darkwell

    S_Darkwell

    Joined:
    Oct 20, 2013
    Posts:
    320
    Yes, I had previously tried that as well. Mathf.Epsilon was my final attempt.

    So, I tried multiplying and dividing (or rather, multiplying by the appropriate decimal to be more performant), and it still seems to decelerate at about the same rate.

    Code (CSharp):
    1. private void UpdateAvatar(int id, float delay = 0)
    2. {
    3.     var storedPosition = (float)players[id].position;
    4.  
    5.     Effect<GameObject, float> moveAvatarEffect = new Effect<GameObject, float>()
    6.     {
    7.         Duration = 0.15f,
    8.         RetrieveEnd = (avatar) => storedPosition * 10000.0f,
    9.         OnUpdate = (avatar, value) => avatar.transform.position = new Vector3(value * 0.0001f, avatar.transform.position.y, avatar.transform.position.z),
    10.         RunEffectUntilValue = (curVal, startVal, endVal) => Mathf.Abs(endVal - curVal) > 0.001f,
    11.         OnDone = avatar => { MoveCompleteNotify(); }
    12.     };
    13.  
    14.     Sequence<GameObject, float> moveAvatarSequence = new Sequence<GameObject, float>()
    15.     {
    16.         Inertia = 0.0001f,
    17.         RetrieveSequenceStart = avatar => avatar.transform.position.x,
    18.         Reference = players[id].avatarObject,
    19.         Effects = new[] { moveAvatarEffect }
    20.     };
    21.  
    22.     Timing.CallDelayed(delay, () => Movement.Run(moveAvatarSequence));
    23. }
    Any other ideas?

    And definitely looking forward to the easing effects!

    Thank you!
    - S.
     
  19. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, try this out for me: Comment out the RunEffectUntilValue and the Inertia, and drop this into the Effect:

    CalculatePercentDone = (timeSoFar, timespan) => Mathf.Sin((timeSoFar / timespan) * (Mathf.PI / 2f)),

    That should do the EaseOutSin (I haven't tested it yet, so hopefully it just works.)

    (Edit: Replaced the semicolon at the end with a comma.)
     
    Last edited: Feb 27, 2016
  20. S_Darkwell

    S_Darkwell

    Joined:
    Oct 20, 2013
    Posts:
    320
    Ahh, yes, I caught the semicolon issue.

    So, the motion is great, but the final position is incorrect.

    Code (CSharp):
    1. private void UpdateAvatar(int id, float delay = 0)
    2.   {
    3.   var storedPosition = (float)players[id].position;
    4.  
    5.   Effect<GameObject, float> moveAvatarEffect = new Effect<GameObject, float>()
    6.   {
    7.   Duration = 0.15f,
    8.   RetrieveEnd = (avatar) => storedPosition * 10000.0f,
    9.   OnUpdate = (avatar, value) => avatar.transform.position = new Vector3(value * 0.0001f, avatar.transform.position.y, avatar.transform.position.z),
    10.   CalculatePercentDone = (timeSoFar, timespan) => Mathf.Sin((timeSoFar / timespan) * (Mathf.PI / 2f)),
    11.   //RunEffectUntilValue = (curVal, startVal, endVal) => Mathf.Abs(endVal - curVal) > 0.001f,
    12.   OnDone = avatar => { MoveCompleteNotify(); }
    13.   };
    14.  
    15.   Sequence<GameObject, float> moveAvatarSequence = new Sequence<GameObject, float>()
    16.   {
    17.   //Inertia = 0.0001f,
    18.   RetrieveSequenceStart = avatar => avatar.transform.position.x,
    19.   Reference = players[id].avatarObject,
    20.   Effects = new[] { moveAvatarEffect }
    21.   };
    22.  
    23.   Timing.CallDelayed(delay, () => Movement.Run(moveAvatarSequence));
    24.   }
    25.  
    Here is the optimized version I used as well (removed apparently unnecessary code, and refined Sine function using an already-solved value of PI / 2).

    Both result in strange end-placement.

    Code (CSharp):
    1. private void UpdateAvatar(int id, float delay = 0)
    2. {
    3.   var moveAvatarEffect = new Effect<GameObject, float>
    4.   {
    5.   Duration = 0.2f,
    6.   RetrieveEnd = (avatar) => players[id].position,
    7.   OnUpdate = (avatar, value) => avatar.transform.position = new Vector3(value, avatar.transform.position.y, avatar.transform.position.z),
    8.   CalculatePercentDone = (timeSoFar, timespan) => Mathf.Sin(timeSoFar / timespan) * 1.5707965f,
    9.   OnDone = avatar => { MoveCompleteNotify(); }
    10.   };
    11.  
    12.   var moveAvatarSequence = new Sequence<GameObject, float>
    13.   {
    14.   RetrieveSequenceStart = avatar => avatar.transform.position.x,
    15.   Reference = players[id].avatarObject,
    16.   Effects = new[] { moveAvatarEffect }
    17.   };
    18.  
    19.   Timing.CallDelayed(delay, () => Movement.Run(moveAvatarSequence));
    20. }
    The the easing itself is spot-on. Just the final position is off.

    Thoughts?

    Be well!
    - S.
     
    Trinary likes this.
  21. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Put the half-Pi constant inside the Sin function. You shouldn't actually need to optimize it that way. .net has this amazing behind the scenes optimization where it pre-caches any function that it can determine will always return the same value. In fact, if you use system.stopwatch to profile your code you'll find that a lot of functions take 0 ms to execute after the first run.

    Anyway, Mathf.Sin((timeSoFar / timespan) * 1.5707965f)

    I think the value may have to be clamped as well. The standard CalculatePercentDone is clamped in order to not overrun values and leave results in weird places. So try this:

    CalculatePercentDone = (timeSoFar, timespan) => Mathf.Clamp01(Mathf.Sin((timeSoFar / timespan) * 1.5707965f))
     
  22. S_Darkwell

    S_Darkwell

    Joined:
    Oct 20, 2013
    Posts:
    320
    Apologies that it has been taking me a short bit to respond. I'm implementing a GUI messaging system, so have to resolve any NullReferenceException errors before checking out your new code.

    Wow. That's most excellent. I never realized that! Good call. It's an old habit that I replace common divisions with their equivalent multipliers (eg: /2f with *0.5f), but no argument here that Mathf.PI is more readable than it's float equivalent.

    Code (CSharp):
    1. private void UpdateAvatar(int id, float delay = 0)
    2. {
    3.     var moveAvatarEffect = new Effect<GameObject, float>
    4.     {
    5.         Duration = 0.2f,
    6.         RetrieveEnd = avatar => players[id].position,
    7.         OnUpdate = (avatar, value) => avatar.transform.position = new Vector3(value, avatar.transform.position.y, avatar.transform.position.z),
    8.         CalculatePercentDone = (timeSoFar, timespan) => Mathf.Clamp01(Mathf.Sin(timeSoFar / timespan) * 1.5707965f),
    9.         OnDone = avatar => { MoveCompleteNotify(); }
    10.     };
    11.  
    12.     var moveAvatarSequence = new Sequence<GameObject, float>
    13.     {
    14.         RetrieveSequenceStart = avatar => avatar.transform.position.x,
    15.         Reference = players[id].avatarObject,
    16.         Effects = new[] { moveAvatarEffect }
    17.     };
    18.  
    19.     Timing.CallDelayed(delay, () => Movement.Run(moveAvatarSequence));
    20. }

    This seems to work perfectly! :D

    For anyone who might read this, though, I did have to change "soFar" to "timeSoFar".

    The current build of my game won't let me test this extensively, so I don't know if there are any floating-point type issues, but as far as I can tell, you've nailed it!

    Thank you so much again! Be well!
    - S.
     
    Deeeds likes this.
  23. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Awesome :)

    Thanks for helping me test. I'll put delegates in the next version so you can just do CalculatePercentDone = EaseOutSin

    and I might have to change Inertia a bit. I should do that ASAP, so that it effects as few people as possible.
     
    S_Darkwell likes this.
  24. S_Darkwell

    S_Darkwell

    Joined:
    Oct 20, 2013
    Posts:
    320
    My pleasure. Always happy to help if I can!

    I look forward to the next update. :)

    Be well!
    - S.
     
  25. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Last edited: Jun 26, 2016
  26. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I made a video of the easing functions. This is demo scene #4, which will be included in the next update:

     
    ilmario likes this.
  27. Querke

    Querke

    Joined:
    Feb 21, 2013
    Posts:
    54
    Hello, I feel really dumb and don't understand how to use this asset even for the most basic things.

    Could you help me out? For example, how would you move an object from a vector2 position to another position over 1 second?

    No need to elaborate too much. But please show the code from the start to finish. :)
     
  28. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Sorry, I have been focusing a lot of my attention on Memory Efficient Coroutines lately. Streamlining this platform and making a video tutorial is next on my list for this plugin.

    MoT works by defining a sequence and then setting it running. The sequence is an array of individual Effects and some other variables. I like to think of a sequence as a roadmap: It tells MoT how to get from here to there, but it doesn't do anything on it's own. Everything is put into motion by calling Movement.Run.

    There are several different ways to define a sequence. You only really need to use one style. The simplest way is to use one of the helper functions in MovementEffects.Extensions.

    Code (CSharp):
    1. using UnityEngine;
    2. using MovementEffects;
    3. using MovementEffects.Extensions;
    4.  
    5. public class MyClass
    6. {
    7.     public GameObject myObject;
    8.     public Vector2 target;
    9.     public float timeToTake;
    10.  
    11.     void Start()
    12.     {
    13.         Movement.Run(myObject.transform.MoTWorldSpaceMoveTo(timeToTake, target));
    14.     }
    15. }
    MoTWorldSpaceMoveTo defines a "move from here to there" sequence for you. The real power is that instead of immediately calling Movement.Run, like in the line above, you can take the sequence that MoTWorldSpaceMoveTo returns and modify it or combine it in all kinds of different ways, and THEN call Movement.Run.
     
    Last edited: Jun 26, 2016
  29. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    Instead of calling Movement.Run is there a way to control the execution of the engine? For example if I control my game by ticks, is there some sort of Movement.Tick(float time) function that advanced all sequences by a set amount of time?

    I'm trying to decide if this is going to be more useful to me than DOTween which I already purchased, but I can't really tell if it'll be worth it from reading the manual. Do you have complete API docs somewhere?
     
  30. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I'm sorry about the documentation, doq. I do consider the current state of the documentation to be "minimum viable", but I just haven't had time to do more than describe each feature. The package comes with an API overview in pdf form, but it's currently quite basic.

    I hadn't considered making a manual time segment for the entire engine. I think it would be very do-able and now that you've suggested it I'll probably put that in the next major release.

    If you're just working with what is currently implemented, however, you can make that effect in one of two ways. Each sequence can be set to run in unity independent time or in regular time. You can set all your UI sequences to be timescale independent, and just move Unity's timescale forward by one second at a time.

    The second approach would be to make a list of sequence instance variables. The sequence instance controls the timescale for that specific sequence and it can be set to 0 most of the time and only turned into a positive number when you want to move your world forward. It can also be set to negative values if you want to go back.
     
  31. Querke

    Querke

    Joined:
    Feb 21, 2013
    Posts:
    54
    Thank you sir! Helped me a lot!
    Also, notify us when you've updated the documentation. I'll be waiting :)
     
  32. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    @Trinary Thanks for the update and suggested ideas for controlling sequences. I like the ideas behind the library, I noticed that there's a dll in the package. Is the source available with a purchase? In lieu of complete docs, I'd read the source.
     
  33. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Some of the code is pre-compiled and some isn't. In general anything you might need to modify or upgrade is in uncompiled source. The time critical sections are pre-compiled for speed.

    A lot of the documentation is in the example scenes. Scene #2 has more lines of comments explaining how and why everything is done than it has lines of code.
     
    Last edited: Jun 26, 2016
  34. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    Cool, I'll pick up Movement / Time then. Thanks for the prompt replies. Looking forward to the update.
     
  35. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    @Trinary I'm trying to use ScottGarland's BigInteger as the value for tweening. Can you suggest how I would approach this? Basically Movement.Run won't accept my BigInteger value type.

    Code (CSharp):
    1. public class TextController : MonoBehaviour
    2. {
    3.  
    4.     public TextMeshProUGUI tm;
    5.  
    6.     public class Score
    7.     {
    8.         public BigInteger val;
    9.         public Score()
    10.         {
    11.             val = new BigInteger();
    12.         }
    13.     }
    14.  
    15.     public Score score;
    16.  
    17.     void Awake()
    18.     {
    19.  
    20.         score = new Score();
    21.  
    22.         var counter = new Effect<Score, BigInteger>(10f,
    23.             (thescore, lastEndValue) => thescore.val,
    24.             (thescore) => new BigInteger("1000000"),
    25.             (thescore, value) => thescore.val = value
    26.             );
    27.  
    28.         var sequence = new Sequence<Score, BigInteger>(score, counter);
    29.  
    30.         Movement.Run(sequence); // <--- signature error
    31.     }
    32.  
    33.     void Update()
    34.     {
    35.         tm.SetText(score.val.ToString());
    36.     }
    37. }
     
  36. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Wow.

    Well.. at it's core MoT is pretty much a math library. It runs an algorithm on the values you pass in, for instance on a float, that progresses it from the initial value to the ending value in a particular way. Every frame the value that is returned is progressed by some variable amount based on how much time has passed and what options you passed in. So you can pass in anything you want for the first templated value, but the second value has to be some continuous type like float, double, Vector2, 3, 4, Quaternion, or Color. The math is a bit different for all of these types, so there has to be a specific function to handle each supported type. That's why it's not defined for your arbitrary data type.

    It doesn't make any sense to lerp something like an integer.. it would have to be rounded in some way since you can't have fractions for an int and there are several ways to round a number. That's why it's best just to use a double for this and round it yourself. so..
    Code (CSharp):
    1.         var counter = new Effect<Score, double>(10f,
    2.             (thescore, lastEndValue) => double.Parse(thescore.val.ToString()),
    3.             (thescore) => 1000000d,
    4.             (thescore, value) => thescore.val = System.Math.Round(value).ToString()
    5.             );
    A double isn't exactly infinite precision, but you can store some pretty big numbers. It'll kind of work.
     
    Last edited: Apr 26, 2016
  37. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    It makes total sense that ultimately you'd have to use a float or double to represent the changing value. I guess another option for the reference would be to have two BigInteger values (the current value and start value). Then I could tween over a float/double and onUpdate add the float to the start to get the current value. Thanks for explaining that!

    Code (CSharp):
    1.  
    2.     public class Score
    3.     {
    4.         public BigInteger cur;
    5.         public BigInteger start;
    6.  
    7.         public Score()
    8.         {
    9.             cur = new BigInteger("92923829302344234324220");
    10.             start = new BigInteger();
    11.         }
    12.     }
    13.  
    14.     public Score score;
    15.  
    16.     void Awake()
    17.     {
    18.  
    19.         score = new Score();
    20.  
    21.         var counter = new Effect<Score, float>(10f,
    22.             (thescore, lastEndValue) =>
    23.             {
    24.                 thescore.start = thescore.cur;
    25.                 return 0f;
    26.             },
    27.             (thescore) => 100000f,
    28.             (thescore, value) => thescore.cur = thescore.start + Mathf.RoundToInt(value)
    29.             );
    30.  
    31.         var sequence = new Sequence<Score, float>(score, counter);
    32.  
    33.         Movement.Run(sequence);
    34.     }
     
  38. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    We can create sequences by adding effects and multiplying effects by an integer. Can we do the same for sequences? Say I have 2 effects to change color toRed and toWhite, I'd like to create a sequence like

    Code (CSharp):
    1. var rwSeq = (toRed + toWhite) * 2;
    What about joining 2 sequences together?
     
  39. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I'm glad to see that you are putting MoT through it's paces.

    SequenceA + SequenceB returns sequenceA with sequenceB's effects appended to the end. In every case that I know of this works perfectly. I didn't implement multiplying sequences because I didn't know what the order should be.

    So if we change your line just a little bit it should do exactly what you want:
    Code (CSharp):
    1. var rwSeq = (toRed * 2) + (toWhite * 2);
    Unless you want it to go red, white, red, white.. then it would be more like this:
    Code (CSharp):
    1. var rwSeq = toRed + toWhite;
    2. rwSeq += rwSeq;
     
    Last edited: Jun 26, 2016
  40. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    My intent was to do the latter red, white, red, white to replicate a dotween I had that would yoyo to red and back 2 twice.

    Looking at the first alternative, I'd assume that'd run red, red, white, white which seems a little strange. :)

    From the samples I assumed (Sequence)x * (int)y operation as repeat x, y number of times to mirror the * operator for Effect. I was looking for a way to mirror how dotween let's you specify looping a number of times.
     
    Trinary likes this.
  41. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, that makes sense. I'll put it on the todo list. Maybe I'll go completely crazy and implement the power (^) operator as well.
     
  42. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    Sweet! What do you think of adding a copy/duplicate function as well? I was trying to create a "Reverse" function for an effect but in RetrieveEnd delegate we don't get the previous Effect's start value. In the red, white case I have code like this:

    Code (CSharp):
    1.     var toRed = new Effect<TextMeshProUGUI, Color>(
    2.             0.25f,
    3.             (text, lastValue) => lastValue,
    4.             (text) => Color.red,
    5.             (text, color) => text.color = color);
    6.  
    7.     var toWhite = new Effect<TextMeshProUGUI, Color>(
    8.             0.25f,
    9.             (text, lastValue) => lastValue,
    10.             (text) => Color.white,
    11.             (text, color) => text.color = color);
    which gets really verbose when you have several effects going.. If I could get the previous start value in RetrieveEnd then I could create a sequence like: toRed + toRed.Reverse(); or if you want to go nuts with operators toRed + ~toRed :)

    But since I don't have that value, I created a Copy function as an extension method, so I could create toWhite with less boilerplate:
    Code (CSharp):
    1.     var toWhite = toRed.Copy();
    2.     toWhite.RetrieveEnd = (text) => Color.white;
     
    Last edited: May 3, 2016
  43. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Effects and Sequences are both value types (instead of reference types) so you don't need an explicit copy function. This will work just fine:
    Code (CSharp):
    1. var toWhite = toRed;
    2. toWhite.RetrieveEnd = (text) => Color.white;
    If you catch the SequenceInstance that is returned by Movement.Run then you can set timescale to -1 to run the effect backwards, but I get your point. It might be nice to be able to define that behavior before starting the sequence. I'll think on a way to implement that.
     
  44. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    Oh I see, they're structs. Thanks!

    So how would the SequenceInstance with timescale -1 work if I wanted to create the toRed then toWhite sequence?

    Can I run a SequenceInstance after it completes with different properties? Seems like I would need to write some glue code to sequence things.
     
    Last edited: May 3, 2016
  45. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    Do you have any recommendations on synchronizing a series of effects? For instance, if I wanted to change the color and alpha as separate effects/sequences, both have the same duration, and OnComplete on one of the sequences I fire a callback, I'm find there could be a race condition. I have this issue with dotween too where I just make one of the sequences a little longer and give it the OnComplete callback.

    What would be a succinct way of calling OnComplete of a parallel set of sequences? Since these sequences might not operate on the same type of reference, if say I want to synch a text change with some transform movement like when a character takes damage.

    I feel like there should be a construct to run sequences in parallel, but setting up the C# to do this eludes me because of types.

    I'd like to do something like:
    Code (CSharp):
    1. var synch = new Synch(MyOnComplete, textSeq, transformSeq);
    2.  
    3. // synch.OnComplete = MyOnComplete;
    4. // synch.Sequences = new Sequence[] {textSeq, transformSeq};
    5.  
    6. Movement.Run(synch);
    I have some code to get the job done, but it's fugly.
     
  46. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    After a couple of iterations, I came up with this to synchronize Sequences.

    Code (CSharp):
    1. using MovementEffects;
    2. /*
    3. * Synchronize Movement / Time Sequences
    4. * Description:
    5. *     Sequences that are synched should be run at the same time and have the same duration.
    6. *     The synchronization is started with Movement.Run calls on each sequence.
    7. *  Usage:
    8. *
    9. *  SequenceSyncher synchFx;
    10. *
    11. *  synchFx = new SequenceSyncher(OnFxDone);
    12. *
    13. *  Movement.Run(synchFx.Synch(tweenScore));
    14. *  Movement.Run(synchFx.Synch(tweenMovement));
    15. */
    16.  
    17. public class SequenceSyncher
    18. {
    19.     int completedSequences = 0;
    20.     int numSequences;
    21.     Action OnComplete;
    22.  
    23.     public SequenceSyncher(Action onComplete)
    24.     {
    25.         numSequences = 0;
    26.         this.OnComplete = onComplete;
    27.     }
    28.  
    29.     public Sequence<Tref, Tval> Synch<Tref, Tval>(Sequence<Tref, Tval> seq)
    30.     {
    31.         completedSequences = 0;
    32.         ++numSequences;
    33.         seq.OnComplete = delegate (Tref _)
    34.         {
    35.             ++completedSequences;
    36.             if (completedSequences == numSequences)
    37.             {
    38.                 numSequences = 0;
    39.                 if (OnComplete != null)
    40.                 {
    41.                     OnComplete();
    42.                 }
    43.             }
    44.         };
    45.  
    46.         return seq;
    47.     }
    48. }
    It'd be nice to have something like this in the package.
     
    Last edited: May 3, 2016
    Trinary likes this.
  47. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Awesome. Yes, creating an intermediate function that counts down and calls your OnComplete when it reaches zero is the way I would do it too.

    I should note that everything is executed in a deterministic way. If you start two sequences with the exact same time-frame and you don't mess with the sequence's rate of time while they are running then the one that you called Movement.Run on first will also always end first. So the easy way is to just use the last sequence that you set running.

    I think that you are thinking about multithreading when you worry about those race conditions. While MoT could be multithreaded, I've hesitated to go that route largly because of the non-denominational implications.

    I think I need to add something to the docs about that. A reference counted callback would also be a good addition, for when someone tries to make a game that runs sequences at variable rates of time.
     
    Last edited: May 3, 2016
  48. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    One way would be to add an OnDone callback to the effect that called back into your calling class. The function in the calling class could then set the instance timescale to -1.

    I agree that there should be a way to do it without hooking into your parent.
     
  49. doq

    doq

    Joined:
    Aug 17, 2015
    Posts:
    121
    Yeah, you're right about the race condition, it was late and I didn't thoroughly test things. Must have been my imagination. I'll try it out again with just the one OnComplete for the last sequence. Though having the synchronizer let's me not worry about breaking something as I'm tweaking things.

    Btw, I haven't done stuff with multithreading, but I've never heard anyone use the phrase "non-denominational" implications, not sure what that means.

    I was trying to tackle creating trees of sequences as well, but I'm probably over engineering a this point.
     
  50. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Blame it on my phone's spell check. That should have been non-determinational, not non-denominational.