#include "KGame.h" #include "SDL.h" #include #include #include #include #include #include #include #include "Utils.h" #include "Constants.h" #include "KCamera.h" #include "KCirclePawn.h" #include "KTexture.h" #include "KTile.h" #include "KVector2d.h" #include "KSolidTile.h" namespace KapitanGame { KGame::KGame() : Time(0), PreviousTime(0), Map(), Settings(Constants::MAX_JUMP_HEIGHT, Constants::HORIZONTAL_DISTANCE_TO_MAX_JUMP_HEIGHT) { //Initialize SDL if (SDL_Init(SDL_INIT_VIDEO) < 0) { throw std::runtime_error(Utils::StringFormat("SDL could not initialize! SDL_Error: %s", SDL_GetError())); } #ifndef NDEBUG SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); #endif ////Set texture filtering to linear //if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) //{ // printf("Warning: Linear texture filtering not enabled!\n"); //} Input.Init(); if (TTF_Init() < 0) { throw std::runtime_error(Utils::StringFormat("SDL_TTF could not initialize! TTF_Error: %s", TTF_GetError())); } if (IMG_Init(IMG_INIT_PNG) < 0) { throw std::runtime_error(Utils::StringFormat("SDL_IMG could not initialize! IMG_Error: %s", IMG_GetError())); } //Create window Window = SDL_CreateWindow(Constants::WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Constants::SCREEN_WIDTH, Constants::SCREEN_HEIGHT, SDL_WINDOW_SHOWN); if (Window == nullptr) { throw std::runtime_error(Utils::StringFormat("Window could not be created! SDL_Error: %s", SDL_GetError())); } //Create renderer for window Renderer = SDL_CreateRenderer(Window, -1, SDL_RENDERER_ACCELERATED); if (Renderer == nullptr) { throw std::runtime_error( Utils::StringFormat("Renderer could not be created! SDL Error: %s", SDL_GetError())); } //Initialize renderer color SDL_SetRenderDrawColor(Renderer, 0, 0xFF, 0, 0xFF); SDL_Log("KGame initialized..."); } KGame::~KGame() { //Free loaded images Textures.clear(); for (auto& layer : BackgroundLayers) { layer.clear(); } Objects.clear(); for (auto& layer : ForegroundLayers) { layer.clear(); } Pawns.clear(); PlayerControllers.clear(); Fonts.clear(); TTF_Quit(); IMG_Quit(); Input.Free(); //Destroy window SDL_DestroyRenderer(Renderer); SDL_DestroyWindow(Window); Window = nullptr; Renderer = nullptr; //Quit SDL subsystems SDL_Quit(); } bool KGame::LoadLevel() { bool success = true; Objects.clear(); Pawns.clear(); std::ifstream levelFile; levelFile.open("assets/levels/level" + std::to_string(LvlCounter) + ".txt"); if (levelFile.fail()) { printf("Failed to load assets/levels/level%d.txt!\n", LvlCounter); success = false; } else { int y = 0; int layer = 0; float mapWidth = 0, mapHeight = 0; KVector2D startPosition{ 0.f, 0.f }; std::string line; while (std::getline(levelFile, line)) { if (line.length() > 0 && line[0] == '#') { std::string mapSizes = line.substr(1); const auto delimPos = mapSizes.find(','); mapWidth = std::stof(mapSizes.substr(0, delimPos)) * Constants::TILE_WIDTH; mapHeight = std::stof(mapSizes.substr(delimPos + 1)) * Constants::TILE_HEIGHT; continue; } if (line.length() > 0 && line[0] == '!') { layer = std::stoi(line.substr(1)); y = 0; continue; } for (auto i = 0ull; i < line.length(); ++i) { KVector2D position{ static_cast(i * Constants::TILE_WIDTH + Constants::TILE_WIDTH / 2), // NOLINT(bugprone-integer-division) static_cast(y * Constants::TILE_HEIGHT + Constants::TILE_HEIGHT / 2) // NOLINT(bugprone-integer-division) }; if (layer == 3) { switch (line[i]) { case 'P': startPosition = position; break; default: auto clip = -1; SDL_RendererFlip flip = SDL_FLIP_NONE; if (std::isdigit(line[i])) { clip = line[i] - '0'; } else if (line[i] >= 'a' && line[i] <= 'z') { clip = line[i] - 'a'; flip = SDL_FLIP_HORIZONTAL; } if (clip >= 0) Objects.emplace(std::make_pair(static_cast(i), y), std::make_shared(position, Textures["tiles"], &TileClips[clip], flip)); break; } } else if (layer < 3) { auto clip = -1; SDL_RendererFlip flip = SDL_FLIP_NONE; if (std::isdigit(line[i])) { clip = line[i] - '0'; } else if (line[i] >= 'a' && line[i] <= 'z') { clip = line[i] - 'a'; flip = SDL_FLIP_HORIZONTAL; } if (clip >= 0) BackgroundLayers[layer].emplace(std::make_pair(static_cast(i), y), std::make_shared(position, Textures["tiles"], &TileClips[clip], flip)); } else { const auto index = layer - 4; auto clip = -1; SDL_RendererFlip flip = SDL_FLIP_NONE; if (std::isdigit(line[i])) { clip = line[i] - '0'; } else if (line[i] >= 'a' && line[i] <= 'z') { clip = line[i] - 'a'; flip = SDL_FLIP_HORIZONTAL; } if (clip >= 0) ForegroundLayers[index].emplace(std::make_pair(static_cast(i), y), std::make_shared(position, Textures["tiles"], &TileClips[clip], flip)); } } ++y; } levelFile.close(); Map = { 0,0,mapWidth,mapHeight }; Pawns.emplace_back(std::make_shared(startPosition, Textures["P1.bmp"], PlayerControllers[static_cast(KPlayer::Player1)], &Settings)); PlayerControllers[static_cast(KPlayer::Player1)]->Possess(Pawns.back().get()); } return success; } bool KGame::LoadMedia() { //Loading success flag bool success = true; for (auto player : Utils::KPlayerIterator()) { std::string filename = "P" + std::to_string(static_cast(player) + 1) + ".bmp"; Textures.emplace(filename, KTexture()); if (!Textures[filename].LoadFromFile("assets/textures/" + filename, Renderer)) { Textures.erase(filename); printf("Failed to load player texture image!\n"); success = false; break; } PlayerControllers.emplace_back(std::make_shared(player)); PlayerControllers.back()->SetupInputBindings(Input); } Textures.emplace("tiles", KTexture()); if (!Textures["tiles"].LoadFromFile("assets/textures/maptiles.png", Renderer)) { printf("Failed to load map tiles texture image!\n"); Textures.erase("tiles"); success = false; } else { for (int i = 0; i < Constants::TILE_COUNT; ++i) { TileClips[i].x = (i % 4) * Constants::TILE_WIDTH; TileClips[i].y = (i / 4) * Constants::TILE_HEIGHT; TileClips[i].w = Constants::TILE_WIDTH; TileClips[i].h = Constants::TILE_HEIGHT; } } Fonts.emplace("PressStart2P-Regular", KFont()); if (!Fonts["PressStart2P-Regular"].LoadFromFile("assets/fonts/PressStart2P-Regular.ttf", 8)) { printf("Failed to load PressStart2P-Regular font!\n"); Fonts.erase("PressStart2P-Regular"); success = false; } if (std::ifstream configFile("config.json"); configFile.fail()) { printf("Failed to load config.json!\n"); success = false; } else { nlohmann::json configJson; configFile >> configJson; configFile.close(); for (auto& player : configJson["Input"].items()) { const auto playerEnum = Utils::GetPlayerFromString(player.key()); for (auto& axisMapping : player.value()["AxisMappings"]) { Input.AddAxisMapping(axisMapping["AxisName"].get(), axisMapping["Scale"].get(), axisMapping["Key"].get(), playerEnum); } for (auto& actionMapping : player.value()["ActionMappings"]) { Input.AddActionMapping(actionMapping["ActionName"].get(), actionMapping["Key"].get(), playerEnum); } } } if (!LoadLevel()) success = false; return success; } int KGame::Run(int argc, char* args[]) { //Load media if (!LoadMedia()) { printf("Failed to load media!\n"); } else { //Main loop flag bool quit = false; //Event handler SDL_Event e; KCamera camera(Map); KCamera debugCamera(Map); debugCamera.SetDebug(Map); char devMode = 0; camera.Update(Pawns, Map); SettingsTextTextureDirty = true; Time = PreviousTime = SDL_GetTicks(); printf("\n"); //While application is running while (!quit) { PreviousTime = Time; Time = SDL_GetTicks(); float deltaTime = static_cast(Time - PreviousTime) * 0.001f; Input.HandleInputPreEvents(); //Handle events on queue while (SDL_PollEvent(&e)) { //User requests quit if (e.type == SDL_QUIT) { quit = true; continue; } Input.HandleEvent(e); } Input.HandleInputPostEvents(Playing); if (Input.IsKeyboardButtonPressed(SDL_SCANCODE_F1)) { if (++devMode > 2) devMode = 0; SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Changed devMode to %d\n", devMode); } if (devMode >= 1) { if (Input.IsKeyboardButtonPressed(SDL_SCANCODE_F2)) { Settings.CollisionEnabled = !Settings.CollisionEnabled; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_F3)) { Settings.MaxJumpHeight -= 0.1f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_F4)) { Settings.MaxJumpHeight += 0.1f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_F5)) { Settings.HorizontalDistanceToMaxJumpHeight -= 0.1f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_F6)) { Settings.HorizontalDistanceToMaxJumpHeight += 0.1f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_5)) { Settings.BgPrl0 -= 0.01f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_6)) { Settings.BgPrl0 += 0.01f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_7)) { Settings.BgPrl1 -= 0.01f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_8)) { Settings.BgPrl1 += 0.01f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_9)) { Settings.BgPrl2 -= 0.01f; SettingsTextTextureDirty = true; } if (Input.IsKeyboardButtonHeld(SDL_SCANCODE_0)) { Settings.BgPrl2 += 0.01f; SettingsTextTextureDirty = true; } } if (Playing) { for (const auto& pc : PlayerControllers) { pc->Update(deltaTime); } for (const auto& pawn : Pawns) { if (!Playing) break; pawn->MovementStep(deltaTime); } if (Settings.CollisionEnabled) { for (const auto& pawn : Pawns) { if (!Playing) break; pawn->CollisionDetectionStep(Objects); } } for (const auto& pawn : Pawns) { if (!Playing) break; pawn->CollisionDetectionWithMapStep(Map); } for (const auto& pawn : Pawns) { if (!Playing) break; pawn->CollisionDetectionWithMapStep(camera.GetPlayArea()); } camera.Update(Pawns, Map); } Textures.insert_or_assign("Text_Settings", Fonts["PressStart2P-Regular"].GetTextTexture( Utils::StringFormat( "Developer Mode (F1): %d\n" "Collision Enabled (F2): %s\n" "Max Jump Height (F3/F4): %f\n" "Horizontal Distance to Max Jump Height (F5/F6): %f\n" "BG0 Scroll (5/6): %f\n" "BG1 Scroll (7/8): %f\n" "BG2 Scroll (9/0): %f\n" "Initial Jump Velocity: %f\n" "Gravity: %f\n" "Player Position X: %f\n" "Player Position Y: %f", static_cast(devMode), Settings.CollisionEnabled ? "True" : "False", static_cast(Settings.MaxJumpHeight), static_cast(Settings.HorizontalDistanceToMaxJumpHeight), static_cast(Settings.BgPrl0), static_cast(Settings.BgPrl1), static_cast(Settings.BgPrl2), static_cast(Settings.JumpInitialVelocity), static_cast(Settings.Gravity), Pawns.back()->GetPosition().X, Pawns.back()->GetPosition().Y), { 0, 0, 0, 0xFF }, Renderer)); if (!Playing) { if (Time > RestartTick) { LoadLevel(); debugCamera.SetDebug(Map); camera.Update(Pawns, Map); Playing = true; continue; } } //Clear screen //SDL_SetRenderDrawColor(Renderer, 66, 135, 245, 0xFF); SDL_SetRenderDrawColor(Renderer, 21, 36, 143, 0xFF); SDL_RenderClear(Renderer); const auto& cameraInUse = devMode >= 2 ? debugCamera : camera; if (Playing) { for (int i = 0; i < Constants::BG_LAYER_COUNT; ++i) { for (const auto& [tile, drawable] : BackgroundLayers[i]) drawable->Render(Renderer, cameraInUse, Settings.GetBgPrl(i)); } for (const auto& [tile, obj] : Objects) obj->Render(Renderer, cameraInUse); for (const auto& pawn : Pawns) { pawn->Render(Renderer, cameraInUse); } for (auto& layer : ForegroundLayers) { for (const auto& [tile, drawable] : layer) drawable->Render(Renderer, cameraInUse); } } if (devMode >= 1) { Textures["Text_Settings"].Render(Renderer, 10.f, 25.f); } if (devMode >= 2) { SDL_RenderDrawPointF(Renderer, camera.GetFocusPoint().X * debugCamera.GetScale(), camera.GetFocusPoint().Y * debugCamera.GetScale()); SDL_SetRenderDrawColor(Renderer, 0xFF, 0x00, 0x00, 0xFF); SDL_FRect debugViewport = { camera.GetViewport().x * debugCamera.GetScale(), camera.GetViewport().y * debugCamera.GetScale(), camera.GetViewport().w * debugCamera.GetScale(), camera.GetViewport().h * debugCamera.GetScale() }; SDL_RenderDrawRectF(Renderer, &debugViewport); SDL_FRect debug2Viewport = { camera.GetPlayArea().x * debugCamera.GetScale(), camera.GetPlayArea().y * debugCamera.GetScale(), camera.GetPlayArea().w * debugCamera.GetScale(), camera.GetPlayArea().h * debugCamera.GetScale() }; SDL_SetRenderDrawColor(Renderer, 0x00, 0xFF, 0x00, 0xFF); SDL_RenderDrawRectF(Renderer, &debug2Viewport); } //Update screen SDL_RenderPresent(Renderer); } } return 0; } }