Cube Dodger A 3D 'endless runner' game

Dependencies:   mbed

Game/Game.cpp

Committer:
el17cd
Date:
2019-05-08
Revision:
46:824ec81ff578
Parent:
39:41dcf1604fdf

File content as of revision 46:824ec81ff578:

#include "Game.h"

SDFileSystem sd(PTE3, PTE1, PTE2, PTE4, "sd"); // MOSI, MISO, SCK, CS

Serial pc(USBTX, USBRX); // tx, rx

Game::Game(){
    noOfCubes = 25; //How many cubes are in the scene
    menuSelections.homeMenuSelection = 0; //default selection for the home screen
    gamepad.init();
    renderer.init();
    resetScene();
    helpScreenNumber = -1; //Set help screen to inactive (-1)
    input.x = 0; //Set joystick input to 0
    input.bCooldown = input.yCooldown = input.aCooldown = false; //Allow buttons to be pressed
    menuSelections.deathMenuSelection = 0; //Set death menu to highlight restart option
    highScore = readHighScore();
}

void Game::resetScene(){
    for(int i = 0; i < noOfCubes; i++){ //Set initial position of all cubes
        cubeArray[i].translate(rand()%250-125, 0, 10 + i*5); //Position will be based on random integers
    }
}

int Game::readHighScore(){    
    int highScore = 0;
    filePointer = fopen("/sd/el17cdHighScore.txt", "r");

    if (filePointer != NULL) {
        fscanf(filePointer, "%d", &highScore); //read integer from sd card file
        fclose(filePointer);
    }
    return highScore;;
}

void Game::writeHighScore(int score){
    filePointer = fopen("/sd/el17cdHighScore.txt", "w");

    if (filePointer != NULL) {
        fprintf(filePointer, "%d", score); //write integer to sd card file
        fclose(filePointer);
    }
}


void Game::run(){
    inHomeMenu = true; //Start at home screen
    score = 0;
    playing = true; //set game state to playing and death screen selection to 'restart'
    while(true) {
        processInput(); //process user input and store values in input struct
        renderer.clear();
        if(helpScreenNumber > -1){
            helpScreen(); //Show the help screen if the help screen number is above -1
        }
        else if(inHomeMenu){
            homeScreen();
        }
        else{
            play();
        }
        renderer.refresh(); //refresh lcd
    }
}

void Game::play(){
    renderer.drawHorizon(input.x/15); //draw the horizon line
    
    for (int c = 0; c< noOfCubes; c++){  
        moveCubes(&cubeArray[c]); //translate cubes depending on joystick location and score
        cubeToBeRendered(&cubeArray[c], c); //add all cube faces to array to later be rendered
        checkDespawn(&cubeArray[c]); //check if any cubes are behind the perspective, then respawn them
        checkDeath(&cubeArray[c]); //check if any cubes are too close to user
    }
    renderer.drawAllFaces(faceArray, noOfCubes, input.x); //draw all faces added to the face array
    displayDeathMenu(); //display death menu if cube is too close to user
    addScore();
    renderer.printScore(score, 0, 0); //print score on top left of screen
}

void Game::checkDespawn(Cube *cube){ //checks if the cube is behind the perspective
    if (cube->despawn()){
        cube->resetPos(); //reset position to origin
        cube->translate(rand()%250-125,0,140); //translate cube to new far away position
    }
}

void Game::checkDeath(Cube *cube){ //Check whether the user has hit a cube
    if (cube->tooClose()){
        playing = false; //set state of game to not playing
        cube->resetPos();
        cube->translate(rand()%250-125,0,140); //reset and translate cube to back position
    }
}

void Game::cubeToBeRendered(Cube *cube, int cubeIndex){ //Adds all faces within the cube to an array of faces to be rendered
    for (int i = 0; i < 6; i++){
        faceArray[cubeIndex*6 + i] = cube->getFace(i);
    }
}


void Game::moveCubes(Cube *cube){ //animate cubes
    if(playing){
        if(score < 6000)
            cube->translate(-input.x*1.4f,0,-1-(float)score/1000); //move cubes closer to user and in x axis depending on joystick pos
        else{
            cube->translate(-input.x*1.4f,0,-3); //once max speed reached maintain constant z speed
        }
    }
    else{
        input.x = 0; //if not playing (dead) prevent joystick moving anything
    }
}

void Game::displayDeathMenu() { //display restart or back to home menu
    if(!playing){
        if(score > highScore) {
            highScore = score;
            writeHighScore(highScore);
        }
        renderer.drawDeathScreen(menuSelections.deathMenuSelection, highScore); //draw death screen if game over
        deathButtonSelections(); //select menu option
    }

}

void Game::addScore(){ //increment score by 1 each frame
    if(playing){
        score++; //add score if still playing
    }
}

