2dkg/2dkg_zad5/2dgk_zad5/KGame.cpp
2022-01-20 14:07:29 +01:00

510 lines
14 KiB
C++

#include "KGame.h"
#include "SDL.h"
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <nlohmann/json.hpp>
#include <cstdio>
#include <string>
#include <fstream>
#include <vector>
#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<float>(i * Constants::TILE_WIDTH + Constants::TILE_WIDTH / 2), // NOLINT(bugprone-integer-division)
static_cast<float>(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<int>(i), y), std::make_shared<KSolidTile>(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<int>(i), y), std::make_shared<KDrawable>(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<int>(i), y), std::make_shared<KDrawable>(position, Textures["tiles"], &TileClips[clip], flip));
}
}
++y;
}
levelFile.close();
Map = { 0,0,mapWidth,mapHeight };
Pawns.emplace_back(std::make_shared<KCirclePawn>(startPosition, Textures["P1.bmp"], PlayerControllers[static_cast<int>(KPlayer::Player1)], &Settings));
PlayerControllers[static_cast<int>(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<int>(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<KPlayerController>(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<std::string>(), axisMapping["Scale"].get<float>(), axisMapping["Key"].get<std::string>(), playerEnum);
}
for (auto& actionMapping : player.value()["ActionMappings"]) {
Input.AddActionMapping(actionMapping["ActionName"].get<std::string>(), actionMapping["Key"].get<std::string>(), 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<float>(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<int>(devMode),
Settings.CollisionEnabled ? "True" : "False",
static_cast<float>(Settings.MaxJumpHeight),
static_cast<float>(Settings.HorizontalDistanceToMaxJumpHeight),
static_cast<float>(Settings.BgPrl0),
static_cast<float>(Settings.BgPrl1),
static_cast<float>(Settings.BgPrl2),
static_cast<float>(Settings.JumpInitialVelocity),
static_cast<float>(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;
}
}