Writing Servo Animations

As we discussed in the last section, an animation in Johnny-Five is created and then you enqueue segments that run first-in, first-out. We're going to go from the inside out in our exploration of creating an animation: first, we'll learn about writing keyframes, then segments, and finally we'll explore the Animation object.

Writing keyframes

Writing keyframes are at the core of the Animation API—the power of this API is its ability to tween between our keyframes. Each keyframe is an object, and you'll pass your keyframes into your segment via an array. Remember: you'll want to write a keyframe for each of your cue points.

The keyframe object

As each keyframe is an object, we have access to a few properties that we can establish for each one:

Keyframe

Properties

degrees

degrees is what the name implies; the degree you want the servo to be at when the keyframe is reached. It should be an integer value between 0 and 180 inclusive.

step

step is similar to degrees, but relative to the last keyframe; for instance, if degrees is set to 135 in the first keyframe and step is set to -45 in the next keyframe, the second keyframe will move the servo to 135 - 45 = 90 degrees.

easing

When you create an animation segment, by default any tweening is done at the same speed for the entire span between two keyframes. The easing functions are applied to these frames to change the speed. This can make a motion look more fluid or serve a practical purpose; easing can make quick movements in different directions easier on your servos and equipment.

There are several easing functions available via Johnny-Five's use of the ease-component module; a very popular one is inOutCirc, which causes the frames to move slowly at first, quickly increasing in the middle, and slowing down again towards the end. See the Johnny-Five and ease-component docs for more examples of easing functions.

Keyframe

Properties

copyDegrees

copyDegrees calculates the calculated or explicitly set value from the frame at the index given. For instance, let's say we have two keyframes: one has degrees set to 90, and the second has step set to 45. If we create a third keyframe with copyDegrees set to 0, it will copy the first frame and set to 90. If we set the index to 1, it will copy the second frame and 90 + 45 = 135 degrees will be the setting for the third keyframe.

copyFrame

copyFrame is similar to copyDegrees, but it copies all the attributes of the given frame instead of just the degrees; this includes easing functions and so on.

position

position is an advanced and newer concept in Johnny-Five's Animation API. This allows you to give three tuples that represent a 3D coordinate in space, and it will move the array of servos to this point.

It is worth noting here that you need more than just Johnny-Five for this to work; you'll need an IK (Inverse Kinematic) solver, such as Donovan Buck's tharp project. Going into the details of position is outside the scope of this book, as it is still being actively developed and the functionality might change. However, if you're looking to build a robot that moves with you, check this out.

Keyframe shorthand

You can also define keyframes without using objects; if you pass your keyframes as an array of integer values, each integer value will be interpreted as a step value. Consider the following array:

keyFrames: [0, 90, -45, 90, -90]

This will (assuming a 0 start point) move the servo to 0, 90, 45, 135, and 45 degrees for each cue point.

You can also use non-integer values in shorthand syntax to mean specialized values:

Values

Properties

null

When null is passed into a shorthand keyframes array, its value depends on its position. If it is the first keyframe, the segment will use the existing servo position as its first keyframe. If it is last in the array, it will copy the previous keyframe's value.

However, if it is used between two frames, something interesting happens: it will drop the keyframe, and tweening will be calculated between the keyframe before and after the null value. This can be handy if you'd like a servo to take more time between two keyframes.

false

false, used anywhere in a shorthand keyframe array, will copy the last known value. It will not move the servo between the two keyframes.

So, in closing, you can use either a keyframe object, a number, null, or false to represent a keyframe in a segment definition. Now that we've explored the attributes available on a keyframe object, let's write some keyframes to match certain situations.

Examples of writing keyframes

Let's explore some examples of keyframes:

Example 1: Write a set of keyframes that starts a servo at the last position it was at, moves to it by 90 degrees with an inOutCirc easing, then moves it by 45 degrees back with no easing:

  1. The first keyframe can be handled using the null value:
    var myFrames = [null];
  2. The second requires a keyframe object because we want to establish an easing function:
    var myFrames = [null, { degrees: 90, easing: 'inOutCirc' }];
  3. The last could be done with a keyframe object:
    var myFrames = [null, { degrees: 90, easing: 'inOutCirc' }, { step: -45 }];

However, we can also use shorthand—remember that passing a number as a keyframe will lead to using that number as a step value:

var myFrames = [null, {degrees: 90, easing: 'inOutCirc' }, -45];

