Animation clip

An Animation Clip is a set of Animation Curves that contains all animation data.

Animation Curve

The Animation Curve describes the change of a certain attribute value on an object with time. Internally, the Animation Curve stores a series of time points, and each time point corresponds to a (curve) value, called a frame, or key frame.

When the animation system is operating, the Animation Component calculates the (result) value at the specified time point according to the current animation state and assigns it to the object to complete the attribute change; this calculation process is called sampling.

The following code snippet demonstrates how to create Animation Clips programmatically:

  1. import { AnimationClip, animation, js } from 'cc';
  2. const animationClip = new AnimationClip();
  3. animationClip.duration = 1.0; // The cycle of the entire animation clip, no frame time should be greater than this attribute.
  4. animationClip.keys = [ [ 0.3, 0.6, 0.9 ] ]; // Frame time shared by all curves of this animation clip
  5. animationClip.curves = [{ // The property curve on the component
  6. modifiers: [ // The target is the current node
  7. // "Body" child node
  8. new animation.HierarchyPath('Body'),
  9. // 'MyComponent'
  10. new animation.ComponentPath(js.getClassName(MyComponent)),
  11. // 'value' attribute
  12. 'value',
  13. ],
  14. data: {
  15. keys: 0, // Index to 'AnimationClip.keys', ie [0.3, 0.6, 0.9]
  16. values: [ 0.0, 0.5, 1.0 ],
  17. },
  18. }];

The above Animation Clip contains a curve to control the value property of the MyComponent component of the Body sub-node. The curve has three frames, so that the value property becomes 0.5 at 0.3 seconds and 0.5 at 0.6 seconds and then becomes 1.0 at 0.9 seconds.

Note: the frame time of the curve is indexed into the AnimationClip.keys array by reference. Multiple curves can share the frame time. This will bring additional performance optimizations.

Target

The target of the Animation Curve can be any JavaScript object. The modifiers field specifies how runtime addresses the current node object to the target object.

modifiers is an array, each element of it expresses how to address from the object at the upper level to another object. The object addressed by the last element is the target object of the curve. This behavior is like a file system path, so each element is called a target path.

When the target path is string/number, this indicates the attribute addressed to the upper-level object, which itself specifies the attribute name. Otherwise, the target path must be an object that implements the interface animation.TargetPath.

Cocos Creator has the following built-in classes that implement the self-interface animation.TargetPath:

  • animation.HierarchyPath treats the upper-level object as a node and addresses it to one of its child nodes;
  • animation.ComponentPath treats the upper-level object as a node and addresses it to one of its components.

Target paths can be combined arbitrarily, as long as they have the correct meaning:

  1. // The target object is
  2. modifiers: [
  3. // "nested_1" child node "nested_2" child node "nested_3" child node
  4. new animation.HierarchyPath('nested_1/nested_2/nested_3'),
  5. // 'BlahBlahComponent' component
  6. new animation.ComponentPath(js.getClassName(BlahBlahComponent)),
  7. // of the 'names' attribute
  8. 'names',
  9. // The first element
  10. 0,
  11. ]

When your target object is not a property, but must be returned from a method, custom target path is useful:

  1. class BlahBlahComponent extends Component {
  2. public getName(index: number) { return _names[index]; }
  3. private _names: string[] = [];
  4. }
  5. // The target object is
  6. modifiers: [
  7. // "nested_1" child node "nested_2" child node "nested_3" child node
  8. new animation.HierarchyPath('nested_1/nested_2/nested_3'),
  9. // 'BlahBlahComponent' component
  10. new animation.ComponentPath(js.getClassName(BlahBlahComponent)),
  11. // of the 'names' attribute
  12. {
  13. get: (target: BlahBlahComponent) => target.getName(0),
  14. },
  15. ]

If you want your custom target paths to be serializable, declare them as classes:

  1. @ccclass
  2. class MyPath implements animation.TargetPath {
  3. @property
  4. public index = 0;
  5. constructor(index: number) { this.index = index; }
  6. get (target: BlahBlahComponent) {
  7. return target.getName(this.index);
  8. }
  9. }
  10. // Target
  11. modifiers: [
  12. "nested_1" child node "nested_2" child node "nested_3" child node
  13. new animation.HierarchyPath('nested_1/nested_2/nested_3'),
  14. // 'BlahBlahComponent' component
  15. new animation.ComponentPath(js.getClassName(BlahBlahComponent)),
  16. // of the 'names' attribute
  17. new MyPath(0),
  18. ]

The addressing of the target object is done at runtime, this feature allows Animation Clips to be reused on multiple objects.

Assignment

When the value is sampled, the assignment operator = will be used to set the value to the target object by default.

Sometimes, however, it is not possible use the assignment operator to set values. For example, when the uniform of a Material object needs to be set, it cannot be performed through the assignment operator. This is because the Material object only provides setUniform(uniformName, value) method to change the uniform.

For this case, the curve field valueAdapter provides a mechanism for you to customize how the value to the target object is set.

Examples are as follows:

  1. class BlahBlahComponent {
  2. public setUniform(index: number, value: number) { /* */ }
  3. }
  4. { // Curve
  5. valueAdapter: {
  6. // Called when the curve is instantiated
  7. forTarget(target: BlahBlahComponent) {
  8. // do something useful here
  9. return {
  10. // Called every time the value of the target
  11. // object is set
  12. set(value: number) {
  13. target.setUniform(0, value);
  14. }
  15. };
  16. }
  17. },
  18. };

If you want your custom assignments to be serializable, declare them as classes:

  1. @ccclass
  2. class MyValueProxy implements animation.ValueProxyFactory {
  3. @property
  4. public index: number = 0;
  5. constructor(index: number) { this.index = index; }
  6. // Called when the curve is instantiated
  7. public forTarget(target: BlahBlahComponent) {
  8. // do something useful here
  9. return {
  10. // Called every time the value of the target object
  11. // is set
  12. set(value: number) {
  13. target.setUniform(0, value);
  14. }
  15. };
  16. }
  17. }

animation.UniformProxyFactory is one such example of a custom assignment class, that implements the uniform value of setting the material:

  1. { // the target object
  2. modifiers: [
  3. // 'MeshRenderer' Component
  4. new animation.ComponentPath(js.getClassName(MeshRenderer)),
  5. // 'sharedMaterials' attribute
  6. 'sharedMaterials',
  7. // The first material
  8. 0,
  9. ],
  10. valueAdapter: new animation.UniformProxyFactory(
  11. 0, // Pass index
  12. 'albedo', // Uniform name
  13. ),
  14. };

Sampling

If the sampling time point is exactly equal to the time point of a key frame, the animation data on the key frame is used.

Otherwise, when the sampling time is between two frames, the resulting value should be affected by the two frames of data at the same time. The ratio of the sampling time point to the time interval of two key frames ([0,1]) reflects the degree of influence.

Cocos Creator allows this ratio to be mapped to another ratio to achieve different gradient effects. These mapping methods are called gradient methods. After the ratio is determined, the final result value is calculated according to the specified interpolation method. Both the gradient and interpolation methods affect the smoothness of the animation.

Gradient method

You can specify the gradient method for each frame, or you can specify a uniform gradient method for all frames. The gradient method can be the name of the built-in gradient method or the Bezier control point.

The following lists several commonly used gradient methods.

  • linear keeps the original ratio, that is, linear gradient; this method is used by default when no gradient method is specified.
  • constant always uses a scale of 0, i.e. no gradient; similar to the interpolation method Step;
  • The gradient of quadIn changes from slow to fast.
  • The gradient of quadOut changes from fast to slow.
  • The gradient of quadInOut changes from slow to fast to slow again.
  • The gradient of quadOutIn changes from fast to slow to fast.
  • IBezierControlPoints

Expand comparison

Curve value and interpolation method

Some interpolation algorithms require additional data to be stored in the curve value of each frame. Therefore, the value type of the curve value and the target attribute are not necessarily the same. For numeric types or value types, Cocos Creator provides several general interpolation methods. Also, custom interpolation method can be defined.

When the interpolate property of the curve data is true, the curve will try to use the interpolation function:

  • If the type of curve value is number, Number, linear interpolation will be applied;
  • If the curve value inherits from ValueType, the lerp function of ValueType will be called to complete the interpolation. Most of the value types built into Cocos Creator implement its lerp as linear interpolation.
  • If the curve value is interpolable, the curve value’s lerp function will be called to complete the interpolation 2.

