Timothy Liu — Final Project

tcliu-FINALPROJECT

// Timothy Liu
// 15-104, Section C
// tcliu@andrew.cmu.edu
// FINAL PROJECT: Crossy Chicken, the game!

var showStart = true; // start off the game showing the start screen

var myChar; // the player (the chicken)
var charSpeed = 2; // speed player moves at

var borderW = 40; // border used in all popup screens

var firstCars = []; // arrays of cars (from bottom of screen to top)
var secondCars = [];
var thirdCars = [];
var fourthCars = [];

// road characteristics
var roadWidth = 100;
var roadLine = 2;
var carSpacing;

// river and bridge characteristics
var riverStart = 243; // identifies the top of the river
var riverWidth = 100; // identifies the width of the river
var riverEnd = riverStart + riverWidth; // identifies the bottom of the river
var bridgeWidth = 30; // width of the bridge
var bridgeMiddle = riverStart + riverWidth / 2; // the top part of the middle bridge segment
var randomBridgeStart; // where the first segment of the bridge starts 
var randomBridgeLength; // how long the middle segment of the bridge is

// variables that help draw/locate the egg and nest
var eggLocY = 50;
var eggW = 12;
var eggH = 16;
var nestLocY = 60;
var nestShadowY = 57;
var nestW = 30;
var nestH = 18;

// sound
var soundtrack;
var winSound;
var loseSound;

// preload all sounds
function preload() {

    // background track
    soundtrack = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/froggertrack.wav");
    soundtrack.setVolume(0.5);

    // losing jingle
    loseSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/lose.wav");
    loseSound.setVolume(0.6);

    // winning jingle
    winSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/win.wav");
    winSound.setVolume(0.9);

}

function setup() {

    createCanvas(600, 600); // create the canvas

    soundtrack.play(); // play the frogger soundtrack that's been preloaded

    myChar = makeCharacter(); // makes the character

    carSpacing = random(30, 45); // determines spacing between cars; ensures they don't hit each other (but varies the spacing randomly)
    
    randomBridgeStart = random(50, 500); // determines the random location of the bridge
    randomBridgeLength = random(60, 250); // determines the random length of the bridge

    firstCarSetup(); // defines properties for all the different rows of cars
    secondCarSetup(); // second row of cars (2nd from bottom)
    thirdCarSetup(); // third row of cars (3rd from bottom)
    fourthCarSetup(); // top row of cars

}

function draw() {

    background(87, 168, 84); // grass floor

    noStroke();

    drawFirstRoad(); // draw in the bottom road
    drawRiver(); // draw in the river
    drawSecondRoad(); // draw in the top road

    drawNest(); // draw in the nest

    // draw in and move the character!
    myChar.draw();
    myChar.move();

    // draws in the cars on the road using drawCars()
    showFirstCars();
    showSecondCars();
    showThirdCars();
    showFourthCars();

    characterWins(); // function that runs when the character reaches the egg and wins
    characterLoses(); // function that identifies when the character loses

    startScreen(); // starting screen shows right at the beginning

}

// the starting screen that shows rules and game info
function startScreen() {

    // right at start of game, this statement is true UNTIL SPACE IS PRESSED
    if (showStart === true) {

        noLoop();

        // the frame/popup screen
        fill(235, 131, 131);
        rect(width / 6, height / 6, width * 2 / 3, height * 2 / 3);

        fill(255);
        rect(width / 6 + borderW / 2, height / 6 + borderW / 2, width * 2 / 3 - borderW, height * 2 / 3 - borderW);

        // Start screen
        fill(0);
        textSize(20);

        textAlign(CENTER); // center the text
        textFont('Avenir'); // text font

        // starting screen text: the intro!
        textStyle(NORMAL);
        text("Welcome to Crossy Chicken!", width / 2, 225);
        text("Watch out for cars, and", width / 2, 255);
        text("don't let your feet get wet!", width / 2, 285);

        text("Use the WASD keys to move, and", width / 2, 345); 
        textStyle(BOLD);
        text("press SPACE to get started!", width / 2, 375);

    }

}

// triggers to start and reset the game
function keyPressed() {

    if (keyCode === 32) { // if SPACEBAR is pressed, let the game begin!
        showStart = false;
        loop();
    }
    if (keyCode === ENTER) { // if ENTER is pressed, the level restarts!
        restart(); // restart function resets the stage
    }

    return false; // prevents the webpage from moving when arrows are pressed

}

// this function initializes all variables and then reruns the setup function, effectively restarting the game
function restart() {

    firstCars = []; // initialize all of the car arrays so they can be redrawn/setup
    secondCars = [];
    thirdCars = [];
    fourthCars = [];

    setup(); // re-run setup, which resets the stage
    loop(); // start running the code again!

}

// define all character properties (the chicken)
function makeCharacter() {

    return {
        x: 300,
        y: 550,
        c: 255,
        w: 25,
        h: 40,
        r: 25,
        hX: 300,
        hY: 490,
        hW: 15,
        hH: 20,
        eyeC: 0,
        eyeW: 2,
        beakC: color(252, 202, 3),
        crownW: 5,
        crownH: 10,
        crownC: color(207, 58, 58),
        footW: 7,
        footH: 4,
        draw: drawCharacter,
        move: moveCharacter
    }

}

// uses the obj characteristics and var myChar to draw the chicken
function drawCharacter() {

    var headOffset = 18;
    var eyeOffsetX = 3;
    var eyeOffsetY = 21;
    var beakOffsetX = 4;
    var beakOffsetY = 19;
    var beakOffsetY2 = 15;
    var crownOffsetY = 27;
    var footOffset = 5;

    // red crown on head
    fill(myChar.crownC);
    arc(myChar.x, myChar.y - crownOffsetY, myChar.crownW, myChar.crownH, PI, TWO_PI);

    // feet
    fill(myChar.beakC);
    arc(myChar.x - footOffset, myChar.y, myChar.footW, myChar.footH, 0, PI);   
    arc(myChar.x + footOffset, myChar.y, myChar.footW, myChar.footH, 0, PI);  

    // head and body   
    fill(myChar.c);
    arc(myChar.x, myChar.y, myChar.w, myChar.h, PI, TWO_PI);
    arc(myChar.x, myChar.y - headOffset, myChar.hW, myChar.hH, PI, TWO_PI);

    // eyes
    fill(myChar.eyeC);
    ellipse(myChar.x - eyeOffsetX, myChar.y - eyeOffsetY, myChar.eyeW, myChar.eyeW);
    ellipse(myChar.x + eyeOffsetX, myChar.y - eyeOffsetY, myChar.eyeW, myChar.eyeW);

    // beak
    fill(myChar.beakC);
    triangle(myChar.x - beakOffsetX, myChar.y - beakOffsetY, myChar.x + beakOffsetX, myChar.y - beakOffsetY,
            myChar.x, myChar.y - beakOffsetY2);

}

// this function allows the character to move
function moveCharacter() {

    if (keyIsDown(65)) { // if pressing the A key
        myChar.x -= charSpeed; // move the character's x position left
        if (myChar.x + myChar.r < 0) { // if the character moves off the screen, have it wrap to the other side
            myChar.x = width + myChar.r;
        }
    }

    if (keyIsDown(68)) { // if pressing the D key 
        myChar.x += charSpeed; // move the character's x position right
        if (myChar.x - myChar.r > width) { // if the character moves off the screen, have it wrap to the other side
            myChar.x = -myChar.r;
        }
    }

    if (keyIsDown(87)) { // if pressing the W key
        myChar.y -= charSpeed; // move the character's y position up
    }

    if (keyIsDown(83)) { // if pressing the S key
        myChar.y += charSpeed; // move the character's y position down
        if (myChar.y + myChar.r > height) { // if it hits the bottom, don't let it go off the page
            myChar.y = height - myChar.r;
        }
    }

}

