//======================================================================================================================================== // F4 Racing //---------------------------------------------------------------------------------------------------------------------------------------- // This is an advancement in my game making skills, building off the back of my work on the "Collect the Balls" game. //---------------------------------------------------------------------------------------------------------------------------------------- // created by Rune // black box code by Sigonasr2 from the OLC (one lone coder) Discord server //======================================================================================================================================== #include "pixelGameEngine.h" // used for drawing the game //======================================================================================================================================== // protected variables //======================================================================================================================================== class Racer : public olc::PixelGameEngine { public: Racer() { sAppName = "F4 Racing"; } private: float curvature = 0.f; // probably the player's horizontal position in relation to the track's curve float track_curve = 0.f; float car_curve = 0.f; // wtf.... float track_dist = 0.f; // total distance of the circuit float cur_lap_time = 0.f; std::vector> vecTrack; std::list list_times; bool bothKeysPressed = false; // checking for dual key press; could probably work with more than 2 keys // custom controls template olc::Key Move_Up = olc::UP; olc::Key Move_Down = olc::DOWN; olc::Key Move_Left = olc::LEFT; olc::Key Move_Right = olc::RIGHT; std::array keyslist = { "UP", "DOWN", "LEFT", "RIGHT" }; int configKeyIndex = -1; char pressedKey; // selection screen variables int cup = -1; int track = -1; int player = 0; //======================================================================================================================================== // Menu Setup //======================================================================================================================================== public: bool activeOption = true; // highlighting current option int highlighted; struct MenuItem { std::string label; // label olc::vi2d pos; // label's position std::string label2; // another label to right of label }; struct Object { olc::vf2d pos{ 0, 0 }; // the x position represents how far to the left/right it is from the center. -1 ~ 1: road's range; the y position is how far along the track it is olc::vf2d size{ 1, 1 }; olc::Decal* decal = nullptr; olc::vf2d offset{ 0, 0 }; // how far off the image will be bool rendered = false; olc::vf2d drawPos; olc::vf2d drawSize; olc::vf2d drawOffsetPos; }; std::vector mainMenu; std::vector playGame; std::vector pickTrack; std::vector grandPrix; std::vector selectCar; std::vector controls; std::vector pause; std::vector gameOver; std::vector gameObjects; enum class state { MAIN_MENU, PLAY_GAME, RUN_GAME, CONTROLS, PAUSED, RESUME, END_GAME, // main TRACK_SELECT, CUP_SELECT, CAR_SELECT, // game INDIE, STREET, PRO, GRAND_PRIX // cups }; state gameState = state::MAIN_MENU; //======================================================================================================================================== // Variables //======================================================================================================================================== // lap times // player int cur_place; // current place player is in int total_place = 8; // total number of places in race int tracks_left; struct Racecar { float car_pos = 0.f; // track the horizontal location of player, as the track begins to curve away float distance = 0.f; // track the "vertical" location of player; basically, they're somewhere on the track between the start and finish line float speed = 0.f; int color = 0; // track the player color; unselected colors become AI racers int score = 0; // score at end of track; based on final place int cur_lap = 0; // current lap player is on bool is_Player; }; std::vector cars; // initialize sprites olc::Decal* car; olc::Decal* hill; olc::Decal* grass; olc::Decal* road; olc::Decal* banner; olc::Decal* title; olc::Decal* cups; olc::Decal* tracks; olc::Decal* select; std::string KeyToString(olc::Key key) { const std::array keyStrings = { "NONE","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","0","1","2","3","4","5","6","7","8","9", "F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","UP","DOWN","LEFT","RIGHT","SPACE","TAB","SHIFT","CTRL","INS","DEL","HOME","END","PGUP","PGDN","BACK","ESCAPE","ENTER","ENTER", "PAUSE","SCROLL LK","Kp-0","Kp-1","Kp-2","Kp-3","Kp-4","Kp-5","Kp-6","Kp-7","Kp-8","Kp-9","Kp-*","Kp-/","Kp-+","Kp--","Kp-.",".","=",",","-","OEM_1", "OEM_2","OEM_3","OEM_4","OEM_5","OEM_6","OEM_7","OEM_8","CAPS LOCK","END" }; return keyStrings[key]; } struct Track { std::vector > curves; // curves std::string name; // names int laps; // laps }; // special track ;) Track javidx9 = { // they'll likely be wonky for the game as it's not really modified to fit my units of // these happen to be measured in meters, as per Javid's original construction measure {{0.f, 10.f}, {0.f, 200.f}, {1.f, 200.f}, {0.f, 400.f}, {-1.f, 100.f}, {0.f, 200.f}, {-1.f, 200.f}, {1.f, 200.f}, {0.f, 200.f}, {0.2f, 500.f}, {0.f, 200.f}}, "JAVIDX9", 3 }; // all 9 tracks below are running on yards for their distance units // this means the end results are in miles; rather than kilometers // enjoy the US measures of Burgers per Freedom Eagle //================================================================================================== Track circuit = { // Classic Oval; 1 mile loop, 1,760 yards { {0.f, 10.f}, {0.f, 270.f}, {-1.f, 330.f}, {0.f, 550.f}, {-1.f, 330.f}, {0.f, 270.f} }, "CIRCUIT", 3 }; Track infinity = { // figure eight {{0.f, 10.f}, // flag (straight); this is positioned somewhere after the intersection {0.f, 190.f }, {-1.f, 200.f}, {0.f, 500.f }, // straight; this crosses the first straight, to provide for chances of crashing {1.f, 200.f }, {0.f, 300.f }}, "INFINITY", 3 }; Track arrow_head = { // fancy triangle {{0.f, 10.f}, {0.f, 490.f}, {1.f, 130.f}, {0.f, 1000.f}, {1.f, 130.f}, {0.f, 500.f}, {-1.f, 130.f}, {0.f, 500.f}, {1.f, 130.f}, {0.f, 500.f}}, "ARROW HEAD", 3 }; Track track_4 = // ??? { //{{0.f, 10.f},}, // ripped from circuit { {0.f, 10.f}, {0.f, 270.f}, {-1.f, 330.f}, {0.f, 550.f}, {-1.f, 330.f}, {0.f, 270.f} }, "Track 4", 3 }; Track overpass = { // kind of a folded figure eight //{{0.f, 10.f}, {0.f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, {1.f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, {1.f, 0.f}, {0.f, 0.f}}, // ripped from infinity {{0.f, 10.f}, {0.f, 190.f }, {-1.f, 200.f}, {0.f, 500.f }, {1.f, 200.f }, {0.f, 300.f }}, "OVERPASS", 3 }; Track peanut= { // No more rhymes now. I mean it! // Anybody want a peanut? // GAH!!! //{{0.f, 10.f},}, // ripped from arrow head {{0.f, 10.f}, {0.f, 490.f}, {1.f, 130.f}, {0.f, 1000.f}, {1.f, 130.f}, {0.f, 500.f}, {-1.f, 130.f}, {0.f, 500.f}, {1.f, 130.f}, {0.f, 500.f}}, "PEANUT", 4 }; Track track_7 = // ??? { //{{0.f, 10.f},}, // ripped from circuit { {0.f, 10.f}, {0.f, 270.f}, {-1.f, 330.f}, {0.f, 550.f}, {-1.f, 330.f}, {0.f, 270.f} }, "Track 7", 3 }; Track trident = { // is your preferred Neptune or Poseidon? //{{0.f, 10.f},}, // ripped from infinity {{0.f, 10.f}, {0.f, 190.f }, {-1.f, 200.f}, {0.f, 500.f }, {1.f, 200.f }, {0.f, 300.f }}, "TRIDENT", 3 }; Track star = { // spangled banner; oh say can you see.... // look, I suck at singing, and not gonna look up the lyrics, just, enjoy American awesomeness //{{0.f, 10.f},}, // ripped from arrow head {{0.f, 10.f}, {0.f, 490.f}, {1.f, 130.f}, {0.f, 1000.f}, {1.f, 130.f}, {0.f, 500.f}, {-1.f, 130.f}, {0.f, 500.f}, {1.f, 130.f}, {0.f, 500.f}}, "STAR", 3 }; std::vector trackList = { circuit, infinity, arrow_head, track_4, overpass, peanut, track_7, trident, star, javidx9, }; //======================================================================================================================================== // Create Game Objects //======================================================================================================================================== bool OnUserCreate() override { mainMenu = // show menu options { { "START GAME", { ScreenWidth() / 2, ScreenHeight() / 5 * 3} }, { "CONTROLS", { ScreenWidth() / 2, ScreenHeight() / 5 * 3 + 64} }, { "QUIT", {ScreenWidth() / 2, ScreenHeight() / 5 * 3 + 128 } } }; playGame = { { "TRACK SELECTION", { ScreenWidth() / 2, ScreenHeight() / 4 } }, { "GRAND PRIX", { ScreenWidth() / 2, ScreenHeight() / 2 } }, { "BACK", { ScreenWidth() / 2, ScreenHeight() / 6 * 5} } }; pickTrack = // show track options { { "CIRCUIT", { ScreenWidth() / 4, ScreenHeight() / 4 } }, { "INFINITY", { ScreenWidth() / 2, ScreenHeight() / 4 } }, { "ARROWHEAD", { ScreenWidth() / 4 * 3, ScreenHeight() / 4 } }, { "Track 4", { ScreenWidth() / 4, ScreenHeight() / 2 }}, { "OVERPASS", { ScreenWidth() / 2, ScreenHeight() / 2 } }, { "PEANUT", { ScreenWidth() / 4 * 3, ScreenHeight() / 2 } } , { "Track 7", { ScreenWidth() / 4, ScreenHeight() / 4 * 3 } }, { "TRIDENT", { ScreenWidth() / 2, ScreenHeight() / 4 * 3 } }, { "STAR", { ScreenWidth() / 4 * 3, ScreenHeight() / 4 * 3 } }, { "BACK", { ScreenWidth() / 2, ScreenHeight() / 6 * 5} } }; grandPrix = // show race cups { { "INDIE CUP", { ScreenWidth() / 3, ScreenHeight() / 3 } }, { "STREET CUP", { ScreenWidth() / 3 * 2, ScreenHeight() / 3 } }, { "PRO CUP", { ScreenWidth() / 3, ScreenHeight() / 3 * 2 } }, { "GRAND PRIX", { ScreenWidth() / 3 * 2, ScreenHeight() / 3 * 2 } }, { "BACK", { ScreenWidth() / 2, ScreenHeight() / 6 * 5 } } }; selectCar = // pick a color { { "RED", { ScreenWidth() / 5, ScreenHeight() / 3 } }, { "BLUE", { ScreenWidth() / 5 * 2, ScreenHeight() / 3 } }, { "GREEN", { ScreenWidth() / 5 * 3, ScreenHeight() / 3 } }, { "GOLD", { ScreenWidth() / 5 * 4, ScreenHeight() / 3 } }, { "PURPLE", { ScreenWidth() / 5, ScreenHeight() / 3 * 2 } }, { "WHITE", { ScreenWidth() / 5 * 2, ScreenHeight() / 3 * 2 } }, { "ORANGE", { ScreenWidth() / 5 * 3, ScreenHeight() / 3 * 2 } }, { "BLACK", { ScreenWidth() / 5 * 4, ScreenHeight() / 3 * 2 } }, { "BACK", { ScreenWidth() / 2, ScreenHeight() / 6 * 5} } }; controls = // customize controls { { "FORWARD", { ScreenWidth() / 3, ScreenHeight() / 9 * 2 }, KeyToString(Move_Up) }, { "BACK", { ScreenWidth() / 3, ScreenHeight() / 9 * 3 }, KeyToString(Move_Down) }, { "LEFT", { ScreenWidth() / 3, ScreenHeight() / 9 * 4 }, KeyToString(Move_Left) }, { "RIGHT", { ScreenWidth() / 3, ScreenHeight() / 9 * 5 }, KeyToString(Move_Right) }, { "DEFAULT", { ScreenWidth() / 2, ScreenHeight() / 9 * 6} }, { "BACK", { ScreenWidth() / 2, ScreenHeight() / 6 * 5} } }; pause = { { "RESUME", { ScreenWidth() / 2, ScreenHeight() / 9 * 3} }, { "RESTART", { ScreenWidth() / 2, ScreenHeight() / 9 * 4} }, { "MENU", { ScreenWidth() / 2, ScreenHeight() / 9 * 5} }, { "QUIT", { ScreenWidth() / 2, ScreenHeight() / 9 * 6} } }; gameOver = { { "MENU", { ScreenWidth() / 2, ScreenHeight() / 9 * 5} }, { "QUIT", { ScreenWidth() / 2, ScreenHeight() / 9 * 6} } }; //======================================================================================================================================== // load sprites car = new olc::Decal(new olc::Sprite("art/car.png")); // will contain 8 car, with 3 angles per car hill = new olc::Decal(new olc::Sprite("art/hills.png"), false, false); grass = new olc::Decal(new olc::Sprite("art/grass.png"), false, false); road = new olc::Decal(new olc::Sprite("art/road.png"), false, false); // holds both road and flag line banner = new olc::Decal(new olc::Sprite("art/start.png"), false, false); title = new olc::Decal(new olc::Sprite("art/title.png")); cups = new olc::Decal(new olc::Sprite("art/cups.png")); // 64 px tiles; 128 px image tracks = new olc::Decal(new olc::Sprite("art/tracks.png")); // 64 px tiles; 192 px image select = new olc::Decal(new olc::Sprite("art/f4 racer.png")); // track stuff list_times = {}; // this needs modification, as most tracks will likely have 4 laps, some with 3, and some with 5 gameObjects.push_back({ {-1.5,500}, {10,6}, banner, {0,240} }); cars.push_back({}); cars.push_back({}); cars.push_back({}); cars.push_back({}); cars.push_back({}); cars.push_back({}); cars.push_back({}); cars.push_back({}); return true; } //======================================================================================================================================== // reset game //======================================================================================================================================== void Reset() { // car_pos = 0.0f; // distance = 0.0f; // speed = 0.0f; // // curvature = 0.0f; // track_curve = 0.0f; // car_curve = 0.0f; // track_dist = 0.0f; // // cur_lap_time = 0.0f; // cur_lap = 0; // // // Note: the following all need to be reset during the process, when they've been implemented // // -all lap times // // -score // // // // if called from other parts of the program, other than the 'F1' key, it should be resetting to the menu // // the 'F1' key is meant to reset the player on the current track they're on // // } //======================================================================================================================================== // curves //======================================================================================================================================== void FindCurves() { vecTrack.clear(); for (int i = 0; i < trackList[track].curves.size(); i++) { vecTrack.push_back(trackList[track].curves[i]); track_dist += vecTrack[i].second; } } //======================================================================================================================================== // main menu //======================================================================================================================================== void DisplayMenu(std::vector list) { // drawing and coloring menu selections for (int i = 0; i < list.size(); i++) { olc::vi2d textSize = GetTextSize(list[i].label) / 2 * 4; olc::Pixel textCol = olc::GREY; // greys out unselected options if (highlighted == i) { textCol = olc::Pixel(255, 201, 14); // gold } DrawStringDecal(list[i].pos - textSize, list[i].label, textCol, { 4.0f, 4.0f }); if (list[i].label2.size() > 0) { olc::vi2d offset = { ScreenWidth() / 3, - textSize.y / 2 }; DrawStringDecal(list[i].pos + offset, list[i].label2, textCol, { 4.0f, 4.0f }); } } // selecting options highlighted = std::clamp(highlighted, 0, (int)list.size() - 1); // loop if out of range int shortest = 9999999; int largest = -9999999; int targetItem = highlighted; //======================================================================================================================================== if (GetKey(olc::Key::DOWN).bPressed) { for (int i = 0; i < list.size(); i++) { if (highlighted != i) { if (list[highlighted].pos.x == list[i].pos.x) { if (list[highlighted].pos.y < list[i].pos.y) { // it's below us int dist = abs(list[highlighted].pos.y - list[i].pos.y); if (dist < shortest) { targetItem = i; shortest = dist; } } } } } for (int i = 0; i < list.size(); i++) { if (highlighted != i) { if (list[highlighted].pos.y < list[i].pos.y) { // it's above us int dist = abs(list[highlighted].pos.y - list[i].pos.y); if (dist < shortest) { targetItem = i; shortest = dist; } } } } // it failed, try another method if (highlighted == targetItem) { targetItem = 0; } highlighted = targetItem; } //======================================================================================================================================== if (GetKey(olc::Key::UP).bPressed) { for (int i = 0; i < list.size(); i++) { if (highlighted != i) { if (list[highlighted].pos.x == list[i].pos.x) { if (list[highlighted].pos.y > list[i].pos.y) { // it's above us int dist = abs(list[highlighted].pos.y - list[i].pos.y); if (dist < shortest) { targetItem = i; shortest = dist; } } } } } for (int i = 0; i < list.size(); i++) { if (highlighted != i) { if (list[highlighted].pos.y > list[i].pos.y) { // it's below us int dist = abs(list[highlighted].pos.y - list[i].pos.y); if (dist < shortest) { targetItem = i; shortest = dist; } } } } // it failed, try another method if (highlighted == targetItem) { targetItem = list.size() - 1; } highlighted = targetItem; } //======================================================================================================================================== if (GetKey(olc::Key::RIGHT).bPressed) { for (int i = 0; i < list.size(); i++) { if (highlighted != i) { if (list[highlighted].pos.y == list[i].pos.y) { if (list[highlighted].pos.x < list[i].pos.x) { // it's to the right int dist = abs(list[highlighted].pos.x - list[i].pos.x); if (dist < shortest) { targetItem = i; shortest = dist; } } } } } if (highlighted == targetItem) { for (int i = 0; i < list.size(); i++) { if (highlighted != i) { if (list[highlighted].pos.y == list[i].pos.y) { if (list[highlighted].pos.x > list[i].pos.x) { // it's to the left int dist = abs(list[highlighted].pos.x - list[i].pos.x); if (dist > largest) { targetItem = i; largest = dist; } } } } } } highlighted = targetItem; } //======================================================================================================================================== if (GetKey(olc::Key::LEFT).bPressed) { for (int i = 0; i < list.size(); i++) { if (highlighted != i) { if (list[highlighted].pos.y == list[i].pos.y) { if (list[highlighted].pos.x > list[i].pos.x) { // it's to the right int dist = abs(list[highlighted].pos.x - list[i].pos.x); if (dist < shortest) { targetItem = i; shortest = dist; } } } } } if (highlighted == targetItem) { for (int i = 0; i < list.size(); i++) { if (highlighted != i) { if (list[highlighted].pos.y == list[i].pos.y) { if (list[highlighted].pos.x < list[i].pos.x) { // it's to the left int dist = abs(list[highlighted].pos.x - list[i].pos.x); if (dist > largest) { targetItem = i; largest = dist; } } } } } } highlighted = targetItem; } //======================================== if (highlighted >= (int)list.size()) { highlighted = 0; // wrap around when above max } if (highlighted < 0) { highlighted = list.size() - 1; // wrap around when below zero } } //======================================================================================================================================== // controls //======================================================================================================================================== void GetAnyKeyPress(olc::Key keypress) override { if (configKeyIndex != -1) { switch (configKeyIndex) { case 0: Move_Up = keypress; break; case 1: Move_Right = keypress; break; case 2: Move_Left = keypress; break; case 3: Move_Down = keypress; break; } controls[configKeyIndex].label2 = KeyToString(keypress); configKeyIndex = -1; } } //======================================================================================================================================== // Main Game Function //======================================================================================================================================== bool OnUserUpdate(float fElapsedTime) override { if (GetKey(olc::Key::CTRL).bHeld && GetKey(olc::Key::F12).bHeld && !bothKeysPressed) { return false; } // draw skybox GradientFillRectDecal({ 0.0f, 0.0f }, { ScreenWidth() + 0.0f, ScreenHeight() / 2 + 0.0f }, olc::DARK_BLUE, olc::BLUE, olc::BLUE, olc::DARK_BLUE); // the hills have eyes; y'know, like in Super Mario Bros. What reference did you think was going to be here? DrawPartialDecal({ 0.0f, ScreenHeight() / 2 - 100.0f }, hill, { 0.0f + track_curve * 200, 0.0f }, { ScreenWidth() + 0.0f, (float)hill->sprite->height }, { 1.5f, 1.0f }); //DrawPartialDecal({ 180.0f, ScreenHeight() / 2 - 100.0f }, hill, { 0.0f + track_curve * 200, 0.0f }, { ScreenWidth() + 0.0f, (float)hill->sprite->height }); DrawPartialWarpedDecal // draw grass ( grass, { { 0.0f, ScreenHeight() / 2 + 0.0f }, { -ScreenWidth() * 2 + 0.0f, ScreenHeight() + 0.0f}, { ScreenWidth() * 2 + ScreenWidth() + 0.0f, ScreenHeight() + 0.0f }, { ScreenWidth() + 0.0f, ScreenHeight() / 2 + 0.0f } }, { 0.0f, -cars[0].distance}, {1 * 32, 4 * 64} ); //======================================================================================================================================== if (gameState == state::MAIN_MENU) { FillRectDecal({ 0, 0 }, { ScreenWidth() + 0.f, ScreenHeight() + 0.f }, olc::Pixel(0, 0, 0, 16)); // display title DrawDecal({ 95.5f, 95.5f }, title/*, {0.4f, 0.4f}*/); DisplayMenu(mainMenu); //DrawStringDecal({ ScreenWidth() / 2 + 0.0f, ScreenHeight() / 2 + 0.0f }, "START GAME", olc::WHITE, { 4.0f, 4.0f }); if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed) { // vertical menu if (highlighted == 0) { gameState = state::PLAY_GAME; highlighted = 0; } else if (highlighted == 1) { gameState = state::CONTROLS; highlighted = 0; } else { return false; // quit game } } if (GetKey(olc::Key::ESCAPE).bPressed) { return false; // exit via Escape key } } //======================================================================================================================================== else if (gameState == state::PLAY_GAME) { FillRectDecal({ 0, 0 }, { ScreenWidth() + 0.f, ScreenHeight() + 0.f }, olc::Pixel(0, 0, 0, 16)); //for (int i = 0; i < 2; i++) //{ // int image_x = i % 1; // int image_y = i / 2; // DrawPartialDecal // ( // { ScreenWidth() / 2 * (image_x + 1) - 168.f, ScreenHeight() / 5 * (image_y + 1) - 76.f }, // select, { image_x * 168.f, image_y * 38.f }, { 168, 76 }, { 2.0f, 2.0f }, // (highlighted == i) ? olc::WHITE : olc::DARK_GREY // ); //} DisplayMenu(playGame); if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed) { // vertical menu if (highlighted == 0) { gameState = state::TRACK_SELECT; highlighted = 0; } else if (highlighted == 1) { gameState = state::CUP_SELECT; highlighted = 0; } else { gameState = state::MAIN_MENU; // return to menu highlighted = 0; } } if (GetKey(olc::Key::ESCAPE).bPressed || GetKey(olc::Key::BACK).bPressed) { gameState = state::MAIN_MENU; highlighted = 0; } } //======================================================================================================================================== else if (gameState == state::TRACK_SELECT) { FillRectDecal({ 0, 0 }, { ScreenWidth() + 0.f, ScreenHeight() + 0.f }, olc::Pixel(0, 0, 0, 16)); for (int i = 0; i < 9; i++) { int image_x = i % 3; int image_y = i / 3; DrawPartialDecal ( { ScreenWidth() / 4 * (image_x + 1) - 64.f, ScreenHeight() / 4 * (image_y + 1) - 150.f }, tracks, { image_x * 64.f, image_y * 64.f }, { 64,64 }, { 2.0f, 2.0f }, (highlighted == i) ? olc::WHITE : olc::DARK_GREY ); } DisplayMenu(pickTrack); if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed) { if (highlighted >= 0 && highlighted <= 8) { track = highlighted; gameState = state::CAR_SELECT; highlighted = 0; } else { gameState = state::PLAY_GAME; highlighted = 0; track = -1; } } if (GetKey(olc::Key::CTRL).bHeld && GetKey(olc::Key::F9).bHeld) { // cheat code for Javidx9's original track track = 0; gameState = state::CAR_SELECT; } if (GetKey(olc::Key::ESCAPE).bPressed || GetKey(olc::Key::BACK).bPressed) { gameState = state::PLAY_GAME; highlighted = 0; } } //======================================================================================================================================== else if (gameState == state::CUP_SELECT) { FillRectDecal({ 0, 0 }, { ScreenWidth() + 0.f, ScreenHeight() + 0.f }, olc::Pixel(0, 0, 0, 16)); for (int i = 0; i < 4; i++) { int image_x = i % 2; int image_y = i / 2; DrawPartialDecal ( { ScreenWidth() / 3 * (image_x + 1) - 96.f, ScreenHeight() / 3 * (image_y + 1) - 192.f }, cups, { image_x * 64.f, image_y * 64.f }, { 64, 64 }, { 3.0f, 3.0f }, (highlighted == i) ? olc::WHITE : olc::DARK_GREY ); } DisplayMenu(grandPrix); if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed) { // 2 x 2 menu std::array gameStates = { state::INDIE, state::STREET, state::PRO, state::GRAND_PRIX }; if (highlighted >= 0 && highlighted <= 3) { gameState = gameStates[highlighted]; cup = highlighted; gameState = state::CAR_SELECT; tracks_left = 2; track = highlighted * 3; if (highlighted == 3) { track = 0; tracks_left = 8; } highlighted = 0; } else { gameState = state::PLAY_GAME; // return to menu cup = -1; highlighted = 0; } } if (GetKey(olc::Key::CTRL).bHeld && GetKey(olc::Key::F9).bHeld) { // cheat code for Javidx9 Grand Prix Cup // add's Javid's track to the end of the standard Grand Prix cup = 0; track = 0; gameState = state::CAR_SELECT; tracks_left = 9; } if (GetKey(olc::Key::ESCAPE).bPressed || GetKey(olc::Key::BACK).bPressed) { gameState = state::PLAY_GAME; cup = -1; highlighted = 0; } } //======================================================================================================================================== else if (gameState == state::CAR_SELECT) { FillRectDecal({ 0, 0 }, { ScreenWidth() + 0.f, ScreenHeight() + 0.f }, olc::Pixel(0, 0, 0, 16)); for (int i = 0; i < 8; i++) { int image_x = i % 4; int image_y = i / 4; DrawPartialDecal ( { ScreenWidth() / 5 * (image_x + 1) - 96.f, ScreenHeight() / 3 * (image_y + 1) - 192.f }, car, { image_x * 256.f, image_y * 192.f }, {256, 192}, {.75f, .75f}, (highlighted == i) ? olc::WHITE : olc::DARK_GREY ); } DisplayMenu(selectCar); if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed) { if (highlighted >= 0 && highlighted <= 7) { player = highlighted; int carIndex = 1; // start at 1, as the player's car is index 0 for (int i = 0; i < 8; i++) { if (i != highlighted) { cars[carIndex].color = i; // when color is not player selected color, assign it to next AI car carIndex++; } else { cars[0].color = i; // player chosen color; assigned to player } } gameState = state::RUN_GAME; FindCurves(); } else { if (cup != -1) { // return to cup select gameState = state::CUP_SELECT; } else { // return to track select gameState = state::TRACK_SELECT; } highlighted = 0; } } if (GetKey(olc::Key::ESCAPE).bPressed || GetKey(olc::Key::BACK).bPressed) { if (cup != -1) { // return to cup select gameState = state::CUP_SELECT; } else { // return to track select gameState = state::TRACK_SELECT; } highlighted = 0; } } //======================================================================================================================================== else if (gameState == state::CONTROLS) { FillRectDecal({ 0, 0 }, { ScreenWidth() + 0.f, ScreenHeight() + 0.f }, olc::Pixel(0, 0, 0, 16)); DisplayMenu(controls); if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed) { if (highlighted >= 0 && highlighted <= 3) { configKeyIndex = highlighted; } else if (highlighted == 4) { // reset default controls DisplayMenu(controls); Move_Up = olc::W; Move_Down = olc::S; Move_Left = olc::A; Move_Right = olc::D; } else { gameState = state::MAIN_MENU; highlighted = 0; } } if (GetKey(olc::Key::ESCAPE).bPressed || GetKey(olc::Key::BACK).bPressed) { gameState = state::MAIN_MENU; } } //======================================================================================================================================== else if (gameState == state::PAUSED) // pause menu { FillRectDecal({ 0, 0 }, { ScreenWidth() + 0.f, ScreenHeight() + 0.f }, olc::Pixel(0, 0, 0, 64)); DisplayMenu(pause); if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed) { if (highlighted == 0) { gameState = state::RUN_GAME; highlighted = 0; } if (highlighted == 1) { // restart the track // this will ONLY work on tracks after the track selection screen // or on the first track in each cup (1, 4, 7, 1) Reset(); gameState = state::RUN_GAME; highlighted = 0; } if (highlighted == 2) { // main menu Reset(); gameState = state::MAIN_MENU; highlighted = 0; } if (highlighted == 3) { return false; } } if (GetKey(olc::Key::ESCAPE).bPressed || GetKey(olc::Key::PAUSE).bPressed) { gameState = state::RUN_GAME; highlighted = 0; } } //======================================================================================================================================== else if (gameState == state::END_GAME) { // alter this code so as to implement an END TRACK and END CUP state // according to Sigonasr, booleans should be used and set/unset appropriately to pull it off DrawStringDecal({ ScreenWidth() / 3 + 0.f, ScreenHeight() / 3 + 0.f }, "RACE COMPLETE!", olc::WHITE, { 4.f, 4.f }); DisplayMenu(gameOver); if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed) { if (highlighted == 0) { // main menu Reset(); gameState = state::MAIN_MENU; highlighted = 0; } if (highlighted == 1) { return false; } } // at the end of a single track, a list of all times should be shown, along with positions // the player's should be highlighted, to stick out from the AI times; this will likely be White, with AI being Grey // based on position at the end of the race, one of 3 trophies should be thrown onto the screen (if in 1, 2, or 3); and nothing if 4+ // however, these should *only* show up under the single track races // under the cup races, as players complete each track, it should *NOT* show the trophies upon completing each track, regardless of place // at the end of the cup, it should throw up the cup appropriate to the race; indie = bronze, street = silver, pro = gold, grand prix = platinum // this should happen for first place winners // will need to work out something for 2nd and 3rd for the Indie and Street cups // points will be counted up, based on position per track; this determines final cup winner // if perfect on grand prix, players will get platinum cup, otherwise, they'll get a gold cup // these cup options, may very well change where 1st is gold, 2nd is silver, 3rd is bronze, and perfect is platinum, regardless of the cup } //======================================================================================================================================== //======================================================================================================================================== else if (gameState == state::RUN_GAME) // run main game loop { Clear(olc::BLACK); // this still needed? we've since replaced it with the gradient sky box int sec = (int)cur_lap_time % 60; olc::vf2d center = { (float)ScreenWidth() / 2, 32 }; olc::vf2d textSize = GetTextSize(trackList[track].name) * 4; if (sec < 3 && cars[0].cur_lap < 1) { DrawStringDecal(center - textSize / 2, trackList[track].name, olc::WHITE, { 4.f, 4.f }); } else { DrawStringDecal(center - textSize / 2, trackList[track].name, olc::Pixel(0, 0, 0, 96), { 4.f, 4.f }); } DrawStringDecal({0,32},std::to_string(cars[0].distance)); if (GetKey(olc::Key::ESCAPE).bPressed || GetKey(olc::Key::PAUSE).bPressed) { gameState = state::PAUSED; highlighted = 0; } // debug quick-reset if (GetKey(olc::Key::F4).bPressed) { Reset(); } // jump to end of lap if (GetKey(olc::Key::F12).bPressed) { cars[0].distance = track_dist - 50.f; } for (Object& obj : gameObjects) { obj.rendered=false; } //======================================================================================================================================== // | | player location on track //======================================================================================================================================== // get a point on the track float offset = 0; int TrackSection = 0; cur_lap_time += fElapsedTime; // find position on track (could optimize) << should probably optimize given the complexity of this game while (TrackSection < vecTrack.size() && offset <= cars[0].distance) { offset += vecTrack[TrackSection].second; TrackSection++; } float TargetCurvature = vecTrack[TrackSection - 1].first; // drawing curves to screen float TrackCurveDiff = (TargetCurvature - curvature) * fElapsedTime * cars[0].speed; // correcting the drawing of curves to the screen curvature += TrackCurveDiff; // update track curve difference track_curve += (curvature)*fElapsedTime * cars[0].speed; //======================================================================================================================================== // | | draw racetrack canvas //======================================================================================================================================== std::vector pos; std::vector uvs; for (int y = 0; y < ScreenHeight() / 2; y++) { // racetrack canvas variables float perspective = (float)y / (ScreenHeight() / 2.0f); float mid_point = 0.5f + curvature * powf((1.0f - perspective), 3); float road_width = 0.1f + perspective * 0.8f; float curb_width = road_width * 0.15f; road_width *= 0.5f; int lf_curb = (mid_point - road_width) * ScreenWidth(); int rt_curb = (mid_point + road_width) * ScreenWidth(); int row = ScreenHeight() / 2 + y; float horizon = powf(1.0f - perspective, 2) * 80; // check for finish line if (cars[0].distance + horizon > track_dist - 20 && cars[0].distance + horizon < track_dist) { // draw checkerboard pattern uvs.push_back({ 0, (float)fmod(((cars[0].distance + horizon) - (track_dist - 20)) / 20.0f, 0.5f) + 0.5f }); uvs.push_back({ 1, (float)fmod(((cars[0].distance + horizon) - (track_dist - 20)) / 20.0f, 0.5f) + 0.5f }); } else { // draw standard track piece uvs.push_back({ 0, sinf(80.0f * powf(1.0f - perspective, 2) + cars[0].distance) / 4 + 0.25f }); uvs.push_back({ 1, sinf(80.0f * powf(1.0f - perspective, 2) + cars[0].distance) / 4 + 0.25f }); } // drawing grass and curb pos.push_back({ lf_curb + 0.0f, row + 0.0f }); pos.push_back({ rt_curb + 0.0f, row + 0.0f }); // black box rendering shenanigans; if I ever understand programming enough at a later date, I can figure out wtf is going on here // render distant objects via perspective int i = 0; for (Object& obj : gameObjects) { float horizonRange = powf(1.0f - perspective, 2) * ((cars[0].distance + horizon) - obj.pos.y) / horizon; if (!obj.rendered && (cars[0].distance + horizon) > obj.pos.y && (cars[0].distance + horizon) < obj.pos.y + 80 && row >= ScreenHeight() / 2 + ScreenHeight() / 2 * horizonRange) { obj.drawPos = { (mid_point + road_width * obj.pos.x) * ScreenWidth(), horizonRange * (ScreenHeight() / 2) + ScreenHeight() / 2 }; obj.drawOffsetPos = { obj.offset.x, obj.size.y * road_width * -obj.offset.y }; obj.drawSize = { obj.size.x * road_width, obj.size.y * road_width }; obj.rendered = true; // yeah.... so, even after copying it manually, i still have no clue what this thing is doing, lol } } } //======================================================================================================================================== // on the road again; drawing the track SetDecalStructure(olc::DecalStructure::STRIP); DrawPolygonDecal(road, pos, uvs); SetDecalStructure(olc::DecalStructure::FAN); // draw trackside props for (Object& obj : gameObjects) { if (obj.rendered) { if (obj.decal != nullptr) { DrawDecal(obj.drawPos + obj.drawOffsetPos, obj.decal, obj.drawSize); } else { FillRectDecal(obj.drawPos, obj.drawSize, olc::BLUE); } } } //======================================================================================================================================== // | | car movement //======================================================================================================================================== int car_dir = 0; // default car facing if (GetKey(Move_Up).bHeld) { cars[0].speed += 2.0f * fElapsedTime; } else // might remove this in a later stage of development { cars[0].speed -= 1.0f * fElapsedTime; } if (GetKey(Move_Down).bHeld) { // move back ? cars[0].distance -= 20.0f * fElapsedTime; } if (GetKey(Move_Left).bHeld) { car_curve -= 0.7f * fElapsedTime; car_dir = -1; } if (GetKey(Move_Right).bHeld) { car_curve += 0.7f * fElapsedTime; car_dir = +1; } // slow car if on grass if (fabs(car_curve - track_curve) >= 0.8f) { cars[0].speed -= 5.0f * fElapsedTime; } // clamp speed if (cars[0].speed < 0.0f) cars[0].speed = 0.0f; if (cars[0].speed > 1.0f) cars[0].speed = 1.0f; // move car along track according to car speed cars[0].distance += (70.0f * cars[0].speed) * fElapsedTime; //// super cheap AI movement; just pushes cars along the track currently //for (int i = 1; i < 8; i++) //{ // cars[i].distance += cars[i].speed; //} // //// sort car distance and laps //std::sort(cars.begin(), cars.end(), [&](auto& car1, auto& car2) // { // return car1.distance > car2.distance; // }); cars[0].car_pos = car_curve - track_curve; // draw car for (int i = 0; i < 8; i++) { int image_x = cars[i].color % 4; int image_y = cars[i].color / 4; DrawPartialDecal({ ScreenWidth() / 2 - 128.f + cars[i].car_pos * ScreenWidth() / 2, ScreenHeight() / 4 * 3.0f - 128.f }, car, { image_x * 256.f, image_y * 192.f }, { 256, 192 }, { 1, 1 }, olc::WHITE); SetPixelMode(olc::Pixel::ALPHA); } //======================================================================================================================================== // | | track lap times //======================================================================================================================================== // crossing the finish line if (cars[0].distance >= track_dist) { // record lap time cars[0].distance -= track_dist; list_times.push_back(cur_lap_time); // pop time to back cur_lap_time = 0.f; cars[0].cur_lap++; if (tracks_left == 0 && cars[0].cur_lap >= trackList[track].laps) { gameState = state::END_GAME; } else if (cup >= 0 && cars[0].cur_lap >= trackList[track].laps) { Reset(); FindCurves(); track++; tracks_left--; } } // draw lap counter DrawStringDecal({ 10.f, 16 }, std::to_string(cars[0].cur_lap + 1) + "/" + std::to_string(trackList[track].laps), olc::WHITE, {3.f, 3.f}); // draw place counter DrawStringDecal({ ScreenWidth() - 90.f, 16 }, std::to_string(cur_place) + "/" + std::to_string(total_place), olc::WHITE, {3.f, 3.f}); // show time std::string disp_time; int min = (int)cur_lap_time / 60; // int sec; is found at beginning of 'RUN_GAME' state int milli = (int)( (cur_lap_time - (int)cur_lap_time) * 1000 ); if (min < 10) { disp_time += "0"; } disp_time += std::to_string(min); disp_time += ":"; if (sec < 10) { disp_time += "0"; } disp_time += std::to_string(sec); disp_time += "."; if (milli < 10) { disp_time += "0"; } if (milli < 100) { disp_time += "0"; } disp_time += std::to_string(milli); DrawStringDecal({ 100, 16 }, disp_time, olc::WHITE, { 1.5f, 1.5f }); // display lap times float j = 30; for (auto l : list_times) { //DrawStringDecal({ 10, j }, disp_time(l)); j += 10; } //------------------------------ } return true; } }; //======================================================================================================================================== int main() { Racer game; if (game.Construct(1280, 720, 1, 1)) game.Start(); return 0; } //======================================================================================================================================== // END OF FILE //========================================================================================================================================