Among the preceding alternatives, which one you use is entirely up to your own preference.

Example 2: Write a set of keyframes that starts the servo at 0, moves to 90, spends two cue points moving to 180, and then moves to 135:

  1. The first and second keyframes can be set via a keyframe object, as follows:
    var myFrames = [{degrees: 0}, {degrees: 90}];
  2. Otherwise for the second keyframe, we can use a shorthand, as shown here:
    var myFrames = [{degrees: 0}, 90];

As we know that the first keyframe will set the servo to 0, we can use step instead of degrees. For the next step, we want the servo to take two cue points to move to 180. We could calculate how to do this with standard keyframes, or we could use a shorthand null to tell the segment to skip that cue point and tween between 90 and 180 over two cue points:

var myFrames = [{degrees: 0}, 90, null, {degrees: 180}]

Now that you've learned how to write keyframe arrays and objects, let's take a look at writing the rest of our Animation object.

Writing segments

We know that segments consist of an array of keyframes, an array of cue points, a duration, and some options. Let's take a look at the options available to us in each segment, and then build some segments to get us ready for our next project.

Segment options

The segment options and their properties are as follows:

Segment options

Properties

target

You can use this in the segment to override the target this segment operates on; this is usually specified by the animation, so setting it in the segment overrides the animation's assignment.

easing

Just like keyframes, you can set an easing function for an entire segment. Note that easing functions stack; if you use an easing function on keyframes and a segment, the tweening will calculate the keyframe easing and then the segment easing.

loop

This is a Boolean; true causes this segment to loop when enqueued until it is stopped by the animation it was queued to.

loopBack

If you'd like a looping segment to loop back to some place other than the first cue point, you can specify the index of that cue point here. For instance if a segment has the cue points [0, .25, .75, 1] and you set loopBack to 1, the segment will loop the cue points [.25, .75, 1] only.

metronomic

This is a Boolean; if true, the segment will run from the first cue point to the last, then back to the first in reverse order. You can combine this with the loop option, but it will not loop by default.

progress