// what happens if the character reaches the egg
function characterWins() {

    // if the character reaches the egg successfully
    if (myChar.y > eggLocY - eggH / 2 & myChar.y < nestLocY + nestH / 2) {
        if (myChar.x > width / 2 - nestW / 2 && myChar.x < width / 2 + nestW / 2) {
            noLoop(); // game stops
            winningScreen(); // display winning screen
            winSound.play(); // play winning jingle!
        }
    }

}

// this function determines if the character loses using two other functions: hit by car, or fall in river
function characterLoses() {

    characterHitByCar(); // function if myChar is hit by a car
    characterFallsInRiver(); // function if myChar falls into the river

}

// if the character is hit by a car
function characterHitByCar() {

    // if char is hit by first row of cars
    for (var i = 0; i < firstCars.length; i++) {

        var carMiddleX = firstCars[i].x + (firstCars[i].w / 2);
        var carMiddleY = firstCars[i].y + (firstCars[i].h / 2);

        var charMiddleX = myChar.x;
        var charMiddleY = myChar.y - (myChar.h / 2);

        // if the edges of the character fall within the edges of the car, you lose!
        if (charMiddleX - myChar.w / 4 < carMiddleX + firstCars[i].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - firstCars[i].w / 2
            && myChar.y > carMiddleY - firstCars[i].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + firstCars[i].h / 2) {
            noLoop(); // game stops
            losingScreenCar(); // show losing screen
            loseSound.play(); // losing sound plays
        }

    }

    // if char is hit by second row of cars
    for (var j = 0; j < secondCars.length; j++) {

        var carMiddleX = secondCars[j].x + (secondCars[j].w / 2);
        var carMiddleY = secondCars[j].y + (secondCars[j].h / 2);

        var charMiddleX = myChar.x;
        var charMiddleY = myChar.y - (myChar.h / 2);

        // if the edges of the character fall within the edges of the car, you lose!
        if (charMiddleX - myChar.w / 4 < carMiddleX + secondCars[j].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - secondCars[j].w / 2
            && myChar.y > carMiddleY - secondCars[j].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + secondCars[j].h / 2) {
            noLoop(); // game stops
            losingScreenCar(); // show losing screen
            loseSound.play(); // losing sound plays
        }

    }

    // if char is hit by third row of cars
    for (var k = 0; k < thirdCars.length; k++) {

        var carMiddleX = thirdCars[k].x + (thirdCars[k].w / 2);
        var carMiddleY = thirdCars[k].y + (thirdCars[k].h / 2);

        var charMiddleX = myChar.x;
        var charMiddleY = myChar.y - (myChar.h / 2);

        // if the edges of the character fall within the edges of the car, you lose!
        if (charMiddleX - myChar.w / 4 < carMiddleX + thirdCars[k].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - thirdCars[k].w / 2
            && myChar.y > carMiddleY - thirdCars[k].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + thirdCars[k].h / 2) {
            noLoop(); // game stops
            losingScreenCar(); // show losing screen
            loseSound.play(); // losing sound plays
        }

    }

    // if char is hit by fourth row of cars
    for (var l = 0; l < fourthCars.length; l++) {

        var carMiddleX = fourthCars[l].x + (fourthCars[l].w / 2);
        var carMiddleY = fourthCars[l].y + (fourthCars[l].h / 2);

        var charMiddleX = myChar.x;
        var charMiddleY = myChar.y - (myChar.h / 2);

        // if the edges of the character fall within the edges of the car, you lose!
        if (charMiddleX - myChar.w / 4 < carMiddleX + fourthCars[l].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - fourthCars[l].w / 2
            && myChar.y > carMiddleY - fourthCars[l].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + fourthCars[l].h / 2) {
            noLoop(); // game stops
            losingScreenCar(); // show losing screen (car version)
            loseSound.play(); // losing sound plays
        }
    }

}

// if the strays from the bridge and falls into the river
function characterFallsInRiver() {

    // if the character is crossing the river...
    if (myChar.y > riverStart & myChar.y < riverEnd) {

        // first segment of the bridge (vertical)
        if (myChar.y > bridgeMiddle + bridgeWidth && myChar.y < riverEnd) {

            // if char is outside the bridge boundaries
            if (myChar.x < randomBridgeStart || myChar.x > randomBridgeStart + bridgeWidth) {
                noLoop(); // game stops
                losingScreenRiver(); // show losing screen (splash version)
                loseSound.play(); // play losing sound
            }
        }
        
        // the bridge randomly goes left or right based on starting position...
        // if the bridge goes rightward:
        if (randomBridgeStart < width / 2) {

            // second segment of the bridge (horizontal)
            if (myChar.y > bridgeMiddle & myChar.y < bridgeMiddle + bridgeWidth) {
                // if char is outside the boundaries of the horizontal bridge segment
                if (myChar.x < randomBridgeStart || myChar.x > randomBridgeStart + randomBridgeLength + bridgeWidth) {
                    noLoop(); // game stops
                    losingScreenRiver(); // show losing screen (splash version)
                    loseSound.play(); // play losing sound
                }
            }
        
            // last segment of the bridge (vertical)
            if (myChar.y > riverStart & myChar.y < bridgeMiddle) {
                if (myChar.x < randomBridgeStart + randomBridgeLength || myChar.x > randomBridgeStart + randomBridgeLength + bridgeWidth) {
                    noLoop(); // game stops
                    losingScreenRiver(); // show losing screen (splash version)
                    loseSound.play(); // play losing sound
                }
            }

        // if the bridge goes leftward:
        } else {

            // second segment of the bridge (horizontal)
            if (myChar.y > bridgeMiddle & myChar.y < bridgeMiddle + bridgeWidth) {
                if (myChar.x < randomBridgeStart - randomBridgeLength || myChar.x > randomBridgeStart + bridgeWidth) {
                    noLoop(); // game stops
                    losingScreenRiver(); // show losing screen (splash version)
                    loseSound.play(); // play losing sound
                }
            }

            // last segment of the bridge (vertical)
            if (myChar.y > riverStart & myChar.y < bridgeMiddle) {
                if (myChar.x < randomBridgeStart - randomBridgeLength || myChar.x > randomBridgeStart - randomBridgeLength + bridgeWidth) {
                    noLoop(); // game stops
                    losingScreenRiver(); // show losing screen (splash version)
                    loseSound.play(); // play losing sound
                }
            }
        }
    }
}

// the popup screen when you win
function winningScreen() {

	soundtrack.stop(); // stop background sound

    // border + screen
    fill(255, 213, 0);
    rect(width / 4, height / 4, width / 2, height / 2);

    fill(255);
    rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);

    // winning screen text for when you make it to the egg
    fill(0);
    textSize(20);
    textAlign(CENTER); // center the text
    textFont('Avenir'); // text font

    // text:
    textStyle(NORMAL);
    text("You protected your egg;", width / 2, 270);
    textStyle(BOLD);
    text("Congratulations!", width / 2, 300);
    textStyle(NORMAL);
    text("Hit ENTER to play again!", width / 2, 330);

}

