nixel-AnimatedLoop

I had a lot of fun with this project!

My process:

I started with some messy brainstorms from when we first got the prompt:

I thought that loops of characters bouncing down stairs, or being transported by a never ending escalator would be fun. I also considered doing skyscapes that looped between sunrise and sunset and a swinging character. In the end, I went with rain because it was the simplest in terms of shape and color design.

I thought that I would do some sort of walk cycle and plotted some leg rotations.

I went online and found a shape-heavy walk cycle gif and spliced some frames out to break it down.

However, when I began to code and actually look into the easing functions, I realized that it would be simpler to pair them with a bouncing animation instead of a walking one. It was sad to leave all the walking animation work I did, but I think I made a better choice in terms of time and simplicity. I think I will go back one day and make all of the ideas I had initially.

After a lot of experimentation (and suffering) I chose to use primarily the Flat Top Window easing function since it was bouncy, playful, and worked well for a looping animation since it was symmetrical.

Here is a thumbnail I made half way through coding in order to hash out the locations of some body parts.

I struggled with the rain since I thought it added to the narrative of the gif, but didn't necessarily loop. I also struggled with the umbrella since it's an interesting visual element but doesn't make much sense since the character isn't holding on to it. I did at one point code an umbrella handle and hands, but it made the image too complex so I got rid of them.

Critiquing myself, I think that I did well in keeping the design simple and appealing. I could have been more ambitious with connecting the umbrella to the character and been more efficient with my code (I organized it in groups of body parts but ended up repeating a lot of simple functions like noStroke() or fill() when I could have just organized the code more efficiently).

 

 
// This is a template for creating a looping animation in Processing/Java. 
// When you press the 'F' key, this program will export a series of images
// into a "frames" directory located in its sketch folder. 
// These can then be combined into an animated gif. 
// Known to work with Processing 3.3.6
// Prof. Golan Levin, January 2018
 
 
//===================================================
// Global variables. 
String  myNickname = "nixel"; 
int     nFramesInLoop = 60;
int     nElapsedFrames;
boolean bRecording; 
Drop[] drops = new Drop[50];
 
