510 lines
14 KiB
C++
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;
|
|
}
|
|
}
|