In this project, I created a dancer, "Lulu", a capybara who twists his body regularly. I referenced Lulu's meme from the internet to complete this project.
This is how Lulu looks without animation.
This is how Lulu looks with animation.
You can find my code here. I used push() and pop() for each specific component to make the overall positioning clear. The specific explanation is listed below with highlighted colors.
class Lulu {
// Lulu is initialized with:
// basic x, y position, and two colors
// orange position
// rotation angle, speed, and maximum angle
constructor(startX, startY) {
this.x = startX;
this.y = startY;
this.bodyColor = "yellow";
this.orangeColor = "orange";
this.orangePosY = -37;
this.orangeSpeed = -0.1;
this.angle = 0; // Current angle of rotation
this.wiggleSpeed = 0.2; // Speed of oscillation
this.wiggleAmount = PI / 30; // Maximum angle of rotation
}
// In the update function, two aspects are changed each frame
update() {
// Lulu wiggles in accordance with the back-and-forth pattern
// designed by a sine wave and time (franeCount)
let oscillation = sin(frameCount * this.wiggleSpeed);
this.angle = oscillation * this.wiggleAmount;
// orange position is changed
// this.orangeY go from -37 to -42 and then back
this.orangePosY += this.orangeSpeed
if (this.orangePosY <= -42 || this.orangePosY >= -37) {
this.orangeSpeed = -this.orangeSpeed;
}
console.log(this.orangePosY);
}
// Then Lulu are drawn with two parts: Head and Body
// Head: with an orange (drawOrange()), 2 eyes (drawEyes()), 2 ears(drawEyes()), (both eyes and ears rotate at a given angle), and the basic components
// drawOrange() takes orange position (X and Y), and orange size as parameters
// It first draws the orange itself, and then a small stem
// the orange size was originally designed to be changed with interaction but currently not used
// Orange kept bouncing up and down.
drawOrange(orangePosX, orangePosY, orangeWidth, orangeHeight) {
push();
translate(orangePosX, orangePosY);
fill(this.orangeColor);
ellipse(0, 0, orangeWidth, orangeHeight);
push();
translate(0, -orangeHeight / 2);
fill("brown");
arc(0, 0, 2, 5, HALF_PI, 0, CHORD);
pop();
pop();
}
// drawEyes() takes eyePosition and eyeRotation as parameters
// It draws the left eye first, and then the right eye with symmetry
// I did not use eye size as parameter since it has two parts in one eye, and adjusting inside the
// function was enough during my design
drawEyes(eyePosX, eyePosY, eyeRotation) {
push();
push();
translate(-eyePosX, eyePosY);
rotate(eyeRotation);
fill("white");
ellipse(0, 0, 15, 10);
fill("black");
circle(0.5, 1, 8);
pop();
push();
translate(eyePosX, eyePosY);
rotate(-eyeRotation);
fill("white");
ellipse(0, 0, 15, 10);
fill("black");
circle(-0.5, 1, 8);
pop();
pop();
}
// drawEars() takes earPosition, earSize, and earSize as parameters
// It draws the left ear first, and then the right ear with symmetry
// The two ears were drawn as two ellipses, but the head itself
// which shares the same color as the two ears, will cover most of the two ellipses
drawEars(earPosX, earPosY, earRotation, earWidth, earHeight) {
push();
push();
translate(-earPosX, earPosY);
rotate(-earRotation);
fill(this.bodyColor);
ellipse(0, 0, earWidth, earHeight);
pop();
push();
translate(earPosX, earPosY);
rotate(earRotation);
fill(this.bodyColor);
ellipse(0, 0, earWidth, earHeight);
pop();
pop();
}
// drawHead takes the head position as the parameter to translate to that position
// The whole head rotates with an angle of (-this.angle)
// It first draws the two basic components, and then calls functions of eyes, orange, and ears
drawHead(headPosX, headPosY) {
push();
rotate(-this.angle);
translate(headPosX, headPosY);
fill(this.bodyColor);
noStroke();
ellipse(0, 0, 80, 66);
// lower ellipse (nose and mouth)
push();
fill(this.orangeColor);
translate(0, 13);
ellipse(0, 0, 60, 40);
pop();
// eyes
this.drawEyes(20, -10, PI / 15);
// orange
this.drawOrange(0, this.orangePosY, 15, 12);
// ears
this.drawEars(30, -20, PI / 9, 10, 15);
pop();
}
// Body: with 2 arms, an upper body (body color), a lower body (with pants of orange color), 2 feet
// As the head rotates with -this.angle as a group, some of the body parts (lower pants and feet)
// does not rotate since Lulu is "standing" on the floor
// drawArm: it takes the arm position, arm rotation, and arm size as parameters
// The overall design of drawArm for body, is similar to the design of drawEar for head
drawArm(armPosX, armPosY, armRotation, armWidth, armHeight) {
push();
// left
push();
translate(-armPosX, armPosY);
rotate(-armRotation);
fill(this.bodyColor);
ellipse(0, 0, armWidth, armHeight);
pop();
// right
push();
translate(armPosX, armPosY);
rotate(armRotation);
fill(this.bodyColor);
ellipse(0, 0, armWidth, armHeight);
pop();
pop();
}
// drawBody takes the body position as the parameter to translate to that position
// It first calls the function of drawing arms, then the upper body, the lower body with feet and pants
drawBody(bodyCenterX, bodyCenterY) {
push();
noStroke();
rectMode(CENTER);
translate(bodyCenterX, bodyCenterY);
// arms (rotating with this.angle)
// It should be drawn first to make sure the main body parts are in the front of the arms
push();
rotate(this.angle);
this.drawArm(30, 0, -PI/12, 16, 36);
pop();
// upper body (not rotating)
// The rotation of the arms and the head makes body look like rotating visually
push();
fill(this.bodyColor);
rect(0, 0, 54, 50);
pop();
// pants and feet
// lower left part
push();
translate(-20, 30);
// foot
fill(this.bodyColor);
ellipse(0, 2, 22, 20);
// pant left
fill(this.orangeColor);
rect(0, 0, 22, 10);
pop();
// lower right part
push();
translate(20, 30);
// foot
fill(this.bodyColor);
ellipse(0, 2, 22, 20);
// pant right
fill(this.orangeColor);
rect(0, 0, 22, 10);
pop();
// upper center pant (only this part rotating)
push();
rotate(this.angle);
fill(this.orangeColor);
rect(0, 20, 60, 20);
pop();
pop();
}
// drawDancer() combines the Head and Body together.
drawDancer() {
push();
this.drawBody(0, -10);
this.drawHead(0, -50);
pop();
}
// display() translate the center point to this.x and this.y and then draws the dancer
display()
push();
translate(this.x, this.y);
this.drawDancer();
pop();
}
First, it ensures portability since we can directly copy and paste the entire Lulu class into a new .js file with many other dancer classes. It is guaranteed to work instantly because it does not need any special global variables or functions to be present in that new file. Next, it prevents global code conflicts with others' code. A more detailed explanation could be seen in the answer for the question⬇️.
Originally, I would like to make some design on the orange motion with human interaction: the orange would be "hit" and disappear when we click on the screen. However, the challenge appears as the function of mousePressed is actually a global function, and thus, I need a global variable to record the status. Therefore, I finally gave up the design. Considering harmonizing with the code written by others, I would think about the challenge of using global variables, since people tend to use duplicate variable names, and might change others' variables with their code. Take my orange as an example, I might just use "hit" with false and true to hold the orange status, while others might also "hit" their characters and use hit as a number count. If we are coding together and using hit as a global variable, we might use each other's data in the wrong way.
Modularity: Lulu is composed of two modules (body and head), and each module contains different, smaller, and separate modules (ear, eye, orange, basic part for head, and arm, feet, pants, main body for body) as well. Modularity makes it more convenient to debug or change, especially when only a few body parts need to be included. For example, modularity helps me to build different rotations for different body components described in the coding part.
Reusability: Reusability can be seen as the result of good modularity, as we can pass in different parameters to construct another Lulu dancer. Both the class itself and the method defined inside need reusability to make the construction more flexible. My Lulu class, in fact, can be improved by adding more reusability, including passing in more parameters (especially for the size of eyes), and adjusting symmetry drawing (eyes, arms, ears) to singular drawing while calling them repeatedly in the general component method call with different given parameters.