Project Concept
My concept for this project is to portray a guy (supposedly) running on the road. Then I started to think about the elements I could add because just a static picture is boring. I figured the two elements I could improve are the background and the figure. I decided to add some animation to the figure and some interactivity with the user’s mouse so that it would chase the user’s mouse along the X-axis. For the background at first, I only wanted to implement a sky featuring a sunrise, that is to use a gradient color for the background. But it still seemed boring so I decided to add a moon and alternate them over time. Naturally, that comes with stars and different sky colors so I implemented that also. I already have the cycle for a day, why not extend it to a month? Therefore I also added phases of the moon. Then the project finally seemed complete, and it is shown below.
Code I am Proud of
I am not very proud of anything in particular actually, because it is all simple code, and not many calculations were needed. If I have to mention something then it would be the implementation of the Sky. It involved calculations based on deltaTime, relative positions of the sun and the moon, integrated loops with the looping nature of the draw() function to ensure the coherency of sky color changes, and used a wide variety of shapes and curves. The code goes on like this, and the comments should be enough to explain the code.
class Star{
constructor(){
// Random position of stars
this.x = random(400);
this.y = random(400);
this.speed = 1;
}
display(t){
this.y -= this.speed * deltaTime / 20; // Star movement
if(this.y <= 0){
this.y = 400 - this.y % 400;
}
// Transparency control
fill("rgba(255,255,255,"+t+")");
stroke("rgba(255,255,255,"+t+")");
let starBody = rect(this.x,this.y,2,2);
}
}
class CrescentMoon{
constructor(y){
this.phase = 0;
this.moonY = y;
}
display(){
this.phase = this.phase % 16;
fill("white");
// Moon shape
beginShape();
vertex(200,this.moonY-75);
if(this.phase <= 9){
bezierVertex(200-(100-25*(this.phase)),this.moonY-75,200-(100-25*(this.phase)),this.moonY+75,200,this.moonY+75);
bezierVertex(200+100,this.moonY+75,200+100,this.moonY-75,200,this.moonY-75);
}
else{
bezierVertex(200-100,this.moonY-75,200-100,this.moonY+75,200,this.moonY+75);
bezierVertex(200+(100-25*(16-this.phase)),this.moonY+75,200+(100-25*(16-this.phase)),this.moonY-75,200,this.moonY-75);
}
endShape();
}
}
class Sky{
constructor(){
this.sunY = 400;
this.speed = 1;
this.moonPhase = 0;
this.timer = 0;
}
display(){//10sec/round
// Timer sync
this.timer = this.timer % 20;
this.sunY = -this.timer * 50 + 400;
// Moon phase add
if(this.sunY + 500 <= -125){
this.moonPhase++;
}
// Create colors for gradient
let topColor;
let botColor;
// Transparency control
let displayVar;
// Frames 0-300 back to sunrise
if(this.timer < 3){
topColor = lerpColor(color("rgb(23,23,197)"),color("rgb(135,206,235)"),this.timer/3);
botColor = lerpColor(color("rgb(23,23,197)"),color("rgb(255,77,00)"),this.timer/3);
displayVar = 1-(this.timer/3);
}
// Frames 300-1000
else if(this.timer >= 3 && this.timer < 10){
// Frames 300-650 sunrise
if(this.timer<6.5){
botColor=lerpColor(color("rgb(255,77,00)"),color("rgb(135,206,235)"),(this.timer-3)/3.5);
}
// Frames 650-1000 day
else{
botColor = color("rgb(135,206,235)");
}
topColor = color("rgb(135,206,235)");
displayVar = 0; // Transparent
}
// Frames 1000-1300 dawn
else if(this.timer >= 10 && this.timer < 13){
topColor = lerpColor(color("rgb(135,206,235)"),color("rgb(23,23,197)"),(this.timer-10)/3);
botColor = lerpColor(color("rgb(135,206,235)"),color("rgb(23,23,197)"),(this.timer-10)/3);
displayVar = (this.timer-10)/3;
}
// Frames 1300-2000 night
else if(this.timer >= 13 && this.timer < 20){
topColor = color("rgb(23,23,197)");
botColor = color("rgb(23,23,197)");
displayVar = 1; // Solid
}
// For loop used to create gradient
for(let y = 0; y < 250; y++){
let lineColor = lerpColor(topColor,botColor,y/250);
stroke(lineColor);
line(0,y,400,y);
}
// Display stars
for(let i = 0; i < 120; i++){
starlist[i].display(displayVar);
}
// Display sun and moon
noStroke();
fill("#FFC107");
ellipse(200,this.sunY,150,150);
moon = new CrescentMoon(this.sunY + 500);
moon.phase = this.moonPhase;
moon.display();
}
}
Possible Improvements
As I said, I am not particularly proud of the project. It has lots of room for improvement in many aspects. To begin with, the code itself is sloppy and unorganized. It is stitched with different coding styles and practices, and it is unoptimized in aspects such as (but not limited to) time and methodology. This can be improved with more advanced coding techniques and better planning in code structure. The figure’s animation is also unsatisfying. Because p5js doesn’t support rigging, therefore it was extremely difficult to animate the figure. It was impossible to accurately illustrate each of the keyframes of the figure running because the figure is fundamentally just a set of shapes combined together. Each approximate keyframe takes a huge amount of time to finish. It also doesn’t support interpolating the keyframes so the intended fluctuations in the Y-axis to make the character more lively turned out to seem mechanical and shaky. I could try to smoothly interpolate the keyframes using algorithms, but I don’t have enough mathematical knowledge to support me in achieving this. This can be improved by using careful calculations to accurately orient the keyframes and adding a lot more frames to the animation, then using linear interpolation to connect the keyframes together. However, this method will still feel unnatural because linear interpolation will result in mechanical movements with sharp turns. I tried a few third-party libraries but none could really achieve this. For instance, I tried using Toxiclibsjs’ modules to create a skeleton, but it focuses more on physics instead of animation, therefore it still cannot be used for creating a perfect animation, unless I can find mathematical equations that could represent the movements of every joint, which I tried to do but failed dramatically.