// the popup screen when you're hit by a car
function losingScreenCar() {

    soundtrack.stop();

    fill(235, 131, 131);
    rect(width / 4, height / 4, width / 2, height / 2);

    fill(255);
    rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);

    // losing screen text for when a car runs into you
    fill(0);
    textSize(20);
    textAlign(CENTER); // center the text
    textFont('Avenir'); // text font

    // text:
    textStyle(NORMAL);
    text("Oh no, a car got you!", width / 2, 285);
    text("Hit ENTER to try again!", width / 2, 315);

}

// the popup screen when you fall into the river
function losingScreenRiver() {

    soundtrack.stop();

    fill(53, 99, 240);
    rect(width / 4, height / 4, width / 2, height / 2);

    fill(255);
    rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);

    // losing screen text for when you fall into the water
    fill(0);
    textSize(20);

    textAlign(CENTER); // center the text
    textFont('Avenir'); // text font

    // text:
    textStyle(BOLD); // bold the sound effect (splash)
    text("Splash...", width / 2, 270);
    textStyle(NORMAL);
    text("You fell into the water!", width / 2, 300);
    text("Hit ENTER to try again!", width / 2, 330);

}

// draw in the bottom (first) road
function drawFirstRoad() {

    var roadLocX = 0;
    var roadLocY = 385;
    var lineLocY = 434;

    fill(180);
    rect(roadLocX, roadLocY, width, roadWidth); // draws the road

    fill(245, 224, 66);
    rect(roadLocX, lineLocY, width, roadLine); // the dividing yellow line

}

// draw in the top (second) road
function drawSecondRoad() {

    var roadLocX = 0;
    var roadLocY = 100;
    var lineLocY = 149;

    fill(180);
    rect(roadLocX, roadLocY, width, roadWidth); // draws the road

    fill(245, 224, 66);
    rect(roadLocX, lineLocY, width, roadLine); // the dividing yellow line

}

// draw in the river
function drawRiver() {

    var riverEdgeL = 0;

	fill(100, 100, 255); // blue color
    rect(riverEdgeL, riverStart, width, riverWidth);

    drawBridge(); // draw in bridge using bridge function

}

// draws the bridge the crosses the river. the bridge is randomly generated each time the game is played!
// it will go rightward if the starting point is on the left of the screen, and leftward if the starting point is to the right
function drawBridge() {

    fill(186, 145, 104); // brown bridge color

    rect(randomBridgeStart, bridgeMiddle, bridgeWidth, riverWidth / 2); // first bridge segment
    
    // this if-else statement determines which direction the bridge goes in
    if (randomBridgeStart < width / 2) {
        rect(randomBridgeStart, bridgeMiddle, randomBridgeLength, bridgeWidth); // second bridge segment
        rect(randomBridgeStart + randomBridgeLength, riverStart, bridgeWidth, riverWidth / 2 + bridgeWidth); // final bridge segment
    } else {
        rect(randomBridgeStart - randomBridgeLength, bridgeMiddle, randomBridgeLength, bridgeWidth); // second bridge segment
        rect(randomBridgeStart - randomBridgeLength, riverStart, bridgeWidth, riverWidth / 2 + bridgeWidth); // final bridge segment
    }

}

// characteristics of the car objects
function makeCars(px, py) {
    return {
        x: px,
        y: py,
        c: color(random(255), random(255), random(255)),
        w: 50,
        h: 30,
        lightsW: 2,
        lightsH: 5,
        lightsC: color(242, 235, 19),
        tireW: 9,
        tireH: 3,
        tireC: color(0, 0, 0),
        windshieldW: 4,
        windshieldH: 25,
        windshieldC: color(100, 100, 100),
        draw: drawCars
    }
}

// uses the obj characteristics to draw cars
function drawCars() {

	var tireOffsetX = 6;
	var tireOffsetY = 2;
    var windshieldOffsetX = 10;
    var windshieldOffsetY = 2.5;

    // car body
    fill(this.c);
    rect(this.x, this.y, this.w, this.h);

    // car headlights (x4)
    fill(this.lightsC);
    rect(this.x + this.w - this.lightsW, this.y, this.lightsW, this.lightsH);
    rect(this.x + this.w - this.lightsW, this.y + this.h - this.lightsH, this.lightsW, this.lightsH);
    rect(this.x, this.y, this.lightsW, this.lightsH);
    rect(this.x, this.y + this.h - this.lightsH, this.lightsW, this.lightsH);

    // car tires
    fill(this.tireC);
    rect(this.x + this.w - (2.5 * tireOffsetX), this.y - tireOffsetY, this.tireW, this.tireH);
    rect(this.x + tireOffsetX, this.y - tireOffsetY, this.tireW, this.tireH);
    rect(this.x + this.w - (2.5 * tireOffsetX), this.y + this.h - (tireOffsetY / 2), this.tireW, this.tireH);
    rect(this.x + tireOffsetX, this.y + this.h - (tireOffsetY / 2), this.tireW, this.tireH);

    // car windshields
    fill(this.windshieldC);
    rect(this.x + (3.5 * windshieldOffsetX), this.y + windshieldOffsetY, this.windshieldW, this.windshieldH);
    rect(this.x + (1.2 * windshieldOffsetX), this.y + windshieldOffsetY, this.windshieldW, this.windshieldH);

    if (this.x > width) {
        this.x = -this.w;
    }

    if (this.x + this.w < 0) {
        this.x = width;
    }
}

// draws the nest using nest variables
function drawNest() {

    // the nest
    fill(219, 181, 75);
    ellipse(width / 2, nestLocY, nestW * 1.3, nestH);

    fill(163, 129, 34);
    ellipse(width / 2, nestShadowY, nestW, nestH / 2);
 
    // the egg
    fill(250, 242, 220);
    ellipse(width / 2, eggLocY, eggW, eggH);

}

// setting up the first row of cars and placing them (very bottom row of cars)
function firstCarSetup() { 

    var carPlacement; // car placement variable
    var numCars = 5; // number of cars in the first row of cars
    var carLocY = 445;

    var newCar = makeCars();
    firstCars.push(newCar); // add each new car to the empty array firstCars

    // place each car in the first row of cars and add to the array firstCars
    for (var i = 0; i < numCars; i++) {
        carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
        firstCars[i] = makeCars(carPlacement, carLocY); // placement of the first row of cars
    }

}

// setting up the second row of cars and placing them
function secondCarSetup() {

    var carPlacement; // car placement variable
    var numCars = 5; // number of cars in the first row of cars
    var carLocY = 395;

    var newCar = makeCars();
    secondCars.push(newCar); // add each new car to the empty array secondCars

    // place each car in the second row of cars and add to the array secondCars
    for (var i = 0; i < numCars; i++) {
        carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
        secondCars[i] = makeCars(carPlacement, carLocY);
    }

}

// setting up the third row of cars and placing them
function thirdCarSetup() {

    var carPlacement; // car placement variable
    var numCars = 5; // number of cars in the first row of cars
    var carLocY = 160;

    var newCar = makeCars();
    thirdCars.push(newCar); // add each new car to the empty array secondCars

    // place each car in the third row of cars and add to the array secondCars
    for (var i = 0; i < numCars; i++) {
        carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
        thirdCars[i] = makeCars(carPlacement, carLocY);
    }

}

