Created
August 27, 2019 07:08
-
-
Save CompaqDisc/fad8be584903c84655961043d230e958 to your computer and use it in GitHub Desktop.
Re-implementation of Javidx9's Console FPS Game using olcPixelGameEngine.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define OLC_PGE_APPLICATION | |
#include "olcPixelGameEngine.h" | |
#include <math.h> | |
#include <cstring> | |
#include <vector> | |
#include <algorithm> | |
#include <utility> | |
class Demo : public olc::PixelGameEngine | |
{ | |
private: | |
uint32_t m_TilesX; | |
uint32_t m_TilesY; | |
uint32_t m_MapWidth = 32; | |
uint32_t m_MapHeight = 32; | |
float m_fPlayerX = 14.7f; | |
float m_fPlayerY = 5.09f; | |
float m_fPlayerA = 0.0f; // Player angle (aka theta). | |
float m_fFOV = M_PI / 4.0f; | |
float m_fDepth = 32.0f; | |
float m_fSpeed = 3.0f; | |
char* m_sBufferedOutput; | |
uint32_t* m_nBufferedShading; | |
// 32x32 maps are totally cool... | |
const char* map = { | |
"################################" | |
"# #" | |
"# ####### #" | |
"# # # #" | |
"# # #" | |
"# # # #" | |
"# ## #### #" | |
"# #" | |
"# #" | |
"# #" | |
"# #" | |
"# #### #" | |
"# #### #" | |
"# #### # #" | |
"# #### # #" | |
"# # #" | |
"# # #" | |
"# # #" | |
"# ################## #" | |
"# #" | |
"# #" | |
"# #" | |
"# #" | |
"# #" | |
"# #### #" | |
"# # #" | |
"# #" | |
"# #" | |
"# #" | |
"# #" | |
"# #" | |
"################################" | |
}; | |
public: | |
Demo() | |
{ | |
sAppName = "Demo App"; | |
} | |
~Demo() | |
{ | |
delete m_sBufferedOutput; | |
delete m_nBufferedShading; | |
} | |
bool OnUserCreate() override | |
{ | |
m_TilesX = ScreenWidth() / 8; | |
m_TilesY = ScreenHeight() / 8; | |
m_sBufferedOutput = new char[m_TilesX * m_TilesY]; | |
for (int i = 0; i < (m_TilesX * m_TilesY); i++) | |
m_sBufferedOutput[i] = '&'; | |
m_nBufferedShading = new uint32_t[m_TilesX * m_TilesY]; | |
for (int i = 0; i < (m_TilesX * m_TilesY); i++) | |
m_nBufferedShading[i] = olc::WHITE.n; | |
return true; | |
} | |
bool OnUserUpdate(float fElapsedTime) override | |
{ | |
Clear(olc::BLACK); | |
// Reset our buffers. | |
for (int i = 0; i < (m_TilesX * m_TilesY); i++) | |
m_sBufferedOutput[i] = ' '; | |
for (int i = 0; i < (m_TilesX * m_TilesY); i++) | |
m_nBufferedShading[i] = olc::WHITE.n; | |
// Get input. | |
if (GetKey(olc::Key::A).bHeld) | |
m_fPlayerA -= (m_fSpeed * 0.75) * fElapsedTime; | |
if (GetKey(olc::Key::D).bHeld) | |
m_fPlayerA += (m_fSpeed * 0.75) * fElapsedTime; | |
// FOV Keys are trippy, I may have to cap the FOV, because things get wierd outside of (0 <= A <= 360) | |
if (GetKey(olc::Key::PGUP).bHeld) | |
m_fFOV += (m_fSpeed * M_PI_4) * fElapsedTime; | |
if (GetKey(olc::Key::PGDN).bHeld) | |
m_fFOV -= (m_fSpeed * M_PI_4) * fElapsedTime; | |
// More input. | |
if (GetKey(olc::Key::W).bHeld) | |
{ | |
m_fPlayerX += cosf(m_fPlayerA) * m_fSpeed * fElapsedTime; | |
m_fPlayerY += sinf(m_fPlayerA) * m_fSpeed * fElapsedTime; | |
if (map[(int) m_fPlayerY * m_MapWidth + (int) m_fPlayerX] == '#') | |
{ | |
m_fPlayerX -= cosf(m_fPlayerA) * m_fSpeed * fElapsedTime; | |
m_fPlayerY -= sinf(m_fPlayerA) * m_fSpeed * fElapsedTime; | |
} | |
} | |
if (GetKey(olc::Key::S).bHeld) | |
{ | |
m_fPlayerX -= cosf(m_fPlayerA) * m_fSpeed * fElapsedTime; | |
m_fPlayerY -= sinf(m_fPlayerA) * m_fSpeed * fElapsedTime; | |
if (map[(int) m_fPlayerY * m_MapWidth + (int) m_fPlayerX] == '#') | |
{ | |
m_fPlayerX += cosf(m_fPlayerA) * m_fSpeed * fElapsedTime; | |
m_fPlayerY += sinf(m_fPlayerA) * m_fSpeed * fElapsedTime; | |
} | |
} | |
// Limit value range. | |
m_fPlayerA = fmod(m_fPlayerA, (2*M_PI)); | |
if (m_fPlayerA < 0) | |
m_fPlayerA += (2*M_PI); | |
// Render to buffer. | |
for (int x = 0; x < m_TilesX; x++) | |
{ | |
// For each column, calculate the projected ray angle into world space. | |
float fRayAngle = (m_fPlayerA - m_fFOV/2.0f) + ((float) x / (float) m_TilesX) * m_fFOV; | |
// Find distance to wall. | |
float fStepSize = 0.1f; | |
float fDistanceToWall = 0.0f; | |
bool bHitWall = false; | |
bool bBoundary = false; | |
float fEyeX = cosf(fRayAngle); | |
float fEyeY = sinf(fRayAngle); | |
while (!bHitWall && (fDistanceToWall < m_fDepth)) | |
{ | |
fDistanceToWall += fStepSize; | |
int nTestX = (int)(m_fPlayerX + fEyeX * fDistanceToWall); | |
int nTestY = (int)(m_fPlayerY + fEyeY * fDistanceToWall); | |
if (nTestX < 0 || nTestX >= m_MapWidth || nTestY < 0 || nTestY >= m_MapHeight) | |
{ | |
// Outside bounds. | |
bHitWall = true; | |
fDistanceToWall = m_fDepth; | |
} | |
else | |
{ | |
if (map[nTestY * m_MapWidth + nTestX] == '#') | |
{ | |
// Hit! | |
bHitWall = true; | |
std::vector<std::pair<float, float>> p; | |
for (int tx = 0; tx < 2; tx++) | |
{ | |
for (int ty = 0; ty < 2; ty++) | |
{ | |
// Angle of corner to eye | |
float vy = (float)nTestY + ty - m_fPlayerY; | |
float vx = (float)nTestX + tx - m_fPlayerX; | |
float d = sqrt(vx*vx + vy*vy); | |
float dot = (fEyeX * vx / d) + (fEyeY * vy / d); | |
p.push_back(std::make_pair(d, dot)); | |
} | |
} | |
std::sort(p.begin(), p.end(), [](const std::pair<float, float> &left, const std::pair<float, float> &right) {return left.first < right.first; }); | |
float fBound = 0.005f; | |
if (acos(p.at(0).second) < fBound) bBoundary = true; | |
if (acos(p.at(1).second) < fBound) bBoundary = true; | |
} | |
} | |
} | |
int nCeiling = (float)(m_TilesY/2.0) - m_TilesY / ((float)fDistanceToWall); | |
int nFloor = m_TilesY - nCeiling; | |
uint32_t nShade = olc::BLACK.n; | |
char nChar = ' '; | |
if (fDistanceToWall <= m_fDepth / 4.0f) nShade = olc::WHITE.n; | |
else if (fDistanceToWall < m_fDepth / 3.0f) nShade = olc::GREY.n; | |
else if (fDistanceToWall < m_fDepth / 2.0f) nShade = olc::DARK_GREY.n; | |
else if (fDistanceToWall < m_fDepth) nShade = olc::VERY_DARK_GREY.n; | |
else nShade = olc::BLACK.n; | |
if (bBoundary) nShade = olc::BLACK.n; | |
for (int y = 0; y < m_TilesY; y++) | |
{ | |
if (y <= nCeiling) | |
{ | |
m_sBufferedOutput[y*m_TilesX + x] = ' '; | |
m_nBufferedShading[y*m_TilesX + x] = olc::WHITE.n; | |
} | |
else if (y > nCeiling && y <= nFloor) | |
{ | |
m_sBufferedOutput[y*m_TilesX + x] = '@'; | |
m_nBufferedShading[y*m_TilesX + x] = nShade; | |
} | |
else | |
{ | |
// Shade floor based on distance | |
float b = 1.0f - (((float) y - m_TilesY / 2.0f) / ((float) m_TilesY / 2.0f)); | |
if (b < 0.25) nChar = '#'; | |
else if (b < 0.5) nChar = 'x'; | |
else if (b < 0.75) nChar = '.'; | |
else if (b < 0.9) nChar = '-'; | |
else nChar = ' '; | |
m_sBufferedOutput[y*m_TilesX + x] = nChar; | |
} | |
} | |
} | |
sprintf(m_sBufferedOutput, "X=%3.2f, Y=%3.2f, A=%3.2f FPS=%3.2f ", m_fPlayerX, m_fPlayerY, m_fPlayerA, 1.0f/fElapsedTime); | |
// Display Map | |
for (int nx = 0; nx < m_MapWidth; nx++) | |
{ | |
for (int ny = 0; ny < m_MapWidth; ny++) | |
{ | |
m_sBufferedOutput[(ny+1)*m_TilesX + nx] = map[ny * m_MapWidth + nx]; | |
m_nBufferedShading[(ny+1)*m_TilesX + nx] = olc::WHITE.n; | |
} | |
} | |
// Calculate minimap pointer and target. | |
// Do after main render loop so this shows on top. | |
{ | |
int x = m_TilesX / 2; | |
// For each column, calculate the projected ray angle into world space. | |
float fRayAngle = (m_fPlayerA - m_fFOV/2.0f) + ((float) x / (float) m_TilesX) * m_fFOV; | |
// Find distance to wall. | |
float fStepSize = 0.1f; | |
float fDistanceToWall = 0.0f; | |
bool bHitWall = false; | |
float fEyeX = cosf(fRayAngle); | |
float fEyeY = sinf(fRayAngle); | |
while (!bHitWall && (fDistanceToWall < m_fDepth)) | |
{ | |
fDistanceToWall += fStepSize; | |
int nTestX = (int)(m_fPlayerX + fEyeX * fDistanceToWall); | |
int nTestY = (int)(m_fPlayerY + fEyeY * fDistanceToWall); | |
if (nTestX < 0 || nTestX >= m_MapWidth || nTestY < 0 || nTestY >= m_MapHeight) | |
{ | |
// Outside bounds. | |
bHitWall = true; | |
fDistanceToWall = m_fDepth; | |
} | |
else | |
{ | |
if (map[nTestY * m_MapWidth + nTestX] == '#') | |
{ | |
// Hit! | |
bHitWall = true; | |
m_sBufferedOutput[(nTestY+1) * m_TilesX + nTestX] = '!'; | |
m_nBufferedShading[(nTestY+1) * m_TilesX + nTestX] = olc::BLUE.n; | |
} | |
else if (fDistanceToWall < 1.2f) | |
{ | |
m_sBufferedOutput[(nTestY+1) * m_TilesX + nTestX] = '%'; | |
m_nBufferedShading[(nTestY+1) * m_TilesX + nTestX] = olc::DARK_GREEN.n; | |
} | |
} | |
} | |
} | |
// Player icon. | |
m_sBufferedOutput[((int) m_fPlayerY+1) * m_TilesX + (int) m_fPlayerX] = 'P'; | |
m_nBufferedShading[((int) m_fPlayerY+1) * m_TilesX + (int) m_fPlayerX] = olc::GREEN.n; | |
// Render from buffer. | |
for (int i = 0; i < (m_TilesX * m_TilesY); i++) | |
{ | |
char c[2]; | |
c[0] = m_sBufferedOutput[i]; | |
c[1] = 0; | |
DrawString((i % m_TilesX)*8, (i / m_TilesX)*8, c, m_nBufferedShading[i]); | |
} | |
return true; | |
} | |
}; | |
int main(void) | |
{ | |
Demo* demo = new Demo(); | |
//if (demo->Construct(1920, 1080, 1, 1, true)) { | |
//if (demo->Construct(320, 200, 3, 3, false)) { | |
if (demo->Construct(1600, 900, 1, 1, false)) { | |
demo->Start(); | |
} | |
delete demo; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment