Link Search Menu Expand Document
Table of contents

Animations Example

Animations Example

This example can be found in the Examples folder of the Gorilla Engine SDK.

It introduces:

  • Which types of animations exist in Gorilla Engine
  • When to use which type
  • How add animations to your plugin UI

Summary

Gorilla Engine supports two types of animations: filmstrips and Lottie. Both types have their unique use cases and also partially overlap in the ways they can be used. This example shows how to use Lottie animations in-depth and only gives a brief overview of filmstrips.

Filmstrips

A filmstrip is a flipbook style animation. It can be added to knobs, sliders and meters. To use it, you have to provide a flipbook style image in png format. Gorilla Engine will then, depending on the value of the knob, slider or meter, automatically choose the correct page from the flipbook. The flipbook is just a metaphor and in fact the individual pages or frames of the animation must be arranged in a sprite map like image. All frames of the animation must be arranged on top of each other in a single image. Gorilla Engine then automatically selects the correct area of the image to display. A typical number of individual frames per animation is 128.

Example

The first eight frames of a filmstrip can be seen below. To see the whole file have a look at [email protected] in assets folder of the Piano Pad example of the Gorilla Engine SDK.

Dynamic Controls Example

Using a filmstrip

To apply a filmstrip to a knob for example you just have to provide the path to the png file and the number of frames in that file. This is how you can add a filmstrip from a yaml file:

- knob:
  x: 0
  y: 0
  width: 80
  height: 80
  value&: instrument.volume # The knob is bound to the volume parameter. The value of this parameter therefore determines which frame of filmstrip is visible
  filmstrip:
    path: images/[email protected] # The path to the filmstrip png file relative to the plugins asset directory
    count: 128 # The number of individual frames in the filmstrip

Lottie

Lottie animations are different from filmstrips in that they do not rely on images but small snippets of JSON that describe vector shapes and colors and how they change throughout the animation. The main advantage of this approach is that the animations can be interactive if they need to. You can for example add a hover effect to an animated knob which is not possible with a filmstrip. Lottie animations can be created with industry standard tools like Adobe Affer Effects and can be applied to knobs, sliders, triggers, meters and toggles. They can also be used as standalone components for when you just need a loading indicator or something similar.

Standalone animations

To create an animation that is not attached to another control you can just instantiate it, passing the desired properties, and then append it to the viewport.

const loadingAnimation = new GorillaEngine.UI.LottieAnimation({
  x: 0,
  y: 0,
  width: 80,
  height: 80,
  // Path to the animation file; absolute or realtive to the plugins asset directory
  filePath: 'myLoadingIndicator.json',
  // Whether playback starts automatically after creation
  autoplay: false,
  // Whether playback loops or stops after reaching the last frame
  loop: true
});

// Finally append the animation to viewport
GorillaEngine.UI.getControlById('root').appendChild(animation);

Playback of the animation is then controlled from JavaScript by a set of functions available on each animation instance. Say you want to loop your animation until some long running task completes:

// Start playback
loadingAnimation.play();

// Imaginary wait for some long running task to complete
await new Promise(resolve => setTimeout(_ => resolve(), 5000));

// Stop playback (pause and reset to frame 0)
loadingAnimation.stop()

// Or pause playback at the current frame
loadingAnimation.pause()

You can also manually select the frame to display. Say you are downloading something and want to display the progress:

// Imaginary download start where 'size' is the total number of bytes to load and 'download' is 
// something that emits a 'progress' event every now and then to indicate how many bytes have been
// downloaded already.
const { size, download } = await startDownload('https://example.com/my-large-file');

// Handle the progress event by setting the animations current frame based on a linear transformation
download.on('progress', progress => loadingAnimation.setFrameFromLinearTransform(progress, 0, size));

We used the setFrameFromLinearTransform shorthand to determine which of the animations frame corresponds to the given progress. The following calls to setFrame and setFrameFromLinearTransform are equivalent.

const min = -15;
const max = 33;
const value = 17;

animation.setFrame((value - min) * (amination.totalFrames / (max - min)));
animation.setFrameFromLinearTransform(value, min, max);