void Game::resetScore(){  //reset score to 0
    score = 0;
}

void Game::deathButtonSelections(){ //determine selection on death screen
        if(input.yButton){ //if y pressed highlight top option (restart)
            menuSelections.deathMenuSelection = 0;
        }
        else if(input.aButton){ //if a pressed highlight bottom option (home menu)
            menuSelections.deathMenuSelection = 1;
        }
        if (menuSelections.deathMenuSelection == 0 && input.bButton){ //if top option highlighted and b pressed then restart game
            playing = true;
            score = 0;
            resetScore();
            resetScene();
        }
        else if(menuSelections.deathMenuSelection == 1 && input.bButton){ //if bottom option highlighted and b pressed then go to home screen
            resetScore();
            inHomeMenu = true;
        }
}

void Game::homeButtonSelections(){ //determine selection on home screen
        if(input.yButton && menuSelections.homeMenuSelection > 0){ //if top option isnt highlighted and y pressed then move highlight up
            menuSelections.homeMenuSelection--;
        }
        else if(input.aButton && menuSelections.homeMenuSelection < 2){ //if bottom option isnt highlighted and a pressed then move highlight down
            menuSelections.homeMenuSelection++;
        }
        if (input.bButton && menuSelections.homeMenuSelection == 0){ //if top highlighted and b pressed then start game
            inHomeMenu = false;
            playing = true;
        }
        else if(input.bButton && menuSelections.homeMenuSelection == 1){ //if bottom highlighted and b pressed then exit game
            helpScreenNumber = 1;
        }
        else if(input.bButton && menuSelections.homeMenuSelection == 2){ //if bottom highlighted and b pressed then exit game
            renderer.turnOff();
            exit(0);
        }
}

void Game::homeScreen(){//draw home screen
    homeButtonSelections(); //determine selection on home screen
    renderer.drawHomeScreen(menuSelections.homeMenuSelection);
}

void Game::helpScreen(){ //Show help screen depending on help screen number
    if (helpScreenNumber == 1) {
        renderer.drawHelpScreen1();
        checkNextHelpScreen();
    }
    else if (helpScreenNumber == 2) {
        renderer.drawHelpScreen2();
        checkNextHelpScreen();
    }
    else if (helpScreenNumber == 3) {
        renderer.drawHelpScreen3();
        checkNextHelpScreen();
    }
    else if (helpScreenNumber == 4) {
        renderer.drawHelpScreen4();
        checkNextHelpScreen();

    }
    else if (helpScreenNumber == 5) { //if past last screen then set help screen number to disabled (-1)
        helpScreenNumber = -1;
    }
}

void Game::checkNextHelpScreen(){ //if a button pressed then advance help screen
    if(input.bButton){
        helpScreenNumber ++;
    }
}

void Game::processInput(){ //Obtain user inputs and store in input struct
    renderer.setContrast(gamepad.read_pot()); //Set the contrast of the screen based on the pot
    input.x = gamepad.get_coord().x; //Get value of joystick x axis
    bool y = gamepad.check_event(Gamepad::Y_PRESSED); //Get Y, A, B button states
    bool a = gamepad.check_event(Gamepad::A_PRESSED);
    bool b = gamepad.check_event(Gamepad::B_PRESSED);

    disableYButton(y);
    disableAButton(a);
    disableBButton(b);
}

void Game::disableYButton(bool y){ //Set y button to disabled, call function to reenable in 0.2 seconds
    if(!input.yCooldown && y){
        input.yCooldown = true;
        disableY.attach(callback(this, &Game::enableY), 0.2); //attach function to ticker to renable y button
        input.yButton = true;
    }
    else{
        input.yButton = false;
    }
}

void Game::disableAButton(bool a){ //Set a button to disabled, call function to reenable in 0.2 seconds
    if(!input.aCooldown && a){
        input.aCooldown = true;
        disableA.attach(callback(this, &Game::enableA), 0.2); //attach function to ticker to renable a button
        input.aButton = true;
    }
    else{
        input.aButton = false;
    }    
}

void Game::disableBButton(bool b){ //Set b button to disabled, call function to reenable in 0.2 seconds
    if(!input.bCooldown && b){
        input.bCooldown = true;
        disableB.attach(callback(this, &Game::enableB), 0.2); //attach function to ticker to renable b button
        input.bButton = true;
    }
    else{
        input.bButton = false;
    }
}

void Game::enableY(){ //reenable y button
    disableY.detach(); //detach ticker so function only runs once
    input.yCooldown = false;
}

void Game::enableA(){ //reenable a button
    disableA.detach(); //detach ticker so function only runs once
    input.aCooldown = false;
}

void Game::enableB(){ //reenable b button
    disableB.detach(); //detach ticker so function only runs once
    input.bCooldown = false;
}