// setting up the fourth row of cars and placing them
function fourthCarSetup() {

    var carPlacement; // car placement variable
    var numCars = 5; // number of cars in the first row of cars
    var carLocY = 110;

    var newCar = makeCars();
    fourthCars.push(newCar); // add each new car to the empty array secondCars

    // place each car in the fourth row of cars and add to the array secondCars
    for (var i = 0; i < numCars; i++) {
        carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
        fourthCars[i] = makeCars(carPlacement, carLocY);
    }

}

// draws the first row of cars (closest to the bottom of the screen)
function showFirstCars() {

    for (var i = 0; i < firstCars.length; i++) {
        firstCars[i].draw(); // draws in cars
        firstCars[i].x += 1; // makes the cars drive rightward on the first road (bottom)
    }

}

// draws the second row of cars (next row up from bottom of the screen)
function showSecondCars() {

    for (var i = 0; i < secondCars.length; i++) {
        secondCars[i].draw();
        secondCars[i].x -= 1; // makes the cars move in the opposite direction (leftward) on the first road
    }

}

// draws the third row of cars (next row up from bottom of the screen)
function showThirdCars() {

    for (var i = 0; i < thirdCars.length; i++) {
        thirdCars[i].draw();
        thirdCars[i].x += 1.8; // faster cars to increase the difficulty (rightward, top road)!
    }

}

// draws the fourth row of cars (row of cars near top of screen)
function showFourthCars() {

    for (var i = 0; i < fourthCars.length; i++) {
        fourthCars[i].draw();
        fourthCars[i].x -= 1.8; // faster cars in opposite direction to increase the difficulty (leftward, top road)!
    }

}

Welcome to Crossy Chicken, my final project for 15-104! A tribute to the original Frogger, Crossy Chicken involves a chicken trying to reach her beloved egg. But to get there, the chicken must first cross two busy streets as well as a perilous bridge; can you get her back to her egg?

To play, use the WASD keys (W = Up, S = Down, A = Left, D = Right) to direct the chicken and follow the instructions on screen! Make sure you dodge all the cars and don’t let the chicken’s feet touch the water!

I had a fantastic time coding this project, and I’m proud of how I distributed the work. I broke my project down into phases, and I worked through them in the following order:

  1. Character movement
  2. Introducing cars
  3. Adding roads
  4. Creating the river and bridge
  5. Adding the next 3 rows of cars
  6. Losing screen
  7. Winning screen
  8. Reset button (ENTER)
  9. Start button (SPACE)
  10. Adding sounds

In the end, I wound up with what I felt was a very polished and complete game. I’m really happy that I was able to build a visually pleasing game with fun and retro graphics while using a multitude of elements from p5js. From objects, to loops, to sound, my game really has a bit of everything in it. The soundtrack (which is actually the original Frogger music!) adds an element of excitement and nostalgia, and the losing and winning sound effects complete the experience for the gamer. I hope you have a great time playing!

(*Note 2: as is typically the case with p5js, the sound is sometimes a bit glitchy on Chrome and certain other browsers. However, the implementation of sound is all correct, as can be seen in my code.)

Timothy Liu — Project 12 — Proposal

The original Frogger from 1981!

For my final project, I want to build an homage to Frogger. I’d like to try programming a game that involves a character trying to reach the end of the stage by making it through a series of moving obstacles and objects. This is an idea that really appeals to me, because

  1. I haven’t had the chance to try coding a game in this class and I think it’d be a really fun challenge to take on, and
  2. I’m excited to play around with the graphics, design, and gameplay in this project!
Some sketches of my initial ideas for my game. On the left is what the gameplay will look and feel like, as well as the keyboard controls used to move the character. On the right are some potential character ideas!

I’ve come up with a few ideas for gameplay and graphics which mainly involve animal characters moving across a terrain filled with natural obstacles. For instance, a penguin waddling across snow while dodging moving snowballs to try to make it to an igloo is something I’d love to build.

From a functional standpoint, my ultimate goals for this project are to build a smooth, well-oiled game with a soundtrack and irresistible charm. I want the arrow keys to direct the character’s motion and for the movement of the obstacles to feel natural, not mechanistic.

Artwork from the original Frogger that my game’s concept will be modeled off of.

In terms of art-style/design, I really admire video games that incorporate cel-shading and minimalism. I hope to utilize color palettes and shapes to establish a calming, consistent feel with my game, as I think it would add to the desired natural ambience.

The Legend of Zelda, Wind Waker utilizes cel shading and bright, cheerful color palettes. It feels fun and clean!
Monument Valley, a popular iPhone game, also uses really clean graphics, designs, and colors.

I can’t wait to get started!

Timothy Liu — Looking Outwards — 12

It’s final project time! When I read the prompt for the final project, I immediately knew I wanted to do something related to a game. As referenced in my last 2 Looking Outwards, I love video games and I think it would be really cool to try and build a game experience through p5.js. A game that came to mind was Frogger, a classic concept where a character must cross a road with incoming traffic to reach the other side. For this Looking Outwards, I looked at two examples of Frogger-style games: the classic Frogger on the C64, and Crossy Road on the iPhone.

The original Frogger is a clear flash-to-the-past, with insanely retro graphics and color schemes. Frogger was released in 1981 by Konami, and it was an instant hit; players loved the simplicity of the game as well as its addictive appeal. The premise is straightforward: the player, playing as a frog, must weave through traffic and jump across a variety of animal-backs and logs to reach safety on the other side of the screen. The player then goes through a series of levels to try and set their high score without losing all of their lives. Frogger’s simplicity was part of its charm, but I personally felt their was a level of animated complexity lacking; of course, this was likely because of the datedness of the game (1981 was a while ago, and computers have come a long way since!).

The original Frogger on the Commodore 64!

Crossy Road, released in 2014 by the gaming studio Hipster Whale, is an attempt to fix this issue by adding in flashy new graphics, fun animations, and 3D characters. It’s modeled with block/pixel-style characters, seemingly a reference to the game’s historical roots, but it’s clear after playing Crossy Road that gameplay is immensely smoother and more flowy. I’ve played Crossy Road in the past, and it was a great experience. The game was fun, and losing doesn’t even feel bad because of the charm of the game. However, one thing I felt could be improved on is the simplicity of the game; in a weird way, Crossy Road almost does too much to the original frogger to the point where the various rocks, trees, cars, and rivers the player needs to avoid become almost distracting. There’s something sophisticatedly simple about the original Frogger that reduces gameplay distractions, and that’s something I think Crossy Road misses at times.

Some gameplay from Crossy Road, a mobile game released in 2014 that drew from the concept of Frogger.

Timothy Liu — Project 11 — Landscape

tcliu-project-11

// Timothy Liu
// 15-104, Section C
// tcliu@andrew.cmu.edu
// OpenProject-11

var cowArray = []; // array for all cows
var birdArray = []; // array for all birds

// variables to control the shape and speed of the rows of trees
var backTreesStructure = 0.04;
var backTreesSpeed = 0.0009;
var frontTreesStructure = 0.04;
var frontTreesSpeed = 0.0009;

// hill in the front
var rollingPlain = 0.005;
var rollingPlainSpeed = 0.0001;

