//======================================================================================================================================== // 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 //======================================================================================================================================== #define OLC_PGE_APPLICATION #include "pixelGameEngine.h" // used for drawing the game using namespace olc; //======================================================================================================================================== // protected variables //======================================================================================================================================== class Racer : public olc::PixelGameEngine { public: Racer() { sAppName = "F4 Racing"; } private: float car_pos = 0.0f; float distance = 0.0f; float speed = 0.0f; float curvature = 0.0f; float track_curve = 0.0f; float car_curve = 0.0f; float track_dist = 0.0f; float cur_lap_time = 0.0f; std::vector> vecTrack; std::list list_times; bool bothKeysPressed = false; // checking for dual key press; could probably work with more than 2 keys // custom control template olc::Key Move_Up = olc::W; olc::Key Move_Down = olc::S; olc::Key Move_Left = olc::A; olc::Key Move_Right = olc::D; 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, CAR_SELECT, CONTROLS, RUN_GAME, // game TRACK_SELECT, CUP_SELECT, // tracks CIRCUIT, FIG_EIGHT, ARROW_HEAD, track_4, OVERPASS, PEANUT, track_7, TRIDENT, STAR, JX9, // special track ;) // cups INDIE, STREET, PRO, GRAND_PRIX, PAUSED, RESUME }; state gameState = state::MAIN_MENU; //======================================================================================================================================== // Variables //======================================================================================================================================== // lap times // player int score; // score at end of track; based on final place int lap; // current lap player is on int place; // current place player is in // 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* 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]; } // list of all track SECTIONS to be drawn per track // std::pair is the sections of the track in question std::vector > test_track = { {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} }; std::vector > circuit_track = { {0.f, 10.f}, {0.f, 270.f}, {-1.f, 330.f}, {0.f, 550.f}, {-1.f, 330.f}, {0.f, 270.f} }; std::vector > infinity_track = { {0.f, 10.f}, {0.f, 270.f}, {1.f, 330.f}, {0.f, 550.f}, {1.f, 330.f}, {0.f, 270.f} }; // list of ALL tracks to pick from std::vector >> trackList = { test_track, circuit_track, infinity_track, }; //======================================================================================================================================== // 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 = { "Return to Main Menu" }; //======================================================================================================================================== // load sprites car = new olc::Decal(new olc::Sprite("art/car.png")); // will contain 8 cars, 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 select = new olc::Decal(new olc::Sprite("art/selection.png")); //======================================================================================================================================== //======================================================================================================================================== // may need to separate the track list into it's own function so as to declutter this one // especially, given that there's a bunch of stuff going on with the tracks with much redundancy //trackList= {track_1, track_2}; // JX9 track layout // 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 constructionmeasure // vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) // vecTrack.push_back(std::make_pair(0.0f, 200.0f)); // straight // vecTrack.push_back(std::make_pair(1.0f, 200.0f)); // sharp right // vecTrack.push_back(std::make_pair(0.0f, 400.0f)); // long straight // vecTrack.push_back(std::make_pair(-1.0f, 100.0f)); // soft left // vecTrack.push_back(std::make_pair(0.0f, 200.0f)); // straight // vecTrack.push_back(std::make_pair(-1.0f, 200.0f)); // sharp left // vecTrack.push_back(std::make_pair(1.0f, 200.0f)); // sharp right // vecTrack.push_back(std::make_pair(0.0f, 200.0f)); // straight // vecTrack.push_back(std::make_pair(0.2f, 500.0f)); // gradual right // vecTrack.push_back(std::make_pair(0.0f, 200.0f)); // straight //} // 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 /*{ // Classic Oval // Circuit = { // 1 mile loop; 1,760 yards vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) vecTrack.push_back(std::make_pair(0.0f, 270.0f)); // straight vecTrack.push_back(std::make_pair(-1.0f, 330.0f)); // left vecTrack.push_back(std::make_pair(0.0f, 550.0f)); // straight vecTrack.push_back(std::make_pair(-1.0f, 330.0f)); // left vecTrack.push_back(std::make_pair(0.0f, 270.0f)); // straight } // Infinity // FigureEight = { vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight); this is positioned somewhere after the intersection vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight vecTrack.push_back(std::make_pair(-1.0f, 0.0f)); // left vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight; this crosses the first straight, to provide for chances of crashing vecTrack.push_back(std::make_pair(1.0f, 0.0f)); // right vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight } // Arrow Head // ArrowHead = { // miles vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight vecTrack.push_back(std::make_pair(1.0f, 0.0f)); // right vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight vecTrack.push_back(std::make_pair(1.0f, 0.0f)); // right vecTrack.push_back(std::make_pair(-1.0f, 0.0f)); // left vecTrack.push_back(std::make_pair(1.0f, 0.0f)); // right vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight } // track 4 // ??? = { vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) } // Over/Under // OverPass = { vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight vecTrack.push_back(std::make_pair(1.0f, 0.0f)); // right vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight vecTrack.push_back(std::make_pair(1.0f, 0.0f)); // right vecTrack.push_back(std::make_pair(1.0f, 0.0f)); // right vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight vecTrack.push_back(std::make_pair(1.0f, 0.0f)); // right vecTrack.push_back(std::make_pair(0.0f, 0.0f)); // straight } // Peanut // Peanut = { vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) } // track 7 // ??? = { vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) } // Trident // Trident = { vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) } // Star // ??? = { vecTrack.push_back(std::make_pair(0.0f, 10.0f)); // flag (straight) } }//*/ for (auto t : vecTrack) { track_dist += t.second; } distance = track_dist - 100; list_times = { 0,0,0,0,0 }; gameObjects.push_back({ /*{-1.5,track_dist - 50}, {10,6}, {0,240}*/ }); 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; will need to be moved elsewhere; the game will calculate *ALL* track sizes at the start, and then simply use the appropriate numbers when loading each track on request cur_lap_time = 0.0f; // Note: the following all need to be reset during the process, when they've been implemented // -player position (on screen) // -player position (on track) // -track section // -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 // } //======================================================================================================================================== // 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 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; } } void DrawCenteredStringDecal(olc::vf2d pos,std::string str,olc::Pixel col=olc::WHITE,olc::vf2d scale={1,1}) { olc::vf2d textOffset = olc::vf2d(GetTextSize(str))*scale/2; DrawStringDecal(pos-textOffset,str,col,scale); } //======================================================================================================================================== // Main Game Function //======================================================================================================================================== bool OnUserUpdate(float fElapsedTime) override { DrawLineDecal({(float)ScreenWidth()/2,0},{(float)ScreenWidth()/2,(float)ScreenHeight()},RED); DrawLineDecal({0,(float)ScreenHeight()/2},{(float)ScreenWidth(),(float)ScreenHeight()/2},RED); DrawCenteredStringDecal({(float)ScreenWidth()/2,(float)ScreenHeight()/2},"Goblin",olc::GREEN,{2.5,2.5}); //DrawStringDecal({0,0},"TEST",WHITE,{1,1}); return true; } }; //======================================================================================================================================== int main() { Racer game; if (game.Construct(256, 240, 4, 4)) game.Start(); return 0; } //======================================================================================================================================== // END OF FILE //========================================================================================================================================