This attribute can be either used as information about a running segment (as we'll see in a later project) or set; you can set the current progress of the animation if you'd like to start at a different point. This should be a value between 0 and 1, much like cue points.

currentSpeed

Much like progress, this can be used as information and set. The default is 1.0, and this changes the speed multiplier at which the segment runs.

fps

This sets the maximum frames per second that a segment can run at. The default is 60. Changing the maximum fps of a segment will not change the speed or cue points.

Multiple servos in one segment

More often than not, you'll be animating more than one servo at a time. How do you handle keyframes for more than one servo? By passing an array of arrays; each array represents the keyframes for a servo in the target servo array. For instance, the following segment contains the keyframes for two servos in an array:

var myMultiServoSegment = {
  duration: 2000,
  cuePoints: [0, .5, 1],
  keyFrames: [
    [{degrees: 135}, -45, -45]
    [{degrees: 45], 45, 45]
  ]
}

Examples of writing segments

Let's walk through a couple of examples of writing segments.

Example 1: Write a sweeping segment that goes from 0 to 180 and back. Give it the inOutCirc easing and make it loop. The duration should be 5 seconds, with as few keyframes as possible.

Now, we could write our keyframes to go from 0 to 180 and back, or we could use the metronomic option to help us out. Using the metronomic function also means that we only need two keyframes and cue points—0 and 1! We'll also need the easing option and the loop option. Here's the result of this example:

var sweepingSegment = {
  duration: 5000,
  metronomic: true,
  loop: true,
  easing: 'inOutCirc',
  cuePoints: [0, 1],
  keyFrames: [{degrees: 0}, {degrees: 180}]
}

Example 2: Write a segment that runs once. This segment contains instructions for two servos. Servo one starts at whatever position the servo is already at, then adds 45 degrees over 1 second, then removes 30 degrees over 2 seconds, and finally adds 15 degrees over 1 second. The second servo simply moves from 20 degrees to 120 degrees over the duration, with full tweening and the inOutCirc easing. The duration of the segment should be 4 seconds.

We're using relative positions here, so it looks like the keyframes shorthand will come in handy. We have to set unequal cue points at: 0/4, 1/4, 3/4, and 4/4, that is at .25, .75, and 1. Remember— in a keyframe shorthand, null at the beginning means use the current position of the servo, and null in the middle means skip the cue point and recalculate tweening!

Keeping all of this in mind, we get the following:

var mySegment = {
  duration: 4000,
  cuePoints: [0, .25, .75, 1] ,
  keyFrames: [
    [null, 45, -30, 15],
    [{degrees: 20}, null, null, {degrees: 120}]
  ]
}

This is the true power of the Animation API—we can describe complex movements in objects that mathematically work out.

Now that we know how to write keyframes and segments, let's take a look at writing Animation objects and running segments using them.

The Animation object

The analogy that I like to use for the Animation object is to think of it like an MP3 player. You load tracks into it, and you can press play, pause, or stop. You queue segments into an animation in any order, and you can play, pause, or stop the animation at any time.

Let's take a quick look at the constructor: it only takes one parameter, which is the target. As we mentioned earlier, the target is the servo or an array of servos being animated. So, let's take a look at a sample program that constructs a servo array and constructs an Animation object:

var five = require('johnny-five')

var board = new five.Board()

board.on('ready', function(){
  var servos = new five.Servos([3, 5, 6])
  var animation = new five.Animation(servos)
})

Once we've constructed the Animation object, the next important function is .enqueue(). You pass a segment to this function to add it to the animation queue. Animations are run first-in, first-out, so our code becomes this:

var five = require('johnny-five')

var board = new five.Board()

board.on('ready', function(){
  var servos = new five.Servos([3, 5, 6])
  var animation = new five.Animation(servos)

  var mySegment = {
    easing: 'inOutCirc',
    duration: 3000,
    cuePoints: [0, .25, .75, 1]
    keyframes: [{degrees: 45}, 45, 45, -45]
  }

  animation.enqueue(mySegment)
})

That's the whole kit and caboodle; animations are set to run as soon as a segment is enqued! Let's take an in-depth look at the functions available to the Animation object:

Functions

Properties

.enqueue()

.enqueue() places a segment in the queue for the animation. Segments are run first-in, first-out when an animation is played.

.play()

.play() plays the animation, starting with the first segment put in that hasn't already been run. It will also continue the last segment in progress if the animation was paused.

.pause()

.pause() stops the animation, but maintains progress on any in-progress segments and maintains the segment queue.

.stop()

.stop() clears the segment queue as well as stops all animation.

.next()

.next() clears the current segment and moves on to the next. However, this is not automatically called by the animation when each segment finishes and is normally not called by the user.

.speed([speed])

.speed() can be used to view (when no arguments are passed) or set (when a number representing a multiplier is passed) the speed of the current animation.

Now that we know how animations work in general, let's build our own project with our three servos. Let's animate!

Project – animating our servo array

You should still have your servo array from the last project. Let's animate it! We'll use the REPL to modify our animation segment in real time and play with some of the true power of the Animation API.

Let's start with the code that initializes our board, sets up our servo array, creates an animation and a segment, and inserts them into the REPL. Place the following into animation-project.js:

var five = require('johnny-five')

var board = new five.Board()

board.on('ready', function(){
  var servos = new five.Servos([3, 5, 6])
  var animation = new five.Animation(servos)

  var mySegment = {
    easing: 'inOutCirc',
    duration: 3000,
    cuePoints: [0, .25, .75, 1],
    keyFrames: [
      [{degrees: 45}, 45, 45, -45],
      [{degrees: 30}, 30, 30, 30],
      [{degrees: 20}, 40, 40, 40]
    ]
  }

  this.repl.inject({
    animation,
    mySegment
  })

  animation.enqueue(mySegment)
})

Go ahead and run this with the following:

node animation-project.js

Your servos should spring to life and should start running the segment (we queued it on the last line!). Once it's done running, let's see it again on a loop. In the REPL, type the following:

>mySegment.loop = true
>animation.enqueue(mySegment)

The segment should start running on a loop. Want to see it slower? Let's change the speed using the Animation object's speed() function:

>animation.speed(.5)

This should slow it down to half speed. Let's go ahead and stop the animation, clearing the queue:

>animation.stop()

Let's see what happens when we make our segment metronomic using an easing function, and then run it:

>mySegment.easing = 'inOutCirc'
>mySegment.metronomic = true
>animation.enqueue(mySegment)

The segment now loops forward and reverses and looks more fluid!

This is cool, but what if we want animations to run one after the other? We could use a series of durations and timers, or we could tap into Johnny-Five's event system, which extends through the Animation API. Next we'll explore how to tap into these events to create timed animations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.234.83