function setup() {

    createCanvas(600, 240); 

    // start with 3 cows on the screen
    for (var i = 0; i < 3; i++){
        var cowPlacement = random(0, width);
        cowArray[i] = cowObj(cowPlacement); // places the 3 cows in random locations

    }

    // draws in birds; start with 5 on the screen
    for (var j = 0; j < 5; j++) {
        birdArray.push(birdObj(j * 30 + random(40, width - 40),
                        random(20, 60), random(10, 25))); // gives the birds random x, y, and size
    }

    frameRate(40);

}

function draw() {

    background(124, 196, 230); // sky color

    drawTrees(); // draw the two rows of trees first (in the background)

    updateAndShowCows(); // draw and have the cows move

    updateCowsOffScreen(); // account for when cows move off screen

    updateAndShowBirds(); // draw and have the birds move

    updateBirdsOffScreen(); // account for when birds move off screen

    newCows(); // draws in new cows (from edge of screen)

    newBirds(); // draws in new birds (from edge of screen)
}

function drawTrees() {

    // the back (darker) row of trees
    beginShape();
    fill(40, 120, 61);
    noStroke();
    vertex(0, height);

    // use noise function to draw random tree line
    for (var x = 0; x < width; x++) {
        var t = (x * backTreesStructure) + (millis() * backTreesSpeed);
        var y = map(noise(t), 0, 1, 40, 150);
        vertex(x, y); 
    }
    vertex(width, height);
    endShape();

    // the front (lighter) row of trees
    beginShape(); 
    fill(50, 168, 82);
    noStroke();
    vertex(0, height);

    //use noise function to draw random, lower tree line to create depth
    for (var x = 0; x < width; x++) {
        var t = (x * frontTreesStructure) + (millis() * frontTreesSpeed);
        var y = map(noise(t * 0.7), 0, 1, 80, 200);
        vertex(x, y); 
    }
    vertex(width, height);
    endShape();

    // the light-green hills in front
    beginShape(); 
    fill(154, 196, 118);
    noStroke();
    vertex(0, height);

    // use a dampened version of the noise function to make slowly rolling hills
    for (var x = 0; x < width; x++) {
        var t = (x * rollingPlain) + (millis() * rollingPlainSpeed);
        var y = map(noise(t * 0.5), 0, 1, 170, 200);
        vertex(x, y); 
    }
    vertex(width, height);
    endShape();

}

// puts the cows on the canvas and makes them move
function updateAndShowCows(){

    for (var i = 0; i < cowArray.length; i++){

        cowArray[i].move(); // move property of the cow obj
        cowArray[i].show(); // show property of the bird obj

    }
}

// puts the birds on the canvas and makes them move
function updateAndShowBirds(){

    for (var i = 0; i < birdArray.length; i++){

        birdArray[i].moveB(); // move property of the bird obj
        birdArray[i].showB(); // show property of the bird obj

    }
}

// eliminates the cows that move off screen
function updateCowsOffScreen(){

    var cowsToKeep = []; // to determine which cows are still on screen

    for (var i = 0; i < cowArray.length; i++) {
        if (cowArray[i].x + cowArray[i].cowWidth > 0) { // if the cow is still on the screen
            cowsToKeep.push(cowArray[i]); // put it into the new array
        }
    }

    // cowsToKeep[] becomes the new array of cows currently on screen
    cowArray = cowsToKeep;
}

// eliminates the birds that move off screen
function updateBirdsOffScreen(){

    var birdsToKeep = []; // to determine which birds are still on screen

    for (var i = 0; i < birdArray.length; i++) {
        if (birdArray[i].bx + birdArray[i].sizeOfBird > 0) { // if the bird is still on the screen
            birdsToKeep.push(birdArray[i]); // put it into the new array
        }
    }

    // birdsToKeep[] becomes the new array of birds currently on screen
    birdArray = birdsToKeep;
}

// occasionally, add a new cow to the end of the screen
function newCows() {
    var newCowOdds = 0.008; 
    if (random(0, 1) < newCowOdds) {
        cowArray.push(cowObj(width)); // add a new cow to the array
    }
}

// occasionally, add a new bird to the end of the screen
function newBirds() {
    var newBirdOdds = 0.008;
    if (random(0, 1) < newBirdOdds) {
        birdArray.push(birdObj(width, random(20, 50), random(10, 25))); // add a new bird to the array
    }
}

// makes the cows move
function movingCows() {
    this.x += this.speed;
}

// makes the birds move
function movingBirds() {
    this.bx += this.speedBird;
}

// this is what actually draws in, and displays, the cows using various cow object properties
function showCows() {

    var cowPlacement = this.randomLoc;
    var earOffset = 7;
    var headOffset = 15;
    var eyeOffset = 10;
    var spot2Offset = 5;
    var spot3Offset = 4;
    var snoutOffset = 6;

    push();

    translate(this.x, height); // places the cows onto the canvas

    // shadow
    noStroke();
    fill(7, 66, 25);
    ellipse(0, -cowPlacement + 10, this.cowWidth, this.cowHeight / 4);

    // cow body
    fill(this.randomColor);
    arc(0, -cowPlacement, this.cowWidth, this.cowHeight * 0.75, 2 * PI / 3, PI / 3, CHORD);
    
    // cow ears
    arc(-headOffset - earOffset, -cowPlacement - headOffset, this.earW, this.earW * 0.75, 4 * PI / 3, PI / 3, CHORD);
    arc(-headOffset + earOffset, -cowPlacement - headOffset, this.earW, this.earW * 0.75, 2 * PI / 3, 5 * PI / 3, CHORD);
    
    // cow head
    ellipse(-headOffset, -(cowPlacement + eyeOffset), this.cowHead * 1.25, this.cowHead);

    // three spots
    fill(0);
    ellipse(this.spot1, -cowPlacement, this.spotW, this.spotH);
    ellipse(this.spot2, -cowPlacement + spot2Offset, this.spotW * 1.2, this.spotH);
    ellipse(this.spot3, -cowPlacement - spot3Offset, this.spotW, this.spotH * 1.2);

    // eyes
    ellipse(-18, -cowPlacement - eyeOffset, this.eye, this.eye * 1.2);
    ellipse(-12, -cowPlacement - eyeOffset, this.eye, this.eye * 1.2);

    // snout
    fill("#eba4dd");
    ellipse(-15, -(cowPlacement + snoutOffset), this.snout * 1.6, this.snout);
    
    pop();

}

// this is what actually draws in, and displays, the birds using various bird object properties
function showBirds() {

    var birdBod = 5; // body size of bird

    noFill();
    stroke(0);
    strokeWeight(2);

    push();

    // places and sizes the birds
    translate(this.bx, this.by);
    scale(this.sizeOfBird / 60);
    ellipseMode(CENTER);

    // uses arcs and ellipses to draw the wings and body
    arc(35, 35, 100, 100, 5 * PI / 4, 3 * PI / 2);
    arc(-35, 35, 100, 100, 3 * PI / 2, 7 * PI / 4);
    fill(0);
    ellipse(0, 0, birdBod, birdBod); 

    pop();

}

// here are all the properties of the cow object
function cowObj(x) {

    let colors = ["#8f6846", "#ffffff", "#918880"] // different colors of cows

    // cow obj properties
    var cow = {x: x,
                cowWidth: random(30, 40), // each cow has a diff size and shape
                cowHeight: random(25, 40),
                cowHead: random(10, 16),
                snout: 7,
                spot1: random(-12, -8),
                spot2: random(1, 5),
                spot3: random(5, 10),
                eye: 3,
                earW: 8,
                spotW: 8,
                spotH: 6,
                speed: random(-3, -1), // each cow moves at a diff speed
                move: movingCows,
                show: showCows,
                randomLoc: random(35, 60),
                randomColor: random(colors), // each cow has a different color
            }

    return cow;

}

