A repository that sets up a C++ Project with the olcPixelGameEngine already loaded.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1242 lines
40 KiB

//========================================================================================================================================
// 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
//========================================================================================================================================
// protected variables
//========================================================================================================================================
class Racer : public olc::PixelGameEngine
{
public:
Racer()
{
sAppName = "F4 Racing";
}
private:
float car_pos = 0.f; // track the horizontal location of player
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;
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<std::pair<float, float>> vecTrack;
std::list<float> 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<std::string, 4> 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<MenuItem> mainMenu;
std::vector<MenuItem> playGame;
std::vector<MenuItem> pickTrack;
std::vector<MenuItem> grandPrix;
std::vector<MenuItem> selectCar;
std::vector<MenuItem> controls;
std::vector<MenuItem> pause;
std::vector<std::string> gameOver;
std::vector<Object> gameObjects;
enum class state
{
MAIN_MENU, PLAY_GAME, RUN_GAME, CONTROLS, PAUSED, RESUME, // main
TRACK_SELECT, CUP_SELECT, CAR_SELECT, // game
INDIE, STREET, PRO, GRAND_PRIX // cups
};
state gameState = state::MAIN_MENU;
//========================================================================================================================================
// Variables
//========================================================================================================================================
// lap times
// player
int score = 0; // score at end of track; based on final place
int lap = 0; // current lap player is on
int place; // current place player is in
int tracks_left;
// initialize sprites
olc::Decal* car;
olc::Decal* cars;
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<std::string, 98> 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];
}
// add laps to a new track struct
struct Track
{
std::vector <std::pair <float, float>> 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},}, "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}}, "OVERPASS", 3
};
Track peanut=
{
// No more rhymes now. I mean it!
// Anybody want a peanut?
// GAH!!!
{{0.f, 10.f},}, "PEANUT", 4
};
Track track_7 = // ???
{
{{0.f, 10.f},}, "Track 7", 3
};
Track trident =
{
// is your preferred Neptune or Poseidon?
{{0.f, 10.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},}, "STAR", 3
};
std::vector <Track> trackList =
{
javidx9,
circuit, infinity, arrow_head,
track_4, overpass, peanut,
track_7, trident, star
};
//========================================================================================================================================
// 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
cars = new olc::Decal(new olc::Sprite("art/cars.png")); // temp file; holds all 8 cars
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"));
// track stuff
list_times = { 0,0,0,0,0 }; // this needs modification, as most tracks will likely have 4 laps, some with 3, and some with 5
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;
cur_lap_time = 0.0f;
lap = 0;
// 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
//
}
void DrawTrack()
{
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<MenuItem> 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, -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));
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));
DisplayMenu(pickTrack);
if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed)
{
if (highlighted >= 0 && highlighted <= 8)
{
track = highlighted + 1;
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));
DrawPartialDecal({ ScreenWidth() / 3 - 96.f, ScreenHeight() / 3 - 192.f }, cups, { 0, 0 }, { 64, 64 }, { 3.0f, 3.0f }, (highlighted == 0) ? olc::WHITE : olc::DARK_GREY); // bronze
DrawPartialDecal({ ScreenWidth() / 3 * 2 - 96.f, ScreenHeight() / 3 - 192.f }, cups, { 64, 0 }, { 64, 64 }, { 3.0f, 3.0f }, (highlighted == 1) ? olc::WHITE : olc::DARK_GREY); // silver
DrawPartialDecal({ ScreenWidth() / 3 - 96.f, ScreenHeight() / 3 * 2 - 192.f }, cups, { 0, 64 }, { 64, 64 }, { 3.0f, 3.0f }, (highlighted == 2) ? olc::WHITE : olc::DARK_GREY); // gold
DrawPartialDecal({ ScreenWidth() / 3 * 2 - 96.f, ScreenHeight() / 3 * 2 - 192.f }, cups, { 64, 64 }, { 64, 64 }, { 3.0f, 3.0f }, (highlighted == 3) ? olc::WHITE : olc::DARK_GREY); // platinum
DisplayMenu(grandPrix);
if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed)
{
// 2 x 2 menu
std::array<state, 4> gameStates = { state::INDIE, state::STREET, state::PRO, state::GRAND_PRIX };
if (highlighted >= 0 && highlighted <= 3)
{
gameState = gameStates[highlighted];
cup = highlighted + 1;
gameState = state::CAR_SELECT;
tracks_left = 3;
track = 1 + highlighted * 3;
if (highlighted == 3)
{
tracks_left = 9;
}
highlighted = 0;
}
//if (highlighted == 0)
//{
// gameState = state::INDIE;
// cup = 1;
// gameState = state::CAR_SELECT;
// highlighted = 0;
//}
//else if (highlighted == 1)
//{
// gameState = state::STREET;
// cup = 2;
// gameState = state::CAR_SELECT;
// highlighted = 0;
//}
//else if (highlighted == 2)
//{
// gameState = state::PRO;
// cup = 3;
// gameState = state::CAR_SELECT;
// highlighted = 0;
//}
//else if (highlighted == 3)
//{
// gameState = state::GRAND_PRIX;
// cup = 4;
// gameState = state::CAR_SELECT;
// 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;
gameState = state::CAR_SELECT;
tracks_left = 10;
}
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));
DrawPartialDecal({ ScreenWidth() / 5 - 96.f, ScreenHeight() / 3 - 192.f }, cars, { 0, 0 }, { 256, 192}, { .75f, .75f }, (highlighted == 0) ? olc::WHITE : olc::DARK_GREY); // red
DrawPartialDecal({ ScreenWidth() / 5 * 2 - 96.f, ScreenHeight() / 3 - 192.f }, cars, { 256, 0 }, { 256, 192 }, { .75f, .75f }, (highlighted == 1) ? olc::WHITE : olc::DARK_GREY); // blue
DrawPartialDecal({ ScreenWidth() / 5 * 3 - 96.f, ScreenHeight() / 3 - 192.f }, cars, { 512, 0 }, { 256, 192 }, { .75f, .75f }, (highlighted == 2) ? olc::WHITE : olc::DARK_GREY); // green
DrawPartialDecal({ ScreenWidth() / 5 * 4 - 96.f, ScreenHeight() / 3 - 192.f }, cars, { 768, 0 }, { 256, 192 }, { .75f, .75f }, (highlighted == 3) ? olc::WHITE : olc::DARK_GREY); // gold
DrawPartialDecal({ ScreenWidth() / 5 - 96.f, ScreenHeight() / 3 * 2 - 192.f }, cars, { 0, 192 }, { 256, 192 }, { .75f, .75f }, (highlighted == 4) ? olc::WHITE : olc::DARK_GREY); // purple
DrawPartialDecal({ ScreenWidth() / 5 * 2 - 96.f, ScreenHeight() / 3 * 2 - 192.f }, cars, { 256, 192 }, { 256, 192 }, { .75f, .75f }, (highlighted == 5) ? olc::WHITE : olc::DARK_GREY); // white
DrawPartialDecal({ ScreenWidth() / 5 * 3 - 96.f, ScreenHeight() / 3 * 2 - 192.f }, cars, { 512, 192 }, { 256, 192 }, { .75f, .75f }, (highlighted == 6) ? olc::WHITE : olc::DARK_GREY); // orange
DrawPartialDecal({ ScreenWidth() / 5 * 4 - 96.f, ScreenHeight() / 3 * 2 - 192.f }, cars, { 768, 192 }, { 256, 192 }, { .75f, .75f }, (highlighted == 7) ? olc::WHITE : olc::DARK_GREY); // black
DisplayMenu(selectCar);
if (GetKey(olc::Key::ENTER).bPressed || GetKey(olc::Key::SPACE).bPressed)
{
if (highlighted >= 0 && highlighted <= 7)
{
player = highlighted;
gameState = state::RUN_GAME;
DrawTrack();
//vecTrack.clear();
//for (int i = 0; i < trackList[track].size(); i++)
//{
// vecTrack.push_back(trackList[track][i]);
// track_dist += vecTrack[i].second;
//}
}
else
{
if (cup != -1)
{
// return to cup select
gameState = state::CUP_SELECT;
}
if (track != -1)
{
// 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;
}
if (track != -1)
{
// 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;
}
}
}
//========================================================================================================================================
//========================================================================================================================================
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
if (GetKey(olc::Key::ESCAPE).bPressed || GetKey(olc::Key::PAUSE).bPressed)
{
gameState = state::PAUSED;
}
// debug quick-reset
if (GetKey(olc::Key::F4).bPressed)
{
Reset();
}
// jump to end of lap
if (GetKey(olc::Key::F12).bPressed)
{
distance = track_dist - 50.f;
}
// get a point on the track
float offset = 0;
int TrackSection = 0;
cur_lap_time += fElapsedTime;
// record lap time
if (distance >= track_dist)
{
distance -= track_dist;
list_times.push_front(cur_lap_time); // push time to front
list_times.pop_back(); // pop time to back
cur_lap_time = 0.0f;
lap++;
}
if (cup >= 0 && lap >= 1)
{
track++;
Reset();
DrawTrack();
}
if (track != -1)
{
//olc::vi2d textSize = GetTextSize() / 2 * 4;
olc::vi2d textSize0 = GetTextSize("JAVIDX9") / 2 * 4;
olc::vi2d textSize1 = GetTextSize("CIRCUIT") / 2 * 4;
switch (track)
{
case 0:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "JAVIDX9", olc::WHITE, { 3.f, 3.f });
break;
case 1:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "CIRCUIT", olc::WHITE, { 3.f, 3.f });
break;
case 2:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "INFINITY", olc::WHITE, { 3.f, 3.f });
break;
case 3:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "ARROWHEAD", olc::WHITE, { 3.f, 3.f });
break;
case 4:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "TRACK 4", olc::WHITE, { 3.f, 3.f });
break;
case 5:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "OVERPASS", olc::WHITE, { 3.f, 3.f });
break;
case 6:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "PEANUT", olc::WHITE, { 3.f, 3.f });
break;
case 7:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "TRACK 7", olc::WHITE, { 3.f, 3.f });
break;
case 8:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "TRIDENT", olc::WHITE, { 3.f, 3.f });
break;
case 9:
DrawStringDecal({ ScreenWidth() / 2 - 0.f, ScreenHeight() / 8 + 0.f }, "STAR", olc::WHITE, { 3.f, 3.f });
break;
}
}
// find position on track (could optimize) << should probably optimize given the complexity of this game
while (TrackSection < vecTrack.size() && offset <= distance)
{
offset += vecTrack[TrackSection].second;
TrackSection++;
}
float TargetCurvature = vecTrack[TrackSection - 1].first; // drawing curves to screen
float TrackCurveDiff = (TargetCurvature - curvature) * fElapsedTime * speed; // correcting the drawing of curves to the screen
curvature += TrackCurveDiff; // update track curve difference
track_curve += (curvature)*fElapsedTime * speed;
//========================================================================================================================================
// | | draw racetrack canvas
//========================================================================================================================================
std::vector<olc::vf2d> pos;
std::vector<olc::vf2d> 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 (distance + horizon > track_dist - 20 && distance + horizon < track_dist)
{
// draw checkerboard pattern
uvs.push_back({ 0, (float)fmod(((distance + horizon) - (track_dist - 20)) / 20.0f, 0.5f) + 0.5f });
uvs.push_back({ 1, (float)fmod(((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) + distance) / 4 + 0.25f });
uvs.push_back({ 1, sinf(80.0f * powf(1.0f - perspective, 2) + 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
int i = 0;
for (Object& obj : gameObjects)
{
float horizonRange = powf(1.0f - perspective, 2) * ((distance + horizon) - obj.pos.y) / horizon;
if (!obj.rendered && (distance + horizon) > obj.pos.y && (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)
{
speed += 2.0f * fElapsedTime;
}
else // might remove this in a later stage of development
{
speed -= 1.0f * fElapsedTime;
}
if (GetKey(Move_Down).bHeld)
{
// move back ?
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)
{
speed -= 5.0f * fElapsedTime;
}
// clamp speed
if (speed < 0.0f) speed = 0.0f;
if (speed > 1.0f) speed = 1.0f;
// move car along track according to car speed
distance += (70.0f * speed) * fElapsedTime;
// draw car
car_pos = car_curve - track_curve;
DrawDecal({ ScreenWidth() / 2 - 128 + car_pos * ScreenWidth() / 2, ScreenHeight() / 4 * 3.0f - 128 }, car);
SetPixelMode(olc::Pixel::ALPHA);
//========================================================================================================================================
//========================================================================================================================================
// spedometer
std::string mph = std::to_string(speed);
DrawString(4, 4, mph);
// show time
auto disp_time = [](float t)
{
int min = t / 60.0f;
int sec = t - (min * 60.0f);
int milli = (t - (float)sec) * 1000.0f;
// need to change this out to a 'DrawString()' function instead
return std::to_string(min) + "." + std::to_string(sec) + ":" + std::to_string(milli);
};
// correct for the first '0' in the seconds timer
//if ()
//{
// // draw min + '0' + sec + milli
// DrawString(4, 16, disp_time(cur_lap_time));
//}
//else
//{
DrawString(4, 16, disp_time(cur_lap_time));
//}
// display last 5 lap times
int j = 30;
for (auto l : list_times)
{
DrawString(10, j, disp_time(l));
j += 10;
}
// debug code
//std::string numb1 = std::to_string(car_pos);
//DrawString(4, 26, numb1);
//std::string numb2 = std::to_string(track_dist);
//DrawString(4, 38, numb2);
}
return true;
}
};
//========================================================================================================================================
int main()
{
std::vector<std::string> test= {"Apple","Apple","Pear","Something","Tire","Orange","Orange","Orange"};
std::vector<std::string>::iterator it = std::unique(test.begin(),test.end());
test.erase(it,test.end());
for (int i=0;i<test.size();i++) {
std::cout<<test[i]<<"\n";
}
Racer game;
if (game.Construct(1280, 720, 1, 1))
game.Start();
return 0;
}
//========================================================================================================================================
// END OF FILE
//========================================================================================================================================