#include "Engine.h" // ============================================================================ // Standard library // ============================================================================ #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 "Net.h" #include "font.h" // Character / animation #include "Rig.h" #include "Animator.h" #include "Character.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); rendersystem = new RenderSystem(); rendersystem->Initialize(); physicsengine = new Physics(); 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"); } // ============================================================================ // Initiate � scene setup + game loop // ============================================================================ void Engine::Initiate() { // ------------------------------------------------------------------------ // Assets // ------------------------------------------------------------------------ Font* arial = new Font("assets/ft/arial.ttf"); Texture* blanktex = new Texture("assets/blanktexture.png"); Texture* magic = new Texture("assets/itsmagicbitch.jpg"); Texture* snowflake = new Texture("assets/snowflake.png"); Texture* leontex = new Texture("assets/revolvertex.png"); // ------------------------------------------------------------------------ // Shared rig + animations // ------------------------------------------------------------------------ Rig* testrig = new Rig("assets/rig.glb"); Animation* testanimation = new Animation("assets/walkanim.glb"); Animation* punchanim = new Animation("assets/punchanim.glb"); // ------------------------------------------------------------------------ // Player character // ------------------------------------------------------------------------ Character* testcharacter = new Character(); testcharacter->name = "character"; testcharacter->castshadow = true; testcharacter->body->t.ScaleBy({ 0.5f, 1.8f, 0.5f }); testcharacter->body->t.TranslateTo({ 0.0f, 0.9f, 0.0f }); testcharacter->body->SetRig(testrig); // Base animator (weight 0) � walk, looped testcharacter->body->SetAnimation(testanimation); testcharacter->body->animator->loops = true; // Punch animator (weight 1) � one-shot, starts stopped Animator* punchAnimator = testcharacter->body->AddAnimator(1); punchAnimator->loops = false; punchAnimator->SetAnimation(punchanim); punchAnimator->StopAnimation(); // First person: don't draw our own model (other players still see it via the network) p* characterBody = new p(testcharacter->body); characterBody->mass = 80.0f; characterBody->lockRotation = true; characterBody->friction = 0.0f; physicsengine->AddObject(characterBody); // ------------------------------------------------------------------------ // NPC // ------------------------------------------------------------------------ Character* npc = new Character(); npc->name = "npc"; npc->castshadow = true; npc->body->t.ScaleBy({ 0.5f, 1.8f, 0.5f }); npc->body->t.TranslateTo({ 3.0f, 0.9f, 3.0f }); npc->body->SetRig(testrig); npc->body->SetAnimation(testanimation); npc->BindToRenderSystem(); p* npcBody = new p(npc->body); npcBody->mass = 80.0f; npcBody->lockRotation = true; npcBody->friction = 0.0f; physicsengine->AddObject(npcBody); // ------------------------------------------------------------------------ // Remote players (other PCs), keyed by network id. Not in physics: their transform // comes from the network. Created on first packet, removed when packets stop. // ------------------------------------------------------------------------ struct RemotePlayer { Character* character = nullptr; double lastSeen = 0.0; }; std::map remotes; auto SpawnRemote = [&]() -> Character* { Character* c = new Character(); c->name = "remote"; c->castshadow = true; c->body->t.ScaleBy({ 0.5f, 1.8f, 0.5f }); c->body->SetRig(testrig); c->body->SetAnimation(testanimation); c->BindToRenderSystem(); return c; }; // In a LAN game, start at a random spot so players don't all spawn inside each other if (lan && lan->IsActive()) { std::mt19937 rng(std::random_device{}()); std::uniform_real_distribution spread(-8.0f, 8.0f); testcharacter->body->t.TranslateTo({ spread(rng), 0.9f, spread(rng) }); } // ------------------------------------------------------------------------ // 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 // ------------------------------------------------------------------------ Crosshair* crosshair = new Crosshair(); crosshair->Color = { 0.0f, 0.0f, 0.0f }; crosshair->Dot = true; crosshair->Apply(); TextBox* tb = new TextBox(); tb->font = arial; tb->text = "ASDASD\nASDASD\nADSADSA"; tb->fontsize = 0.5f; tb->Color = { 0.0f, 0.0f, 0.0f }; tb->t2d.position = { 0.5f, 0.5f, 0.0f, 0.0f }; tb->t2d.center = { 0.0f, 0.0f }; tb->textCenter = { 0.0f, 0.5f }; tb->t2d.size = { 0.2f, 0.1f, 0.0f, 0.0f }; tb->BindToRenderSystem(); TextBox* fpsCounter = new TextBox(); fpsCounter->font = arial; fpsCounter->text = "TEST1231231123"; 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* netText = new TextBox(); netText->font = arial; netText->text = ""; netText->fontsize = 0.5f; netText->Color = { 1.0f, 1.0f, 1.0f }; netText->t2d.position = { 1.0f, 0.0f, 0.0f, 40.0f }; netText->t2d.center = { 1.0f, 0.0f }; netText->textCenter = { 1.0f, 0.5f }; netText->t2d.size = { 0.0f, 0.0f, 360.0f, 40.0f }; netText->BindToRenderSystem(); TextBox* hpText = new TextBox(); hpText->font = arial; hpText->text = "100/100 HP"; hpText->fontsize = 0.7f; hpText->Color = { 1.0f, 1.0f, 1.0f }; hpText->t2d.position = { 0.0f, 1.0f, 20.0f, -20.0f }; hpText->t2d.center = { 0.0f, 1.0f }; hpText->textCenter = { 0.0f, 0.5f }; hpText->t2d.size = { 0.0f, 0.0f, 220.0f, 50.0f }; hpText->BindToRenderSystem(); TextBox* grappleText = new TextBox(); grappleText->font = arial; grappleText->text = "GRAPPLE READY"; grappleText->fontsize = 0.5f; grappleText->Color = { 1.0f, 1.0f, 1.0f }; grappleText->t2d.position = { 0.5f, 1.0f, 0.0f, -20.0f }; grappleText->t2d.center = { 0.5f, 1.0f }; grappleText->textCenter = { 0.5f, 0.5f }; grappleText->t2d.size = { 0.0f, 0.0f, 300.0f, 40.0f }; grappleText->BindToRenderSystem(); ImageBox* testgui = new ImageBox(); testgui->tex = magic; testgui->Color = { 1.0f, 1.0f, 1.0f }; testgui->Opacity = 0.8f; testgui->rounding = 0.1f; testgui->t2d.center = { 0.0f, 1.0f }; testgui->t2d.position = { 0.0f, 1.0f, 10.0f, -10.0f }; testgui->t2d.size = { 0.0f, 0.0f, 744.0f / 2.0f, 914.0f / 2.0f }; testgui->BindToRenderSystem(); // ------------------------------------------------------------------------ // World meshes // ------------------------------------------------------------------------ Model leonrevolver("assets/leonrevolver.obj"); for (Mesh* mesh : leonrevolver.meshes) { auto newmesh = mesh->Clone(); newmesh->name = "revolver"; newmesh->castshadow = true; newmesh->meshTexture = leontex; newmesh->t.TranslateBy({ 4.0f, 4.0f, 4.0f }); newmesh->BindToRenderSystem(); mainf->AddChild(newmesh); } Mesh* debugcube = CreateCubeMesh(); debugcube->name = "debugcube"; debugcube->t.ScaleTo({ 0.8f, 0.8f, 0.8f }); debugcube->BindToRenderSystem(); mainf->AddChild(debugcube); Mesh* floor = CreateCubeMesh(); floor->name = "floor"; floor->meshTexture = blanktex; floor->t.ScaleTo({ 100.0f, 2.0f, 100.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); // Wall: frictionless (player friction is 0 too) so you slide along it Mesh* wall = CreateCubeMesh(); wall->name = "wall"; wall->meshTexture = blanktex; wall->t.ScaleTo({ 1.0f, 6.0f, 20.0f }); wall->t.TranslateTo({ -8.0f, 3.0f, 0.0f }); wall->BindToRenderSystem(); mainf->AddChild(wall); p* wallBody = new p(wall); wallBody->velocity = false; wallBody->friction = 0.0f; physicsengine->AddObject(wallBody); Mesh* cube = CreateCubeMesh(); cube->name = "cube"; cube->meshTexture = blanktex; cube->t.ScaleTo({ 1.0f, 1.0f, 1.0f }); cube->t.TranslateTo({ 5.0f, 4.0f, 6.5f }); cube->BindToRenderSystem(); mainf->AddChild(cube); Mesh* cube2 = CreateCubeMesh(); cube2->name = "cubee"; cube2->meshTexture = blanktex; cube2->t.ScaleTo({ 1.0f, 1.0f, 1.0f }); cube2->t.TranslateTo({ 10.0f, 4.0f, 10.5f }); cube2->BindToRenderSystem(); mainf->AddChild(cube2); // Ground check 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; }; // Hitscan shot from the camera. Head = top 20% of the target's box = instant kill, // body = 50 damage (two shots). Respawns the target when it dies. // Movement tuning const float airControl = 0.5f; // fraction of ground acceleration available in the air const float grappleRange = 4.0f; // metres from the player const float grappleSpeed = 28.0f; // launch speed toward the hit point const float grappleCooldownTime = 2.5f; // seconds float grappleCooldown = 0.0f; // Slide (key C): locked direction, committed for slideDuration, speed eases from // slideBoost x ground top speed down to 1x by slideFalloffTime. const float slideBoost = 2.0f; const float slideDuration = 0.8f; const float slideFalloffTime = 0.7f; const float slideDropY = 0.65f; // standing centre height (0.9) minus lying (0.25) const float standEyeOffset = 0.7f; // eyes 0.7 above body centre (body centre 0.9, height 1.8) const float slideEyeOffset = 0.25f; // lying down, eyes just above the floor float eyeOffset = standEyeOffset; bool sliding = false; float slideTime = 0.0f; glm::vec3 slideDir(0.0f); const int maxHealth = 100; const int bodyDamage = 50; const float headFraction = 0.8f; auto Shoot = [&]() { Ray ray; ray.origin = camera->t.GetTranslation(); ray.direction = glm::normalize(camera->t.GetFrontVector()); auto targetHit = IsRayInT(ray, npc->body->t); if (!targetHit.has_value()) return; const float targetDist = glm::distance(ray.origin, targetHit.value()); // Walls/props in front of the target block the shot for (p* obj : physicsengine->GetObjects()) { if (obj->pointer == testcharacter->body || obj->pointer == npc->body) continue; if (!obj->collision) continue; auto blocked = IsRayInT(ray, obj->pointer->t); if (blocked.has_value() && glm::distance(ray.origin, blocked.value()) < targetDist) return; } const float heightFrac = (targetHit->y - npc->body->t.GetTranslation().y) / npc->body->t.GetScale().y + 0.5f; const bool headshot = heightFrac >= headFraction; npc->body->Health -= headshot ? maxHealth : bodyDamage; std::cout << (headshot ? "HEADSHOT" : "BODY SHOT") << " - NPC HP " << npc->body->Health << "\n"; camera->AddScreenshake(0.05f); if (npc->body->Health <= 0) { npc->body->Health = maxHealth; npc->body->t.TranslateTo({ 3.0f, 0.9f, 3.0f }); npcBody->linearvelocity = glm::vec3(0.0f); npcBody->WakeUp(); } }; // ======================================================================== // Game loop // ======================================================================== while (!glfwWindowShouldClose(window->handle)) { frame++; const float deltatime = 1.0f / float(fps); const glm::vec3 camFront = camera->t.GetFrontVector(); const glm::vec3 camRight = camera->t.GetRightVector(); const float movespeed = 3.0f * 1.3f; // -------------------------------------------------------------------- // Input � mouse // -------------------------------------------------------------------- while (Mouse::Event buffer = mouse->Read()) { if (buffer.GetType() == Mouse::Event::Type::Move) { float xoffset = mouse->GetX() - mouse->GetLastX(); float yoffset = mouse->GetLastY() - mouse->GetY(); camera->ProcessMouseMovement(xoffset, yoffset); } else if (buffer.GetType() == Mouse::Event::Type::LPress) { Shoot(); } else if (buffer.GetType() == Mouse::Event::Type::RPress) { camera->lockedcursor = !camera->lockedcursor; } } // -------------------------------------------------------------------- // Input � held movement // -------------------------------------------------------------------- glm::vec3 inputVel = glm::vec3(0.0f); if (keyboard->IsKeyDown('W')) inputVel += glm::normalize(glm::vec3(camFront.x, 0.0f, camFront.z)); if (keyboard->IsKeyDown('S')) inputVel -= glm::normalize(glm::vec3(camFront.x, 0.0f, camFront.z)); if (keyboard->IsKeyDown('A')) inputVel += glm::normalize(glm::vec3(camRight.x, 0.0f, camRight.z)); if (keyboard->IsKeyDown('D')) inputVel -= glm::normalize(glm::vec3(camRight.x, 0.0f, camRight.z)); if (glm::length(inputVel) > 0.0f) inputVel = glm::normalize(inputVel) * movespeed; // -------------------------------------------------------------------- // Input � keyboard events // -------------------------------------------------------------------- while (Keyboard::Event buffer = keyboard->ReadKey()) { if (buffer.GetCode() == 'E' && buffer.IsPress()) { Mesh* nc = CreateCubeMesh(); nc->name = "cube!"; nc->t.ScaleTo(1.0f); nc->t.TranslateTo(camera->t.GetTranslation()); nc->t.RotateByEulerAngles({ 0.0f, 0.0f, 0.0f }); nc->BindToRenderSystem(); mainf->AddChild(nc); physicsengine->AddObject(new p(nc)); } if (buffer.GetCode() == 'F' && buffer.IsPress()) { const glm::vec3 playerPos = testcharacter->body->t.GetTranslation(); const glm::vec3 playerFront = glm::normalize(glm::vec3(camFront.x, 0.0f, camFront.z)); // Restart punch on high-priority animator punchAnimator->m_CurrentTime = 0.0f; punchAnimator->PlayAnimation(); debugcube->t.TranslateTo(playerPos + playerFront * 0.8f); debugcube->t.ScaleTo({ 0.8f, 0.8f, 0.8f }); debugcube->t.RotateToQuaternion(LookAt(-playerFront)); std::optional hit = TInT(debugcube->t, npc->body->t); if (hit.has_value()) { std::cout << "HIT NPC\n"; npcBody->WakeUp(); npcBody->linearvelocity += playerFront * 5.0f + glm::vec3(0.0f, 3.0f, 0.0f); camera->AddScreenshake(0.15f); // landing a punch feels impactful } else { std::cout << "MISSED\n"; } } if (buffer.GetCode() == GLFW_KEY_LEFT_SHIFT && buffer.IsPress() && !sliding) { glm::vec3 dashDir = glm::length(inputVel) > 0.0f ? glm::normalize(inputVel) : glm::normalize(glm::vec3(camFront.x, 0.0f, camFront.z)); characterBody->linearvelocity.x += dashDir.x * 18.0f; characterBody->linearvelocity.z += dashDir.z * 18.0f; camera->AddScreenshake(0.04f); } if (buffer.GetCode() == 'C' && buffer.IsPress() && !sliding && IsGrounded()) { const glm::vec3 horizVel(characterBody->linearvelocity.x, 0.0f, characterBody->linearvelocity.z); if (glm::length(inputVel) > 0.0f || glm::length(horizVel) > 1.0f) { slideDir = glm::length(inputVel) > 0.0f ? glm::normalize(inputVel) : glm::normalize(horizVel); sliding = true; slideTime = 0.0f; // Lie down feet-first: tilt the model's up axis to point back along the slide. // Hitbox is the same box, rotated with it; drop it so the feet stay on the floor. const glm::vec3 tiltAxis = glm::normalize(glm::cross(slideDir, glm::vec3(0.0f, 1.0f, 0.0f))); testcharacter->body->t.RotateToQuaternion( glm::angleAxis(glm::radians(90.0f), tiltAxis) * LookAt(-slideDir)); glm::vec3 pos = testcharacter->body->t.GetTranslation(); pos.y -= slideDropY; testcharacter->body->t.TranslateTo(pos); camera->AddScreenshake(0.06f); } } if (buffer.GetCode() == 'R' && buffer.IsPress() && !sliding && grappleCooldown <= 0.0f) { // Short-range grapple: aim with the camera, latch onto a surface within range Ray ray; ray.origin = camera->t.GetTranslation(); ray.direction = glm::normalize(camFront); float bestDist = grappleRange; glm::vec3 bestPoint(0.0f); bool found = false; for (p* obj : physicsengine->GetObjects()) { if (obj->pointer == testcharacter->body || obj->pointer == npc->body) continue; if (!obj->collision) continue; auto hit = IsRayInT(ray, obj->pointer->t); if (!hit.has_value()) continue; const float d = glm::distance(ray.origin, hit.value()); if (d < bestDist) { bestDist = d; bestPoint = hit.value(); found = true; } } if (found) { characterBody->linearvelocity = glm::normalize(bestPoint - ray.origin) * grappleSpeed; characterBody->WakeUp(); grappleCooldown = grappleCooldownTime; camera->AddScreenshake(0.1f); } } if (buffer.GetCode() == 'G' && buffer.IsPress()) { camera->AddScreenshake(0.2f); } if (buffer.GetCode() == GLFW_KEY_SPACE && buffer.IsPress()) { if (!sliding && IsGrounded()) characterBody->linearvelocity.y = 5.0f; } } // -------------------------------------------------------------------- // Input � held keys // -------------------------------------------------------------------- if (keyboard->IsKeyDown(GLFW_KEY_ESCAPE)) glfwSetWindowShouldClose(window->handle, 1); // Movement + damping grappleCooldown = std::max(grappleCooldown - deltatime, 0.0f); const float accel = 40.0f; const float damp = 0.75f; const float groundTopSpeed = movespeed * accel * deltatime * damp / (1.0f - damp); if (sliding) { // Direction is locked; speed eases from boost x down to 1x (quadratic: drops fastest early) slideTime += deltatime; const float u = glm::clamp(slideTime / slideFalloffTime, 0.0f, 1.0f); const float speed = groundTopSpeed * (1.0f + (slideBoost - 1.0f) * (1.0f - u) * (1.0f - u)); characterBody->linearvelocity.x = slideDir.x * speed; characterBody->linearvelocity.z = slideDir.z * speed; if (slideTime >= slideDuration) { sliding = false; glm::vec3 pos = testcharacter->body->t.GetTranslation(); pos.y += slideDropY + 0.02f; // stand back up (rotation is reset below) testcharacter->body->t.TranslateTo(pos); } } else if (IsGrounded()) { characterBody->linearvelocity.x *= damp; characterBody->linearvelocity.z *= damp; characterBody->linearvelocity.x += inputVel.x * accel * deltatime; characterBody->linearvelocity.z += inputVel.z * accel * deltatime; } else if (glm::length(inputVel) > 0.0f) { // Air: no ground drag so momentum (grapple, dash) carries. Input only adds // speed along the wish direction up to movespeed, at airControl of ground accel. 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; } // -------------------------------------------------------------------- // Scene updates // -------------------------------------------------------------------- { const glm::vec3 playerPos = testcharacter->body->t.GetTranslation(); const glm::vec3 playerFront = glm::normalize(glm::vec3(camFront.x, 0.0f, camFront.z)); debugcube->t.TranslateTo(playerPos + playerFront * 0.8f); debugcube->t.ScaleTo({ 0.8f, 0.8f, 0.8f }); debugcube->t.RotateToQuaternion(LookAt(-playerFront)); } rain->t.TranslateTo(camera->t.GetTranslation() + glm::vec3(0.0f, 20.0f, 0.0f)); for (int i = 0; i < 30; i++) rain->Emit(); if (!sliding) { testcharacter->body->t.RotateToQuaternion( LookAt(-glm::normalize(glm::vec3(camFront.x, 0.0f, camFront.z))) ); } // First person: camera at the eyes (eased down while sliding) eyeOffset += ((sliding ? slideEyeOffset : standEyeOffset) - eyeOffset) * 0.3f; camera->t.TranslateTo(testcharacter->body->t.GetTranslation() + glm::vec3(0.0f, eyeOffset, 0.0f)); Sound* music = static_cast(mainf->GetFirstChildOfName("music")); Mesh* cube = static_cast(mainf->GetFirstChildOfName("cube")); music->Update3DPosition(cube->t.GetTranslation()); soundsystem->SetListenerPosition( camera->t.GetTranslation(), camera->t.GetFrontVector(), camera->t.GetUpVector() ); // -------------------------------------------------------------------- // 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"; } hpText->text = std::to_string(std::max(testcharacter->body->Health, 0)) + "/100 HP"; grappleText->text = grappleCooldown > 0.0f ? "GRAPPLE " + std::to_string(grappleCooldown).substr(0, 3) + "s" : "GRAPPLE READY"; // -------------------------------------------------------------------- // Step systems // -------------------------------------------------------------------- camera->Step(deltatime); rain->Step(deltatime); testcharacter->Step(deltatime); npc->Step(deltatime); soundsystem->Update(); physicsengine->Step(deltatime); // -------------------------------------------------------------------- // LAN: send our transform, apply the other player's // -------------------------------------------------------------------- if (lan && lan->IsActive()) { NetState mine; mine.position = testcharacter->body->t.GetTranslation(); mine.rotation = testcharacter->body->t.GetRotationQuaternion(); mine.sliding = sliding; lan->Send(mine); const double now = glfwGetTime(); std::vector updates; lan->Receive(updates); for (const NetUpdate& u : updates) { auto it = remotes.find(u.id); if (it == remotes.end()) { // First packet from this player: spawn them exactly at their pose RemotePlayer rp; rp.character = SpawnRemote(); rp.character->body->t.TranslateTo(u.state.position); rp.character->body->t.RotateToQuaternion(u.state.rotation); it = remotes.emplace(u.id, rp).first; } else { // Smooth over the 60 Hz updates t& rt = it->second.character->body->t; rt.TranslateTo(glm::mix(rt.GetTranslation(), u.state.position, 0.5f)); rt.RotateToQuaternion(glm::slerp(rt.GetRotationQuaternion(), u.state.rotation, 0.5f)); } it->second.lastSeen = now; } // Drop players we haven't heard from in 2 s, animate the rest for (auto it = remotes.begin(); it != remotes.end();) { if (now - it->second.lastSeen > 2.0) { rendersystem->RemoveRenderable(it->second.character); delete it->second.character; it = remotes.erase(it); } else { it->second.character->Step(deltatime); ++it; } } if (lan->GetMode() == Lan::Mode::Host) netText->text = "LAN: hosting - " + std::to_string(lan->PeerCount() + 1) + "/" + std::to_string(Lan::MaxPlayers) + " players"; else netText->text = lan->IsConnected() ? "LAN: connected - " + std::to_string(remotes.size() + 1) + " players" : "LAN: joining..."; } // -------------------------------------------------------------------- // Render // -------------------------------------------------------------------- rendersystem->Render(*camera); glfwPollEvents(); } } // ============================================================================ // GLFW callbacks // ============================================================================ void framebuffer_size_callback(GLFWwindow* window, int w, int h) { } void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (action == GLFW_PRESS) keyboard->KeyDown(key); else if (action == GLFW_RELEASE) keyboard->KeyUp(key); } 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)); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetCursorPos(window, ::window->width / 2, ::window->height / 2); mouse->OnMouseMove(::window->width / 2.0f, ::window->height / 2.0f); } 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(); } }