// here are all the properties of the bird object
function birdObj(x, y, size) {

    // bird obj properties
    var bird = {bx: x, 
                by: y, 
                sizeOfBird: size, // each bird has a random size
                speedBird: random(-4, -1), // each bird has a random speed
                showB: showBirds,
                moveB: movingBirds
            };

    return bird;

}

When I thought about the “generative landscape” prompt for this week’s assignment, one of the first things that came to mind was the scenery during the long road trips my family takes every year from the Bay Area to Southern California. I distinctly remember staring out the window as a kid, watching cows idly graze in fields and the rolling trees and hills zoom by as we drove down I-5. Thus, I wanted to model my project after this landscape because it’s a very fond childhood memory of mine.

An example of a landscape similar to what I was trying to capture in my project.

In my generative landscape, I have cows of all sorts of shapes, colors, and sizes on grassy plains as the screen pans leftward. In the background, rows of trees and grassy hills roll by, and birds fly overhead at varying speeds. My piece is meant to represent an idyllic view of what a kid sees when they stare out the window on a road trip through California’s central valley. The blue sky, the flocking birds, and the vast greenery all convey warmth and happiness. Ultimately, I think it’s the motion and dynamism of my piece that really help capture the feeling of blissfulness.

Timothy Liu — Project 10 — Sonic Sketch

Click on a drum to hear its sound!

tcliu-openended-10

// Timothy Liu
// 15-104, Section C
// tcliu@andrew.cmu.edu
// OpenEnded-10

// bass drum variables
var bassDrumW = 200;
var bassDrumX;
var bassDrumY;
var rimWidth = 20;
var bassRadius = bassDrumW / 2;

// high tom tom variables
var tomTomW = 100;
var tomTomH = 40;
var tomTomBaseW = 100;
var tomTomBaseH = 50;
var tomTomX1 = 220;
var tomTomY1 = 200;
var tomTomX2 = 380;
var tomTomY2 = 200;
var baseOffset = tomTomW / 2;
var tomTomRodW = 5;
var tomTomRodH = 250;
var httRodOffset = tomTomRodW / 2;
var hTTDRadius = 50;

// floor tom tom variables
var fTomTomW = 150;
var fTomTomH = 60;
var fTomTomBaseW = 150;
var fTomTomBaseH = 100;
var fTomTomX = 135;
var fTomTomY = 300;
var fBaseOffsetX = fTomTomW / 2;
var fBaseOffsetY = 100;
var fTomTomRodW = 8;
var fTomTomRodH = 100;
var fRodOffset = fTomTomRodW / 2;
var fTTDRadius = 95;

// snare drum
var snareW = 120;
var snareH = 40;
var snareBaseW = 120;
var snareBaseH = 30;
var snareX = 450;
var snareY = 275;
var snareOffsetX = snareW / 2;
var snareOffsetY = snareBaseH;
var snareRodW = 8;
var snareRodH = 200;
var snareRodOffset = snareRodW / 2;
var snareRadius = 50;

// cymbals
var cymbalX1 = [100, 130];
var cymbalY1 = [30, 150];
var cymbalZ1 = [170, 150];
var cymbalX2 = [500, 130];
var cymbalY2 = [570, 150];
var cymbalZ2 = [430, 150];
var cymbalRodH = 350;
var cymbalRodW = 4;
var cymbalOffset = cymbalRodW / 2;
var cymbalH = 20;

// variables for sounds
var hTTDSound;
var FTTDSound;
var bassDSound;
var cymbalSound;
var snareDSound;

// preload all the different drum sounds: snare, bass, high tom tom, floor tom tom, and cymbal
function preload() {
    snareDSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/snare.wav");
    snareDSound.setVolume(0.5);

    bassDSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/bass.wav");
    bassDSound.setVolume(0.9);

    hTTDSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/HTTD.wav");
    hTTDSound.setVolume(0.5);

    fTTDSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/FTTD.wav");
    fTTDSound.setVolume(0.5);

    cymbalSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/cymbal.wav");
    cymbalSound.setVolume(0.5);

}

function setup() {
    createCanvas(600, 600);
}

function draw() {

    background(200, 200, 255); // blue background

    noStroke();

    highTomTomDrum(tomTomX1, tomTomY1); // calls the high Tom Tom Drum drawing function and places it at the first x, y
    highTomTomDrum(tomTomX2, tomTomY2); // calls the high Tom Tom Drum drawing function again and places it at the other x, y
    cymbals(cymbalX1, cymbalY1, cymbalZ1); // calls the cymbal drawing function and places it at the first x, y
    cymbals(cymbalX2, cymbalY2, cymbalZ2); // calls the cymbal drawing function again and places it at the other x, y
    floorTomTomDrum(fTomTomX, fTomTomY); // calls the floor Tom Tom Drum drawing function and places it at the given x, y
    snareDrum(snareX, snareY); // calls the snare drum drawing function and places it at the given x, y
    bassDrum(); // calls the bass drum drawing function

}

function mousePressed() {
    // play the high tom tom drum sound when the mouse is pressed on the left high tom tom drum
    if (insideHTTDrumLeft() === true) {
        hTTDSound.play();
    }

    // play the high tom tom drum sound when the mouse is pressed on the right high tom tom drum
    if (insideHTTDrumRight() === true) {
        hTTDSound.play();
    }

    // play the floor tom tom drum sound when the mouse is pressed on the floor tom tom drum
    if (insideFTTDrum() === true) {
        fTTDSound.play();
    }
    
    // play the bass drum sound when the mouse is pressed on the bass drum
    if (insideBassDrum() === true) {
        bassDSound.play();
    }

    // play the bass drum sound when the mouse is pressed on the snare drum
    if (insideSnare() === true) {
        snareDSound.play();
    }

    // play the bass drum sound when the mouse is pressed on the left cymbal
    if (insideCymbalLeft() === true) {
        cymbalSound.play();
    }

    // play the bass drum sound when the mouse is pressed on the right cymbal
    if (insideCymbalRight() === true) {
        cymbalSound.play();
    }
}

function insideHTTDrumLeft() {
    var dHTTDrumLeft = dist(mouseX, mouseY, tomTomX1, tomTomY1 + 30); // dist from mouse to center of left HTT Drum
    if (dHTTDrumLeft <= hTTDRadius) { // checks if the distance from mouse to the center is less than the radius of the HTT drum (which would mean it's inside the drum)
        return true;
    }
}

function insideHTTDrumRight() {
    var dHTTDrumRight = dist(mouseX, mouseY, tomTomX2, tomTomY2 + 30); // dist from mouse to center of right HTT Drum
    if (dHTTDrumRight <= hTTDRadius) { // checks if the distance from mouse to the center is less than the radius of the HTT drum (which would mean it's inside the drum)
        return true;
    }
}

function insideFTTDrum() {
    var dFTTDrum = dist(mouseX, mouseY, fTomTomX, fTomTomY + 75); // dist from mouse to center of FTT Drum
    if (dFTTDrum <= fTTDRadius) { // checks if the distance from mouse to the center is less than the radius of the FTT drum (which would mean it's inside the drum)
        return true;
    }
}

