// Joanne Lee
// Section C
// joannele@andrew.cmu.edu
// Term Project
// Visual Quadrant Map:
// Q1 Q2 Q3
// Q4 Q5 Q6
// Q7 Q8 Q9
// logistics
var game = "inactive"
var fCount = 150;
var playBoardW = 270;
// playing board variables
var boardWeight = 10;
var playBoxW = 270;
var effectCounter = 6;
// white, orange, gray box variables
var boxW = playBoardW / 4;
var boxX = [165.625, 250, 334.375];
var boxY = [240.625, 325, 409.375];
var grayBoxes = [];
var newOrange = false;
var whiteX = 0;
var whiteY = 2;
var orangeX = 2;
var orangeY = 0;
// score keeper
var score = 0;
var bestScore = 0;
// sound variables
var coin;
var boing;
function preload() {
coin = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/coin.wav");
coin.setVolume(0.5);
boing = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/boing.wav");
boing.setVolume(0.5);
}
function setup() {
createCanvas(500,650);
frameRate(100);
}
function draw() {
background(255,211,222);
// "growth" effect seen when white + gray boxes collide
if (effectCounter < 2) { // staggered growth effect
playBoxW = 280;
}
if (effectCounter > 2 & effectCounter < 4) { // staggered growth effect
playBoxW = 290;
}
if (effectCounter > 4 & effectCounter < 6) { // staggered growth effect
playBoxW = 280;
}
if (effectCounter > 6) { // staggered growth effect
playBoxW = 270;
}
effectCounter += 1;
drawPlayingBoard();
drawPlayerBox();
drawGoalBox();
displayScore();
if (game == "inactive") {
displayStarterText();
}
if (game == "active") {
drawGrayBox();
// control speed that new boxes are generated
if (frameCount % fCount == 0 & frameCount != 0) {
generateGrayBox();
}
updateGrayBox();
checkCollision();
}
}
function displayScore() {
fill(255,80); // display current score
textSize(200);
textStyle(BOLD);
textFont('Helvetica');
if (score < 10) {
text(score, width - 120, height - 10);
}
else if (score < 100) { // shift over one spot for 2 digit score
text(score, width - 225, height - 10);
}
else if (score < 999) {
text(score, width - 330, height - 10 );
}
if (score >= bestScore) { // record the best score
bestScore = score
}
fill(255); // display the best score
textSize(20);
textStyle(BOLD);
textFont('Helvetica');
text("High Score " + bestScore, 110, height-160);
}
function displayStarterText() {
fill(40,99);
textSize(20);
textStyle(BOLD);
textFont('Helvetica');
text("Press enter to begin", 155, height/2);
}
function drawPlayingBoard() { // light pink w/ thick white border
fill(255,211,222);
stroke(255);
strokeWeight(boardWeight);
rectMode(CENTER);
rect(width / 2, height / 2, playBoxW, playBoxW);
}
function drawPlayerBox() { // white box, starts in Q7
strokeWeight(0);
fill(255);
rect(boxX[whiteX], boxY[whiteY], boxW, boxW);
}
function drawGoalBox() { // orange box, starts in Q3
fill(255,170,107);
rect(boxX[orangeX], boxY[orangeY], boxW*0.7, boxW*0.7);
}
function randomOrangeXY() { // generate random position for orange box
var randX = int(random(0,3));
var randY = int(random(0,3));
// make sure orange x & y are in same line or column as previous position
while (randX == whiteX || randY == whiteY) {
randX = int(random(0,3));
randY = int(random(0,3));
}
orangeX = randX;
orangeY = randY;
}
function checkBoard() {
// if white box position = orange box, you need a new orange box
if (whiteX == orangeX & whiteY == orangeY) {
coin.play();
newOrange = true; // indicate need for new orange box
score += 1; // increment score
// increase speed at which new boxes spawn, cap at 90 frame count
if (score % 10 == 0 & score != 0 && fCount > 90) {
fCount -= 20;
}
}
// if board needs new orange box, randomly generate
if (newOrange == true) {
randomOrangeXY();
newOrange = false;
}
}
function generateGrayBox() { // will create a box with random attributes
if (int(random(0,2)) == 0) { // create a box going left or right
if (int(random(0,2)) == 0) { // create box going right
createBox(-23.625, boxY[int(random(0,3))], 1, 0);
}
else { // create box going left
createBox(523.625, boxY[int(random(0,3))], -1, 0);
}
}
else { // create a box going up or down
if (int(random(0,2)) == 0) { // create box going down
createBox(boxX[int(random(0,3))], -23.625, 0, 1);
}
else { // create box going up
createBox(boxX[int(random(0,3))], 673.625, 0, -1);
}
}
}
function createBox(tx, ty, dx, dy) { // store as an object in the array
var grayBox = { x: tx, y: ty,
dx: dx, dy: dy,
speed: 2.5, state: 'active'
}
grayBoxes.push(grayBox);
}
function updateGrayBox() { // moves the box along
for (var a = 0; a < grayBoxes.length; a ++) {
grayBoxes[a].x += grayBoxes[a].dx * grayBoxes[a].speed;
grayBoxes[a].y += grayBoxes[a].dy * grayBoxes[a].speed;
}
}
function drawGrayBox() {
for (var b = 0; b < grayBoxes.length; b ++) {
fill(107,105,107);
rect(grayBoxes[b].x, grayBoxes[b].y, boxW*0.7, boxW*0.7);
}
}
function checkGrayBox() {
for (var c = 0; c < grayBoxes.length; c ++) {
// delete box from array once it is off the screen
if (grayBoxes[c].x < -24 || (grayBoxes[c].x > 524) ||
(grayBoxes[c].y < -24) || (grayBoxes[c].y > 674)) {
count += 1;
grayBoxes.splice(c,1);
}
}
}
function checkCollision() {
for (var d = 0; d < grayBoxes.length; d ++) {
if ((grayBoxes[d].x + boxW * 0.35) > (boxX[whiteX] - boxW * 0.5) &
(grayBoxes[d].x - boxW * 0.35) < (boxX[whiteX] + boxW * 0.5) &&
(grayBoxes[d].y - boxW * 0.35) < (boxY[whiteY] + boxW * 0.5) &&
(grayBoxes[d].y + boxW * 0.35) > (boxY[whiteY] - boxW * 0.5)) {
gameReset();
break;
}
}
}
function gameReset() {
boing.play();
score = 0;
grayBoxes = [];
fCount = 150;
effectCounter = 0;
game = "inactive";
}
function keyPressed() {
if (game == "active") {
if (keyCode == UP_ARROW & whiteY > 0) {
whiteY -= 1;
}
if (keyCode == DOWN_ARROW & whiteY < 2) {
whiteY += 1;
}
if (keyCode == LEFT_ARROW & whiteX > 0) {
whiteX -= 1;
}
if (keyCode == RIGHT_ARROW & whiteX < 2) {
whiteX += 1;
}
}
if (game == "inactive" & keyCode == ENTER) {
game = "active"
generateGrayBox();
}
// CHECK BOARD: to see if white box "ate" orange box
checkBoard();
}
The objective of the game is quite simple: collect the orange box by using your arrow keys to move the white box while avoiding the incoming gray boxes!
As the game progresses, you’ll find that the gray boxes will appear quicker, creating more obstacles to dodge. The game was based off of a game I saw a while ago, but was unable to find again on the internet — so the idea is not original, however, the interface is!
Although seemingly simple, there were a lot of technical obstacles I had to overcome with the main one being having to make sure that too many gray boxes did not appear at once. I ran into some bugs along the way, but I believe that my current code is clean and efficient!
Something I also tried to focus on was simple, but meaningful UI. Attention to detail was put in to small things such as a visual (and audio) cue when you mess up or the placement of the score. Hopefully this is a soothing yet addictive game that will get people through finals week! It was fun to create something of my own.
]]>For my final project, I plan to create a virtual Rube Goldberg machine geared towards children ages 5-9. Because my project is not necessarily “art”, I took inspiration from a media sample and contest design. One project that I found relevant and interesting was a media sample of a simple Rube Goldberg machine being explained on Sesame Street.
Sesame Street explaining how a simple Rube Goldberg machine works.
I was unable to find the year of this media sample however they call it the “What Happens Next Machine”. I found it to be useful to see how the machine was shown and expressed in very simple terms and helped me to better understand the type of terminology and level of difficulty I will implement in my actual experiment. Something that this media sample lacks is interactivity due to it being shown via television. I believe learning is done best through interaction and trial & error!
Another project I found was the Rube Goldberg exhibit at Children’s Museum of Pittsburgh. At this exhibit, children are able to activate chain reactions in order to complete every day tasks. I think this is a great exhibit because as a child, I was very fascinated by Rube Goldberg machines and still am! It is important to foster creativity in children and by using these inefficient machines to complete every day tasks, it helps to keep the creative juices flowing! I don’t think they overlooked anything large, but I would like to increase accessibility to these interactive exhibits by creating my own virtually. I hope my project turns out well.
]]>I want to create an educational Rube Goldberg game geared towards children ages 5-9. I think that Rube Goldberg games are very helpful in learning cause and effect. I would like to use my virtual Rube Goldberg machine to complete ‘green’ tasks such as watering a plant, turning off the lights, recycling, etc. The purpose of the game is to teach children to understand cause and effect better and also understand the impact we can make when we go green!
In terms of functionality, I’ll have a virtual Rube Goldberg machine with certain parts that are broken. Through trial and error, they will have to pass three stages — each stage dedicated to a green task. I’m still juggling between what tasks I want to do. Once they pass each level, they’ll be shown a blurb about the environmental impact that the stage’s task makes on the environment. Below is a rough mock-up of what a level could look like.
Alan Walker’s 2015 hit single, ‘Faded’.
When I realized this week’s theme was computational music, I immediately thought of Alan Walker specifically. He is a music producer and he creates his music (EDM type of music) using a computer program that allows him to combine synthetic instruments and sounds. He is also able to edit the sound waves in the program to tweak it any which way he wants and add effects such as echo or reverb to name a few. The song that I chose specifically is ‘Faded’ which is one of his most popular songs. When I first came across this song, it really piqued my interest in computer music and I actually felt motivated to learn about computer music.
Walker obviously begins with an artistic vision and then he tinkers with the program to make sure every beat and sound is placed exactly where he wants it to be. Provided below is a video of one of this studio sessions and he has other studio sessions on his channel. I am thankful to live in a generation where computer music allows someone to create music with all sorts of synthetic instruments on their own from the comforts of their own home / studio!
Alan Walker in his studio session for ‘Faded’, showing snippets of the thought process behind his hit song.
]]>// Joanne Lee
// Section C
// joannele@andrew.cmu.edu
// Project-11
// global variables
var maze;
var character = [];
var brightnessThreshold = 10; // low threshold bc working with black background
// color variables
var redR = 255;
var redG = 96;
var redB = 95;
var pinkR = 255;
var pinkG = 184;
var pinkB = 255;
var cyanR = 2;
var cyanG = 255;
var cyanB = 255;
var orangeR = 255;
var orangeG = 184;
var orangeB = 82;
// array with color r,g,b values
var randomCol = [redR, redG, redB, pinkR, pinkG, pinkB, cyanR, cyanG, cyanB, orangeR, orangeG, orangeB]
function preload() {
maze = loadImage("https://i.imgur.com/hGU36Hn.png") // load maze image
}
function setup() {
createCanvas(480, 323);
image(maze, 0, 0, 480, 323);
maze.loadPixels();
for (var a = 0; a < 6; a ++) { // create 6 hexagonal character in the maze
if (a < 3) { // positions for character startion left on maze
character[a] = makeCharacter(112, 57 + (95 * a));
}
else if (a >= 3) { // positions for character starting right on maze
character[a] = makeCharacter(360, 57 + (95 * (a % 3)));
}
setColor(a); // randomly set colors
character[a].setWeight(6);
character[a].penDown(); // begin drawing
character[a].forward(6);
// note that it doesn't stop drawing
}
}
function setColor(a) { // pick a random color from red, pink, cyan, orange
var randNum = random(0,4);
var index = floor(randNum) * 3; // take floor to make sure they're whole #'s
character[a].setColor(color(randomCol[index], randomCol[index + 1], randomCol[index + 2]));
}
function draw() {
for (var x = 0; x < 6; x ++) {
chrMove(x);
chrReset(x);
}
}
// getPixel: fast access to pixel at location x, y from image
function getPixel(image, x, y) {
var i = 4 * (y * image.width + x);
return color(image.pixels[i], image.pixels[i + 1], image.pixels[i + 2]);
}
function chrMove(x) { // update the object's position
var theColorAtPxPy = getPixel(maze, character[x].x, character[x].y); // get color of px, py position
// Fetch the brightness at PxPy, and save it.
var theBrightnessOfTheColorAtPxPy = brightness(color(theColorAtPxPy));
// If the brightness at PxPy is greater than the brightness threshold, move it down.
if (theBrightnessOfTheColorAtPxPy < brightnessThreshold) {
character[x].forward(1);
}
// If the brightness at PxPy is lower than darkness threshold, move up until it isn't.
while (theBrightnessOfTheColorAtPxPy > brightnessThreshold & this.py != 0) {
character[x].back(2);
if (random(0,1) < 0.5) {
character[x].right(90);
}
else {
character[x].left(90);
}
theColorAtPxPy = getPixel(maze, character[x].x, character[x].y);
theBrightnessOfTheColorAtPxPy = brightness(color(theColorAtPxPy));
}
}
function chrReset(x) { // reset the object to the top of the screen
if (character[x].x > width) { // bring back to left of screen if it moves off the right
character[x].x = 0;
}
else if (character[x].x < 0) { // bring back to right of screen if it moves off the left
character[x].x = width;
character[x].back(150);
character.right(90);
}
}
// turtle graphic commands
function turtleLeft(d) {
this.angle -= d;
}
function turtleRight(d) {
this.angle += d;
}
function turtleForward(p) {
var rad = radians(this.angle);
var newx = this.x + cos(rad) * p;
var newy = this.y + sin(rad) * p;
this.goto(newx, newy);
}
function turtleBack(p) {
this.forward(-p);
}
function turtlePenDown() {
this.penIsDown = true;
}
function turtlePenUp() {
this.penIsDown = false;
}
function turtleGoTo(x, y) {
if (this.penIsDown) {
stroke(this.color);
strokeWeight(this.weight);
line(this.x, this.y, x, y);
}
this.x = x;
this.y = y;
}
function turtleDistTo(x, y) {
return sqrt(sq(this.x - x) + sq(this.y - y));
}
function turtleAngleTo(x, y) {
var absAngle = degrees(atan2(y - this.y, x - this.x));
var angle = ((absAngle - this.angle) + 360) % 360.0;
return angle;
}
function turtleTurnToward(x, y, d) {
var angle = this.angleTo(x, y);
if (angle < 180) {
this.angle += d;
} else {
this.angle -= d;
}
}
function turtleSetColor(c) {
this.color = c;
}
function turtleSetWeight(w) {
this.weight = w;
}
function turtleFace(angle) {
this.angle = angle;
}
function makeCharacter(tx, ty) {
var turtle = {x: tx, y: ty,
angle: 0.0,
penIsDown: true,
color: color(128),
weight: 1,
left: turtleLeft, right: turtleRight,
forward: turtleForward, back: turtleBack,
penDown: turtlePenDown, penUp: turtlePenUp,
goto: turtleGoTo, angleto: turtleAngleTo,
turnToward: turtleTurnToward,
distanceTo: turtleDistTo, angleTo: turtleAngleTo,
setColor: turtleSetColor, setWeight: turtleSetWeight,
face: turtleFace, move: chrMove, // update the character's position
reset: chrReset, // reset the character to maze entrance
}
return turtle;
}
I created an endless self-navigating maze inspired by Pac-Man! The colors of the lines are randomly generated from the red, cyan, pink, orange colors of the ghosts from the games. I wish I had the ability and time to add more randomness in the maze’s twists and turns as well as more smooth line as opposed to hitting the wall then moving back a step. However, I am very happy with my final product as is. I’ve included pictures of different iterations below.
The project I chose to discuss is called SUPERHYPERCUBE. It is a “VR first person puzzler” that was originally developed for PlayStationVR. In order to play this game, you must figure out how to rotate the cubes on your screen to fit the approaching wall. With each successful completion, you get more and more cubes, making it more difficult to see the shape of the wall behind you. At that point you must quickly peek around the cubes to see the shape of the wall and figure out the correct rotations.
This fascinating game was created by Heather Kelley who is a veteran game designer, digital artist, and media curator among many things. She has served as the co-chair of the IGDA’s Women in Game Development Special Interest Group for 7 years and is clearly very passionate and good at what she does. She is the co-founder of Kokoromi (an experimental game collective) and has created many creative games under her belt. I admired this game in particular because I really love puzzle games and the idea of a VR version was very interesting to me. I also admire how involved she has been in advocating women in game development and it has inspired me to try to create a small, simple game as a side project during the remainder of this semester!
]]>// Joanne Lee
// Section C
// joannele@andrew.cmu.edu
// Project-09
var christine;
function preload() {
var myImageURL = "https://i.imgur.com/beoY7rv.jpg"; // load image
christine = loadImage(myImageURL);
}
function setup() {
createCanvas(480, 480);
background(0);
christine.loadPixels();
frameRate(50000000);
}
function draw() {
var px = random(width);
var py = random(height);
var ix = constrain(floor(px), 0, width-1);
var iy = constrain(floor(py), 0, height-1);
var rx = px + random(0,6); // randomize line x-value
var ry = py + random(0,4); // randomize line y-value
var rs = random(1,4); // randomize stroke weight
var theColorAtLocationXY = christine.get(ix, iy);
strokeWeight(rs);
stroke(theColorAtLocationXY);
line(px,py,rx,ry);
}
I chose a photo of my gorgeous current roommate (although the style of the photo made it hard to really capture her eyes!). I knew I wanted to do lines in order to emulate “strokes” and to further emulate this, I tried to vary the stroke weights as well as direction and lengths of the line. I didn’t want to make the line extend too far in order to ensure that facial features could moderately be made out.
I very much admire impressionist type paintings and so I tried to emulate thicker brush strokes. I made the frame rate ‘50000000’ because I preferred to see the photo render quickly. Below are photos further in to rendering and near completion.
Nunu & Willump League of Legends Champion Spotlight
In Robert Oh’s first Looking Outward, he reviewed the visual game update of a longtime League of Legends (multiplayer online game) champion. He mentioned that, “Riot is aiming towards making their boring characters more interesting by adding new skills that make them feel more fun to play.” As an avid player of this game, I wholeheartedly agree with this statement. Although I only started playing League of Legends 2 years ago, I find that something that keeps me engaged in the game is the consistent changes they make to their champions, gameplay, items, and abilities. This is a very great strategy implemented by Riot Games to keep the game feeling fresh despite the base mechanics of the game remaining the same.
One aspect I would like to clarify on is when Robert states that every couple months, Riot chooses to to update old characters to make them feel fresh and new. The original Nunu was released far back on February 21, 2009. The updated Nunu was released on August 29, 2018. It took about 9 years for them to update this champion. Many other champions are also waiting on a visual game update. My hypothesis is that they want to make sure that their updates will be radical and refreshing and not just minor changes. In between, they will often release brand new champions, further delaying the updates of previous champions.
As a player of this champion, the visual game update proved to be very exciting and worth the wait. I hope Riot will continue to re-envision their previous art and champion styles in order to keep the game a fresh and exciting game to play!
]]>Flight Visualizations over 24 Periods, Institution: NATS (2014)
NATS is an air traffic control services provider and in this particular video, they visualized the air traffic coming and going to European airspace over the course of 24 hours on a summer day in 2014. Despite the fact that there is not much information available on it, I chose this work because after I graduated high school, my parents moved to Germany for 3 years. During those 3 years, I had the privilege of making many trips to, from, and within Europe. Throughout the countless delayed flights, time spent on the runway, and in the air, I became increasingly aware of the sheer number of flights that there are in a day and wondered how air traffic was managed and controlled.
It is particularly fascinating and meaningful to me to see this visualization. In terms of creating this visualization, I image that NATS has access to all of the flight routes of any given day and they carefully compiled these routes onto a 3D model of the Earth. I really like that they used a dark background for the Earth and used an eye-catching neon color while also making it slightly opaque — so that we can see places where many of the routes overlap.
(* Note: This assignment was submitting using one grace day.)
]]>// Joanne Lee
// Section C
// joannele@andrew.cmu.edu
// Project-07
var nPoints = 100;
var colorR = 167;
var colorG = 168;
var colorB = 212;
var div = 0;
var w = 0;
var h = 0;
function setup() {
createCanvas(480,480);
}
function draw() {
background (240);
push();
translate(0,0);
for (var c = 0; c < 6; c ++) {
h = c * 96; // shifts height of flower
for (var d = 0; d < 7; d ++) {
w = d * 80; // shift width of flower
drawEpitrochoidCurve(colorR, colorG, colorB, div, w, h);
}
}
if (mouseY > 0){
div = (mouseY / 15.0) + 5; // flower size scales to mouseY position
}
pop();
}
function drawEpitrochoidCurve(colorR, colorG, colorB, div, w, h) {
// Epicycloid:
// http://mathworld.wolfram.com/Epicycloid.html
var x;
var y;
var a = 80.0;
var b = a / 2.0;
fill(colorR, colorG, colorB);
beginShape();
for (var i = 0; i < 100; i++) {
var t = map(i, 0, nPoints, 0, TWO_PI);
x = a * (6 * cos(t) - cos(6 * t)) / div + w;
y = a * (6 * sin(t) - sin(6 * t)) / div + h;
vertex(x, y);
}
endShape(CLOSE);
}
function mouseClicked() { // petal color changes upon mouseClick
var colNum = int(random(1,4));
if (colNum == 1) {
colorR = 167;
colorG = 168;
colorB = 212;
}
else if (colNum == 2) {
colorR = 207;
colorG = 61;
colorB = 160;
}
else if (colNum == 3) {
colorR = 90;
colorG = 76;
colorB = 181;
}
}
For this week’s project, I first thought of flower petals when I was looking at the possible curves. I looked up flower petals and liked the colors and shapes of these, so I used ranuncloid curves to create them. Ranuncloid curves are essentially epicycloids with 5 cusps. For interactivity, I thought it would be interesting to make the flower “grow” using the mouseY position as well as randomly alternate between the flower colors depicted above with mouse clicks. I purposely had the flowers overlap with each other at its largest size because I thought that it formed an interesting pattern.
]]>