If your calculations are more involved than a linear transform, the following properties and setFrame will give you full control.

// Display the total number of frames in the loaded animation
animation.totalFrames

// Display the duration of the animation
animation.duration

// Display the currently selected frame of the animation
animation.currentFrame

Shorthands

To add an animation to a control, like for a example a knob, you just instantiate the animation and then pass it to the controls animation property. Doing so you can omit properties like x, y, width and height on the animation.

// Create the animation
const knobAnimation = new GorillaEngine.UI.LottieAnimation({
  filePath: 'knobAnimation.json'
}):

// Pass the animation to a knobs animation property
const knob = new GorillaEngine.UI.Knob({
  x: 0,
  y: 0,
  width: 100,
  height: 100,
  min: 0,
  max: 1,
  value: 0.5,
  stepSize: 0.005,
  animation: knobAnimation
});

The animation is now connected to the knob and whenever the knobs value changes, the animation follows. For knobs, sliders and meters Gorilla Engine calculates the frame to display using setFrameFromLinearTransform with the controls value, min and max. For triggers the whole animation plays once, when the trigger is clicked. For toggles the animation plays once when the toggle is checked and once backwards when unchecked. If you just want to connect the value of the control to animation but don’t want the animation to inherit the bounds of the control you can set autoConfigure: false on the animation. In that case you have to manually set x, y, width and height.

Interactivity

Lottie animations can be interactive. That is within limits of course. Each animation object exposes a setProperties function that can be used to modify properties of the animation. Say you have an animated trigger an you want that trigger to change it’s color whenever it is hovered by the mouse pointer.

// Create the animation
const triggerAnimation = new GorillaEngine.UI.LottieAnimation({
  filePath: 'triggerAnimation.json'
}):

// Pass the animation to a triggers animation property
const trigger = new GorillaEngine.UI.Trigger({
  x: 0,
  y: 0,
  width: 100,
  height: 100,
  animation: triggerAnimation
});

// Register a 'mouseEnter' event handler with the trigger
// that sets the fill color of all layers in the animation 
// to white when the mouse enters the trigger control
trigger.on('mouseEnter', () => {
  triggerAnimation.setProperties('**', {
    fillColor: [1, 1, 1]
  });
});

// Register a 'mouseExit' event handler with the trigger
// that sets the fill color of all layers in the animation 
// to red when the mouse exits the trigger control
trigger.on('mouseExit', () => {
  triggerAnimation.setProperties('**', {
    fillColor: [1, 0, 0]
  });
});

The setProperties function takes two arguments. The first one is a selector that determines which elements of the animation are affected by the function call. The selector can either be a glob expression like ** which selects all elements, a wildcard expression like Some Layer.* which selects all shapes in Some Layer, or it can be a path to a specific element of the animation like Some Layer.Some Shape.Fill 1.

Which paths are valid depends on the animation and which names were given to the layers and shapes of the animation during the design process i.e. in Adobe After Effects. You can also inspect the animation in various editors like the Lottie Editor.

The second argument to the setProperties function call is an Object which’s properties determine the effect of the function call. Here is an example of all the possible properties that can be modified in an animation:

 animation.setProperties('**', {
  fillColor: [1, 0, 0], // Fill bright red
  fillOpacity: 50, // Change fills to 50% opacity
  strokeColor: [0, 1, 0], // Color strokes bright green
  strokeOpacity: 50, // Change strokes to 50% opacity
  strokeWidth: 3.5, // Change stroke width to 3.5px
  transformAnchor: [100, 100], // Move the anchor to x = 100, y = 100
  transformPosition: [200, 200], // Move to x = 200, y = 200
  transformScale: [50, 50], // Scale to 50% size in x and y axis
  transformRotation: 180, // Rotate by 180Β°
  transformOpacity: 50, // Change opacity to 50%
})

Creating from yaml

Lottie animations can of course also be created from yaml. Although interactivity can only be controlled from JavaScript this can still be handy, especially when using the shorthand for knobs etc.

