Digital Foundations

Movement and Frame Rate

Rhythm is an essential consideration in the production of motion graphics. One of the most common and simple time signatures is 4/4, where four beats are evenly heard over one bar of music and each note is one beat. House Music has a 4/4 beat. This method of counting time can be applied to animation. In House music, the beat is counted in cycles of eight. The rhythm is established and peaks at the fourth beat. It reduces in the next four beats, in preparation for the next set of eight beats. One of the key concepts in understanding animation is visualizing time. Whether you are keeping track of the musical score or graphic frames on a timeline, counting time is important and "seeing" the count is necessary.

Early experimental animation can be drawn upon as an example of seeing time kept with abstract, basic shapes.  Consider formal design properties of balance and movement while watching early Oscar Fischinger animations (see YouTube). The work we will be doing is more like his later work, "Early Abstractions" (1946 - 57).

Harry Smith's homage to Fischinger remains abstract, though it is even more layered and complicated. Notice the color harmonies and formal balance. Balance changes over time, shapes change in size and transparency. Rhythm is used to create a sense of predictability within the abstraction.

Formal design relationships we have created in still compositions are also active in animations. Repetition, symmetry, asymmetry, balance, and rhythm become enveloped in a new formal element: time. In the visual examples, forms are reused to create unity between different moments in the animation. At the same time, transformations of scale, color, and value create contrast, which helps differentiate moments in the animation. Early experimental animation kept time visually with abstract shapes. Pacing is key to Hans Richter's film, Rhytmus 21 (1921). With the most simple forms Richter was able to explore the transformations of shapes over time through size. Everything is understood through an even, consistent pace, which leads to a contemplation of the purity of form.

See Hans Richter's film here: http://www.ubu.com/film/richter_rhythmus.html

In the 1970s, Lillian Schwartz made cutting edge experimental computer animations at Bell Labs. Her work may look like the earlier Richter animation, but the video was programmed using a computer. Her process was similar to our exercises. In her 1971 animation UFO's the introduction of the computer results in faster edits and elemental shapes. The psychedelic aesthetics fit the topic.

See Lillian Schwartz's video here: http://www.youtube.com/watch?v=kic8YlHbhvI

Exercise 1: Introducing the draw loop

Start with the below code. This looks exactly like the sketch we left off with in chapter 18, but now all the code is arranged in blocks. 

/*
Creators: Rory Solomon and Becky Heritage
Date: Feb. 8, 2009
License: CC-BY-SA
*/

void setup() {
  size(400,400); // the size of the sketch
}

void draw() {
  background(255);

  smooth();
  noStroke();
  fill(100,100,250);

  // draw a circle
  ellipse(200,200,100,100);

  fill(250,100,100,150);

  // draw a square
  rect(150,225,100,100);

  fill(250,250,100,100);
  triangle(125,200, 275,200, 200,100);
}
   

A block is a group of commands between curly braces, { }. Here we have two blocks: setup() and draw().

void setup(){
// setup block code
}

void draw(){
// draw block code
}

These two blocks are special parts of a Processing sketch. The commands in setup() are executed once when you start running your sketch, and the commands in draw() are executed repeatedly as long as your sketch is running.

So now when you press run, even though the image looks static, it is actually getting erased and re-drawn, once per frame. The default frame rate for a processing sketch is 60 frames per second.

Now that we have our code inside the "draw loop" we can create animations. By modifying the commands within the draw() block, the drawn images will differ slightly on each iteration. Keep this in mind as we work through the next exercise on variables.

Exercise 2: Variables

In this exercise we will add a variable to describe the vertical position of the circle. A variable is kind of like a locker at the gym. It's a place where you can store something. And like your gym locker number, a variable has a name so you can reference it later. You can give your variable a name that is intuitive and helpful to read. The following code snippet is a variable declaration:

int circleYPosition = 200;

The letters "int" define the variable as an integer, which means the variable can only store whole numbers. The name of the variable is "circleYPosition." You can name a variable with any name as long as it is made of letters, numbers, dashes, and underscores (a-z, A-Z, 0-9, - and _). In this case, we called our variable "circleYPosition" because it is going to store the y position of our circle. Finally, we assign the value 200 to our variable. To use the gym locker metaphor, our gym locker is called circleYposition, it only stores whole numbers and right now we have stored the whole number with the value "200" in our locker.

With this variable declared and a value assigned to it, we can substitute the word "circleYPosition" for the value 200 throughout, instead of manually entering the value stored in our variable.

1. Copy and paste the new code to your PDE:

/*
Creators: Rory Solomon and Becky Heritage
Date: Feb. 8, 2009
License: CC-BY-SA
*/

// variable declaration
int circleYPosition = 200;

void setup() {
  size(400,400); // the size of the sketch
}

void draw() {
  background(255);

  smooth();
  noStroke();
  fill(100,100,250);

  // draw a circle
  ellipse(200,circleYPosition,100,100);

  fill(250,100,100,150);

  // draw a square
  rect(150,225,100,100);

  fill(250,250,100,100);
  triangle(125,200, 275,200, 200,100);
}
  

2. Play the sketch and analyze the code. Running this code should give you exactly the same results as before. All we've done is replace the value 200 in the ellipse() command with the circleYPosition variable, which is assigned a value of 200.

Exercise 3: Animation

The variable circleYPosition is being used to set the vertical position of the circle. So if we change the value of circleYPosition, the circle will move up and down. In this exercise, our variable is going to be used to create animation. By changing the value of circleYPosition a little bit each time we draw the sketch - that is, every time the draw block executes - and displaying those images one after the next, we'll produce an animated circle.

Take a look at this line of code:

circleYPosition = circleYPosition + 1;

This is doing two things. It's doing some basic arithmetic, in this case adding 1 to circleYposition. It is also doing an assignment by setting circleYposition to a value. Together what this means is, "take the value of circleYposition, add 1 to it, and assign it back to circleYposition". So when you run this code, the value of the variable will increase by 1 on each frame.

2. Add this new line to the end of your draw() block. Your code should now look like this:

/*
Creators: Rory Solomon and Becky Heritage
Date: Feb. 8, 2009
License: CC-BY-SA
*/

// variable declaration
int circleYPosition = 200;

void setup() {
  size(400,400); // the size of the sketch
}

void draw() {
  background(255);

  smooth();
  noStroke();
  fill(100,100,250);

  // draw a circle
  ellipse(200,circleYPosition,100,100);

  fill(250,100,100,150);

  // draw a square
  rect(150,225,100,100);

  fill(250,250,100,100);
  triangle(125,200, 275,200, 200,100);

  // move the circle
  circleYPosition = circleYPosition + 1;
}
  

3. Play the sketch. Your circle will move down and out of the window.

Exercise 4: Bouncing Circle

That was nice, but after a couple seconds, the circle disappears. Let's change our code so the circle bounces between the bottom of the red box and the top of the sketch window. If the circle reaches either of these locations, it should change direction. Right now, the movement of the circle is created by a single + 1.

  circleYPosition = circleYPosition + 1;

To make the circle change direction, we need to modify the value of that + 1. We want this value to vary between + 1 (moving down) and - 1 (moving up). To make any value vary, we need to replace it with a variable. So we'll make a new variable and add it to the variable declaration section of our sketch.

// variable declaration
int circleYPosition = 200;
int circleVelocity = 1;

Now that we have our variable in place, we're going to learn a new technique: pseudo-code. Pseudo-code is writing out what you want to happen in straight forward language, and then translating each part into real code. For example, we want to add this:

If the circle reaches the top of the sketch or the bottom of the red box,
then it changes direction.

First, how do we know when the circle is at either of those two locations? We can compare its position (circleYPosition) with the position numbers for these locations, which are 0 and 325 respectively. Since the circle is 100 pixels in diameter, it will touch an edge when its center is 50 pixels from that edge.

This means that the y position of our circle should be between 50 and 275 at all times. If the circle is within that range, it should keep moving in the same direction. If it is outside that range, it should change direction. So our sentence becomes the following:

If the circle's position is less than 50 or greater than 275, then it changes direction.

Translated into Processing, this is called an "if statement," written like this:

  if ( circleYPosition < 50 || circleYPosition > 275 ) {
    then it changes direction.
  }

Now we need it to actually change direction. This is where our new variable comes in handy. Our circleVelocity variable is initialized with a value of 1, which moves the circle down. When it hits one of our boundaries, we want it to reverse. To change + 1 into - 1, all we have to do is multiply by - 1. This is convenient, because to switch back, we do the exact same thing. We will only need one if statement in our code.

  if ( circleYPosition < 50 || circleYPosition > 275 ) {
    circleVelocity = circleVelocity * -1;
  }

We've arrived at the actual code.

1. Put the above code into the end of your draw block. Change your previous command to move the circle. Instead of + 1, it is now + circleVelocity :

/*
Creators: Rory Solomon and Becky Heritage
Date: Feb. 8, 2009
License: CC-BY-SA
*/

// variable declaration
int circleYPosition = 200;
int circleVelocity = 1;

void setup() {
  size(400,400); // the size of the sketch
}

void draw() {
  background(255);

  smooth();
  noStroke();
  fill(100,100,250);

  // draw a circle
  ellipse(200,circleYPosition,100,100);

  fill(250,100,100,150);

  // draw a square
  rect(150,225,100,100);

  fill(250,250,100,100);
  triangle(125,200, 275,200, 200,100);

  // move the circle
  circleYPosition = circleYPosition + circleVelocity;

  if ( circleYPosition < 50 || circleYPosition > 275 ) {
    circleVelocity = circleVelocity * -1;
  }
}

ch18_08_1ch18_09_1ch18_11_1ch18_13_1ch18_10_1ch18_12_1 

Exercise 5: Modify the frame rate

We mentioned before that Processing executes everything inside of a draw loop with every frame, and that the frames display at a frame rate of 60 per second. You can modify the frame rate at which the sketch runs by adding the following line to the setup block:

frameRate(120);

The circle should now move twice as fast because there are twice as many frames being displayed in the same amount of time. You can experiment with this number to see its effects. The lower you set the frame rate, the slower and choppier your animation; and the higher the frame rate, the animation will appear to run faster and smoother.