feat: add firstpersoncontroller

This commit is contained in:
2026-09-05 01:11:44 +02:00
parent c9fb6e991a
commit e8a56405df
11 changed files with 251 additions and 20 deletions
+1
View File
@@ -8,6 +8,7 @@ set(GAME_SRC
src/Lightkeeper.cpp
src/components/GameComponentRegistry.cpp
src/components/FirstPersonController.cpp
src/components/PrintComponent.cpp
src/components/TriggerLoggerComponent.cpp
)
+3
View File
@@ -10,6 +10,7 @@
#include "destrum/Graphics/Resources/Cubemap.h"
#include "destrum/ObjectModel/GameObject.h"
class FirstPersonController;
class LightKeeper final : public App {
public:
LightKeeper();
@@ -39,6 +40,8 @@ private:
std::unique_ptr<CubeMap> skyboxCubemap;
GameObject* capybara = nullptr;
GameObject* m_Player = nullptr;
FirstPersonController* m_FPSController = nullptr;
char scenePath[512]{"scenes/debug_scene.json"};
char newSceneName[128]{"EmptyScene"};
@@ -0,0 +1,48 @@
#ifndef LIGHTKEEPER_FIRSTPERSONCONTROLLER_H
#define LIGHTKEEPER_FIRSTPERSONCONTROLLER_H
#include <glm/glm.hpp>
#include <destrum/Input/InputManager.h>
#include <destrum/ObjectModel/Component.h>
class Rigidbody;
class CapsuleCollider;
class FirstPersonController final : public Component {
public:
explicit FirstPersonController(GameObject& parent);
std::string GetTypeName() const override { return "FirstPersonController"; }
void Start() override;
void Update(float dt) override;
void FixedUpdate(float fixedDt) override;
void ImGuiInspector() override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
[[nodiscard]] float GetYaw() const { return m_Yaw; }
[[nodiscard]] float GetPitch() const { return m_Pitch; }
[[nodiscard]] float GetEyeHeight() const { return m_EyeHeight; }
void SetMovementSpeed(float speed) { m_MovementSpeed = speed; }
[[nodiscard]] float GetMovementSpeed() const { return m_MovementSpeed; }
void SetMouseSensitivity(float sens) { m_MouseSensitivity = sens; }
[[nodiscard]] float GetMouseSensitivity() const { return m_MouseSensitivity; }
[[nodiscard]] bool IsMouseCaptured() const { return InputManager::GetInstance().IsMouseCaptured(); }
void SetMouseCaptured(bool captured);
private:
Rigidbody* m_Rigidbody{nullptr};
CapsuleCollider* m_Capsule{nullptr};
float m_Yaw{0.0f};
float m_Pitch{0.0f};
float m_MovementSpeed{5.0f};
float m_MouseSensitivity{0.002f};
float m_EyeHeight{1.6f};
};
#endif
@@ -1,10 +1,12 @@
#ifndef LIGHTKEEPER_GAMECOMPONENTLIST_H
#define LIGHTKEEPER_GAMECOMPONENTLIST_H
#include <components/FirstPersonController.h>
#include <components/PrintComponent.h>
#include <components/TriggerLoggerComponent.h>
#define LIGHTKEEPER_GAME_COMPONENTS(X) \
X(FirstPersonController) \
X(PrintComponent) \
X(TriggerLoggerComponent)
+21 -1
View File
@@ -20,6 +20,7 @@
#include "destrum/ObjectModel/GameObject.h"
#include "destrum/Util/ModelDoc.h"
#include "destrum/Components/Animator.h"
#include <components/FirstPersonController.h>
#include <components/TriggerLoggerComponent.h>
@@ -446,6 +447,13 @@ void LightKeeper::customInit()
triggerObj->AddComponent<TriggerLoggerComponent>();
triggerObj->GetTransform().SetWorldPosition({0.0f, 0.0f, 0.0f});
auto playerObj = scene.CreateGameObject("Player");
playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 2.0f, -5.0f));
auto* fps = playerObj->AddComponent<FirstPersonController>();
m_Player = playerObj;
m_FPSController = fps;
scene.GetPhysics().RegisterGameObject(*playerObj);
const auto texPath = AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png");
texID = resources.loadImageFromFile(gfxDevice, texPath);
@@ -455,7 +463,19 @@ void LightKeeper::customUpdate(float dt)
{
// LightKeeper owns a camera that hides App::camera; update this instance
// after SDL events have been consumed.
camera.Update(dt);
if (m_FPSController != nullptr && m_FPSController->IsMouseCaptured())
{
auto& playerTransform = m_Player->GetTransform();
const glm::vec3 playerPos = playerTransform.GetWorldPosition();
camera.SetRotation(m_FPSController->GetYaw(), m_FPSController->GetPitch());
camera.m_position = playerPos + glm::vec3(0.0f, m_FPSController->GetEyeHeight(), 0.0f);
camera.CalculateViewMatrix();
camera.CalculateProjectionMatrix();
}
else
{
camera.Update(dt);
}
SceneManager::GetInstance().Update(dt);
SceneManager::GetInstance().LateUpdate(dt);
@@ -0,0 +1,140 @@
#include <components/FirstPersonController.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/norm.hpp>
#include <destrum/Components/Physics/CapsuleCollider.h>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Input/InputManager.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Physics/PhysicsWorld.h>
#include <destrum/Scene/Scene.h>
#include <imgui.h>
FirstPersonController::FirstPersonController(GameObject& parent)
: Component(parent, "FirstPersonController")
{
}
void FirstPersonController::SetMouseCaptured(bool captured)
{
InputManager::GetInstance().SetMouseCaptured(captured);
}
void FirstPersonController::Start()
{
auto* obj = GetGameObject();
m_Capsule = obj->AddComponent<CapsuleCollider>(0.5f, 2.0f);
m_Rigidbody = obj->AddComponent<Rigidbody>();
m_Rigidbody->SetMass(80.0f);
m_Rigidbody->SetAllowSleep(false);
m_Rigidbody->SetUseGravity(true);
}
void FirstPersonController::Update(float)
{
const auto& input = InputManager::GetInstance();
if (input.WasKeyPressed(SDL_SCANCODE_F5))
{
SetMouseCaptured(!IsMouseCaptured());
}
if (!IsMouseCaptured())
{
if (input.WasMousePressed(SDL_BUTTON_LEFT) && !ImGui::GetIO().WantCaptureMouse)
{
SetMouseCaptured(true);
}
return;
}
if (input.WasKeyPressed(SDL_SCANCODE_ESCAPE))
{
SetMouseCaptured(false);
return;
}
if (input.WasKeyPressed(SDL_SCANCODE_SPACE) && m_Rigidbody->HasPhysicsBody())
{
m_Rigidbody->AddImpulse(glm::vec3(0.0f, 400.0f, 0.0f));
}
m_Yaw += static_cast<float>(input.MouseDeltaX()) * m_MouseSensitivity;
m_Pitch -= static_cast<float>(input.MouseDeltaY()) * m_MouseSensitivity;
m_Pitch = glm::clamp(m_Pitch, glm::radians(-89.0f), glm::radians(89.0f));
}
void FirstPersonController::FixedUpdate(float)
{
if (!IsMouseCaptured() || m_Rigidbody == nullptr || !m_Rigidbody->HasPhysicsBody())
return;
const auto& input = InputManager::GetInstance();
const float cosY = glm::cos(m_Yaw);
const float sinY = glm::sin(m_Yaw);
const glm::vec3 forward{cosY, 0.0f, sinY};
const glm::vec3 right{-sinY, 0.0f, cosY};
glm::vec3 moveDir(0.0f);
if (input.IsKeyDown(SDL_SCANCODE_W)) moveDir += forward;
if (input.IsKeyDown(SDL_SCANCODE_S)) moveDir -= forward;
if (input.IsKeyDown(SDL_SCANCODE_D)) moveDir += right;
if (input.IsKeyDown(SDL_SCANCODE_A)) moveDir -= right;
glm::vec3 velocity(0.0f);
if (glm::length2(moveDir) > 0.0f)
{
moveDir = glm::normalize(moveDir);
velocity = moveDir * m_MovementSpeed;
}
velocity.y = m_Rigidbody->GetLinearVelocity().y;
m_Rigidbody->SetLinearVelocity(velocity);
auto* world = m_Rigidbody->GetPhysicsWorld();
if (world != nullptr)
{
PhysicsTransform xform = world->GetBodyTransform(m_Rigidbody->GetBody());
xform.rotation = glm::quat(glm::vec3(0.0f, m_Yaw, 0.0f));
world->SetBodyTransform(m_Rigidbody->GetBody(), xform);
}
}
void FirstPersonController::ImGuiInspector()
{
Component::ImGuiInspector();
ImGui::DragFloat("Movement Speed", &m_MovementSpeed, 0.1f, 0.0f, 100.0f);
ImGui::DragFloat("Mouse Sensitivity", &m_MouseSensitivity, 0.0001f, 0.0f, 0.1f);
ImGui::DragFloat("Eye Height", &m_EyeHeight, 0.01f, 0.0f, 5.0f);
ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw));
ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch));
bool captured = IsMouseCaptured();
ImGui::Checkbox("Mouse Captured", &captured);
if (captured != IsMouseCaptured()) {
SetMouseCaptured(captured);
}
}
nlohmann::json FirstPersonController::Serialize() const
{
return {
{"movementSpeed", m_MovementSpeed},
{"mouseSensitivity", m_MouseSensitivity},
{"eyeHeight", m_EyeHeight},
{"yaw", m_Yaw},
{"pitch", m_Pitch},
};
}
void FirstPersonController::Deserialize(const nlohmann::json& data)
{
if (data.contains("movementSpeed")) m_MovementSpeed = data.at("movementSpeed").get<float>();
if (data.contains("mouseSensitivity")) m_MouseSensitivity = data.at("mouseSensitivity").get<float>();
if (data.contains("eyeHeight")) m_EyeHeight = data.at("eyeHeight").get<float>();
if (data.contains("yaw")) m_Yaw = data.at("yaw").get<float>();
if (data.contains("pitch")) m_Pitch = data.at("pitch").get<float>();
}