//===================================================
void setup() {
  size (640, 640); 
  bRecording = false;
  nElapsedFrames = 0;  
  for (int i = 0; i < drops.length; i++) {
   drops[i] = new Drop(); 
  }
}
//===================================================
void keyPressed() {
  if ((key == 'f') || (key == 'F')) {
    bRecording = true;
    nElapsedFrames = 0;
  }
}
//===================================================
void draw() {
  // Compute a percentage (0...1) representing where we are in the loop.
  float percentCompleteFraction = 0; 
  if (bRecording) {
    percentCompleteFraction = (float) nElapsedFrames / (float)nFramesInLoop;
  } else {
    percentCompleteFraction = (float) (frameCount % nFramesInLoop) / (float)nFramesInLoop;
  }
 
  // Render the design, based on that percentage. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  if (bRecording) {
    saveFrame("frames/" + myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png");
    nElapsedFrames++; 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
//===================================================
void renderMyDesign (float percent) {
  int bgColor = 255;
  background (bgColor);
  stroke (0, 0, 0); 
  strokeWeight (2); 
  float pennerEase = FlatTopWindow(percent);
  noStroke();
 
  //umbrella
  float umbrellashift = (map(pennerEase, 0, 1, -50, -20)); 
  fill(255, 0, 0);
  ellipse(width/2, 240 + umbrellashift, 360, 360);
 
  //cutout umbrella
  fill(bgColor);
  int r = 250;
  for (int i = 0; i < 360; i += 30){
  float dy = sin(radians(i)) * r;
  float dx = cos(radians(i)) * r;
  ellipse(width/2 + dx, 240 + umbrellashift + dy, 180, 180);
  }
 
  rectMode(CENTER);
 
  //ground
  fill(10, 95, 223);
  rect(width/2, 600, width, 200);
 
  //puddle
  noFill();
  stroke(255);
  float jumpWeight = map(percent, 0, 1, 15, 0);
  float puddleEase = map(PennerEaseOutExpo(percent-0.7), 0, 1, 100, 1000);
  float puddleEaseY = map(PennerEaseOutExpo(percent), 0, 1, 0, 50); 
  strokeWeight(jumpWeight);
 
  if (percent >= 0.7){
    ellipse(355, 530, puddleEase, puddleEaseY);
    ellipse(200, 560, puddleEase, puddleEaseY);
    ellipse(300, 600, puddleEase, puddleEaseY);
    ellipse(50, 650, puddleEase, puddleEaseY);
    ellipse(550, 610, puddleEase, puddleEaseY);
  }
 
  //hair
  noStroke();
  float hairshift = (map(pennerEase, 0, 1, 60, -40)); 
  fill(0);
  arc(width/2, 240 + hairshift, 180, 300, PI, TWO_PI);
 
 
  //legs 
  float rX = map(pennerEase, 0, 1, 350, 340);
  float Y = map(pennerEase, 0, 1, 560, 510);
  float squishX = map(pennerEase, 0, 1, 10, 0);
  float lX = map(pennerEase, 0, 1, width - 360, width - 350);
  fill(247,227,40);
 
  //right leg
  triangle(rX, Y, rX - 5 - squishX, Y - 150, rX + 10 + squishX, Y - 150);
 
  //left leg
  triangle(lX, Y, lX - 5 - squishX, Y - 150, lX + 10 + squishX, Y - 150);
 
  //body
	float bsquish = map(pennerEase, 0, 1, 10, -10);
  float bodyshift = (map(pennerEase, 0, 1, 50, -20)); 
  fill(247, 227, 40);
  triangle(width/2, 170 + bodyshift, width/2 - 100 - bsquish, 380 + bodyshift, width/2 + 100 + bsquish, 380 + bodyshift);
 
  //mirrored legs 
  float mrX = map(pennerEase, 0, 1, 350, 340);
  float mrY = map(pennerEase, 0, 1, 510, 560);
  float msquishX = map(pennerEase, 0, 1, 10, 0);
  float mlX = map(pennerEase, 0, 1, width - 360, width - 350);
  fill(247,227,40);
 
  //right leg
  triangle(mrX, mrY + 50, mrX - 5 - msquishX, mrY + 175, mrX + 10 + msquishX, mrY + 175);
 
  //left leg
  triangle(mlX, mrY + 50, mlX - 5 - msquishX, mrY + 175, mlX + 10 + msquishX, mrY + 175);
  //head
  float headshift = (map(pennerEase, 0, 1, 55, -30)); 
  fill(255);
  ellipse(width/2, 170 + headshift, 100, 100);
 
  //mouth
  float mouthshift = (map(pennerEase, 0, 1, 30, -70)); 
  float mouthY = (map(pennerEase, 0, 1, -5, 10)); 
  fill(255, 0, 0);
  ellipse(width/2, 225 + mouthshift, 20, 15 + mouthY);
 
  //bangs  
  fill(0);
  arc(width/2, 150 + hairshift, 100, 100, PI, TWO_PI);
 
  //eyes
  float eyeY = (map(pennerEase, 0, 1, 1, 5)); 
  float eyeX = (map(pennerEase, 0, 1, 1, 5)); 
  ellipse(width/2 - 30, 200 + mouthshift, 10 + eyeX, 15 + eyeY);
  ellipse(width/2 + 30, 200 + mouthshift, 10 + eyeX, 15 + eyeY);
 
  //arms
  fill(247,227,40);
 
  float Ya = map(pennerEase, 0, 1, 10, 80);
  //right arm
  triangle(345, 230 + bodyshift, 345, 260 + bodyshift, 450, 250 + Ya);
 
  //left arm
  triangle(width - 345, 230 + bodyshift, width - 345, 260 + bodyshift, width - 450, 250 + Ya);
 
  //rain
  for (int i = 0; i < drops.length; i++){
  drops[i].fall();
  drops[i].show();
  }
 
}
 
//===================================================
//rain code refenced from Dan Shiffman's Purple Rain Coding Challenge
class Drop {
 float x = random(width);
 float y = random(0, height);
 float yspeed = 11;
 float len = 15;
 
 void fall(){
   y += yspeed;
 
  if (y > height) {
   y = 0;
   yspeed = 11;
  }
 
   }
 
 void show(){
  stroke(10, 95, 223);
  strokeWeight(2);
  line(x, y, x, y+len);
 }
}
 
//===================================================
float FlatTopWindow (float x) {
  //easing functions from Pattern Master https://github.com/golanlevin/Pattern_Master
  // http://en.wikipedia.org/wiki/Window_function 
 
  final float a0 = 1.000;
  final float a1 = 1.930;
  final float a2 = 1.290;
  final float a3 = 0.388;
  final float a4 = 0.032;
 
  float pix = PI*x;
  float y = a0 - a1*cos(2*pix) + a2*cos(4*pix) - a3*cos(6*pix) + a4*cos(8*pix);
  y /= (a0 + a1 + a2 + a3 + a4); 
 
  return y;
}
 
float PennerEaseOutExpo(float t) {
  return (t==1) ? 1 : (-pow(2, -10 * t) + 1);
}