Skip to content

Instantly share code, notes, and snippets.

@CompaqDisc
Created August 27, 2019 07:08
Show Gist options
  • Save CompaqDisc/fad8be584903c84655961043d230e958 to your computer and use it in GitHub Desktop.
Save CompaqDisc/fad8be584903c84655961043d230e958 to your computer and use it in GitHub Desktop.
Re-implementation of Javidx9's Console FPS Game using olcPixelGameEngine.
#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