Tutorial:Animations: Difference between revisions
>JulienDethurens |
m Text replacement - "</SyntaxHighlight>" to "</syntaxhighlight>" Tags: mobile web edit mobile edit |
||
(8 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
You've probably noticed the cheesy walking animation as your character moves around (the one with your arms and legs waving back and forth? Yeah, that one). This movement is done with nothing but a little math and some well placed [[Joints]]. You've probably also seen an animation of a different sort while using [[Gear]]. That's the kind of animation this tutorial will teach you about. | You've probably noticed the cheesy walking animation as your character moves around (the one with your arms and legs waving back and forth? Yeah, that one). This movement is done with nothing but a little math and some well placed [[Joints]]. You've probably also seen an animation of a different sort while using [[Gear]]. That's the kind of animation this tutorial will teach you about. | ||
In this tutorial, you will be learning about Animation objects. Animation objects are an update to Roblox that extend the simple character movements, allowing your character to make sweet moves and actions using a combination of [[CFrame]]s, [[interpolation]], and what are called "keyframes". | In this tutorial, you will be learning about Animation objects. Animation objects are an update to Roblox that extend the simple character movements, allowing your character to make sweet moves and actions using a combination of [[CFrame]]s, [[interpolation]], and what are called "keyframes". | ||
Line 25: | Line 26: | ||
==How They Work== | ==How They Work== | ||
An animation consists of a sequence of keyframes that tell your character's [[joints]] where to move, and when to move them. The '''KeyframeSequence''' instance is a container for these keyframes. | An animation consists of a sequence of keyframes that tell your character's [[joints]] where to move, and when to move them. The '''KeyframeSequence''' instance is a container for these keyframes. | ||
A '''Keyframe''' can be considered a snapshot of the current position of your character's parts in the animation. As the animation plays, the positions of the parts are [[interpolation|interpolated]] from the the current keyframe to the next to make a smooth-looking sequence. | A '''Keyframe''' can be considered a snapshot of the current position of your character's parts in the animation. As the animation plays, the positions of the parts are [[interpolation|interpolated]] from the the current keyframe to the next to make a smooth-looking sequence. | ||
Inside each Keyframe are multiple Poses. | Inside each Keyframe are multiple Poses. | ||
One '''Pose''' corresponds to one joint in the character, and tells how the joint will be positioned for that keyframe. How it does this is a bit more complex, so that will be explained later. | One '''Pose''' corresponds to one joint in the character, and tells how the joint will be positioned for that keyframe. How it does this is a bit more complex, so that will be explained later. | ||
Line 48: | Line 51: | ||
-- play the animation | -- play the animation | ||
animTrack:Play() | animTrack:Play() | ||
</ | </syntaxhighlight> | ||
===AnimationTrack=== | ===AnimationTrack=== | ||
An '''AnimationTrack''' has two primary methods: '''Play''' and '''Stop'''. Respectively, these play and stop the animation (pretty darn simple, no?). | An '''AnimationTrack''' has two primary methods: '''Play''' and '''Stop'''. Respectively, these play and stop the animation (pretty darn simple, no?). | ||
Play() has 3 arguments, which control how the animation will play: | |||
*<var>fadeTime</var>: | *<var>fadeTime</var>: The amount of time the animation will fade in. This can make transitions between animations more smooth.{{confirm}} | ||
*<var>weight</var>: | *<var>weight</var>: How well the animation will reach its keyframes.{{confirm}} | ||
*<var>speed</var>: Scales how fast the animation will play (1 for normal speed, 2 for x2 speed, 0.5 for half speed, etc) | *<var>speed</var>: Scales how fast the animation will play (1 for normal speed, 2 for x2 speed, 0.5 for half speed, etc). | ||
Stop() only has one argument: | |||
*<var>fadeTime</var>: Similar to <var>fadeTime</var> in Play(), but fades the animation ''out'' instead of ''in''. | |||
{{GoodToKnow|When an animation reaches the last keyframe, it will keep playing that keyframe until the animation is stopped. This can be useful for non-moving animations (such as standing), which require only 1 keyframe.}} | {{GoodToKnow|When an animation reaches the last keyframe, it will keep playing that keyframe until the animation is stopped. This can be useful for non-moving animations (such as standing), which require only 1 keyframe.}} | ||
AnimationTrack also has a few less-important members: '''AdjustSpeed''', '''AdjustWeight''', and '''KeyframeReached'''. | AnimationTrack also has a few less-important members: '''AdjustSpeed''', '''AdjustWeight''', and '''KeyframeReached'''. | ||
*AdjustSpeed is a method used to adjust the | *AdjustSpeed is a method used to adjust the <var>speed</var> of the animation while it is playing.{{confirm}} | ||
*AdjustWeight is like AdjustSpeed, but for the | *AdjustWeight is like AdjustSpeed, but for the <var>weight</var> and <var>fadeTime</var>.{{confirm}} | ||
*KeyframeReached is an event that fires when the animation reaches a keyframe while playing. | *KeyframeReached is an event that fires when the animation reaches a keyframe while playing. | ||
**The Name of the keyframe is passed as a | **The Name of the keyframe is passed as a parameter. | ||
**Doesn't fire on the starting keyframe (can't ''reach'' when it's already there! probably a bug). | **Doesn't fire on the starting keyframe (can't ''reach'' when it's already there! probably a bug). | ||
**Stops firing after the last keyframe has been reached (so it doesn't work with loops; probably a bug). | **Stops firing after the last keyframe has been reached (so it doesn't work with loops; probably a bug). | ||
Line 203: | Line 209: | ||
*Weight | *Weight | ||
'''CFrame''': | '''CFrame''': This property is really what defines ''where''. It represents what the C1 property of the Pose's corresponding joint will become, when the animation has reached that keyframe. | ||
'''MaskWeight''': I don't know.{{confirm}} | '''MaskWeight''': I don't know.{{confirm}} |
Latest revision as of 06:17, 27 April 2023
You've probably noticed the cheesy walking animation as your character moves around (the one with your arms and legs waving back and forth? Yeah, that one). This movement is done with nothing but a little math and some well placed Joints. You've probably also seen an animation of a different sort while using Gear. That's the kind of animation this tutorial will teach you about.
In this tutorial, you will be learning about Animation objects. Animation objects are an update to Roblox that extend the simple character movements, allowing your character to make sweet moves and actions using a combination of CFrames, interpolation, and what are called "keyframes".
History
In the beginning, characters had no animation. None whatsoever. They would only glide elegantly in the direction they meant to go (as shown in the original ROBLOX Game Trailer). In hindsight, this looks pretty ugly and unpro. But at the time, it was FREAKING AWESOME because you could use your collidable arms to hang off walls and make sneaky rocket kills from above.
But, naturally, all good things must come to an end, and change must be made for the better. That's why, in Spring 2007[unconfirmed], Roblox added character animations. The initial reactions were varied, as you could no longer make epic rocket kills from above without getting flung off the map. Luckily, they had also added a setting to turn animations off, which would allow Robloxians to ease into this new idea of arms and legs that actually moved. But with all new features, there were bugs that needed ironing out. Primarily the flinging. Oh the flinging. Walked into a wall? Too bad! You are now moving at Mach 2 towards the skybox! You wanted to ascend that staircase? Sorry, I though you wanted to be jammed inside it. To remedy these bugs, arms and legs were made uncollidable, which meant no more sweet rocket kills. Eventually, the setting was removed as well, making animations permanent, but the community had gotten used to it by then.
In 2010[unconfirmed], Gear was on the rise. The current animation only incorporated two action movements (swing and lunge). This would get stale very fast if all Gear only had two primitive actions to choose from. It would be much better if Gear makers could create custom animations for each Gear they made. That's why a new suite of Animation Instances were added. But what are they?
What They Are
The animation update added 8[unconfirmed] new Instances:
- Animation
- AnimationTrack
- KeyframeSequence
- Keyframe
- Pose
- Animator
- AnimationTrackState
- KeyframeSequenceProvider
That's really great and all, but how do they work?
How They Work
An animation consists of a sequence of keyframes that tell your character's joints where to move, and when to move them. The KeyframeSequence instance is a container for these keyframes.
A Keyframe can be considered a snapshot of the current position of your character's parts in the animation. As the animation plays, the positions of the parts are interpolated from the the current keyframe to the next to make a smooth-looking sequence. Inside each Keyframe are multiple Poses.
One Pose corresponds to one joint in the character, and tells how the joint will be positioned for that keyframe. How it does this is a bit more complex, so that will be explained later.
How to Play an Animation
The Animation instance is used to refer to an asset. When loading, it makes sure that this asset is an Animation asset (which are off limits), so only Animation-type assets will work (no Models, Hats, Decals, etc). So, while you may be able to upload a KeyframeSequence as a Model (if you can figure out how), you still can't use it for an animation.
"But wait!" you say. Why does it have to be uploaded as an asset? Well, there's one definite reason: So that users can't make their own animations. Now before you hit me, let me make a point. Animations are very preliminary. That means not everything necessarily works, or the API isn't set in stone. It is also very difficult to actually make animations without first creating a tool that utilizes the API, which is a challenge in itself. Basically, animations are very limited in their current form.
Anyway, Animations have to be loaded into a Humanoid to work. This is done with the LoadAnimation method. Basically, you send an Animation instance as an argument, and it spits out an AnimationTrack, which is used to play the animation.
-- create an Animation instance
local animation = Instance.new("Animation")
animation.Name = "SlashAnim"
animation.AnimationId = "http://www.roblox.com/Asset?ID=68454049"
-- load it to the humanoid; get AnimationTrack
local animTrack = Humanoid:LoadAnimation(animation)
-- play the animation
animTrack:Play()
</syntaxhighlight>
AnimationTrack
An AnimationTrack has two primary methods: Play and Stop. Respectively, these play and stop the animation (pretty darn simple, no?).
Play() has 3 arguments, which control how the animation will play:
- fadeTime: The amount of time the animation will fade in. This can make transitions between animations more smooth.[unconfirmed]
- weight: How well the animation will reach its keyframes.[unconfirmed]
- speed: Scales how fast the animation will play (1 for normal speed, 2 for x2 speed, 0.5 for half speed, etc).
Stop() only has one argument:
- fadeTime: Similar to fadeTime in Play(), but fades the animation out instead of in.
*When an animation reaches the last keyframe, it will keep playing that keyframe until the animation is stopped. This can be useful for non-moving animations (such as standing), which require only 1 keyframe.
AnimationTrack also has a few less-important members: AdjustSpeed, AdjustWeight, and KeyframeReached.
- AdjustSpeed is a method used to adjust the speed of the animation while it is playing.[unconfirmed]
- AdjustWeight is like AdjustSpeed, but for the weight and fadeTime.[unconfirmed]
- KeyframeReached is an event that fires when the animation reaches a keyframe while playing.
- The Name of the keyframe is passed as a parameter.
- Doesn't fire on the starting keyframe (can't reach when it's already there! probably a bug).
- Stops firing after the last keyframe has been reached (so it doesn't work with loops; probably a bug).
That's pretty much all for AnimationTracks.
Animation API or: How I Learned to Stop Worrying and Make Animations
In order to make an animation, one must know how it works. To start, the hierarchy of an animation model looks something like this:
KeyframeSequence
Keyframe
Pose
Pose -- don't be intimidated by nested poses
Pose
...
Keyframe
Pose
Pose
Pose
...
Keyframe
...
...
It's pretty straight-forward. KeyframeSequences contain Keyframes, which contain poses. Though poses are a bit more complex. But as I said, I'll explain later. First, let's get through the simple stuff.
KeyframeSequence
As previously mentioned, a KeyframeSequence is a container of Keyframes. It also has a few other uses, which can control how the animation will run. First, KeyframeSequence has 3 methods:
- AddKeyframe
- RemoveKeyframe
- GetKeyframes
It's pretty obvious what these do, so I wont go into them. Though, it's worth mentioning that the order in which keyframes are added doesn't affect the order in which they will play. But that will be explained later. Next up, KeyframeSequence has 2 properties:
Loop: A bool, this property tells if the animation will loop. That is, once the animation reaches the last keyframe, instead of doing nothing, it will loop back to the starting keyframe.
Priority: An AnimationPriority enum. This property tells which animations have priority when multiple animations are playing. It has 3 possible states:
- 0: Idle
- 1: Movement
- 2: Action
If you haven't figured it out, Movement has priority over Idle, and Action has priority over Movement. Think of it like so: walking would override standing, and swinging a sword would override walking.
I have to mention that Priority seems a bit buggy in that animations of lower priority are canceled out before the one of higher priority starts playing, which may produce a "jump" in the animation. If you're worried about this, always use the Movement priority, which is what most Gear animations use.
*Animations that have the same Priority are prioritized in the order which they are played. So, if you play a walking animation while a standing animation is already playing, it will work out. Also, the standing animation will resume once the walking animation stops.
And that's all for KeyframeSequences. Let's move on to Keyframes.
Keyframe
I told you that animations tell where and when the character will move. The primary function of Keyframes is to provide the when part.
As shown above, Keyframes contain Poses. So, Keyframes have 3 standard methods, each of which are so obvious that I wont go into what they do:
- AddPose
- RemovePose
- GetPoses
More importantly, Keyframes have one and only one property, which is Time. The Time property is what will determine when. This is what will determine the order in which the Keyframes will play. Now, that's great and all, but how does it actually work? Well, Time is a number (a float, to be exact). This number is the amount of time, in seconds, the keyframe should be reached after the animation has started playing. It is not the amount of time between keyframes (if it were, then what would determine the order?). So, a keyframe with a Time of 0, will always be the starting keyframe, and the keyframe with the greatest Time will always be the last keyframe.
"But wait!" you say. What if two keyframes have the same Time? Well, the answer is, I don't know. Maybe child order works as a tie-breaker, maybe it's undefined. Look into and correct me[unconfirmed].
"But wait!!" you exclaim. What about negative times? Stop asking questions.
Anyway, as an example, here is a list of Keyframes, labeled as NAME:TIME, along with a timeline of when each keyframe would play:
A:0.0, B:0.1, C:0.5, D:0.8, E:0.9, F:1.0
----------------------------------------
0.0|A
0.1|B
0.2|
0.3|
0.4|
0.5|C
0.6|
0.7|
0.8|D
0.9|E
1.0|F
Note how the distribution of these keyframes is uneven. Depending on your animation, you may want to give keyframes specific times in order to adjust the speed of interpolation between each keyframe. Usually, however, you'll want the timing to be even, so that the interpolation speed between keyframes is the same across the entire animation.
Let's see how we can even out the timing. This animation lasts 1 second (the last keyframe 'E' has a Time of 1). There are 6 keyframes. The first keyframe starts at 0, so we wont count that. So what's 1 second divided by 5 keyframes? Easy, 0.2! That's the amount of time we want between each keyframe in order to get an even animation. Now we just need to adjust each keyframe by adding 0.2 from the previous keyframe.
A:0.0, B:0.2, C:0.4, D:0.6, E:0.8, F:1.0
----------------------------------------
0.0|A
0.1|
0.2|B
0.3|
0.4|C
0.5|
0.6|D
0.7|
0.8|E
0.9|
1.0|F
As you can see that looks a lot better. Now that you know the essentials of Keyframes, let's move on to Poses.
Pose
While Keyframes provide when, Poses provide where. These will tell where, at each keyframe, each part should be. You know how I said Poses are complex and that I'd tell you later? Well later is now.
So, in characters, there are joints (usually Motor6Ds) that hold the body parts together. At the moment, all of these joints are held under the Torso part. Because of this, the Torso is considered the root of the character. Remember this.
Poses have 3 methods, each of which should be obvious but I'll kind of explain them anyway.
- AddSubPose
- RemoveSubPose
- GetSubPoses
These methods handle subposes, which are just more Pose instances. The quirk with Poses is that they can contain more Poses. From here-on, I'll use Pose structure to refer to this hierarchy of Poses. How it is used will be explained below.
As mentioned previously, one Pose corresponds to one joint. But how will the Pose get that joint? The answer lies in the hierarchical structure of the Poses (Pose structure). Poses can contain Subposes, which determines how they will get a joint in the character. Here's an example that shows exactly how:
- You start with the Torso, which is the root of the character. So, you start with a Pose named "Torso". Inside the Torso, is a Right Shoulder joint. The Right Shoulder joint has Part0 set to the Torso (also the joint's parent), and Part1 set to the Right Arm part.
- We want to control this Right Shoulder joint. So, we add a subpose named "Right Arm" to the "Torso" pose. We add it as a subpose here because the joint we want has Part0 set to the Torso[unconfirmed]. We name the subpose "Right Arm" because the joint we want has Part1 set to the Right Arm[unconfirmed].
Suppose we want to go further and get the RightGrip joint from a Tool. The RightGrip has Part0 set to the Right Arm, and Part1 set to the Tool's Handle. So, to get that joint, all we need to do is add a subpose to the Right Arm pose named "Handle". Now the pose structure would look like this:
Torso
Right Arm
Handle
And that's all there is to it. For a Pose to gain control of a joint, the joint has to meet the following criteria:
- Part0 must have the same Name as the Pose's parenting Pose.[unconfirmed]
- Part1 must have the same Name as the Pose.[unconfirmed]
*You don't have make a Pose for every joint in the character, only for the ones you want to animate! However, note that if two Keyframes in an animation contain different Pose structures, Roblox will crash (yes, this is a bug).
Phew. Now that we done with the complex part, let's talk about properties. Poses have 3 of them:
- CFrame
- MaskWeight
- Weight
CFrame: This property is really what defines where. It represents what the C1 property of the Pose's corresponding joint will become, when the animation has reached that keyframe.
MaskWeight: I don't know.[unconfirmed]
Weight: I don't know.[unconfirmed]
*Remember Skateboards? Skateboard animations have the skateboard itself as the root instead of the Torso (a joint in the skateboard is connected to the Torso)[unconfirmed]. This is how the animation is able to lean the entire character as you move.
The Other Ones
"But wait!" you say. You forgot a few classes! Indeed I did. There are 3 classes I haven't talked about yet, but they require their own section (for good reason) They are the following:
KeyframeSequenceProvider is probably the most useful of the three, so I'll mention that last.
To start, there's the Animator object. This one doesn't do much. It's only got one method, which is LoadAnimation. This probably works like the LoadAnimation of the Humanoid, receiving an Animation, and returning an AnimationTrack. However, there's no way to even get an Animator instance, so I guess it's useless[unconfirmed].
As for AnimationTrackState, it doesn't even have any unique members. Can't be instanced. Useless[unconfirmed].
KeyframeSequenceProvider
Since it's actually somewhat useful, it can have it's own section. KeyframeSequenceProvider has 3 methods. No, it is not obvious as to what they do, so I'll have to actually explain them.
- GetKeyframeSequence (int assetId)
- RegisterKeyframeSequence (KeyframeSequence keyframeSequence)
- RegisterActiveKeyframeSequence (KeyframeSequence keyframeSequence)
GetKeyframeSequence gets a KeyframeSequence from an asset ID. It can only get Animation-type assets[unconfirmed], which are indeed KeyframeSequences. This might be useful if you need to pick apart an existing animation for some reason.
RegisterKeyframeSequence returns a Content value. This value is the hash of the KeyframeSequence. You can use this hash to test the animation by following these steps[unconfirmed]:
- Save the KeyframeSequence to your files as a model
- Use the Selection service to select the KeyframeSequence (since you can't put it in the workspace)
- Use the Save selection as a Model to a file button
- Make the file name the hash value (no extension!)
- Move the file to Roblox's Temporary Files folder
- Make a new Animation
- Set the AnimationId to the hash value
- Test away
RegisterActiveKeyframeSequence has a long name. It probably does something similar to the one above[unconfirmed].
Followup
Soon.