{"id":3422,"date":"2014-12-28T15:58:40","date_gmt":"2014-12-28T14:58:40","guid":{"rendered":"http:\/\/t-machine.org\/?p=3422"},"modified":"2015-01-27T21:20:38","modified_gmt":"2015-01-27T20:20:38","slug":"easy-parametric-animations-in-unity3d-with-code-and-curves","status":"publish","type":"post","link":"http:\/\/new.t-machine.org\/index.php\/2014\/12\/28\/easy-parametric-animations-in-unity3d-with-code-and-curves\/","title":{"rendered":"Easy parametric animations in #Unity3d with code and curves"},"content":{"rendered":"<p>iOS forces you to use pretty animations: if you don&#8217;t tell it otherwise, it will use &#8220;ease-in\/ease-out&#8221; curves to make things feel &#8230; smoother, less jerky.<\/p>\n<p>Unity is made for programmers; changes you make to position, size, rotation, etc all happen instantaneously. By default, they won&#8217;t even animate, will teleport instead.<\/p>\n<p>So &#8230; moving from &#8220;prototype with no animation&#8221; to &#8220;prototype that won&#8217;t hurt people&#8217;s eyes, and I can share&#8221;, what are some tips and gotchas?<\/p>\n<p><!--more--><\/p>\n<h2>Mecanim<\/h2>\n<p>Personal opinion, but &#8230; most of us are using it to get cheap bipedal walking animations. Which is fine, but a very small percent of the many use-cases for &#8220;animation during a game&#8221;.<\/p>\n<p>So, I&#8217;m ignoring it here.<\/p>\n<h2>Playing an animation correctly<\/h2>\n<p>First off: don&#8217;t use Animation.Play( &#8220;anim-name&#8221; ) &#8211; this method is great for prototyping, but it throws-away the timing info you need later<\/p>\n<p>You can workaround this by putting source-code into every single one of your Animations. Yeah. That works, but .. it&#8217;s as safe and maintainable as it sounds. For very complex anims, you&#8217;ll have to do that anyway, and it&#8217;s a great solution &#8211; but for small games it&#8217;s overkill.<\/p>\n<h3>Always load the AnimationClip<\/h3>\n<p>[csharp]<br \/>\nAnimation anim = myObject.GetComponentInChildren&lt;Animation&gt;();<br \/>\nif( anim == null )<br \/>\n\tanim = myObject.AddComponent&lt;Animation&gt;();<\/p>\n<p>AnimationClip clip = anim.GetClip( animationName );<br \/>\nanim.clip = clip;<br \/>\n[\/csharp]<\/p>\n<p>Now you know how long the anim takes &#8211; it&#8217;s &#8220;clip.length&#8221;<\/p>\n<h2>Applying Animations to GameObjects<\/h2>\n<p>Design flaw in Unity that we&#8217;re stuck with:<\/p>\n<blockquote><p>\nAnimations OVERWRITE the object state, instead of MODIFYING it\n<\/p><\/blockquote>\n<p>i.e. a jumping animation that moves up and down &#8230; will reset your character to the origin, jump them up and down, and then teleport your character back to where they were standing. World&#8217;s biggest WTF in engine-design.<\/p>\n<p>Official workaround:<\/p>\n<ol>\n<li>Create an empty GameObject.\n<li>Parent your object within the new one.\n<li>Apply the animation to the original object (the child).\n<li>Re-write all your game-input, controllers, AI, etc so that it moves the new, empty, PARENT object.\n<\/ol>\n<h2>Parametric Animation<\/h2>\n<p>That&#8217;s not quite enough.<\/p>\n<p>In a game, almost all animation is parametric. It is very rare to find something that SHOULDN&#8217;T be tweakable via simple config file, NOR influenced by the player&#8217;s or AI&#8217;s actions.<\/p>\n<p>Unity doesn&#8217;t directly support parametric animation; but they provide API calls and features that make it very easy to add yourself. If you know what they are&#8230;<\/p>\n<h3>Example: Jump with variable length<\/h3>\n<p>First, split your animation into two parts:<\/p>\n<ol>\n<li>Parametric part (in this case: how much ground-distance does the jump cover?)\n<ul>\n<li>You implement this as an AnimationClip, following Unity&#8217;s standard process \/ official docs.<\/li>\n<\/ul>\n<li>Static part (in this case: how high they jump, the curve of up and down movement (should look like acceleration: moves quickly at first, slower as it nears the peak, then fast again as it falls))\n<ul>\n<li>You implement this in code, using a co-routine. See next paragraph..<\/li>\n<\/ul>\n<\/ol>\n<p>First, you write it without a co-routine, to test it works:<\/p>\n<p>[csharp]<br \/>\npublic void PlayJump( GameObject myObject, Vector3 jumpDistance )<br \/>\n{<br \/>\n\tmyObject.transform.position = myObject.transform.position + jumpDistance;<br \/>\n}<br \/>\n[\/csharp]<\/p>\n<p>Works, right? Great. But it teleports you from start to finish.<\/p>\n<p>Then you convert it to a co-routine, causing Unity to smoothly animate it. W00t! Here, you use Unity&#8217;s provided method for smoothly moving something over time: Vector3.Lerp() (google it if you don&#8217;t know what a &#8220;lerp&#8221; is, it&#8217;s used in almost every game ever written).<\/p>\n<p>[csharp highlight=&#8221;3,6-17&#8243;]<br \/>\npublic void PlayJump( GameObject myObject, Vector3 jumpDistance )<br \/>\n{<br \/>\n\tStartCoroutine( CoMove( myObject, myObject.transform.position + jumpDistance );<br \/>\n}<\/p>\n<p>private IEnumerator CoMove( GameObject myObject, Vector3 finalPosition )<br \/>\n{<br \/>\n\tfloat elapsedTime = 0, transitionTime = 1f \/* spend 1 seconds moving *\/;<br \/>\n\tVector3 savedOriginalPosition = myObject.transform.position;<\/p>\n<p>\twhile( elapsedTime &lt; transitionTime )<br \/>\n\t{<br \/>\n\tmyObject.transform.position = Vector3.Lerp( savedOriginalPosition, finalPosition, (elapsedTime\/transitionTime) );<br \/>\n\telapsedTime += Time.deltaTime;<br \/>\n\tyield return new WaitForEndOfFrame();<br \/>\n\t}<br \/>\n}<br \/>\n[\/csharp]<\/p>\n<p>Your jump should now combine a Unity Animation and a parametric \/ controllable distance.<\/p>\n<p>But &#8230; you want the jump distance and jump animation to be synchronized. For that, you need the clip-length we grabbed earlier:<\/p>\n<p>[csharp highlight=&#8221;14, 1=22, 2=24&#8243; language=&#8221;17-20,&#8221;]<\/p>\n<p>public void AnimateAndPlayJump( GameObject myObject, Vector3 jumpDistance )<br \/>\n{<br \/>\n\tAnimation anim = myObject.GetComponentInChildren&lt;Animation&gt;();<br \/>\n\tif( anim == null )<br \/>\n\t\tanim = myObject.AddComponent&lt;Animation&gt;();<\/p>\n<p>\tAnimationClip clip = anim.GetClip( animationName );<br \/>\n\tanim.clip = clip;<\/p>\n<p>\t\/\/ now you know how long the anim takes &#8211; it&#8217;s &quot;clip.length&quot;<\/p>\n<p>\tanim.Play(); \/\/ uses the new .clip<\/p>\n<p>\tPlayJump( myObject, jumpDistance, clip.length );<br \/>\n}<\/p>\n<p>public void PlayJump( GameObject myObject, Vector3 jumpDistance, float duration )<br \/>\n{<br \/>\n\tStartCoroutine( CoMove( myObject, myObject.transform.position + jumpDistance, duration );<br \/>\n}<\/p>\n<p>private IEnumerator CoMove( GameObject myObject, Vector3 finalPosition, float duration )<br \/>\n{<br \/>\n\tfloat elapsedTime = 0;<br \/>\n\tVector3 savedOriginalPosition = myObject.transform.position;<\/p>\n<p>\twhile( elapsedTime &lt; duration )<br \/>\n\t{<br \/>\n\tmyObject.transform.position = Vector3.Lerp( savedOriginalPosition, finalPosition, (elapsedTime\/transitionTime) );<br \/>\n\telapsedTime += Time.deltaTime;<br \/>\n\tyield return new WaitForEndOfFrame();<br \/>\n\t}<br \/>\n}<br \/>\n[\/csharp]<\/p>\n<p>Now it&#8217;s perfect.<\/p>\n<p>Then you try it in-game, and it&#8217;s about 10% slower than you intended. Easy, speed up the ani&#8211; oh, wait. Unity&#8217;s animation editor DOES NOT ALLOW you to speed up \/ slow down your animationclips. Ouch. Burn!<\/p>\n<h2>Controlling animation speed<\/h2>\n<p>You can change the speed of an animation itself, but it&#8217;s a bit non-obvious how to do it. You unintuitively iterate through the AnimationStates, and set their speed to the INVERSE of the ratio of speeds. Wat? Read the code:<\/p>\n<p>[csharp highlight=&#8221;1,10-17,22&#8243;]<br \/>\npublic void AnimateAndPlayJump( GameObject myObject, Vector3 jumpDistance, float animationDuration = 0f )<br \/>\n{<br \/>\n\tAnimation anim = myObject.GetComponentInChildren&lt;Animation&gt;();<br \/>\n\tif( anim == null )<br \/>\n\t\tanim = myObject.AddComponent&lt;Animation&gt;();<\/p>\n<p>\tAnimationClip clip = anim.GetClip( animationName );<br \/>\n\tanim.clip = clip;<\/p>\n<p>\tif( animationDuration &gt; 0f )<br \/>\n\t{<br \/>\n\t\t\/** Modify the animation-clip, for this animation only *\/<br \/>\n\t\tforeach( AnimationState s in anim )<br \/>\n\t\t{<br \/>\n\t\t\t\t\ts.speed = transitTimeSeconds\/overrideAnimationDuration;<br \/>\n\t\t}<br \/>\n\t}<br \/>\n\t\/\/ now you know how long the anim takes &#8211; it&#8217;s animationDuration<\/p>\n<p>\tanim.Play(); \/\/ uses the new .clip<\/p>\n<p>\tPlayJump( myObject, jumpDistance, animationDuration &gt; 0f ? animationDuration : clip.length );<br \/>\n}<br \/>\n[\/csharp]<\/p>\n<p>Very nice. Fully controllable animations, integrated with gameplay. Aw, yiss!<\/p>\n<h2>Editable curves for your Parametric animations<\/h2>\n<p>There&#8217;s a problem with the above: you make a beautiful jump, which arcs nicely through space, using Unity&#8217;s Curves in the animation window, but &#8230; the distance itself is constantly increasing. You can&#8217;t have the character squat, get ready, then jump &#8211; they will skid along the ground while squatting!<\/p>\n<p>There are many obvious code solutions; they&#8217;re mostly wrong.<\/p>\n<p>Instead, there&#8217;s a 1-line change + new variable (of type AnimationCurve). IF you do it this way, Unity gives you a powerful graphical editor for the PARAMETRIC part of your animation &#8211; IMHO it&#8217;s  easier to use than the Animation window, which is a little tragic, but useful.<\/p>\n<p>[csharp highlight=&#8221;1,6-9,13&#8243;]<br \/>\nprivate IEnumerator CoMove( GameObject myObject, Vector3 finalPosition, float duration, AnimationCurve timingCurve = null )<br \/>\n{<br \/>\n\tfloat elapsedTime = 0;<br \/>\n\tVector3 savedOriginalPosition = myObject.transform.position;<\/p>\n<p>\tif( timingCurve == null )<br \/>\n\t{<br \/>\n\t\ttimingCurve = AnimationCurve.Linear( 0, 0, 1f, 1f ); \/\/ identical to a Lerp<br \/>\n\t}<\/p>\n<p>\twhile( elapsedTime &lt; duration )<br \/>\n\t{<br \/>\n\tmyObject.transform.position = Vector3.Lerp( savedOriginalPosition, finalPosition, timingCurve.Evaluate( (elapsedTime\/transitionTime) ) );<br \/>\n\telapsedTime += Time.deltaTime;<br \/>\n\tyield return new WaitForEndOfFrame();<br \/>\n\t}<br \/>\n}<br \/>\n[\/csharp]<\/p>\n<p>This. Is. Awesome.<\/p>\n<h2>Animation and Physics<\/h2>\n<p>Read Sampsa&#8217;s <a href=\"https:\/\/grovecodeblog.wordpress.com\/2013\/10\/30\/combining-physics-and-animation-in-unity\/\">Excellent short post on this topic, with do&#8217;s and don&#8217;ts, and explanations<\/a>.<\/p>\n<p>Also: run the webplayer demo he included there! It&#8217;s a wonderfully quick way to see for yourself what these (undocumented) core features in Unity actually mean in practice.<\/p>\n<h3>Net changes: enable Kinematic while Animating<\/h3>\n<p>Depending on your situation, you may want to use some or all of Sampsa&#8217;s improvements. But at the very least, you&#8217;ll want to switch to Kinematic during your animation (so that Unity doesn&#8217;t do something dumb, like override the animation with a falling-due-to-gravity).<\/p>\n<p>NB: if you want the animation to play *while falling*, you&#8217;ll have some fun times. You can&#8217;t re-use the official Parenting trick, because you&#8217;re only allowed one set of physics \/ RigidBodies within a single hierarchy of parent\/child\/grandchild\/etc. Bummer.<\/p>\n<p>[csharp highlight=&#8221;3-4,13-14&#8243;]<br \/>\npublic void PlayJump( GameObject myObject, Vector3 jumpDistance, float duration )<br \/>\n{<br \/>\n\t\/** c.f. Sampsa&#8217;s article, but generally you want to do this while you&#8217;re animating *\/<br \/>\n\tmyObject.GetComponent&lt;PlayingCard&gt;().isKinematic = true;<\/p>\n<p>\tStartCoroutine( CoMove( myObject, myObject.transform.position + jumpDistance, duration );<br \/>\n}<\/p>\n<p>private IEnumerator CoMove( GameObject myObject, Vector3 finalPosition, float duration, AnimationCurve timingCurve = null )<br \/>\n{<br \/>\n\t&#8230; \/\/ rest of method here<\/p>\n<p>\t\/** re-enable physics so it can be knocked around, stood on, etc *\/<br \/>\n\tmyObject.GetComponent&lt;PlayingCard&gt;().isKinematic = false;<br \/>\n}<br \/>\n[\/csharp]<\/p>\n<p>&#8230;but you PROBABLY want to enable &#8220;animatePhysics&#8221; too (c.f. Sampsa&#8217;s demo).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>iOS forces you to use pretty animations: if you don&#8217;t tell it otherwise, it will use &#8220;ease-in\/ease-out&#8221; curves to make things feel &#8230; smoother, less jerky. Unity is made for programmers; changes you make to position, size, rotation, etc all happen instantaneously. By default, they won&#8217;t even animate, will teleport instead. So &#8230; moving from [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[69],"tags":[],"_links":{"self":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/3422"}],"collection":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/comments?post=3422"}],"version-history":[{"count":8,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/3422\/revisions"}],"predecessor-version":[{"id":3479,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/3422\/revisions\/3479"}],"wp:attachment":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/media?parent=3422"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/categories?post=3422"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/tags?post=3422"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}