Tap Through Animation Layers
FeaturedHey Everyone!
Let’s see how we can tap through different animation layers!
In my project I currently have three animations in different layers.
Let’s create a script to this object that we can use to play our animations (Inspector Panel -> Add Component -> Script). We want the script to play an animation on turn on and when you tap, so let’s set this script to Initialize so we can bind both events in the same script.
Since we’re dealing a list of animations, the first thing we will add to this script is an input to get the animation, and get the names of the animation we want to play.
// @input Component.AnimationMixer animMixer
// @input string[] tapAnimLayer
When you go back to your Script component in the Inspector panel you should see a field to select your animation, and a way to add strings to a list. We’ll add our animation component to the Anim Mixer field, and the name of our animation layers to the Tap Anim Layer list.
Since both on tap and on lens turn on we will do the same thing (play animation), let’s create a helper function to play our animation. We’ll play from second 0, and loop the animation infinitely (-1). You can look at the animationMixer API page to learn more.
function playAnimIndex(index)
{
script.animMixer.start(script.tapAnimLayer[index], 0, -1);
script.animMixer.setWeight(script.tapAnimLayer[index], 1.0);
}
Now we can call this function on TurnOn and onTap. We’ll also add the index of the animation we want to play. We’ll start with the first in the list.
// Starting animation index
var currentAnimLayerIndex = 0;
// Function runs at start of lens
function onLensTurnOnEvent()
{
// start the new animation
playAnimIndex(currentAnimLayerIndex)
}
var turnOnEvent = script.createEvent("TurnOnEvent");
turnOnEvent.bind(onLensTurnOnEvent);
function onTap(eventData)
{
// start the new animation
playAnimIndex(currentAnimLayerIndex);
}
var tapEvent = script.createEvent("TapEvent");
tapEvent.bind(onTap);
Next we’ll get the onTap function to play the next animation in the list. We’ll create some helper functions to help us achieve this.
Since we don’t want animations to affect each other, we need to tell the current animation to stop.
function stopAnimIndex(index)
{
script.animMixer.stop(script.tapAnimLayer[index]);
script.animMixer.setWeight(script.tapAnimLayer[index], 0.0);
}
Then we also need to tell the Lens to set our currentAnimLayerIndex to the next one in the list. We use a modulus to wrap around so that once we hit the end of the list, we’ll start back on the first animation.
function incrementAnimIndex()
{
currentAnimLayerIndex = currentAnimLayerIndex + 1;
currentAnimLayerIndex = currentAnimLayerIndex % script.tapAnimLayer.length;
}
Finally, we’ll modify our onTap function to call these helper functions before we play the animation.
function onTap(eventData)
{
// stop current animation
stopAnimIndex(currentAnimLayerIndex);
// increment current animation
incrementAnimIndex();
// start the new animation
playAnimIndex(currentAnimLayerIndex);
}
And you should be able to tap through your animation layers!
Happy crafting!
Our final script:
// @input Component.AnimationMixer animMixer
// @input string[] tapAnimLayer
// Starting animation index
var currentAnimLayerIndex = 0;
// Function runs at start of lens
function onLensTurnOnEvent()
{
// start the new animation
playAnimIndex(currentAnimLayerIndex);
}
var turnOnEvent = script.createEvent("TurnOnEvent");
turnOnEvent.bind(onLensTurnOnEvent);
function onTap(eventData)
{
// stop current animation
stopAnimIndex(currentAnimLayerIndex);
// increment current animation
incrementAnimIndex();
// start the new animation
playAnimIndex(currentAnimLayerIndex);
}
var tapEvent = script.createEvent("TapEvent");
tapEvent.bind(onTap);
function incrementAnimIndex()
{
currentAnimLayerIndex = currentAnimLayerIndex + 1;
currentAnimLayerIndex = currentAnimLayerIndex % script.tapAnimLayer.length;
}
function playAnimIndex(index)
{
script.animMixer.start(script.tapAnimLayer[index], 0, -1);
script.animMixer.setWeight(script.tapAnimLayer[index], 1.0);
}
function stopAnimIndex(index)
{
script.animMixer.stop(script.tapAnimLayer[index]);
script.animMixer.setWeight(script.tapAnimLayer[index], 0.0);
}
Hi, would there be anyway to have each animation play its own sound?
Hi Zachery!
Yep! You would do something very similar.
First create an Audio component for each of your sound. In the objects panel: Add New -> Empty Object; then in Inspector panel: Add Component -> Audio. put your mp3 herein the Audio Track field.
You can pass these Audio components in via an array input like in the guide.
Then when you tell the script to play animation in playAnimIndex, you can tell it to play the AudioComponent as well.
Finally, we tell the sound to stop when we stop the animation in stopAnimIndex
Let me know how it goes! Would love to see what you create :)
Cheers,
Jonathan
I used the final script but Im getting any error
SyntaxError: empty expression not allowed (line 40)
at [anon] (Hipster/Scripts/MultiAnim:40) internal
Any idea what could be the problem?
Hi Wayne,
Can you show us your script / line 40? This looks like a JavaScript error, but can't be sure without the code.
Thanks in advance!
Jon
Problem fixed... missed the } off right at the end.
All working but it brings me to another question.
All the animations look at the moment .
Is it possible to keep the idle animation on loop until triggered with the tap and then play the other animation just once then revert back to idle?
Glad to hear you found the issue :)
You can take a look at Interactive Tap templates for an example of that. It's a template that's design to play an animation and then go back to the idle animation.
To have that in this script, something you can do is: instead of
You can use something like
where playIdle is a function that plays your idle animation. You can see more about this function here.
Cheers!
Jon
Thanks Jon,
I replaced
so the script now reads as
// -----JS CODE-----
//@input Component.AnimationMixer animMixer
//@input string[] tapAnimLayer
// Starting animation index
var currentAnimLayerIndex = 0;
// Function runs at start of lens
function onLensTurnOnEvent()
{
// start the new animation
playAnimIndex(currentAnimLayerIndex);
}
var turnOnEvent = script.createEvent("TurnOnEvent");
turnOnEvent.bind(onLensTurnOnEvent);
function onTap(eventData)
{
// stop current animation
stopAnimIndex(currentAnimLayerIndex);
// increment current animation
incrementAnimIndex();
// start the new animation
playAnimIndex(currentAnimLayerIndex);
}
var tapEvent = script.createEvent("TapEvent");
tapEvent.bind(onTap);
function incrementAnimIndex()
{
currentAnimLayerIndex = currentAnimLayerIndex + 1;
currentAnimLayerIndex = currentAnimLayerIndex % script.tapAnimLayer.length;
}
function playAnimIndex(index)
{
script.animMixer.startWithCallback(script.tapAnimLayer[index], 0, 1, playIdle);
script.animMixer.setWeight(script.tapAnimLayer[index], 1.0);
}
function stopAnimIndex(index)
{
script.animMixer.stop(script.tapAnimLayer[index]);
script.animMixer.setWeight(script.tapAnimLayer[index], 0.0);
and I get this error
09:35:26 ReferenceError: identifier 'playIdle' undefined
at playAnimIndex (Hipster/Scripts/MultiAnim:33)
at onLensTurnOnEvent (Hipster/Scripts/MultiAnim:10) preventsyield
Any idea what the problem is ? sorry I don't really have any coding knowledge
Wayne
Hi Wayne,
Ah this error is because you haven't made a playIdle function.
As mentioned above, playIdle should be a function you define to stop any current animation and play your idle animation. For example:
In this case we added a new variable to define our "idle" animation in the Inspector panel at the top of the file.
We should also add a script to stop the idle animation similar to how we stopAnimIndex so that when we play a tap animation our idle animation will stop.
Finally we can tell the script to use these new functions.
When the lens start we want to play our idleAnimLayer instead of our tapAnimLayer
and when we tap we want to stop our idle animation as well.
So the overall lens script can look something like:
Let me know how it goes!
Cheers,
Jon
Hi there, Jon! This is an excellent tutorial, thank you.
I was actually wondering if I could get some help accomplishing something similar.
I have an animation of a bird separated into three layers: Landing, Idle, and Launch. I'd like for the bird to come into frame, landing on a surface at the start of the lens. Then, once that animation is done, cycle through the idle animation. Finally, when the user taps the screen I'd like for the launch animation to play, where he flies out of frame.
I've tried scouring the forums for help, but this tutorial has been the most close-ish I've come to what I want to do. If I had Javascript skills I'd try and figure it out myself, but sadly I'm just a 3D Animator. :)
I would love your thoughts if you have the time!
Hey!
you can use the behavior script to trigger each animation! Let me know if you have any questions.
Ben
Hi there, Ben!
Unfortunately, I haven't been able to get the behavior script to work in my scene at all. I've actually tried using it in a fresh scene without my 3D animation and the script works fine; however, it refuses to function in my scene with my flying bird. It's so odd because other scripts like the TapAnim script, IdleAnim script, and custom scripts all work just fine. I'm sure it's something I'm doing wrong.
I followed the exact instructions given on how to export my animations from Maya, so everything should be properly set up. Maybe it's a matter of where I'm putting the script?
Hi Abigail,
If you upload your project file to google drive, i will take a look for you.
I'm having trouble getting this script to run at all. I've added the script to my 3D object, below the Animation Mixer in the Inspector. However, there are no fields available in the inspector view, to add animation layers.
Peter Steiner
Hey, looks like your script isn't saved make sure to press Crtl + S in the Script Editor to save the script