// BORROWED FROM IRA GREENBERG
var radius = 45, rotAngle = -90;
var accelX = 0.0, accelY = 0.0;
var deltaX = 0.0, deltaY = 0.0;
var springing = 0.0009, damping = 0.98;
//corner nodes
var nodes = 5;
//zero fill arrays
var nodeStartX = [];
var nodeStartY = [];
var nodeX = [];
var nodeY = [];
var angle = [];
var frequency = []; // END OF BORROWED CODE
// array for the rings and boxes
var rings = [];
var boxes = [];
// initializing colors for keypressed changes
var r = 250;
var g = 250;
var b = 250;
var frames = [];
//var score = 0;
function preload() { // loading ring popping animation frames
var f = [];
f[0] = "https://i.imgur.com/AZJNy6P.png";
f[1] = "https://i.imgur.com/coAJvFm.png";
f[2] = "https://i.imgur.com/Y3RE12y.png";
f[3] = "https://i.imgur.com/9sTJNBz.png";
for(var i =0; i < 4; i++){
frames[i] = loadImage(f[i]);
}
}
function setup() {
createCanvas(480, 480);
// BORROWED FROM IRA GREENBERG: initialize arrays to 0
for (var i=0; i<nodes; i++){
nodeStartX[i] = 0;
nodeStartY[i] = 0;
nodeY[i] = 0;
nodeY[i] = 0;
angle[i] = 0;
} // END OF BORROWED CODE
// initializes collection of rings
for (var i = 0; i < 3; i++){
var ry = random(-300, 0); //rings are falling from above the canvas at random
rings[i] = makeRing(ry);
boxes[i] = makeBox(ry);
}
// BORROWED FROM IRA GREENBERG: iniitalize frequencies for corner nodes
for (var i = 0; i < nodes; i++){
frequency[i] = random(4, 5);
} // END OF BORROWED CODE
noStroke();
frameRate(30);
}
function draw() {
fill(50);
rect(0,0,width, height);
// display rings + boxes
updateAndDisplayRings();
ringAdd();
updateAndDisplayBox();
boxAdd();
// for the character
drawShape();
moveShape();
// instructional text
text("press 'Q' to eat blue, 'W' to eat pink, 'E' to eat yellow", 180, 450);
}
// CHARACTER
// CODE IS BASED ON IRA GREENBERG'S SOFT BODY EXAMPLE:
// https://p5js.org/examples/simulate-soft-body.html
// HAS BEEN HEAVILY EDITED BY ME
function drawShape() {
// calculate node starting locations
for (var i=0; i<nodes; i++){
nodeStartX[i] = mouseX+cos(radians(rotAngle))*radius;
nodeStartY[i] = mouseY+sin(radians(rotAngle))*radius;
rotAngle += 360.0/nodes;
}
// draw polygon
curveTightness(0.5); // to create soft body like organic shape
noStroke();
fill(r,g,b);
beginShape();
// creates a point at each (nodeX, nodeY) for the 5 corners in the array
for (var i=0; i<nodes; i++){
curveVertex(nodeX[i], nodeY[i]);
}
// to make shape connect all the way around
for (var i=0; i<nodes-1; i++){
curveVertex(nodeX[i], nodeY[i]);
}
endShape(CLOSE); // END OF BORROWED CODE
// eyes
fill(149, 166, 193);
ellipse(mouseX+30, mouseY, 10, 10);
ellipse(mouseX-30, mouseY, 10, 10);
push();
noFill();
stroke(149, 166, 193);
strokeWeight(3);
ellipse(mouseX+30, mouseY, 15, 15);
ellipse(mouseX-30, mouseY, 15, 15);
pop();
}
function moveShape() { // BORROWED FROM IRA GREENBERG
//move center point
deltaX = mouseX;
deltaY = mouseY;
//create springing effect
deltaX *= springing;
deltaY *= springing;
accelX += deltaX*0.8;
accelY += deltaY*0.8;
// slow down springing
accelX *= damping;
accelY *= damping;
//move nodes
for (var i=0; i<nodes; i++){
nodeX[i] = nodeStartX[i]+sin(radians(angle[i]))*(accelX);
nodeY[i] = nodeStartY[i]+sin(radians(angle[i]))*(accelY);
angle[i] += frequency[i];
}
} // END OF BORROWED CODE
function keyPressed() { // changes blob's color
if(keyCode == 81) { // "Q" blue
r = 202;
g = 220;
b = 247;
}
if(keyCode == 87) { // "W" pink
r = 247;
g = 207;
b = 207;
}
if(keyCode == 69) { // "E" yellow
r = 255;
g = 241;
b = 193;
}
}
// RINGS
function updateAndDisplayRings(){
// Update the rings positions, and display them.
for (var i = 0; i < rings.length; i++){
rings[i].move();
rings[i].display();
}
}
function ringAdd() {
// With a very tiny probability, add a new ring to the end.
var newringLikelihood = 0.02;
if (random(0,1) < newringLikelihood) {
rings.push(makeRing(0));
rings.push(makeRing(0));
}
}
// moving the rings
function ringMove() {
this.y += this.speed;
}
// drawing the rings
function ringDisplay() {
noFill();
stroke(210);
strokeWeight(2);
if (dist(mouseX, mouseY, this.x, this.y) <= 50) { // if the mouse is 50 points close to the ring, it pops
this.isPopped = true;
this.timePopped = frameCount; // record the framecount at which it pops
}
if (this.isPopped == false) { // if the state of the ring is not popped
ellipse(this.x, this.y, this.diam, this.diam);
}
else {
this.popped(); // if this.isPopped == true, then calls ringPop which displays pop animation
}
}
function ringPop() { // animating rings popping
var num = frameCount-this.timePopped;
if (num >= frames.length) {
// if num goes past the number of animation frames, the pop dissapears
}
else {
image(frames[num], this.x, this.y, 50, 50);
}
}
// making the rings
function makeRing(cy) {
var ring = {y: cy,
x: random(0,600),
diam: random(15,25),
speed: random(2.0, 3.0),
move: ringMove,
display: ringDisplay,
isPopped: false,
popped: ringPop,
timePopped: -10
}
return ring;
}
// SQUARES
function updateAndDisplayBox(){
// Update the clouds' positions, and display them.
for (var i = 0; i < boxes.length; i++){
boxes[i].bmove();
boxes[i].bdisplay();
}
}
function boxAdd() {
// With a very tiny probability, add a new ring to the end.
var newboxLikelihood = 0.02;
if (random(0,1) < newboxLikelihood) {
boxes.push(makeBox(0));
}
}
// moving the rings
function boxMove() {
this.by += this.bspeed;
this.py += this.pspeed;
this.yy += this.yspeed;
}
// drawing the rings
function boxDisplay() {
// blue box
if (dist(mouseX, mouseY, this.bx, this.by) <= 50 & r == 202 && g == 220 && b == 247) {
// if the mouse is 50 points close to the box and the blob is blue
this.bisEaten= true;
}
if (this.bisEaten == false) { // if the state of the box is not eaten
noStroke();
fill(202, 220, 247);
rect(this.bx, this.by, this.bs, this.bs, 5);
}
else {
}
// pink box
if (dist(mouseX, mouseY, this.px, this.py) <= 50 & r == 247 && g == 207 && b == 207) {
// if the mouse is 50 points close to the box and the blob is pink
this.pisEaten = true;
}
if (this.pisEaten == false) { // if the state of the box is not eaten
noStroke();
fill(247, 205, 202);
rect(this.px, this.py, this.bs, this.bs, 5);
}
else {
// box disapears when state is true
}
// yellow box
if (dist(mouseX, mouseY, this.yx, this.yy) <= 50 & r == 255 && g == 241 && b == 193) {
// if the mouse is 50 points close to the box and the blob is pink
this.yisEaten = true;
}
if (this.yisEaten == false) { // if the state of the box is not eaten
noStroke();
fill(255, 241, 193);
rect(this.yx, this.yy, this.bs, this.bs, 5);
}
else {
// box disapears when state is true
}
}
// making the rings
function makeBox(ty) {
var box = { // blue box coord
by: ty,
bx: random(0,600),
// pink box coord
py: random(0,200),
px: random(0,600),
// yellow box coord
yy: random(0,200),
yx: random(0,600),
bs: random(20,25),
bspeed: random(1.0, 2.0),
pspeed: random(1.0, 2.0),
yspeed: random(1.0, 2.0),
bmove: boxMove,
bdisplay: boxDisplay,
bisEaten: false, // blue false
pisEaten: false, // pink false
yisEaten: false, // yellow false
}
return box;
}
For my final project, I wanted to create a game with a fun ambiguous character. In a way, the game is like a fight between an organic shape and geometric shapes. It’s slightly different from my proposal but I like the way it turned out. It uses a modified version of the generative landscape functions, making the terrain (falling objects) fall downwards instead of sideways. It was difficult to modify the code so that the shapes would fall in a way that was relatively even, and not in lines or waves. The character is done using a heavily modified version of the soft body code found on the p5.js examples.
The objective of the game is to clear the screen! The rings pop whenever the character touches them, but the character’s own color must match up with the color of the squares to eat them. To change the character’s color, the player must select Q, W or E on their keyboard.