diff --git a/FPSSprites/fps_fireball1.spr b/FPSSprites/fps_fireball1.spr new file mode 100644 index 0000000..f434b49 Binary files /dev/null and b/FPSSprites/fps_fireball1.spr differ diff --git a/FPSSprites/fps_lamp1.spr b/FPSSprites/fps_lamp1.spr new file mode 100644 index 0000000..8c8d499 Binary files /dev/null and b/FPSSprites/fps_lamp1.spr differ diff --git a/FPSSprites/fps_wall1.spr b/FPSSprites/fps_wall1.spr new file mode 100644 index 0000000..1f922b3 Binary files /dev/null and b/FPSSprites/fps_wall1.spr differ diff --git a/OneLoneCoder_ComandLineFPS_2.cpp b/OneLoneCoder_ComandLineFPS_2.cpp new file mode 100644 index 0000000..78eb562 --- /dev/null +++ b/OneLoneCoder_ComandLineFPS_2.cpp @@ -0,0 +1,376 @@ +/* + OneLoneCoder.com - Upgraded Command Line First Person Shooter (FPS) Engine + "Bricks and Lamps people, bricks and lamps..." - @Javidx9 + + Disclaimer + ~~~~~~~~~~ + I don't care what you use this for. It's intended to be educational, and perhaps + to the oddly minded - a little bit of fun. Please hack this, change it and use it + in any way you see fit. BUT, you acknowledge that I am not responsible for anything + bad that happens as a result of your actions. However, if good stuff happens, I + would appreciate a shout out, or at least give the blog some publicity for me. + Cheers! + + Background + ~~~~~~~~~~ + The FPS video was one of my first youtube videos, and I feel one of my better + ones, but its not had the popularity it deserves. So I'm upgrading the engine + to make it more appealling. + + IMPORTANT!! + ~~~~~~~~~~~ + You'll need the FPSSprites folder too! + + + Author + ~~~~~~ + Twitter: @javidx9 + Blog: www.onelonecoder.com + + Video: + ~~~~~~ + https://youtu.be/HEb2akswCcw + + Last Updated: 23/10/2017 +*/ + +#include +#include +#include +using namespace std; + +#include "olcConsoleGameEngine.h" + +class OneLoneCoder_UltimateFPS : public olcConsoleGameEngine +{ +public: + OneLoneCoder_UltimateFPS() + { + m_sAppName = L"Ultimate First Person Shooter"; + } + +private: + int nMapWidth = 32; // World Dimensions + int nMapHeight = 32; + + float fPlayerX = 14.7f; // Player Start Position + float fPlayerY = 8;// 5.09f; + float fPlayerA = -3.14159f / 2.0f; // Player Start Rotation + float fFOV = 3.14159f / 4.0f; // Field of View + float fDepth = 16.0f; // Maximum rendering distance + float fSpeed = 5.0f; // Walking Speed + wstring map; + + olcSprite *spriteWall; + olcSprite *spriteLamp; + olcSprite *spriteFireBall; + + float *fDepthBuffer = nullptr; + + struct sObject + { + float x; + float y; + float vx; + float vy; + bool bRemove; + olcSprite *sprite; + }; + + list listObjects; + +protected: + virtual bool OnUserCreate() + { + map += L"#########.......#########......."; + map += L"#...............#..............."; + map += L"#.......#########.......########"; + map += L"#..............##..............#"; + map += L"#......##......##......##......#"; + map += L"#......##..............##......#"; + map += L"#..............##..............#"; + map += L"###............####............#"; + map += L"##.............###.............#"; + map += L"#............####............###"; + map += L"#..............................#"; + map += L"#..............##..............#"; + map += L"#..............##..............#"; + map += L"#...........#####...........####"; + map += L"#..............................#"; + map += L"###..####....########....#######"; + map += L"####.####.......######.........."; + map += L"#...............#..............."; + map += L"#.......#########.......##..####"; + map += L"#..............##..............#"; + map += L"#......##......##.......#......#"; + map += L"#......##......##......##......#"; + map += L"#..............##..............#"; + map += L"###............####............#"; + map += L"##.............###.............#"; + map += L"#............####............###"; + map += L"#..............................#"; + map += L"#..............................#"; + map += L"#..............##..............#"; + map += L"#...........##..............####"; + map += L"#..............##..............#"; + map += L"################################"; + + + spriteWall = new olcSprite(L"FPSSprites/fps_wall1.spr"); + spriteLamp = new olcSprite(L"FPSSprites/fps_lamp1.spr"); + spriteFireBall = new olcSprite(L"FPSSprites/fps_fireball1.spr"); + fDepthBuffer = new float[ScreenWidth()]; + + listObjects = { + { 8.5f, 8.5f, 0.0f, 0.0f, false, spriteLamp }, + { 7.5f, 7.5f, 0.0f, 0.0f, false, spriteLamp }, + { 10.5f, 3.5f, 0.0f, 0.0f, false, spriteLamp }, + }; + return true; + } + + + virtual bool OnUserUpdate(float fElapsedTime) + { + // Handle CCW Rotation + if (m_keys[L'A'].bHeld) + fPlayerA -= (fSpeed * 0.5f) * fElapsedTime; + + // Handle CW Rotation + if (m_keys[L'D'].bHeld) + fPlayerA += (fSpeed * 0.5f) * fElapsedTime; + + // Handle Forwards movement & collision + if (m_keys[L'W'].bHeld) + { + fPlayerX += sinf(fPlayerA) * fSpeed * fElapsedTime;; + fPlayerY += cosf(fPlayerA) * fSpeed * fElapsedTime;; + if (map.c_str()[(int)fPlayerX * nMapWidth + (int)fPlayerY] == '#') + { + fPlayerX -= sinf(fPlayerA) * fSpeed * fElapsedTime;; + fPlayerY -= cosf(fPlayerA) * fSpeed * fElapsedTime;; + } + } + + // Handle backwards movement & collision + if (m_keys[L'S'].bHeld) + { + fPlayerX -= sinf(fPlayerA) * fSpeed * fElapsedTime;; + fPlayerY -= cosf(fPlayerA) * fSpeed * fElapsedTime;; + if (map.c_str()[(int)fPlayerX * nMapWidth + (int)fPlayerY] == '#') + { + fPlayerX += sinf(fPlayerA) * fSpeed * fElapsedTime;; + fPlayerY += cosf(fPlayerA) * fSpeed * fElapsedTime;; + } + } + + // Handle Strafe Right movement & collision + if (m_keys[L'E'].bHeld) + { + fPlayerX += cosf(fPlayerA) * fSpeed * fElapsedTime; + fPlayerY -= sinf(fPlayerA) * fSpeed * fElapsedTime; + if (map.c_str()[(int)fPlayerX * nMapWidth + (int)fPlayerY] == '#') + { + fPlayerX -= cosf(fPlayerA) * fSpeed * fElapsedTime; + fPlayerY += sinf(fPlayerA) * fSpeed * fElapsedTime; + } + } + + // Handle Strafe Left movement & collision + if (m_keys[L'Q'].bHeld) + { + fPlayerX -= cosf(fPlayerA) * fSpeed * fElapsedTime; + fPlayerY += sinf(fPlayerA) * fSpeed * fElapsedTime; + if (map.c_str()[(int)fPlayerX * nMapWidth + (int)fPlayerY] == '#') + { + fPlayerX += cosf(fPlayerA) * fSpeed * fElapsedTime; + fPlayerY -= sinf(fPlayerA) * fSpeed * fElapsedTime; + } + } + + // Fire Bullets + if (m_keys[VK_SPACE].bReleased) + { + sObject o; + o.x = fPlayerX; + o.y = fPlayerY; + float fNoise = (((float)rand() / (float)RAND_MAX) - 0.5f) * 0.1f; + o.vx = sinf(fPlayerA + fNoise) * 8.0f; + o.vy = cosf(fPlayerA + fNoise) * 8.0f; + o.sprite = spriteFireBall; + o.bRemove = false; + listObjects.push_back(o); + } + + for (int x = 0; x < ScreenWidth(); x++) + { + // For each column, calculate the projected ray angle into world space + float fRayAngle = (fPlayerA - fFOV / 2.0f) + ((float)x / (float)ScreenWidth()) * fFOV; + + // Find distance to wall + float fStepSize = 0.01f; // Increment size for ray casting, decrease to increase + float fDistanceToWall = 0.0f; // resolution + + bool bHitWall = false; // Set when ray hits wall block + bool bBoundary = false; // Set when ray hits boundary between two wall blocks + + float fEyeX = sinf(fRayAngle); // Unit vector for ray in player space + float fEyeY = cosf(fRayAngle); + + float fSampleX = 0.0f; + + bool bLit = false; + + // Incrementally cast ray from player, along ray angle, testing for + // intersection with a block + while (!bHitWall && fDistanceToWall < fDepth) + { + fDistanceToWall += fStepSize; + int nTestX = (int)(fPlayerX + fEyeX * fDistanceToWall); + int nTestY = (int)(fPlayerY + fEyeY * fDistanceToWall); + + // Test if ray is out of bounds + if (nTestX < 0 || nTestX >= nMapWidth || nTestY < 0 || nTestY >= nMapHeight) + { + bHitWall = true; // Just set distance to maximum depth + fDistanceToWall = fDepth; + } + else + { + // Ray is inbounds so test to see if the ray cell is a wall block + if (map.c_str()[nTestX * nMapWidth + nTestY] == '#') + { + // Ray has hit wall + bHitWall = true; + + // Determine where ray has hit wall. Break Block boundary + // int 4 line segments + float fBlockMidX = (float)nTestX + 0.5f; + float fBlockMidY = (float)nTestY + 0.5f; + + float fTestPointX = fPlayerX + fEyeX * fDistanceToWall; + float fTestPointY = fPlayerY + fEyeY * fDistanceToWall; + + float fTestAngle = atan2f((fTestPointY - fBlockMidY), (fTestPointX - fBlockMidX)); + + if (fTestAngle >= -3.14159f * 0.25f && fTestAngle < 3.14159f * 0.25f) + fSampleX = fTestPointY - (float)nTestY; + if (fTestAngle >= 3.14159f * 0.25f && fTestAngle < 3.14159f * 0.75f) + fSampleX = fTestPointX - (float)nTestX; + if (fTestAngle < -3.14159f * 0.25f && fTestAngle >= -3.14159f * 0.75f) + fSampleX = fTestPointX - (float)nTestX; + if (fTestAngle >= 3.14159f * 0.75f || fTestAngle < -3.14159f * 0.75f) + fSampleX = fTestPointY - (float)nTestY; + } + } + } + + // Calculate distance to ceiling and floor + int nCeiling = (float)(ScreenHeight() / 2.0) - ScreenHeight() / ((float)fDistanceToWall); + int nFloor = ScreenHeight() - nCeiling; + + // Update Depth Buffer + fDepthBuffer[x] = fDistanceToWall; + + for (int y = 0; y < ScreenHeight(); y++) + { + // Each Row + if (y <= nCeiling) + Draw(x, y, L' '); + else if (y > nCeiling && y <= nFloor) + { + // Draw Wall + if (fDistanceToWall < fDepth) + { + float fSampleY = ((float)y - (float)nCeiling) / ((float)nFloor - (float)nCeiling); + Draw(x, y, spriteWall->SampleGlyph(fSampleX, fSampleY), spriteWall->SampleColour(fSampleX, fSampleY)); + } + else + Draw(x, y, PIXEL_SOLID, 0); + } + else // Floor + { + Draw(x, y, PIXEL_SOLID, FG_DARK_GREEN); + } + } + } + + // Update & Draw Objects + for (auto &object : listObjects) + { + // Update Object Physics + object.x += object.vx * fElapsedTime; + object.y += object.vy * fElapsedTime; + + // Check if object is inside wall - set flag for removal + if (map.c_str()[(int)object.x * nMapWidth + (int)object.y] == '#') + object.bRemove = true; + + // Can object be seen? + float fVecX = object.x - fPlayerX; + float fVecY = object.y - fPlayerY; + float fDistanceFromPlayer = sqrtf(fVecX*fVecX + fVecY*fVecY); + + float fEyeX = sinf(fPlayerA); + float fEyeY = cosf(fPlayerA); + + // Calculate angle between lamp and players feet, and players looking direction + // to determine if the lamp is in the players field of view + float fObjectAngle = atan2f(fEyeY, fEyeX) - atan2f(fVecY, fVecX); + if (fObjectAngle < -3.14159f) + fObjectAngle += 2.0f * 3.14159f; + if (fObjectAngle > 3.14159f) + fObjectAngle -= 2.0f * 3.14159f; + + bool bInPlayerFOV = fabs(fObjectAngle) < fFOV / 2.0f; + + if (bInPlayerFOV && fDistanceFromPlayer >= 0.5f && fDistanceFromPlayer < fDepth && !object.bRemove) + { + float fObjectCeiling = (float)(ScreenHeight() / 2.0) - ScreenHeight() / ((float)fDistanceFromPlayer); + float fObjectFloor = ScreenHeight() - fObjectCeiling; + float fObjectHeight = fObjectFloor - fObjectCeiling; + float fObjectAspectRatio = (float)object.sprite->nHeight / (float)object.sprite->nWidth; + float fObjectWidth = fObjectHeight / fObjectAspectRatio; + float fMiddleOfObject = (0.5f * (fObjectAngle / (fFOV / 2.0f)) + 0.5f) * (float)ScreenWidth(); + + // Draw Lamp + for (float lx = 0; lx < fObjectWidth; lx++) + { + for (float ly = 0; ly < fObjectHeight; ly++) + { + float fSampleX = lx / fObjectWidth; + float fSampleY = ly / fObjectHeight; + wchar_t c = object.sprite->SampleGlyph(fSampleX, fSampleY); + int nObjectColumn = (int)(fMiddleOfObject + lx - (fObjectWidth / 2.0f)); + if (nObjectColumn >= 0 && nObjectColumn < ScreenWidth()) + if (c != L' ' && fDepthBuffer[nObjectColumn] >= fDistanceFromPlayer) + { + Draw(nObjectColumn, fObjectCeiling + ly, c, object.sprite->SampleColour(fSampleX, fSampleY)); + fDepthBuffer[nObjectColumn] = fDistanceFromPlayer; + } + } + } + } + } + + // Remove dead objects from object list + listObjects.remove_if([](sObject &o) {return o.bRemove; }); + + // Display Map & Player + for (int nx = 0; nx < nMapWidth; nx++) + for (int ny = 0; ny < nMapWidth; ny++) + Draw(nx+1, ny+1, map[ny * nMapWidth + nx]); + Draw(1 + (int)fPlayerY, 1 + (int)fPlayerX, L'P'); + + return true; + } + +}; + +int main() +{ + OneLoneCoder_UltimateFPS game; + game.ConstructConsole(320, 240,4,4); + game.Start(); + return 0; +} \ No newline at end of file diff --git a/OneLoneCoder_SpriteEditor.cpp b/OneLoneCoder_SpriteEditor.cpp new file mode 100644 index 0000000..fdb5f6c --- /dev/null +++ b/OneLoneCoder_SpriteEditor.cpp @@ -0,0 +1,240 @@ +/* +OneLoneCoder.com - Sprite Editor +"Stop Crying about Paint ya big baby" - @Javidx9 + +Disclaimer +~~~~~~~~~~ +I don't care what you use this for. It's intended to be educational, and perhaps +to the oddly minded - a little bit of fun. Please hack this, change it and use it +in any way you see fit. BUT, you acknowledge that I am not responsible for anything +bad that happens as a result of your actions. However, if good stuff happens, I +would appreciate a shout out, or at least give the blog some publicity for me. +Cheers! + +Background +~~~~~~~~~~ +Editing ASCII is not a simple as it should be, especially if you want to +include all the weird characters + +Controls +~~~~~~~~ + + +Author +~~~~~~ +Twitter: @javidx9 +Blog: www.onelonecoder.com + +Video: +~~~~~~ +Several... + +Last Updated: +*/ + +#include +#include +using namespace std; + +#include "olcConsoleGameEngine.h" + + + + + +class OneLoneCoder_SpriteEditor : public olcConsoleGameEngine +{ +public: + OneLoneCoder_SpriteEditor() + { + m_sAppName = L"Sprite Editor"; + } + +private: + int nPosX = 0; + int nPosY = 0; + int nOffsetX = 0; + int nOffsetY = 0; + int nZoom = 4; + int nCurrentGlyph = PIXEL_SOLID; + int nCurrentColourFG = FG_RED; + int nCurrentColourBG = FG_BLACK; + + olcSprite *sprite = nullptr; + wstring sCurrentSpriteFile; + +protected: + // Called by olcConsoleGameEngine + virtual bool OnUserCreate() + { + sprite = new olcSprite(8, 32); + sCurrentSpriteFile = L"fps_fireball1.spr"; + + return true; + } + + // Called by olcConsoleGameEngine + virtual bool OnUserUpdate(float fElapsedTime) + { + + // Zooming + if (m_keys[VK_PRIOR].bReleased) + nZoom <<= 1; + + if (m_keys[VK_NEXT].bReleased) + nZoom >>= 1; + + if (nZoom > 32) nZoom = 32; + if (nZoom < 2) nZoom = 2; + + // Brushes + if (m_keys[VK_F1].bReleased) nCurrentGlyph = PIXEL_SOLID; + if (m_keys[VK_F2].bReleased) nCurrentGlyph = PIXEL_THREEQUARTERS; + if (m_keys[VK_F3].bReleased) nCurrentGlyph = PIXEL_HALF; + if (m_keys[VK_F4].bReleased) nCurrentGlyph = PIXEL_QUARTER; + + // Colours + for (int i = 0; i < 8; i++) + if (m_keys[L"01234567"[i]].bReleased) + if (m_keys[VK_SHIFT].bHeld) + nCurrentColourFG = i + 8; + else + nCurrentColourFG = i; + + + if (m_keys[VK_F7].bReleased) + nCurrentColourBG--; + + if (m_keys[VK_F8].bReleased) + nCurrentColourBG++; + + if (nCurrentColourBG < 0) nCurrentColourBG = 15; + if (nCurrentColourBG > 15) nCurrentColourBG = 0; + + // Cursing :-) + if (m_keys[VK_SHIFT].bHeld) + { + if (m_keys[VK_UP].bReleased) nOffsetY++; + if (m_keys[VK_DOWN].bReleased) nOffsetY--; + if (m_keys[VK_LEFT].bReleased) nOffsetX++; + if (m_keys[VK_RIGHT].bReleased) nOffsetX--; + } + else + { + if (m_keys[VK_UP].bReleased) nPosY--; + if (m_keys[VK_DOWN].bReleased) nPosY++; + if (m_keys[VK_LEFT].bReleased) nPosX--; + if (m_keys[VK_RIGHT].bReleased) nPosX++; + } + + if (sprite != nullptr) + { + if (nPosX < 0) nPosX = 0; + if (nPosX >= sprite->nWidth) nPosX = sprite->nWidth - 1; + if (nPosY < 0) nPosY = 0; + if (nPosY >= sprite->nHeight) nPosY = sprite->nHeight - 1; + + if (m_keys[VK_SPACE].bReleased) + { + sprite->SetGlyph(nPosX - 0, nPosY - 0, nCurrentGlyph); + sprite->SetColour(nPosX - 0, nPosY - 0, nCurrentColourFG | (nCurrentColourBG << 4)); + } + + if (m_keys[VK_DELETE].bReleased) + { + sprite->SetGlyph(nPosX - 0, nPosY - 0, L' '); + sprite->SetColour(nPosX - 0, nPosY - 0, 0); + } + + if (m_keys[VK_F9].bReleased) + { + sprite->Load(sCurrentSpriteFile); + } + + if (m_keys[VK_F10].bReleased) + { + sprite->Save(sCurrentSpriteFile); + } + } + + + + + + // Erase All + Fill(0, 0, ScreenWidth(), ScreenHeight(), L' ', 0); + + // Draw Menu + DrawString(1, 1, L"F1 = 100% F2 = 75% F3 = 50% F4 = 25% F9 = Load File F10 = Save File"); + for (int i = 0; i < 8; i++) + { + DrawString(1 + 6 * i, 3, to_wstring(i) + L" = "); + if (m_keys[VK_SHIFT].bHeld) + Draw(1 + 6 * i + 4, 3, PIXEL_SOLID, (i + 8)); + else + Draw(1 + 6 * i + 4, 3, PIXEL_SOLID, (i)); + } + + DrawString(1, 5, L"Current Brush = "); + Draw(18, 5, nCurrentGlyph, nCurrentColourFG | (nCurrentColourBG << 4)); + + // Draw Canvas + for (int x = 9; x < 138; x++) + { + Draw(x, 9); + Draw(x, 74); + } + + for (int y = 9; y < 75; y++) + { + Draw(9, y); + Draw(138, y); + } + + // Draw Visible Sprite + if (sprite != nullptr) + { + int nVisiblePixelsX = 128 / nZoom; + int nVisiblePixelsY = 64 / nZoom; + + for (int x = 0; x < nVisiblePixelsX; x++) + for (int y = 0; y < nVisiblePixelsY; y++) + { + if (x - nOffsetX < sprite->nWidth && y - nOffsetY < sprite->nHeight && x - nOffsetX >= 0 && y - nOffsetY >= 0) + { + // Draw Sprite + Fill(x * nZoom + 10, y*nZoom + 10, (x + 1)*nZoom + 10, (y + 1)*nZoom + 10, sprite->GetGlyph(x - nOffsetX, y - nOffsetY), sprite->GetColour(x - nOffsetX, y - nOffsetY)); + + + // Draw Pixel Markers + if (sprite->GetGlyph(x - nOffsetX, y - nOffsetY) == L' ') + Draw((x)* nZoom + 10, (y)* nZoom + 10, L'.'); + } + + if (x - nOffsetX == nPosX && y - nOffsetY == nPosY) + Draw((x)* nZoom + 10, (y)* nZoom + 10, L'O'); + } + } + + + // Draw Actual Sprite + for (int x = 0; x < sprite->nWidth; x++) + for (int y = 0; y < sprite->nHeight; y++) + Draw(x + 10, y + 80, sprite->GetGlyph(x, y), sprite->GetColour(x, y)); + + + + return true; + } +}; + + +int main() +{ + // Use olcConsoleGameEngine derived app + OneLoneCoder_SpriteEditor game; + game.ConstructConsole(160, 100, 8, 8); + game.Start(); + + return 0; +} \ No newline at end of file