/* OneLoneCoder.com - Code-It-Yourself! Simple Tile Based Platform Game "Its-a meee-a Jario!" - @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 ~~~~~~~~~~ Tile maps are fundamental to most 2D games. This program explores emulating a classic 2D platformer using floating point truncation to implement robust collision between a moving tile and a tilemap representing the level. Controls ~~~~~~~~ Left and Right arrow keys move Jario, Space bar jumps. (Up and Down also move jario) Author ~~~~~~ Twitter: @javidx9 Blog: www.onelonecoder.com YouTube: www.youtube.com/javidx9 Discord: https://discord.gg/WhwHUMV Video: ~~~~~~ https://youtu.be/oJvJZNyW_rw Last Updated: 04/02/2018 */ #include #include using namespace std; #include "olcConsoleGameEngine.h" class OneLoneCoder_Platformer : public olcConsoleGameEngine { public: OneLoneCoder_Platformer() { m_sAppName = L"Tile Based Platform Game"; } private: // Level storage wstring sLevel; int nLevelWidth; int nLevelHeight; // Player Properties float fPlayerPosX = 1.0f; float fPlayerPosY = 1.0f; float fPlayerVelX = 0.0f; float fPlayerVelY = 0.0f; bool bPlayerOnGround = false; // Camera properties float fCameraPosX = 0.0f; float fCameraPosY = 0.0f; // Sprite Resources olcSprite *spriteTiles = nullptr; olcSprite *spriteMan = nullptr; // Sprite selection flags int nDirModX = 0; int nDirModY = 0; protected: virtual bool OnUserCreate() { nLevelWidth = 64; nLevelHeight = 16; sLevel += L"................................................................"; sLevel += L"................................................................"; sLevel += L".......ooooo...................................................."; sLevel += L"........ooo....................................................."; sLevel += L".......................########................................."; sLevel += L".....BB?BBBB?BB.......###..............#.#......................"; sLevel += L"....................###................#.#......................"; sLevel += L"...................####........................................."; sLevel += L"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG.##############.....########"; sLevel += L"...................................#.#...............###........"; sLevel += L"........................############.#............###..........."; sLevel += L"........................#............#.........###.............."; sLevel += L"........................#.############......###................."; sLevel += L"........................#................###...................."; sLevel += L"........................#################......................."; sLevel += L"................................................................"; spriteTiles = new olcSprite(L"JarioSprites/leveljario.spr"); spriteMan = new olcSprite(L"JarioSprites/minijario.spr"); return true; } virtual bool OnUserUpdate(float fElapsedTime) { // Utility Lambdas auto GetTile = [&](int x, int y) { if (x >= 0 && x < nLevelWidth && y >= 0 && y < nLevelHeight) return sLevel[y * nLevelWidth + x]; else return L' '; }; auto SetTile = [&](int x, int y, wchar_t c) { if (x >= 0 && x < nLevelWidth && y >= 0 && y < nLevelHeight) sLevel[y*nLevelWidth + x] = c; }; //fPlayerVelX = 0.0f; //fPlayerVelY = 0.0f; // Handle Input if (IsFocused()) { if (GetKey(VK_UP).bHeld) { fPlayerVelY = -6.0f; } if (GetKey(VK_DOWN).bHeld) { fPlayerVelY = 6.0f; } if (GetKey(VK_LEFT).bHeld) { fPlayerVelX += (bPlayerOnGround ? -25.0f : -15.0f) * fElapsedTime; nDirModY = 1; } if (GetKey(VK_RIGHT).bHeld) { fPlayerVelX += (bPlayerOnGround ? 25.0f : 15.0f) * fElapsedTime; nDirModY = 0; } if (GetKey(VK_SPACE).bPressed) { if (fPlayerVelY == 0) { fPlayerVelY = -12.0f; nDirModX = 1; } } } // Gravity fPlayerVelY += 20.0f * fElapsedTime; // Drag if (bPlayerOnGround) { fPlayerVelX += -3.0f * fPlayerVelX * fElapsedTime; if (fabs(fPlayerVelX) < 0.01f) fPlayerVelX = 0.0f; } // Clamp velocities if (fPlayerVelX > 10.0f) fPlayerVelX = 10.0f; if (fPlayerVelX < -10.0f) fPlayerVelX = -10.0f; if (fPlayerVelY > 100.0f) fPlayerVelY = 100.0f; if (fPlayerVelY < -100.0f) fPlayerVelY = -100.0f; // Calculate potential new position float fNewPlayerPosX = fPlayerPosX + fPlayerVelX * fElapsedTime; float fNewPlayerPosY = fPlayerPosY + fPlayerVelY * fElapsedTime; // Check for pickups! if (GetTile(fNewPlayerPosX + 0.0f, fNewPlayerPosY + 0.0f) == L'o') SetTile(fNewPlayerPosX + 0.0f, fNewPlayerPosY + 0.0f, L'.'); if (GetTile(fNewPlayerPosX + 0.0f, fNewPlayerPosY + 1.0f) == L'o') SetTile(fNewPlayerPosX + 0.0f, fNewPlayerPosY + 1.0f, L'.'); if (GetTile(fNewPlayerPosX + 1.0f, fNewPlayerPosY + 0.0f) == L'o') SetTile(fNewPlayerPosX + 1.0f, fNewPlayerPosY + 0.0f, L'.'); if (GetTile(fNewPlayerPosX + 1.0f, fNewPlayerPosY + 1.0f) == L'o') SetTile(fNewPlayerPosX + 1.0f, fNewPlayerPosY + 1.0f, L'.'); // Check for Collision if (fPlayerVelX <= 0) // Moving Left { if (GetTile(fNewPlayerPosX + 0.0f, fPlayerPosY + 0.0f) != L'.' || GetTile(fNewPlayerPosX + 0.0f, fPlayerPosY + 0.9f) != L'.') { fNewPlayerPosX = (int)fNewPlayerPosX + 1; fPlayerVelX = 0; } } else // Moving Right { if (GetTile(fNewPlayerPosX + 1.0f, fPlayerPosY + 0.0f) != L'.' || GetTile(fNewPlayerPosX + 1.0f, fPlayerPosY + 0.9f) != L'.') { fNewPlayerPosX = (int)fNewPlayerPosX; fPlayerVelX = 0; } } bPlayerOnGround = false; if (fPlayerVelY <= 0) // Moving Up { if (GetTile(fNewPlayerPosX + 0.0f, fNewPlayerPosY) != L'.' || GetTile(fNewPlayerPosX + 0.9f, fNewPlayerPosY) != L'.') { fNewPlayerPosY = (int)fNewPlayerPosY + 1; fPlayerVelY = 0; } } else // Moving Down { if (GetTile(fNewPlayerPosX + 0.0f, fNewPlayerPosY + 1.0f) != L'.' || GetTile(fNewPlayerPosX + 0.9f, fNewPlayerPosY + 1.0f) != L'.') { fNewPlayerPosY = (int)fNewPlayerPosY; fPlayerVelY = 0; bPlayerOnGround = true; // Player has a solid surface underfoot nDirModX = 0; } } // Apply new position fPlayerPosX = fNewPlayerPosX; fPlayerPosY = fNewPlayerPosY; // Link camera to player position fCameraPosX = fPlayerPosX; fCameraPosY = fPlayerPosY; // Draw Level int nTileWidth = 16; int nTileHeight = 16; int nVisibleTilesX = ScreenWidth() / nTileWidth; int nVisibleTilesY = ScreenHeight() / nTileHeight; // Calculate Top-Leftmost visible tile float fOffsetX = fCameraPosX - (float)nVisibleTilesX / 2.0f; float fOffsetY = fCameraPosY - (float)nVisibleTilesY / 2.0f; // Clamp camera to game boundaries if (fOffsetX < 0) fOffsetX = 0; if (fOffsetY < 0) fOffsetY = 0; if (fOffsetX > nLevelWidth - nVisibleTilesX) fOffsetX = nLevelWidth - nVisibleTilesX; if (fOffsetY > nLevelHeight - nVisibleTilesY) fOffsetY = nLevelHeight - nVisibleTilesY; // Get offsets for smooth movement float fTileOffsetX = (fOffsetX - (int)fOffsetX) * nTileWidth; float fTileOffsetY = (fOffsetY - (int)fOffsetY) * nTileHeight; // Draw visible tile map for (int x = -1; x < nVisibleTilesX + 1; x++) { for (int y = -1; y < nVisibleTilesY + 1; y++) { wchar_t sTileID = GetTile(x + fOffsetX, y + fOffsetY); switch (sTileID) { case L'.': // Sky Fill(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, (x + 1) * nTileWidth - fTileOffsetX, (y + 1) * nTileHeight - fTileOffsetY, PIXEL_SOLID, FG_CYAN); break; case L'#': // Solid Block //Fill(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, (x + 1) * nTileWidth - fTileOffsetX, (y + 1) * nTileHeight - fTileOffsetY, PIXEL_SOLID, FG_RED); DrawPartialSprite(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, spriteTiles, 2 * nTileWidth, 0 * nTileHeight, nTileWidth, nTileHeight); break; case L'G': // Ground Block DrawPartialSprite(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, spriteTiles, 0 * nTileWidth, 0 * nTileHeight, nTileWidth, nTileHeight); break; case L'B': // Brick Block DrawPartialSprite(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, spriteTiles, 0 * nTileWidth, 1 * nTileHeight, nTileWidth, nTileHeight); break; case L'?': // Question Block DrawPartialSprite(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, spriteTiles, 1 * nTileWidth, 1 * nTileHeight, nTileWidth, nTileHeight); break; case L'o': // Coin Fill(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, (x + 1) * nTileWidth - fTileOffsetX, (y + 1) * nTileHeight - fTileOffsetY, PIXEL_SOLID, FG_CYAN); DrawPartialSprite(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, spriteTiles, 3 * nTileWidth, 0 * nTileHeight, nTileWidth, nTileHeight); break; default: Fill(x * nTileWidth - fTileOffsetX, y * nTileHeight - fTileOffsetY, (x + 1) * nTileWidth - fTileOffsetX, (y + 1) * nTileHeight - fTileOffsetY, PIXEL_SOLID, FG_BLACK); break; } } } // Draw Player //Fill((fPlayerPosX - fOffsetX) * nTileWidth, (fPlayerPosY - fOffsetY) * nTileWidth, (fPlayerPosX - fOffsetX + 1.0f) * nTileWidth, (fPlayerPosY - fOffsetY + 1.0f) * nTileHeight, PIXEL_SOLID, FG_GREEN); DrawPartialSprite((fPlayerPosX - fOffsetX) * nTileWidth, (fPlayerPosY - fOffsetY) * nTileWidth, spriteMan, nDirModX * nTileWidth, nDirModY * nTileHeight, nTileWidth, nTileHeight); return true; } }; int main() { OneLoneCoder_Platformer game; if (game.ConstructConsole(256, 240, 4, 4)) game.Start(); return 0; }