Nawon Choi— Final Project

INSTRUCTIONS: Blocks will begin to appear on the screen and approach you! When blocks turn yellow, type the letter displayed on the block to earn a point. You have 5 lives.

sketch

// Nawon Choi
// Section C
// nawonc@andrew.cmu.edu
// Final Project

var numOfStars = 700;
var starSize = 1.2;
var sx = [];
var sy = [];
var sz = [];

var newBlockChance = 100;
var blocks = [];
var blockSize = 50;
var speed = 5;
var lastBlk = 0;
var alphabet = ["a", "b", "c", "d", "e", "f", "g",
                 "h", "i", "j", "k", "l", "m", "n", 
                 "o", "p", "q", "r", "s", "t", "u", 
                 "v", "w", "x", "y", "z"];
var txt;
var ripeBlocks = [];

var level = 1;
var points = 0;
var showPoints;
var lives = 5;
var heart;
var gameOver;
var resetTxt;
var yourScore;
var lvl;

function preload() {
    heart = loadImage("https://i.imgur.com/YvJC0vj.png");
}

function setup() {
    createCanvas(480, 500, WEBGL);
        
    // generate a bunch of randomly placed stars
    for (var i = 0; i < numOfStars; i++) {
        sx.push(random(-width, width));
        sy.push(random(-height, height));
        sz.push(random(-height, height));
    }

    // used for drawing letter on block
    txt = createGraphics(200, 200);
    txt.textSize(75);

    // show points/level at the corner
    showPoints = createGraphics(width, 100);
    showPoints.textSize(60);
    showPoints.fill(255);
    showPoints.textStyle(BOLD);
    lvl = createGraphics(width, 100);
    lvl.textSize(50);
    lvl.fill(255);

    // game over screen text
    gameOver = createGraphics(width + 150, 100);
    gameOver.textSize(100);
    gameOver.fill(255);
    gameOver.textStyle(BOLD);
    resetTxt = createGraphics(width + 150, 100);
    resetTxt.textSize(40);
    resetTxt.fill(255);
    yourScore = createGraphics(width + 150, 100);
    yourScore.textSize(40);
    yourScore.fill(255);


}

function draw() {
    background("#1c2736");
    drawStars();

    if (lives > 0) {
        // draw hearts to represent lives
        for (var i = 0; i < lives; i++) {
            push();
            translate(width / 2 - (30 * (i + 1)), -height / 2 + 25);
            texture(heart);
            plane(30, 30);
            pop();
        }

        // show points/level at the top corner
        push();
        showPoints.background("#1c2736");
        showPoints.text("SCORE = " + points, 10, 60);
        texture(showPoints);
        translate(-width / 2 + 85, -height / 2 + 25);
        plane(150, 30);

        lvl.background("#1c2736");
        lvl.text("LEVEL = " + level, 10, 60);
        texture(lvl);
        translate(0, 25);
        plane(150, 30);
        pop();


        // long road
        push();
        fill("#104670");
        translate(0, 30, -600);
        rotateX(blockSize);
        box(width, 10, 8000);
        pop();

        // highlight zone
        push();
        fill("#497291");
        rotateX(blockSize);
        translate(0, 150);
        box(400, 10, 330); 
        pop();

        // increase level based on points
        var determineLvl = floor(points / 10) + 1;
        if (determineLvl > 3) {
            // only up to 3 levels
            level = 3;
        } else {
            level = determineLvl;
        }

        if (level < 3) {
            // make sure blocks don't overlap
            if (frameCount > (lastBlk + 30)) {
                var newBlock = floor(random(newBlockChance));
                if (newBlock == 1) {
                    lastBlk = frameCount;
                    blocks.push(makeNewBlock());
                }
            }
        } else {
            // blocks can overlap
            var newBlock = floor(random(newBlockChance));
            if (newBlock == 1) {
                lastBlk = frameCount;
                blocks.push(makeNewBlock());
            }
        }

        // draw blocks
        for (var i = 0; i < blocks.length; i++) {
            if (blocks[i].z < 330) {
                blocks[i].draw();
                if (blocks[i].ripe()) {
                    ripeBlocks.push(blocks[i]);
                }
            } else {
                // remove blocks that have gone off the page
                if (blocks[i].scored == 0) {
                    lives--;
                }
                blocks.shift();
            }
        }
    } else {
        // game over screen
        push();
        translate(0, -25);
        gameOver.text("GAME OVER", 0, 80);
        texture(gameOver);
        plane(300, 50);

        resetTxt.text("Press 'R' to restart", 0, 100);
        translate(70, 40);
        texture(resetTxt);
        plane(300, 50);

        yourScore.text("Your score = " + points, 0, 100);
        translate(0, 50);
        texture(yourScore);
        plane(300, 50);
        pop();
    }
}

function resetGame() {
    lives = 5;
    points = 0;
    level = 1;
    blocks = [];
    newBlockChance = 100;
}

function levelUp () {
    // increase difficulty
    newBlockChance -= 5;
}

function blockIsScored() {
    this.scored = 1;
    ripeBlocks.shift();
}

function blockIsRipe() {
    // if block is on the highlighted zone
    if (this.z == 70) {
        return true;
    }
    return false;
}

function drawBlock() {
    push();
    // block changes color based on nearness/if scored
    if (this.scored == 0) {
        if (this.z >= 70) {
            txt.background("yellow");
        } else {
            txt.background("red");
        }
    } else {
        txt.background("green");
    }
        
    // displays letter on the block
    var upper = this.letter.toUpperCase();
    txt.text(upper, 80, 120);
    stroke("#104670");
    texture(txt);
    rotateX(blockSize);
    translate(this.x, this.y, this.z);
    box(blockSize);
    pop();

    // move forward
    this.z += speed;
}

function makeNewBlock() {
    var block = {
        x: floor(random(-100, 100)),
        y: floor(random(-150, 25)),
        z: -2000,
        letter: alphabet[floor(random(26))],
        ripe: blockIsRipe,
        score: blockIsScored,
        scored: 0,
        draw: drawBlock
    }
    return block;
}

function drawStars() {
    fill(200);
    noStroke();
    for (var i = 0; i < numOfStars; i++) {
        push();
        translate(sx[i], sy[i], sz[i]);
        sphere(starSize);
        pop();
    }
}

function keyTyped() {
    if (lives == 0) {
        // reset game
        if (key == "r") {
            resetGame();
        }
    } else {
        // check if a block was pressed correctly/timely
        for (var i = 0; i < ripeBlocks.length; i++) {
            if (key == ripeBlocks[i].letter) {
                points++;
                ripeBlocks[i].score();
                if (points % 10 == 0) {
                    levelUp();
                }   
            }
        }
    }   
}

For my final project I created a 3D typing game. I tried to recreate Blade Saber, a similar VR game where players slash through the blocks using a VR “saber”. For a long time, I tried to have the player score points by dragging their mouse through the block, but due to complexities of 3D in p5js (translations, rotations, etc) it was way out-of-scope for this project. I decided to simplify this to be a keyboard-based game instead. Enjoy!

Leave a Reply