function insideBassDrum() {
    var dBass = dist(mouseX, mouseY, bassDrumX, bassDrumY); // dist from mouse to center of bass drum
    if (dBass <= bassRadius) { // checks if the distance from mouse to the center is less than the radius of the bass drum (which would mean it's inside the drum)
        return true;
    }
}

function insideSnare() {
    var dSnare = dist(mouseX, mouseY, snareX, snareY + 25); // dist from mouse to center of snare drum
    if (dSnare <= snareRadius) { // checks if the distance from mouse to the center is less than the radius of the snare drum (which would mean it's inside the drum)
        return true;
    }
}

function insideCymbalLeft() { // is the mouse inside the left cymbal?
    if (mouseX >= 30 & mouseX <= 170 && mouseY >= 130 && mouseY <= 150) { // checks if the mouse is inside the edges of the cymbal
        return true; 
    }
}

function insideCymbalRight() { // is the mouse inside the right cymbal?
    if (mouseX >= 430 & mouseX <= 570 && mouseY >= 130 && mouseY <= 150) { // checks if the mouse is inside the edges of the cymbal
        return true;
    }
}

function highTomTomDrum(x, y) { // function that draws the high tom tom drums (the 2 small middle ones)

    fill(64, 40, 15);
    rect(x - httRodOffset, y + baseOffset, tomTomRodW, tomTomRodH);

    fill(163, 47, 29);
    ellipse(x, y + baseOffset, tomTomW, tomTomH);
    rect(x - baseOffset, y, tomTomBaseW, tomTomBaseH);

    fill(255, 245, 212);
    ellipse(x, y, tomTomW, tomTomH);

}

function floorTomTomDrum(x, y) { // function that draws the floor tom tom drum (the bigger left one)

    fill(64, 40, 15);
    rect(x - fRodOffset, y + fBaseOffsetY, fTomTomRodW, fTomTomRodH);

    fill(163, 47, 29);
    ellipse(x, y + fBaseOffsetY, fTomTomW, fTomTomH);
    rect(x - fBaseOffsetX, y, fTomTomBaseW, fTomTomBaseH);

    fill(255, 245, 212);
    ellipse(x, y, fTomTomW, fTomTomH);
}

function bassDrum() { // function that draws the bass drum (the big round middle one)

    bassDrumX = width / 2;
    bassDrumY = 2 * height / 3;
    
    fill(163, 47, 29);
    ellipse(bassDrumX, bassDrumY, bassDrumW + rimWidth, bassDrumW + rimWidth)
    
    fill(255, 245, 212);
    ellipse(bassDrumX, bassDrumY, bassDrumW, bassDrumW);

}

function snareDrum(x, y) { // function that draws the snare drum (flatter one on right)

    fill(64, 40, 15);
    rect(x - snareRodOffset, y + snareOffsetY, snareRodW, snareRodH);

    fill(163, 47, 29);
    ellipse(x, y + snareOffsetY, snareW, snareH);
    rect(x - snareOffsetX, y, snareBaseW, snareBaseH);

    fill(255, 245, 212);
    ellipse(x, y, snareW, snareH);

}

function cymbals(x, y, z) { // function that draws the cymbals

    fill(64, 40, 15);
    rect(cymbalX1[0] - cymbalOffset, cymbalX1[1] + cymbalH, cymbalRodW, cymbalRodH);
    rect(cymbalX2[0] - cymbalOffset, cymbalX2[1] + cymbalH, cymbalRodW, cymbalRodH);
    fill(255, 227, 46);
    triangle(x[0], x[1], y[0], y[1], z[0], z[1]);

}

For my sonic sketch, I decided to build an interactive drum set. My drum set is comprised of a bass drum, 2 high tom-tom drums, a snare drum, a floor tom-tom drum, and 2 cymbals (as labeled in the diagram).

A labeled diagram of the different parts of my drum set. Each one, when clicked, makes a different sound!

The drum set took a while to create, as I wanted to create separate functions for each drum in order to simplify my draw function. After drawing the drums, I went to freesound.org and found audio files to match each of the drums. I then implemented the logic within mousePressed() to make sure that the sound played when the mouse clicked on the corresponding drum. I really enjoyed this project because it felt very systematic to program and it encouraged me to utilize various functions as well as ample amounts of logic. In addition, getting to learn how to preload sounds was beneficial for my development as a programmer!

Timothy Liu — Looking Outwards — 10

An early photo of Junichi Masuda in his digital recording studio.

When I read the prompt for this week’s Looking Outwards, I immediately thought of video game music. I’ve always been a fan of videos games—especially Nintendo franchises such as Pokémon, Mario, and more—and their soundtracks have long been considered the gold standard of technical music. One of the most prominent composers in video game history has been Junichi Masuda, the mastermind behind most of the soundtracks in the Pokémon series. His works have ranged from being techno-like in nature to beautifully symphonic in his newer games. But the commonality among all of the works he’s composed is that they were each computationally created.

I first listened to some of Masuda’s soundtracks from his earlier games like Pokémon Red and Blue (1998). I loved the techno-funk feeling conveyed by the music, and after reading up more about Masuda’s processes, I learned that this was partly a byproduct of technical limitations of that era, but also due to Masuda’s self-proclaimed affinity for techno music at the time. Pokémon Red and Blue were developed on UNIX computer stations called the Sun SPARCstation 1, which made programming files susceptible to crashing. These were clear programming limitations that likely limited the quality of sound files and sound effects.

The soundtrack from Pokémon Red and Blue (1998).

Next, for the sake of comparison, I listened to music from Pokémon Black and White, games from 2012. I was blown away by the difference; the soundtracks from the newer games were not only crisper, smoother, and rendered more cleanly, but they legitimately sounded like orchestral movements. It was incredible to me how much Masuda’s work evolved, and after reading more about his inspirations, I learned that he was a big fan of the classical composers Igor Stravinsky and Dmitri Shostakovich. This was evident in the elegance of his compositions, and it blew my mind to learn that he programmed these tunes just like he did the techno-style music of 1998. It’s a testament to Masuda’s talent and understanding of the interplay between technology, computation, and music.

The soundtrack from Pokémon Black and White (2012).

Sources:

https://www.polygon.com/interviews/2018/9/27/17909916/pokemon-red-blue-junichi-masuda-interview

https://en.wikipedia.org/wiki/Junichi_Masuda

Timothy Liu — Looking Outwards — 11

SUGAR, a game experience designed by Heather Kelley.

For this week’s Looking Outwards, I examined the work of Heather Kelley, a game designer, digital artist, and media curator focused on sensory interactions and aesthetics in video games. Heather holds a Masters of Arts from the University of Texas at Austin and has worked in a variety of entertainment-technology realms throughout her career. She’s made stops at Subotron, Quake, Unreal, and even the Entertainment Technology Center at Carnegie Mellon, bringing her expertise and work to the classroom as well as to gamer screens worldwide. As a lifelong video game fan, her name immediately caught my eye when I was browsing the list of accomplished women in the field because of her ties to the video game industry and her incredible success within it. After reading over her bio, I was even more impressed by all she’s accomplished, especially her work on the UNFPA Electronic Game to End Gender Violence. It’s clear that Heather is an inspiration to all game designers out there, as she’s managed to combine her creative talents with her desire to create societal change.

