Cactus Dancer is a project that emphasizes the use of Object-Oriented Programming (OOP) and builds a customized dancing character.
I was inspired by the dancing cactus toy because I find its movement very mesmerizing and funny.
For the dancing movement, I mainly designed two parts: the swaying body and the oscillating arms. I found the Object-Oriented Programming method helpful especially when tweaking the parameters which control the frequency as well as the amplitude of these two movements.
In addition, as the code showed belowed, within the class I constructed mainly with these functions: drawCactusBody, drawArms, drawEyes, drawMouth, drawSpikes and drawPot, each responsible for a certain part of the character, which makes debugging and changing positions of certain part straightforward and intuitive.
CODE for Class CactusDancer:
class CactusDancer {
constructor(startX, startY) {
this.x = startX;
this.y = startY;
// Three colors for the pot
this.potCol1 = color(177, 102, 51);
this.potCol2 = color(199, 135, 77);
this.potCol3 = color(211, 154, 98);
// Color for the cactus body
this.cactusCol = color(90, 141, 23);
// Color for spikes
this.spikeCol = color('red');
// Body sway parameters
this.swayAngle = 0;
this.swaySpeed = 0.05;
this.swayAmount = 0.15;
// Arm movement parameters
this.armLength = 80;
this.armWaveAmplitude = 10;
this.armWaveSegments = 30;
}
update() {
this.swayAngle += this.swaySpeed;
}
display() {
// the push and pop, along with the translate
// places your whole dancer object at this.x and this.y.
// you may change its position on line 19 to see the effect.
push();
translate(this.x, this.y);
// ******** //
// ⬇️ draw your dancer from here ⬇️
push();
translate(0, 40);
rotate(sin(this.swayAngle) * this.swayAmount);
translate(0, -40);
this.drawCactusBody(0, -10);
this.drawArms(0, 0);
// Left Eye
this.drawEyes(-10, -30);
// Right Eye
this.drawEyes(10, -30);
// Mouth
this.drawMouth(0, 5);
// Spikes
this.drawSpikesRight(30, -50);
this.drawSpikesRight(30, -40);
this.drawSpikesRight(30, -30);
this.drawSpikesLeft(-30, -50);
this.drawSpikesLeft(-30, -40);
this.drawSpikesLeft(-30, -30);
pop();
this.drawPot(0, 40);
// ⬆️ draw your dancer above ⬆️
// ******** //
pop();
}
drawPot(potX, potY) {
noStroke();
let steps = 50;
rectMode(CORNER);
for (let i = 0; i < steps; i++) {
let t = i / steps;
let c;
if (t < 0.5) {
c = lerpColor(this.potCol1, this.potCol2, t * 2);
} else {
c = lerpColor(this.potCol2, this.potCol3, (t - 0.5) * 2);
}
fill(c);
let x = potX - 60 + (120 / steps) * i;
let w = 120 / steps + 1;
rect(x, potY - 10, w, 20);
}
for (let i = 0; i < steps; i++) {
let t = i / steps;
let c;
if (t < 0.5) {
c = lerpColor(this.potCol1, this.potCol2, t * 2);
} else {
c = lerpColor(this.potCol2, this.potCol3, (t - 0.5) * 2);
}
fill(c);
let topLeft = potX - 55 + (110 / steps) * i;
let topRight = potX - 55 + (110 / steps) * (i + 1);
let bottomLeft = potX - 40 + (80 / steps) * i;
let bottomRight = potX - 40 + (80 / steps) * (i + 1);
quad(topLeft, potY + 10, topRight, potY + 10, bottomRight, potY + 60, bottomLeft, potY + 60);
}
rectMode(CORNER);
}
drawCactusBody(bodyX, bodyY) {
noStroke();
fill(this.cactusCol);
rectMode(CENTER);
rect(bodyX, bodyY, 60, 150, 80);
rectMode(CORNER);
}
drawArms(bodyX, bodyY) {
strokeWeight(20);
stroke(this.cactusCol);
noFill();
// Left Arm
beginShape();
for (let i = 0; i <= this.armWaveSegments; i++) {
let t = i / this.armWaveSegments;
let x = bodyX - t * this.armLength;
let y = bodyY + sin(t * PI * 2 + this.swayAngle * 2) * this.armWaveAmplitude;
vertex(x, y);
}
endShape();
// Right Arm
beginShape();
for (let i = 0; i <= this.armWaveSegments; i++) {
let t = i / this.armWaveSegments;
let x = bodyX + t * this.armLength;
let y = bodyY + cos(t * PI * 2 + PI + this.swayAngle * 2) * this.armWaveAmplitude;
vertex(x, y);
}
endShape();
}
drawEyes(eyeX, eyeY) {
fill(0);
noStroke();
ellipse(eyeX, eyeY, 8, 8);
}
drawMouth(mouthX, mouthY) {
stroke(0);
strokeWeight(2);
ellipse(mouthX, mouthY, 20, 20);
}
drawSpikesRight(spikeX, spikeY) {
noStroke();
fill(this.spikeCol);
triangle(spikeX, spikeY, spikeX, spikeY + 5, spikeX + 20, spikeY + 2.5);
}
drawSpikesLeft(spikeX, spikeY) {
noStroke();
fill(this.spikeCol);
triangle(spikeX, spikeY, spikeX, spikeY + 5, spikeX - 20, spikeY + 2.5);
}
}
Reflective Prompts:
What is the benefit of your class not relying on any code outside of its own definition?
The benefit of keeping everything within a class is that if I want to add another class to my canvas, the functions and parameters within my previous class won't interfere with my new one. Also, the names of the functions won't conflict across different classes, which means I can give functions with similar or the same effect the same name without causing any errors.
What make it challenging to write code that has to harmonize with the code other people have written?
The major challenge is that the provided code may not strictly follow the principles of OOP. That is to say, in these scenarios, their code may include lots of functions outside a class definition, as well as global variables declared within the setup() function.
Sometimes it can be hard to pass return values from these external functions into the methods within the class. For example, if a returned value is supposed to control the scale of a certain part of the class, but within the class's own methods (like display()), there is no parameter designed to accept it. This will make the rewrite pretty tricky.
Describe the terms modularity and reusability using the example of your dancer class.
Modularity:
As I mentioned in the project description, I mainly deconstructed the character into the following functions: drawCactusBody, drawArms, drawEyes, drawMouth, drawSpikes, and drawPot, each responsible for a certain part of the character. Each function has corresponding X and Y values to control its arrangement on the canvas.
Reusability:
A good example of reusability is that if I wanted to create a static version of my dancer (perhaps to add decorations), I could reuse all the drawing functions I mentioned earlier (drawCactusBody, drawArms, etc.). To achieve this, I would simply remove the call to the update() method, which handles the animation.
Furthermore, I could implement new features, like decorations, by adding new code (e.g., a drawHat() method) without needing to modify or remove any of the original functions.