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); } |