One of Heather’s works that I found most impressive was SUGAR, a “cross media collaborative event featuring an original game, scent-generating networked electronics, and couture fashion” (perfectplum.com). SUGAR is, simply put, an immersive video game experience that light-heartedly satirizes the imperial court and Hapsburg history from a romantic perspective. In the game, two players work together to coordinate the dancing of two horses in order to appease the medieval crowd. The art style and imagery are whimsical and geometric, adding a light-hearted flair to her game design and visuals. But what was most unique about SUGAR is the inclusion of an “Action Olofactorizer,” a device that combines hardware, software, and chemicals to produce scents and smells based on the player’s actions. For example, grass, leather, and even horse poop scents are produced to accompany gameplay! Heather’s ability to fully immerse the player and make SUGAR, as well as the rest of her games, interactive experiences is remarkable, and I really enjoyed learning about her work.

Some of the scents, sights, and visuals from SUGAR, Heather Kelley’s cross-media collaborative gaming experience.

Sources:

perfectplum.com

Timothy Liu — Project 09 — Portrait

I am using 1 grace day on this project.

tcliu-openended-09

// Timothy Liu
// 15-104, Section C
// tcliu@andrew.cmu.edu
// OpenEnded-09

var Eileen; // variable name of the person in my project!

function preload() {
    Eileen = loadImage("https://i.imgur.com/V7NYz2M.jpg"); // preloading the image
}

function setup() {
    createCanvas(300, 450);
    background(255);
    Eileen.loadPixels();
    frameRate(250);
    Eileen.resize(300, 450);
}

function draw() {

    // variables to determine the location of each hamburger
    var burgerX = random(width);
    var burgerY = random(height);

    // variables that ensure the hamburger drawn remains on the canvas
    var burgerOnCanvasX = constrain(floor(burgerX), 0, width - 1);
    var burgerOnCanvasY = constrain(floor(burgerY), 0, height - 1);

    // variables to determine the proportions and shape of each burger
    var burgerW = random(8, 14);
    var burgerH = random(6, 10);
    var meatWStart = burgerW / 2 - burgerW * 0.125;
    var meatW = burgerW * 0.75;
    var meatH = random(2, 4);

    // variable that identifies the pixel color of the underlying images
    var pixelColor = Eileen.get(burgerOnCanvasX, burgerOnCanvasY);

    // applies the pixel color to each hamburger in the foreground
    noStroke();
    fill(pixelColor); 

    // drawing each individual hamburger
    arc(burgerX, burgerY, burgerW, burgerH, PI, TWO_PI);
    rect(burgerX - meatWStart, burgerY, meatW, meatH);
    arc(burgerX, burgerY + meatH, burgerW, burgerH, 0, PI);

}

For this project, I used a photo I took of my girlfriend Eileen at In-n-out burger in California. I really liked the photo because it has a lot of vibrant colors (red, green, yellow, etc) which I thought would be fun to portray through abstract shapes and pixels. I decided to make each building block of the portrait shaped like a hamburger as a reference to the burgers in the foreground of the picture and the restaurant the photo was taken at. Each burger is proportionally built based on a random bun height and width, which means that each burger is a different, randomly generated size. Together, each of the burgers collects to form a portrait!

The early stages of hamburgers…
As more hamburgers are drawn, the color and shape begins to show…
A more complete version of my piece when more of the hamburgers have been drawn. The hamburgers aren’t super small, so it’s hard to depict the fine details, but the shape and figure are definitely there!
The original photograph!

Timothy Liu — Looking Outwards — 09

An image of the Aguahoja I pavilion exhibited in the MIT Media Lab lobby in 2018.

For my Looking Outwards this week, I looked at my classmate Shannon’s Looking Outwards from Week 3 on Aguahoja, a work by the MIT Media Lab. The prompt for that week was on Computational Fabrication, and I really liked the piece Shannon chose because of how majestic yet haunting it felt. Shannon described Aguahoja as an exploration of how human technology—specifically, 3D printing—could emulate natural and biological materials such as tree branches and bones. The MIT Media Lab took an innovative design approach using algorithms, water-based design, and digital fabrication to erect these massive sculptures that represent nature’s intricacies. 

I really enjoyed Shannon’s reflection on Aguahoja because it was concise yet contemplative, explaining how although Aguahoja is an effective representation of nature’s form, it still requires manpower and technology. One thing I would add onto her discussion points are the incredible amount of research the MIT team undertook to develop their algorithms for printing. It took them 6 whole years of research into computationally manufactured functional biopolymers for them to feel confident enough in their ability to emulate these biological structures, a testament to how wonderfully unique nature really is.

 

An example of one of the biological exoskeletons Aguahoja strove to emulate.

Sources:

https://www.media.mit.edu/projects/aguahoja/overview/

Timothy Liu — Looking Outwards — 08

Heather Knight’s lecture on charismatic technology creation in 2011.

For this week’s Looking Outwards, I watched Heather Knight’s lecture from the 2011 Eyeo festival on charismatic and beneficial technology creation — namely, how to build robots that work alongside and benefit people. Heather has a degree in Electrical Engineering from MIT, a PhD in Robotics from CMU (!!), and a postdoc from Stanford exploring minimal robots and autonomous car interfaces. She worked in Paris for a few years at the start of her career before returning to the MIT Media Lab to work on a robot called “Kismet” under Dr. Cynthia Breazeal. This was her segway into the realm of robotics, as it enabled her to see what it meant to work on an interactive, human-like robot (Kismet was meant to be a functional robot head).

Heather is immensely passionate about “social robotics,” the study of technology with social intelligence that can communicate with us in human terms. She strives to understand how we can build robot interfaces that adjust to human needs, not the other way around. This made me realize that Heather was, in a way, a pioneer in social/autonomous robotics; she was discussing the concepts of ethics in robots and human-technology interaction way back in 2011, well before any of the technologies of today were developed!

Due to her affinity for technology, the performing arts, and entertainment, Heather started working for Syyn Labs, an creative technology group that, as described on their website, “fuses the worlds of technology and interactive sciences with artistic mediums to design and construct visually dynamic spectacles that inspire thought and provoke conversation.” There, she began working on the installation of robots in music videos while developing her own robot theater company, Marilyn Monrobot. It’s an unconventional path, but it’s what led to her working on the OK Go Rube Goldberg machine in their music video “This Too Shall Pass,” one of the most famous music videos in YouTube’s existence! Heather managed the top floor of the machine, or roughly the first 2 minutes of the video. I remember watching this video multiple times growing up and marveling at its artistic complexity, so it was really cool learning about Heather’s involvement in the project.

The Rube Goldberg machine that Heather and Syyn Labs helped build for OK Go.

Overall, I think what I admired most about Heather’s talk was her ability to ground and personify robots. Robots are inherently cold, mechanistic beings, but Heather’s passion for the subject matter and artistic understandings allowed her to find a way to bring robots to life charismatically. It’s evident in her presentation style too; she’s loose, lively, and a whole lot of fun to listen to. It’s her charisma that rubs off on her robots most, and I love the fact that her projects all seem to be passion projects. Today, she’s an Assistant Professor of Robotics at Oregon State University, where she runs the—you guessed it—CHARISMA research group, which uses entertainment and artistic influences to develop Social Robots. It’s exciting work that’s befitting of her energetic nature.

SOURCES:

http://www.marilynmonrobot.com/

http://syynlabs.com/

https://en.wikipedia.org/wiki/Kismet_(robot)