window:
  id: 'window'
  width: 1200
  height: 800
  children:

    # Create a standalone animation
    - lottie_animation:
      x: 0
      y: 0
      width: 200
      height: 200
      file_path: loading indicator.json
      autoplay: true
      loop: true

    # Create an animation along with another control
    - knob:
      x: 200
      y: 0
      width: 200
      height: 200
      value&: instrument.reverb
      animation: reverb animation.json

    # The animation property can be bound to a model
    - levelmeter:
      x: 400
      y: 0
      width: 200
      height: 200
      value&: instrument.volume
      animation&: animationController.volumeAnimation

    # Interactivity is still supported from JavaScript. 
    # You could for example do this: 
    #
    # const animation = GorillaEngine.UI.getControlById('volumeAnimation')
    #
    # animation can now be controlled as if it was created by
    # new GorillaEngine.UI.LottieAnimation()
    - knob:
      x: 600
      y: 0
      width: 200
      height: 200
      value&: instrument.volume
      animation: 
        file_path: volume animation.json
        id: volumeAnimation

Supported Adobe After Effects features

Not all of the features available in Adobe After Effects work with our Lottie implementation. This list gives an overview of supported and unsupported features.

Shapes Supported
Shape πŸ‘
Ellipse πŸ‘
Rectangle πŸ‘
Rounded Rectangle πŸ‘
Polystar πŸ‘
Group πŸ‘
Trim Path (individually) πŸ‘
Trim Path (simultaneously) πŸ‘
Renderable Supported
Fill πŸ‘
Stroke πŸ‘
Radial Gradient πŸ‘
Linear Gradient πŸ‘
Gradient Stroke πŸ‘
Transforms Supported
Position πŸ‘
Position (separated X/Y) πŸ‘
Scale πŸ‘
Skew ⛔️
Rotation πŸ‘
Anchor Point πŸ‘
Opacity πŸ‘
Parenting πŸ‘
Auto Orient πŸ‘
Interpolation Supported
Linear Interpolation πŸ‘
Bezier Interpolation πŸ‘
Hold Interpolation πŸ‘
Spatial Bezier Interpolation πŸ‘
Rove Across Time πŸ‘
Masks Supported
Mask Path πŸ‘
Mask Opacity πŸ‘
Add πŸ‘
Subtract πŸ‘
Intersect πŸ‘
Lighten ⛔️
Darken ⛔️
Difference ⛔️
Expansion ⛔️
Feather ⛔️
Mattes Supported
Alpha Matte πŸ‘
Alpha Inverted Matte πŸ‘
Luma Matte πŸ‘
Luma Inverted Matte πŸ‘
Merge Paths Supported
Merge ⛔️
Add ⛔️
Subtract ⛔️
Intersect ⛔️
Exclude Intersection ⛔️
Layer Effects Supported
Fill ⛔️
Stroke ⛔️
Tint ⛔️
Tritone ⛔️
Levels Individual Controls ⛔️
Text Supported
Glyphs ⛔️
Fonts ⛔️
Transform ⛔️
Fill ⛔️
Stroke ⛔️
Tracking ⛔️
Anchor point grouping ⛔️
Text Path ⛔️
Per-character 3D ⛔️
Range selector (Units) ⛔️
Range selector (Based on) ⛔️
Range selector (Amount) ⛔️
Range selector (Shape) ⛔️
Range selector (Ease High) ⛔️
Range selector (Ease Low) ⛔️
Range selector (Randomize order) ⛔️
expression selector ⛔️
Other Supported
Expressions ⛔️
Images ⛔️
Precomps πŸ‘
Time Stretch πŸ‘
Time remap πŸ‘
Markers πŸ‘

Known issues

  1. Rounded rectangles imported from Figma into Adobe After Effects sometimes don’t have rounded corners. Paths work fine though.
  2. Linked animations won’t work.

Comparison

While use cases of filmstrips and Lottie animations partially overlap each of the implementations has it’s niche.

Test Filmstrips Lottie
Use cases skeuomorph, non-vector visuals 2d shapes
Interactivity no yes
File size Β larger Β smaller
Popularity lower Β higher
Standalone Β no yes
Controls knobs, sliders, meters knobs, sliders, meters, toggles, triggers
CPU utilization lower Β higher