#include "Engine.h" // ============================================================================ // Standard library // ============================================================================ #include #include #include #include #include #include #include #include // ============================================================================ // OpenGL // ============================================================================ #include #include // ============================================================================ // Math // ============================================================================ #include #include #include #include // ============================================================================ // Third party // ============================================================================ #include #include FT_FREETYPE_H // ============================================================================ // Engine core // ============================================================================ #include "common.h" #include "Window.h" #include "Camera.h" #include "Keyboard.h" #include "Mouse.h" #include "Physics.h" #include "RenderSystem.h" #include "SoundSystem.h" #include "ResourceManager.h" #include "tFunctions.h" #include "Debug.h" // Rendering #include "shaderClass.h" #include "VBO.h" #include "VAO.h" #include "EBO.h" #include "FBO.h" #include "texture.h" #include "Material.h" #include "Mesh.h" #include "Model.h" #include "Cubemap.h" #include "QuadVertices.h" #include "Particle.h" #include "Lighting.h" #include "DirLight.h" // Scene #include "Folder.h" #include "Sound.h" #include "Gui.h" #include "font.h" // Character / animation #include "Rig.h" #include "Animator.h" #include "Character.h" #include "CharacterManager.h" #include "DefaultBodyComponents.h" #include "BodyComponentActions.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" // ============================================================================ // Constructor - window, callbacks, systems, shaders // ============================================================================ Engine::Engine() { engine = this; glfwSetFramebufferSizeCallback(window->handle, framebuffer_size_callback); glfwSetCursorPosCallback(window->handle, mouse_callback); glfwSetScrollCallback(window->handle, scroll_callback); glfwSetKeyCallback(window->handle, key_callback); glfwSetMouseButtonCallback(window->handle, mouse_button_callback); glfwSwapInterval(1); int fbw, fbh; glfwGetFramebufferSize(window->handle, &fbw, &fbh); framebuffer_size_callback(window->handle, fbw, fbh); rendersystem = new RenderSystem(); rendersystem->Initialize(); physicsengine = new Physics(); physicsengine->SetGravity(physicsengine->GetGravity() * 1.25f); mainf = new Folder(); camera = new Camera(); camera->t.TranslateTo({ 0.0f, 5.0f, 0.0f }); rendersystem->RigShader = new Shader("shaders/rig.vert", "shaders/default.frag"); rendersystem->MeshShader = new Shader("shaders/default.vert", "shaders/default.frag"); rendersystem->MeshShadowShader = new Shader("shaders/meshshadow.vert", "shaders/shadow.frag"); rendersystem->RigShadowShader = new Shader("shaders/rigshadow.vert", "shaders/shadow.frag"); rendersystem->SkyboxShader = new Shader("shaders/skybox.vert", "shaders/skybox.frag"); rendersystem->ScreenShader = new Shader("shaders/ScreenShader.vert", "shaders/ScreenShader.frag"); rendersystem->ImageBoxShader = new Shader("shaders/ImageBox.vert", "shaders/ImageBox.frag"); rendersystem->BoxShader = new Shader("shaders/Box.vert", "shaders/Box.frag"); rendersystem->Text2DShader = new Shader("shaders/2DText.vert", "shaders/2DText.frag"); rendersystem->ParticleShader = new Shader("shaders/Particle.vert", "shaders/Particle.frag"); rendersystem->HitboxShader = new Shader("shaders/Hitbox.vert", "shaders/Hitbox.frag"); } // ============================================================================ // Initiate - scene setup + game loop // ============================================================================ void Engine::Initiate() { Font* arial = new Font("assets/ft/arial.ttf"); Texture* blanktex = new Texture("assets/blanktexture.png"); Texture* snowflake = new Texture("assets/snowflake.png"); charactermanager = new CharacterManager(); charactermanager->LoadRig("humanoid", "assets/rig.glb"); charactermanager->LoadAnimation("walk", "assets/walkanim.glb", "humanoid"); charactermanager->LoadAnimation("punch", "assets/punchanim.glb", "humanoid"); // ------------------------------------------------------------------------ // Player character // ------------------------------------------------------------------------ Character& testcharacter = *charactermanager->CreateCharacter("character", "humanoid", "walk"); charactermanager->SetControlledCharacter("character"); testcharacter.castshadow = true; testcharacter.colorOverride = glm::vec4(0.62f, 0.64f, 0.68f, 1.0f); testcharacter.roughness = 0.65f; testcharacter.body->t.ScaleBy({ 0.7f, 2.5f, 0.7f }); testcharacter.body->t.TranslateTo({ 0.0f, 1.5f, 0.0f }); testcharacter.body->animator->loops = true; testcharacter.SetPunchAnimation(charactermanager->GetAnimation("punch")); testcharacter.BindToRenderSystem(); p* characterBody = new p(testcharacter.body); characterBody->mass = 80.0f; characterBody->lockRotation = true; characterBody->friction = 0.0f; physicsengine->AddObject(characterBody); // ------------------------------------------------------------------------ // NPC // ------------------------------------------------------------------------ Character& npc = *charactermanager->CreateCharacter("npc", "humanoid", "walk"); npc.castshadow = true; npc.colorOverride = glm::vec4(0.10f, 0.20f, 0.48f, 1.0f); npc.metallic = 0.1f; npc.roughness = 0.5f; npc.body->t.ScaleBy({ 0.7f, 2.5f, 0.7f }); npc.body->t.TranslateTo({ 3.0f, 1.25f, 3.0f }); npc.SetPunchAnimation(charactermanager->GetAnimation("punch")); npc.SetPunchLooping(true); npc.Punch(); npc.BindToRenderSystem(); p* npcBody = new p(npc.body); npcBody->mass = 80.0f; npcBody->lockRotation = true; npcBody->friction = 0.0f; physicsengine->AddObject(npcBody); testcharacter.SetWalking(false); npc.SetWalking(false); // ------------------------------------------------------------------------ // Rain particles // ------------------------------------------------------------------------ ParticleEmitter* rain = new ParticleEmitter(); rain->name = "rain"; rain->tex = snowflake; rain->opaque = false; rain->speed = 35.0f; rain->lifespan = 0.6f; rain->angularvelocity = { 0.0f, 0.0f, glm::radians(10.0f) }; rain->size = { 0.04f, 0.4f, 0.0f }; rain->emitangle = { glm::radians(60.0f), glm::radians(360.0f), glm::radians(60.0f) }; rain->facecamera = false; rain->color = { 1.0f, 1.0f, 1.0f, 1.0f }; rain->emitdirection = ParticleEmitter::EmitDirection::Perpendicular; rain->t.TranslateTo({ 0.0f, 10.0f, 0.0f }); rain->t.RotateToQuaternion(glm::quat(glm::vec3(glm::radians(85.0f), 0.0f, 0.0f))); rain->BindToRenderSystem(); mainf->AddChild(rain); // ------------------------------------------------------------------------ // 3D audio // ------------------------------------------------------------------------ Sound* music = new Sound(); music->name = "music"; music->LoadSound("assets/fs.wav", true); music->Update3DPosition(0.0f, 0.0f, 0.0f); music->PlayTrack(true); mainf->AddChild(music); // ------------------------------------------------------------------------ // GUI // ------------------------------------------------------------------------ TextBox* fpsCounter = new TextBox(); fpsCounter->font = arial; fpsCounter->text = ""; fpsCounter->fontsize = 0.5f; fpsCounter->Color = { 0.0f, 0.0f, 0.0f }; fpsCounter->t2d.position = { 1.0f, 0.0f, 0.0f, 0.0f }; fpsCounter->t2d.center = { 1.0f, 0.0f }; fpsCounter->textCenter = { 0.0f, 0.5f }; fpsCounter->t2d.size = { 0.1f, 0.1f, 0.0f, 0.0f }; fpsCounter->BindToRenderSystem(); TextBox* healthText = new TextBox(); healthText->font = arial; healthText->text = ""; healthText->fontsize = 0.5f; healthText->Color = { 0.95f, 0.97f, 1.0f }; healthText->t2d.position = { 0.02f, 0.02f, 0.0f, 0.0f }; healthText->t2d.size = { 0.45f, 0.0f, 0.0f, 40.0f }; healthText->textCenter = { 0.0f, 0.0f }; healthText->BindToRenderSystem(); const float healthBarFillWidth = 314.0f; Box* healthBarBack = new Box(); healthBarBack->Color = { 0.03f, 0.04f, 0.06f }; healthBarBack->Opacity = 0.85f; healthBarBack->t2d.position = { 0.02f, 1.0f, 0.0f, -30.0f }; healthBarBack->t2d.center = { 0.0f, 1.0f }; healthBarBack->t2d.size = { 0.0f, 0.0f, 320.0f, 26.0f }; healthBarBack->BindToRenderSystem(); Box* healthBarFill = new Box(); healthBarFill->Color = { 0.2f, 0.85f, 0.35f }; healthBarFill->t2d.position = { 0.02f, 1.0f, 3.0f, -33.0f }; healthBarFill->t2d.center = { 0.0f, 1.0f }; healthBarFill->t2d.size = { 0.0f, 0.0f, healthBarFillWidth, 20.0f }; healthBarFill->BindToRenderSystem(); struct MenuButton { Box* box; TextBox* label; glm::vec3 color; }; enum class MenuScreen { Main, Paused, Settings }; MenuScreen menuScreen = MenuScreen::Main; MenuScreen settingsReturn = MenuScreen::Main; bool menuActive = true; bool prevMouseDown = false; std::vector menuItems; Box* menuVeil = new Box(); menuVeil->Color = { 0.02f, 0.03f, 0.05f }; menuVeil->Opacity = 0.55f; menuVeil->t2d.position = { 0.0f, 0.0f, 0.0f, 0.0f }; menuVeil->t2d.size = { 1.0f, 1.0f, 0.0f, 0.0f }; menuVeil->BindToRenderSystem(); menuItems.push_back(menuVeil); TextBox* menuTitle = new TextBox(); menuTitle->font = arial; menuTitle->text = "EIIFWHRN"; menuTitle->fontsize = 3.0f; menuTitle->Color = { 0.95f, 0.97f, 1.0f }; menuTitle->t2d.position = { 0.5f, 0.17f, 0.0f, 0.0f }; menuTitle->t2d.center = { 0.5f, 0.5f }; menuTitle->t2d.size = { 0.8f, 0.0f, 0.0f, 170.0f }; menuTitle->textCenter = { 0.5f, 0.5f }; menuTitle->BindToRenderSystem(); menuItems.push_back(menuTitle); TextBox* menuSubtitle = new TextBox(); menuSubtitle->font = arial; menuSubtitle->text = "MAIN MENU"; menuSubtitle->fontsize = 0.65f; menuSubtitle->Color = { 0.55f, 0.65f, 0.78f }; menuSubtitle->t2d.position = { 0.5f, 0.285f, 0.0f, 0.0f }; menuSubtitle->t2d.center = { 0.5f, 0.5f }; menuSubtitle->t2d.size = { 0.4f, 0.0f, 0.0f, 50.0f }; menuSubtitle->textCenter = { 0.5f, 0.5f }; menuSubtitle->BindToRenderSystem(); menuItems.push_back(menuSubtitle); auto MakeMenuButton = [&](const char* text, float x, float y, glm::vec3 color, float width = 340.0f, float height = 62.0f, float fontsize = 0.85f) { Box* box = new Box(); box->Color = color; box->Opacity = 0.92f; box->t2d.position = { x, y, 0.0f, 0.0f }; box->t2d.center = { 0.5f, 0.5f }; box->t2d.size = { 0.0f, 0.0f, width, height }; box->BindToRenderSystem(); menuItems.push_back(box); TextBox* label = new TextBox(); label->font = arial; label->text = text; label->fontsize = fontsize; label->Color = { 0.95f, 0.97f, 1.0f }; label->t2d.position = { x, y, 0.0f, 0.0f }; label->t2d.center = { 0.5f, 0.5f }; label->t2d.size = { 0.0f, 0.0f, width, height }; label->textCenter = { 0.5f, 0.5f }; label->BindToRenderSystem(); menuItems.push_back(label); return MenuButton{ box, label, color }; }; MenuButton playButton = MakeMenuButton("PLAY", 0.5f, 0.46f, glm::vec3(0.12f, 0.30f, 0.22f)); MenuButton settingsButton = MakeMenuButton("SETTINGS", 0.5f, 0.58f, glm::vec3(0.15f, 0.23f, 0.34f)); MenuButton quitButton = MakeMenuButton("QUIT", 0.5f, 0.70f, glm::vec3(0.17f, 0.18f, 0.22f)); MenuButton sensitivityDown = MakeMenuButton("- SENS", 0.40f, 0.47f, glm::vec3(0.15f, 0.23f, 0.34f), 170.0f, 50.0f, 0.65f); MenuButton sensitivityUp = MakeMenuButton("+ SENS", 0.60f, 0.47f, glm::vec3(0.15f, 0.23f, 0.34f), 170.0f, 50.0f, 0.65f); MenuButton fovDown = MakeMenuButton("- FOV", 0.40f, 0.58f, glm::vec3(0.15f, 0.23f, 0.34f), 170.0f, 50.0f, 0.65f); MenuButton fovUp = MakeMenuButton("+ FOV", 0.60f, 0.58f, glm::vec3(0.15f, 0.23f, 0.34f), 170.0f, 50.0f, 0.65f); MenuButton hitboxToggle = MakeMenuButton("HITBOXES: OFF", 0.5f, 0.68f, glm::vec3(0.20f, 0.22f, 0.28f), 340.0f, 50.0f, 0.65f); MenuButton resetButton = MakeMenuButton("RESET DEFAULTS", 0.5f, 0.77f, glm::vec3(0.20f, 0.22f, 0.28f), 340.0f, 52.0f, 0.65f); MenuButton backButton = MakeMenuButton("BACK", 0.5f, 0.86f, glm::vec3(0.12f, 0.30f, 0.22f), 340.0f, 52.0f, 0.85f); TextBox* settingsInfo = new TextBox(); settingsInfo->font = arial; settingsInfo->fontsize = 0.65f; settingsInfo->Color = { 0.85f, 0.90f, 0.97f }; settingsInfo->t2d.position = { 0.5f, 0.37f, 0.0f, 0.0f }; settingsInfo->t2d.center = { 0.5f, 0.5f }; settingsInfo->t2d.size = { 0.8f, 0.0f, 0.0f, 90.0f }; settingsInfo->textCenter = { 0.5f, 0.5f }; settingsInfo->BindToRenderSystem(); menuItems.push_back(settingsInfo); TextBox* menuHint = new TextBox(); menuHint->font = arial; menuHint->text = "WASD TO MOVE | LEFT CLICK TO PUNCH | ESC FOR MENU"; menuHint->fontsize = 0.5f; menuHint->Color = { 0.55f, 0.61f, 0.70f }; menuHint->t2d.position = { 0.5f, 0.92f, 0.0f, 0.0f }; menuHint->t2d.center = { 0.5f, 0.5f }; menuHint->t2d.size = { 0.9f, 0.0f, 0.0f, 40.0f }; menuHint->textCenter = { 0.5f, 0.5f }; menuHint->BindToRenderSystem(); menuItems.push_back(menuHint); auto SetButtonVisible = [](MenuButton& button, bool visible) { button.box->Opacity = visible ? 0.92f : 0.0f; button.label->Opacity = visible ? 1.0f : 0.0f; }; auto ApplyMenuLayout = [&]() { const bool main = menuScreen == MenuScreen::Main; const bool paused = menuScreen == MenuScreen::Paused; const bool settings = menuScreen == MenuScreen::Settings; menuVeil->Opacity = 0.55f; menuTitle->Opacity = 1.0f; menuSubtitle->Opacity = 1.0f; menuHint->Opacity = 1.0f; menuSubtitle->text = main ? "MAIN MENU" : (paused ? "PAUSED" : "SETTINGS"); playButton.label->text = paused ? "RESUME" : "PLAY"; quitButton.label->text = paused ? "QUIT TO MENU" : "QUIT"; menuHint->text = settings ? "ESC TO GO BACK | ADJUST MOUSE, FOV, AND HITBOXES" : "WASD TO MOVE | LEFT CLICK TO PUNCH | ESC FOR MENU"; SetButtonVisible(playButton, !settings); SetButtonVisible(settingsButton, !settings); SetButtonVisible(quitButton, !settings); SetButtonVisible(sensitivityDown, settings); SetButtonVisible(sensitivityUp, settings); SetButtonVisible(fovDown, settings); SetButtonVisible(fovUp, settings); SetButtonVisible(hitboxToggle, settings); SetButtonVisible(resetButton, settings); SetButtonVisible(backButton, settings); settingsInfo->Opacity = settings ? 1.0f : 0.0f; hitboxToggle.label->text = showHitboxes ? "HITBOXES: ON" : "HITBOXES: OFF"; hitboxToggle.color = showHitboxes ? glm::vec3(0.12f, 0.30f, 0.22f) : glm::vec3(0.20f, 0.22f, 0.28f); settingsInfo->text = "MOUSE SENSITIVITY: " + std::to_string((int)std::lround(StartupCmPer360)) + " CM / 360\nFIELD OF VIEW: " + std::to_string((int)std::lround(camera->FOV)); }; auto OpenMenu = [&](MenuScreen screen) { menuActive = true; menuScreen = screen; charactermanager->ClearInput(); testcharacter.SetWalking(false); npc.SetWalking(false); fpsCounter->Opacity = 0.0f; healthText->Opacity = 0.0f; healthBarBack->Opacity = 0.0f; healthBarFill->Opacity = 0.0f; camera->lockedcursor = false; glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); prevMouseDown = mouse->IsLeftDown(); }; auto ResumeGame = [&]() { menuActive = false; charactermanager->ClearInput(); for (Box* item : menuItems) item->Opacity = 0.0f; fpsCounter->Opacity = 1.0f; healthText->Opacity = 1.0f; healthBarBack->Opacity = 0.85f; healthBarFill->Opacity = 1.0f; camera->lockedcursor = true; glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); int ww, wh; glfwGetWindowSize(window->handle, &ww, &wh); glfwSetCursorPos(window->handle, ww * 0.5, wh * 0.5); }; fpsCounter->Opacity = 0.0f; healthText->Opacity = 0.0f; healthBarBack->Opacity = 0.0f; healthBarFill->Opacity = 0.0f; camera->lockedcursor = false; glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); // ------------------------------------------------------------------------ // Baseplate // ------------------------------------------------------------------------ Mesh* floor = CreateCubeMesh(); floor->name = "baseplate"; floor->meshTexture = blanktex; floor->roughness = 0.9f; floor->t.ScaleTo({ 32.0f, 2.0f, 32.0f }); floor->t.TranslateTo({ 0.0f, -1.0f, 0.0f }); floor->BindToRenderSystem(); mainf->AddChild(floor); p* floorBody = new p(floor); floorBody->velocity = false; physicsengine->AddObject(floorBody); const BoundingBox floorBounds = floor->t.GetRotationlessAABB(); const float wallHeight = 4.0f; const float wallThickness = 1.0f; const float wallOffset = 16.0f + wallThickness * 0.5f; const float wallSpan = 32.0f + wallThickness; auto AddWall = [&](const char* name, const glm::vec3& position, const glm::vec3& scale) { Mesh* wall = CreateCubeMesh(); wall->name = name; wall->meshTexture = blanktex; wall->roughness = 0.9f; wall->t.ScaleTo(scale); wall->t.TranslateTo(position); wall->BindToRenderSystem(); mainf->AddChild(wall); p* wallBody = new p(wall); wallBody->velocity = false; physicsengine->AddObject(wallBody); }; AddWall("wall_north", { 0.0f, wallHeight * 0.5f, -wallOffset }, { wallSpan, wallHeight, wallThickness }); AddWall("wall_south", { 0.0f, wallHeight * 0.5f, wallOffset }, { wallSpan, wallHeight, wallThickness }); AddWall("wall_west", { -wallOffset, wallHeight * 0.5f, 0.0f }, { wallThickness, wallHeight, 32.0f }); AddWall("wall_east", { wallOffset, wallHeight * 0.5f, 0.0f }, { wallThickness, wallHeight, 32.0f }); // ------------------------------------------------------------------------ // Ground check + respawn // ------------------------------------------------------------------------ auto IsGrounded = [&]() -> bool { Ray downRay; downRay.origin = testcharacter.body->t.GetTranslation(); downRay.direction = glm::vec3(0.0f, -1.0f, 0.0f); const float halfHeight = testcharacter.body->t.GetScale().y * 0.5f; for (p* obj : physicsengine->GetObjects()) { if (obj->pointer == testcharacter.body) continue; if (!obj->collision) continue; auto hit = IsRayInT(downRay, obj->pointer->t); if (hit.has_value()) { if (glm::distance(downRay.origin, hit.value()) < halfHeight + 0.15f) return true; } } return false; }; std::mt19937 respawnRng(std::random_device{}()); auto RandomSpawn = [&]() -> glm::vec3 { std::uniform_real_distribution spread(-14.0f, 14.0f); return { spread(respawnRng), 15.0f, spread(respawnRng) }; }; auto UpdateHitboxDebug = [&]() { std::vector lines; if (showHitboxes) { for (Character* character : charactermanager->GetCharacters()) { if (!character->body) continue; lines.push_back({ character->body->t.GetMatrix(), glm::vec3(0.2f, 0.9f, 0.35f) }); const Hitbox& punch = character->GetPunchHitbox(); if (punch.active) lines.push_back({ punch.transform.GetMatrix(), glm::vec3(1.0f, 0.25f, 0.1f) }); } } rendersystem->SetHitboxDebugLines(lines); }; // ======================================================================== // Game loop // ======================================================================== while (!glfwWindowShouldClose(window->handle)) { frame++; static double prevFrameTime = glfwGetTime(); const double frameNow = glfwGetTime(); const float deltatime = glm::clamp(float(frameNow - prevFrameTime), 0.001f, 1.0f / 60.0f) * (144.0f / float(fps)); prevFrameTime = frameNow; const float refDt = 1.0f / float(fps); static bool prevEscapeDown = false; const bool escapeDown = keyboard->IsKeyDown(GLFW_KEY_ESCAPE); const bool escapePressed = escapeDown && !prevEscapeDown; prevEscapeDown = escapeDown; if (escapePressed) { if (!menuActive) OpenMenu(MenuScreen::Paused); else if (menuScreen == MenuScreen::Settings) menuScreen = settingsReturn; else if (menuScreen == MenuScreen::Paused) ResumeGame(); } // -------------------------------------------------------------------- // Mouse look // -------------------------------------------------------------------- while (Mouse::Event buffer = mouse->Read()) { if (menuActive) continue; if (buffer.GetType() == Mouse::Event::Type::Move && camera->lockedcursor) { float xoffset = mouse->GetX() - mouse->GetLastX(); float yoffset = mouse->GetLastY() - mouse->GetY(); camera->ProcessMouseMovement(xoffset, yoffset); } else if (buffer.GetType() == Mouse::Event::Type::LPress) { testcharacter.Punch(camera->t.GetFrontVector()); } else if (buffer.GetType() == Mouse::Event::Type::RPress) { camera->lockedcursor = !camera->lockedcursor; if (camera->lockedcursor) { glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); int ww, wh; glfwGetWindowSize(window->handle, &ww, &wh); glfwSetCursorPos(window->handle, ww * 0.5, wh * 0.5); } else { glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } } } if (menuActive) { ApplyMenuLayout(); const bool liveBackdrop = menuScreen == MenuScreen::Main || (menuScreen == MenuScreen::Settings && settingsReturn == MenuScreen::Main); if (liveBackdrop) { static float menuAngle = 0.6f; menuAngle += deltatime * 0.12f; const glm::vec3 menuTarget(0.0f, 1.4f, 0.0f); const glm::vec3 menuCamera = menuTarget + glm::vec3(std::sin(menuAngle) * 7.5f, 2.6f, std::cos(menuAngle) * 7.5f); camera->t.TranslateTo(menuCamera); camera->t.RotateToQuaternion(LookAt(menuTarget - menuCamera)); camera->Step(deltatime); npc.SetTurnDirection(testcharacter.body->t.GetTranslation() - npc.body->t.GetTranslation()); charactermanager->Step(deltatime); rain->t.TranslateTo(camera->t.GetTranslation() + glm::vec3(0.0f, 20.0f, 0.0f)); rain->Step(deltatime); } soundsystem->SetListenerPosition( camera->t.GetTranslation(), camera->t.GetFrontVector(), camera->t.GetUpVector() ); soundsystem->Update(); MenuButton* buttons[] = { &playButton, &settingsButton, &quitButton, &sensitivityDown, &sensitivityUp, &fovDown, &fovUp, &hitboxToggle, &resetButton, &backButton }; for (MenuButton* button : buttons) button->box->t2d.Recalculate(); const glm::vec2 mousePos((float)mouse->GetX(), (float)mouse->GetY()); auto Hovered = [&](const MenuButton& button) { return mousePos.x > button.box->t2d.pixelposition.x && mousePos.y > button.box->t2d.pixelposition.y && mousePos.x < button.box->t2d.pixelposition.x + button.box->t2d.pixelsize.x && mousePos.y < button.box->t2d.pixelposition.y + button.box->t2d.pixelsize.y; }; const bool playHovered = Hovered(playButton); const bool settingsHovered = Hovered(settingsButton); const bool quitHovered = Hovered(quitButton); const bool sensDownHovered = Hovered(sensitivityDown); const bool sensUpHovered = Hovered(sensitivityUp); const bool fovDownHovered = Hovered(fovDown); const bool fovUpHovered = Hovered(fovUp); const bool hitboxHovered = Hovered(hitboxToggle); const bool resetHovered = Hovered(resetButton); const bool backHovered = Hovered(backButton); playButton.box->Color = playHovered ? glm::vec3(0.18f, 0.44f, 0.32f) : playButton.color; settingsButton.box->Color = settingsHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : settingsButton.color; quitButton.box->Color = quitHovered ? glm::vec3(0.30f, 0.20f, 0.22f) : quitButton.color; sensitivityDown.box->Color = sensDownHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : sensitivityDown.color; sensitivityUp.box->Color = sensUpHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : sensitivityUp.color; fovDown.box->Color = fovDownHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : fovDown.color; fovUp.box->Color = fovUpHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : fovUp.color; hitboxToggle.box->Color = hitboxHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : hitboxToggle.color; resetButton.box->Color = resetHovered ? glm::vec3(0.30f, 0.32f, 0.40f) : resetButton.color; backButton.box->Color = backHovered ? glm::vec3(0.18f, 0.44f, 0.32f) : backButton.color; const bool mouseDown = mouse->IsLeftDown(); if (mouseDown && !prevMouseDown) { if (menuScreen == MenuScreen::Settings) { if (sensDownHovered) { StartupCmPer360 = glm::clamp(StartupCmPer360 + 2.0f, 4.0f, 80.0f); camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi); } else if (sensUpHovered) { StartupCmPer360 = glm::clamp(StartupCmPer360 - 2.0f, 4.0f, 80.0f); camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi); } else if (fovDownHovered) { camera->FOV = glm::clamp(camera->FOV - 5.0f, 60.0f, 130.0f); } else if (fovUpHovered) { camera->FOV = glm::clamp(camera->FOV + 5.0f, 60.0f, 130.0f); } else if (hitboxHovered) { showHitboxes = !showHitboxes; } else if (resetHovered) { StartupCmPer360 = 16.0f; StartupMouseDpi = 800.0f; camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi); camera->FOV = 110.0f; showHitboxes = false; } else if (backHovered) { menuScreen = settingsReturn; } } else if (playHovered) { ResumeGame(); } else if (settingsHovered) { settingsReturn = menuScreen; menuScreen = MenuScreen::Settings; } else if (quitHovered) { if (menuScreen == MenuScreen::Main) glfwSetWindowShouldClose(window->handle, 1); else menuScreen = MenuScreen::Main; } } prevMouseDown = mouseDown; UpdateHitboxDebug(); rendersystem->Render(*camera); glfwPollEvents(); continue; } const glm::vec3 camFront = camera->t.GetFrontVector(); const glm::vec3 camRight = camera->t.GetRightVector(); const float movespeed = 3.0f * 1.3f * 0.6f; // -------------------------------------------------------------------- // WASD movement // -------------------------------------------------------------------- const glm::vec3 inputDirection = testcharacter.GetMovementDirection(camFront, camRight); const bool hasMoveInput = glm::length(inputDirection) > 0.0f; if (hasMoveInput) testcharacter.SetTurnDirection(inputDirection); testcharacter.SetWalking(hasMoveInput); const glm::vec3 npcVelocity(npcBody->linearvelocity.x, 0.0f, npcBody->linearvelocity.z); npc.SetWalking(glm::length(npcVelocity) > 0.1f); npc.SetTurnDirection(testcharacter.body->t.GetTranslation() - npc.body->t.GetTranslation()); charactermanager->Step(deltatime); const glm::vec3 inputVel = hasMoveInput ? testcharacter.GetForwardDirection() * movespeed : glm::vec3(0.0f); const float accel = 40.0f; const float damp = 0.75f; const float airControl = 0.5f; const float groundTopSpeed = movespeed * accel * refDt * damp / (1.0f - damp); if (IsGrounded()) { const float dampStep = std::pow(damp, deltatime / refDt); const float accelScale = refDt * damp * (1.0f - dampStep) / (deltatime * dampStep * (1.0f - damp)); characterBody->linearvelocity.x *= dampStep; characterBody->linearvelocity.z *= dampStep; characterBody->linearvelocity.x += inputVel.x * accel * deltatime * accelScale; characterBody->linearvelocity.z += inputVel.z * accel * deltatime * accelScale; } else if (glm::length(inputVel) > 0.0f) { const glm::vec3 wishDir = glm::normalize(inputVel); const float current = glm::dot(glm::vec3(characterBody->linearvelocity.x, 0.0f, characterBody->linearvelocity.z), wishDir); const float add = glm::clamp(groundTopSpeed - current, 0.0f, accel * airControl * movespeed * deltatime); characterBody->linearvelocity.x += wishDir.x * add; characterBody->linearvelocity.z += wishDir.z * add; } charactermanager->ResolveCombat(*physicsengine); { const int substeps = std::max(1, (int)std::ceil(deltatime * 120.0f)); const float subDt = deltatime / float(substeps); for (int i = 0; i < substeps; i++) physicsengine->Step(subDt); } if (testcharacter.body->t.GetTranslation().y < -20.0f) { testcharacter.body->t.TranslateTo(RandomSpawn()); characterBody->linearvelocity = glm::vec3(0.0f); characterBody->WakeUp(); } // -------------------------------------------------------------------- // Third person camera // -------------------------------------------------------------------- { const glm::vec3 playerPos = testcharacter.body->t.GetTranslation(); const glm::vec3 target = playerPos + glm::vec3(0.0f, 0.8f, 0.0f); glm::vec3 cameraPos = target - camera->t.GetFrontVector() * 5.0f; const bool playerOffFloor = playerPos.x < floorBounds.min.x || playerPos.x > floorBounds.max.x || playerPos.z < floorBounds.min.z || playerPos.z > floorBounds.max.z; const bool ignoreFloorCollision = playerOffFloor || playerPos.y < floorBounds.max.y; const glm::vec3 cameraRay = cameraPos - target; const float desiredDistance = glm::length(cameraRay); const glm::vec3 cameraDir = desiredDistance > 0.0001f ? cameraRay / desiredDistance : glm::vec3(0.0f); Ray cameraSweep; cameraSweep.origin = target; cameraSweep.direction = cameraDir; float nearestHit = desiredDistance; for (p* obj : physicsengine->GetObjects()) { if (obj->pointer == testcharacter.body || !obj->collision) continue; if (obj == floorBody && ignoreFloorCollision) continue; auto hit = IsRayInT(cameraSweep, obj->pointer->t); if (!hit.has_value()) continue; const float hitDistance = glm::dot(hit.value() - target, cameraDir); if (hitDistance > 0.001f && hitDistance < nearestHit) nearestHit = hitDistance; } const float cameraPadding = 0.3f; cameraPos = target + cameraDir * glm::max(nearestHit - cameraPadding, 0.05f); camera->t.TranslateTo(cameraPos); } camera->Step(deltatime); rain->t.TranslateTo(camera->t.GetTranslation() + glm::vec3(0.0f, 20.0f, 0.0f)); rain->Step(deltatime); Sound* music = static_cast(mainf->GetFirstChildOfName("music")); if (music) music->Update3DPosition(0.0f, 0.0f, 0.0f); soundsystem->SetListenerPosition( camera->t.GetTranslation(), camera->t.GetFrontVector(), camera->t.GetUpVector() ); soundsystem->Update(); // -------------------------------------------------------------------- // FPS counter // -------------------------------------------------------------------- { static double lastTime = glfwGetTime(); static int frameCount = 0; static int measuredFps = 0; static float accumulator = 0.0f; double now = glfwGetTime(); accumulator += (float)(now - lastTime); lastTime = now; frameCount++; if (accumulator >= 0.25f) { measuredFps = (int)(frameCount / accumulator); frameCount = 0; accumulator = 0.0f; } fpsCounter->text = std::to_string(measuredFps) + " FPS"; } healthText->text = "PLAYER " + std::to_string((int)std::lround(testcharacter.body->Health)) + " HP DUMMY " + std::to_string((int)std::lround(npc.body->Health)) + " HP"; const float healthRatio = glm::clamp((float)testcharacter.body->Health / 100.0f, 0.0f, 1.0f); healthBarFill->t2d.size.z = healthBarFillWidth * healthRatio; healthBarFill->Color = glm::mix(glm::vec3(0.85f, 0.20f, 0.12f), glm::vec3(0.20f, 0.85f, 0.35f), healthRatio); // -------------------------------------------------------------------- // Render // -------------------------------------------------------------------- UpdateHitboxDebug(); rendersystem->Render(*camera); glfwPollEvents(); } } // ============================================================================ // GLFW callbacks // ============================================================================ void framebuffer_size_callback(GLFWwindow* window, int w, int h) { if (w <= 0 || h <= 0) return; ::window->width = w; ::window->height = h; glViewport(0, 0, w, h); if (engine && engine->rendersystem) engine->rendersystem->Resize(w, h); } void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (key == GLFW_KEY_F11 && action == GLFW_PRESS) { static int wx = 100, wy = 100, ww = 1920, wh = 1080; if (glfwGetWindowMonitor(window)) { glfwSetWindowMonitor(window, nullptr, wx, wy, ww, wh, 0); } else { glfwGetWindowPos(window, &wx, &wy); glfwGetWindowSize(window, &ww, &wh); GLFWmonitor* monitor = glfwGetPrimaryMonitor(); const GLFWvidmode* mode = glfwGetVideoMode(monitor); glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); } glfwSwapInterval(1); } if (action == GLFW_PRESS) keyboard->KeyDown(key); else if (action == GLFW_RELEASE) keyboard->KeyUp(key); if (key >= GLFW_KEY_A && key <= GLFW_KEY_Z && engine && engine->charactermanager) { const char letter = (char)('A' + (key - GLFW_KEY_A)); if (action == GLFW_PRESS) engine->charactermanager->PressInput(letter); else if (action == GLFW_RELEASE) engine->charactermanager->ReleaseInput(letter); } } void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { } void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) { mouse->OnMouseMove(static_cast(xposIn), static_cast(yposIn)); if (!engine || !engine->camera || !engine->camera->lockedcursor) return; int ww, wh; glfwGetWindowSize(window, &ww, &wh); const double cx = ww / 2.0; const double cy = wh / 2.0; if (std::abs(xposIn - cx) < 0.5 && std::abs(yposIn - cy) < 0.5) return; glfwSetCursorPos(window, cx, cy); mouse->OnMouseMove((float)cx, (float)cy); } void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) { if (button == GLFW_MOUSE_BUTTON_RIGHT) { if (action == GLFW_PRESS) mouse->RightDown(); else mouse->RightUp(); } else if (button == GLFW_MOUSE_BUTTON_LEFT) { if (action == GLFW_PRESS) mouse->LeftDown(); else mouse->LeftUp(); } }