If the curve value does not satisfy any of the above conditions, or when the interpolate property of the curve data is false, there will be no interpolation operation. Always use the curve value of the previous frame as the result.

  1. import { AnimationClip, color, IPropertyCurveData, SpriteFrame, Vec3 } from 'cc';
  2. const animationClip = new AnimationClip();
  3. const keys = [ 0, 0.5, 1.0, 2.0 ];
  4. animationClip.duration = keys.length === 0 ? 0 : keys[keys.length - 1];
  5. animationClip.keys = [ keys ]; // All curves share a list of frame times
  6. // Linear interpolation using values
  7. const numberCurve: IPropertyCurveData = {
  8. keys: 0,
  9. values: [ 0, 1, 2, 3 ],
  10. // The interpolate property is turned on by default
  11. /* interpolate: true, */
  12. };
  13. // Use lerp() of value type Vec3
  14. const vec3Curve: IPropertyCurveData = {
  15. keys: 0,
  16. values: [ new Vec3(0), new Vec3(2), new Vec3(4), new Vec3(6) ],
  17. interpolate: true,
  18. };
  19. // No interpolation (because interpolation is explicitly disabled)
  20. const colorCuve: IPropertyCurveData = {
  21. keys: 0,
  22. values: [ color(255), color(128), color(61), color(0) ],
  23. interpolate: false, // No interpolation
  24. };
  25. // No interpolation (because SpriteFrame cannot interpolate)
  26. const spriteCurve: IPropertyCurveData = {
  27. keys: 0,
  28. values: [
  29. new SpriteFrame(),
  30. new SpriteFrame(),
  31. new SpriteFrame(),
  32. new SpriteFrame()
  33. ],
  34. };

The following code shows how to customize the interpolation algorithm:

  1. import { ILerpable, IPropertyCurveData, Quat, quat, Vec3, vmath } from 'cc';
  2. class MyCurveValue implements ILerpable {
  3. public position: Vec3;
  4. public rotation: Quat;
  5. constructor(position: Vec3, rotation: Quat) {
  6. this.position = position;
  7. this.rotation = rotation;
  8. }
  9. /** this method will be called for interpolation
  10. * @param this starting curve value
  11. * @param to target curve value
  12. * @param t to target curve value
  13. * @param dt he frame time interval between the start curve value and the target curve value
  14. */
  15. lerp (to: MyCurveValue, t: number, dt: number) {
  16. return new MyCurveValue(
  17. // The position attribute is not interpolated
  18. this.position.clone(),
  19. // Rotate property uses Quat's lerp() method
  20. this.rotation.lerp(to.rotation, t),
  21. );
  22. }
  23. /** This method is called without interpolation.
  24. * It is optional, if this method is not defined, the curve value itself (ie `this`) is used as the result value.
  25. */
  26. getNoLerp () {
  27. return this;
  28. }
  29. }
  30. /**
  31. * A curve is created, which realizes a smooth rotation but a sudden change of position throughout the cycle.
  32. */
  33. function createMyCurve (): IPropertyCurveData {
  34. const rotation1 = quat();
  35. const rotation2 = quat();
  36. const rotation3 = quat();
  37. vmath.quat.rotateY(rotation1, rotation1, 0);
  38. vmath.quat.rotateY(rotation2, rotation2, Math.PI);
  39. vmath.quat.rotateY(rotation3, rotation3, 0);
  40. return {
  41. keys: 0 /* frame time */,
  42. values: [
  43. new MyCurveValue(new Vec3(0), rotation1),
  44. new MyCurveValue(new Vec3(10), rotation2),
  45. new MyCurveValue(new Vec3(0), rotation3),
  46. ],
  47. };
  48. }

Loop Mode

You can set different loop modes for Animation Clips by setting AnimationClip.wrapMode().

The table below represents several commonly used looping modes:

AnimationClip.wrapModeDescription
WrapMode.NormalStop after playing to the end.
WrapMode.LoopLoop playback.
WrapMode.PingPongAfter playing from the beginning to the end of the animation, play backwards from the end to the beginning, and so on

For more looping modes, see WrapMode.

1The node of the Animation Clip is the node attached to the Animation Component that guides the use of the Animation State object of the Animation Clip.
2 For numerical values, quaternions, and various vectors, Cocos Creator provides corresponding interpolable classes to implement cubic spline interpolation.