BIGGERBIGGER
This commit is contained in:
+790
@@ -0,0 +1,790 @@
|
||||
#include "Engine.h"
|
||||
|
||||
// ============================================================================
|
||||
// Standard library
|
||||
// ============================================================================
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL
|
||||
// ============================================================================
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
// ============================================================================
|
||||
// Math
|
||||
// ============================================================================
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// Third party
|
||||
// ============================================================================
|
||||
#include <ft2build.h>
|
||||
#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<int, RemotePlayer> 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<float> 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<TInTInfo> 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<Sound*>(mainf->GetFirstChildOfName("music"));
|
||||
Mesh* cube = static_cast<Mesh*>(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<NetUpdate> 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<float>(xposIn), static_cast<float>(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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user