Added Hot loading and fixed the engine freezing when compiling scripts, Yey!
This commit is contained in:
@@ -2,52 +2,31 @@
|
|||||||
#include "SceneObject.h"
|
#include "SceneObject.h"
|
||||||
#include "ThirdParty/imgui/imgui.h"
|
#include "ThirdParty/imgui/imgui.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
namespace
|
||||||
namespace {
|
{
|
||||||
struct ControllerState {
|
struct ControllerState
|
||||||
float pitch = 0.0f;
|
{
|
||||||
float yaw = 0.0f;
|
ScriptContext::StandaloneMovementState movement; ScriptContext::StandaloneMovementDebug debug;
|
||||||
float verticalVelocity = 0.0f;
|
bool initialized = false;
|
||||||
glm::vec3 debugVelocity = glm::vec3(0.0f);
|
};
|
||||||
bool debugGrounded = false;
|
std::unordered_map<int, ControllerState> g_states;
|
||||||
bool initialized = false;
|
ScriptContext::StandaloneMovementSettings g_settings;
|
||||||
};
|
ControllerState& getState(int id) {return g_states[id];}
|
||||||
|
// aliases for readability
|
||||||
std::unordered_map<int, ControllerState> g_states;
|
glm::vec3& moveTuning = g_settings.moveTuning;
|
||||||
|
glm::vec3& lookTuning = g_settings.lookTuning;
|
||||||
glm::vec3 moveTuning = glm::vec3(4.5f, 7.5f, 6.5f); // walk speed, run speed, jump
|
glm::vec3& capsuleTuning = g_settings.capsuleTuning;
|
||||||
glm::vec3 lookTuning = glm::vec3(0.12f, 200.0f, 0.0f); // sensitivity, max delta clamp, reserved
|
glm::vec3& gravityTuning = g_settings.gravityTuning;
|
||||||
glm::vec3 capsuleTuning = glm::vec3(1.8f, 0.4f, 0.2f); // height, radius, ground snap
|
bool& enableMouseLook = g_settings.enableMouseLook;
|
||||||
glm::vec3 gravityTuning = glm::vec3(-9.81f, 0.4f, 30.0f); // gravity, probe extra, max fall speed
|
bool& requireMouseButton = g_settings.requireMouseButton;
|
||||||
bool enableMouseLook = true;
|
bool& enforceCollider = g_settings.enforceCollider;
|
||||||
bool requireMouseButton = false;
|
bool& enforceRigidbody = g_settings.enforceRigidbody;
|
||||||
bool enforceCollider = true;
|
|
||||||
bool enforceRigidbody = true;
|
|
||||||
bool showDebug = false;
|
|
||||||
|
|
||||||
ControllerState& getState(int id) {
|
|
||||||
return g_states[id];
|
|
||||||
}
|
}
|
||||||
|
extern "C" void Script_OnInspector(ScriptContext& ctx)
|
||||||
void bindSettings(ScriptContext& ctx) {
|
{
|
||||||
ctx.AutoSetting("moveTuning", moveTuning);
|
ctx.BindStandaloneMovementSettings(g_settings);
|
||||||
ctx.AutoSetting("lookTuning", lookTuning);
|
|
||||||
ctx.AutoSetting("capsuleTuning", capsuleTuning);
|
|
||||||
ctx.AutoSetting("gravityTuning", gravityTuning);
|
|
||||||
ctx.AutoSetting("enableMouseLook", enableMouseLook);
|
|
||||||
ctx.AutoSetting("requireMouseButton", requireMouseButton);
|
|
||||||
ctx.AutoSetting("enforceCollider", enforceCollider);
|
|
||||||
ctx.AutoSetting("enforceRigidbody", enforceRigidbody);
|
|
||||||
ctx.AutoSetting("showDebug", showDebug);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
extern "C" void Script_OnInspector(ScriptContext& ctx) {
|
|
||||||
bindSettings(ctx);
|
|
||||||
|
|
||||||
ImGui::TextUnformatted("Standalone Movement Controller");
|
ImGui::TextUnformatted("Standalone Movement Controller");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
ImGui::DragFloat3("Walk/Run/Jump", &moveTuning.x, 0.05f, 0.0f, 25.0f, "%.2f");
|
ImGui::DragFloat3("Walk/Run/Jump", &moveTuning.x, 0.05f, 0.0f, 25.0f, "%.2f");
|
||||||
ImGui::DragFloat2("Look Sens/Clamp", &lookTuning.x, 0.01f, 0.0f, 500.0f, "%.2f");
|
ImGui::DragFloat2("Look Sens/Clamp", &lookTuning.x, 0.01f, 0.0f, 500.0f, "%.2f");
|
||||||
ImGui::DragFloat3("Height/Radius/Snap", &capsuleTuning.x, 0.02f, 0.0f, 5.0f, "%.2f");
|
ImGui::DragFloat3("Height/Radius/Snap", &capsuleTuning.x, 0.02f, 0.0f, 5.0f, "%.2f");
|
||||||
@@ -56,133 +35,22 @@ extern "C" void Script_OnInspector(ScriptContext& ctx) {
|
|||||||
ImGui::Checkbox("Hold RMB to Look", &requireMouseButton);
|
ImGui::Checkbox("Hold RMB to Look", &requireMouseButton);
|
||||||
ImGui::Checkbox("Force Collider", &enforceCollider);
|
ImGui::Checkbox("Force Collider", &enforceCollider);
|
||||||
ImGui::Checkbox("Force Rigidbody", &enforceRigidbody);
|
ImGui::Checkbox("Force Rigidbody", &enforceRigidbody);
|
||||||
ImGui::Checkbox("Show Debug", &showDebug);
|
|
||||||
|
|
||||||
if (showDebug && ctx.object) {
|
|
||||||
auto it = g_states.find(ctx.object->id);
|
|
||||||
if (it != g_states.end()) {
|
|
||||||
const ControllerState& state = it->second;
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text("Move (%.2f, %.2f, %.2f)", state.debugVelocity.x, state.debugVelocity.y, state.debugVelocity.z);
|
|
||||||
ImGui::Text("Grounded: %s", state.debugGrounded ? "yes" : "no");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Begin(ScriptContext& ctx, float /*deltaTime*/) {
|
void Begin(ScriptContext& ctx, float)
|
||||||
if (!ctx.object) return;
|
{
|
||||||
bindSettings(ctx);
|
if (!ctx.object) return; ControllerState& s = getState(ctx.object->id);
|
||||||
ControllerState& state = getState(ctx.object->id);
|
if (!s.initialized)
|
||||||
if (!state.initialized) {
|
{
|
||||||
state.pitch = ctx.object->rotation.x;
|
s.movement.pitch = ctx.object->rotation.x;
|
||||||
state.yaw = ctx.object->rotation.y;
|
s.movement.yaw = ctx.object->rotation.y;
|
||||||
state.verticalVelocity = 0.0f;
|
s.initialized = true;
|
||||||
state.initialized = true;
|
|
||||||
}
|
}
|
||||||
if (enforceCollider) ctx.EnsureCapsuleCollider(capsuleTuning.x, capsuleTuning.y);
|
if (enforceCollider) ctx.EnsureCapsuleCollider(capsuleTuning.x, capsuleTuning.y);
|
||||||
if (enforceRigidbody) ctx.EnsureRigidbody(true, false);
|
if (enforceRigidbody) ctx.EnsureRigidbody(true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TickUpdate(ScriptContext& ctx, float deltaTime) {
|
void TickUpdate(ScriptContext& ctx, float dt)
|
||||||
if (!ctx.object) return;
|
{
|
||||||
|
if (!ctx.object) return; ControllerState& s = getState(ctx.object->id); ctx.TickStandaloneMovement(s.movement, g_settings, dt, nullptr);
|
||||||
ControllerState& state = getState(ctx.object->id);
|
|
||||||
if (enforceCollider) ctx.EnsureCapsuleCollider(capsuleTuning.x, capsuleTuning.y);
|
|
||||||
if (enforceRigidbody) ctx.EnsureRigidbody(true, false);
|
|
||||||
|
|
||||||
const float walkSpeed = moveTuning.x;
|
|
||||||
const float runSpeed = moveTuning.y;
|
|
||||||
const float jumpStrength = moveTuning.z;
|
|
||||||
const float lookSensitivity = lookTuning.x;
|
|
||||||
const float maxMouseDelta = glm::max(5.0f, lookTuning.y);
|
|
||||||
const float height = capsuleTuning.x;
|
|
||||||
const float radius = capsuleTuning.y;
|
|
||||||
const float groundSnap = capsuleTuning.z;
|
|
||||||
const float gravity = gravityTuning.x;
|
|
||||||
const float probeExtra = gravityTuning.y;
|
|
||||||
const float maxFall = glm::max(1.0f, gravityTuning.z);
|
|
||||||
|
|
||||||
if (enableMouseLook) {
|
|
||||||
bool allowLook = !requireMouseButton || ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
|
||||||
if (allowLook) {
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
|
||||||
glm::vec2 delta(io.MouseDelta.x, io.MouseDelta.y);
|
|
||||||
float len = glm::length(delta);
|
|
||||||
if (len > maxMouseDelta) {
|
|
||||||
delta *= (maxMouseDelta / len);
|
|
||||||
}
|
|
||||||
state.yaw -= delta.x * 50.0f * lookSensitivity * deltaTime;
|
|
||||||
state.pitch -= delta.y * 50.0f * lookSensitivity * deltaTime;
|
|
||||||
state.pitch = std::clamp(state.pitch, -89.0f, 89.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 planarForward(0.0f);
|
|
||||||
glm::vec3 planarRight(0.0f);
|
|
||||||
ctx.GetPlanarYawPitchVectors(state.pitch, state.yaw, planarForward, planarRight);
|
|
||||||
|
|
||||||
glm::vec3 move(0.0f);
|
|
||||||
if (ImGui::IsKeyDown(ImGuiKey_W)) move += planarForward;
|
|
||||||
if (ImGui::IsKeyDown(ImGuiKey_S)) move -= planarForward;
|
|
||||||
if (ImGui::IsKeyDown(ImGuiKey_D)) move += planarRight;
|
|
||||||
if (ImGui::IsKeyDown(ImGuiKey_A)) move -= planarRight;
|
|
||||||
if (glm::length(move) > 0.001f) move = glm::normalize(move);
|
|
||||||
|
|
||||||
bool sprint = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift);
|
|
||||||
float targetSpeed = sprint ? runSpeed : walkSpeed;
|
|
||||||
glm::vec3 velocity = move * targetSpeed;
|
|
||||||
|
|
||||||
float capsuleHalf = std::max(0.1f, height * 0.5f);
|
|
||||||
glm::vec3 physVel;
|
|
||||||
bool havePhysVel = ctx.GetRigidbodyVelocity(physVel);
|
|
||||||
if (havePhysVel) state.verticalVelocity = physVel.y;
|
|
||||||
|
|
||||||
glm::vec3 hitPos(0.0f);
|
|
||||||
glm::vec3 hitNormal(0.0f, 1.0f, 0.0f);
|
|
||||||
float hitDist = 0.0f;
|
|
||||||
float probeDist = capsuleHalf + probeExtra;
|
|
||||||
glm::vec3 rayStart = ctx.object->position + glm::vec3(0.0f, 0.1f, 0.0f);
|
|
||||||
bool hitGround = ctx.RaycastClosest(rayStart, glm::vec3(0.0f, -1.0f, 0.0f), probeDist,
|
|
||||||
&hitPos, &hitNormal, &hitDist);
|
|
||||||
bool grounded = hitGround && hitNormal.y > 0.25f &&
|
|
||||||
hitDist <= capsuleHalf + groundSnap &&
|
|
||||||
state.verticalVelocity <= 0.35f;
|
|
||||||
if (!hitGround) {
|
|
||||||
grounded = ctx.object->position.y <= capsuleHalf + 0.12f && state.verticalVelocity <= 0.35f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (grounded) {
|
|
||||||
state.verticalVelocity = 0.0f;
|
|
||||||
if (!havePhysVel) {
|
|
||||||
if (hitGround) {
|
|
||||||
ctx.object->position.y = std::max(ctx.object->position.y, hitPos.y + capsuleHalf);
|
|
||||||
} else {
|
|
||||||
ctx.object->position.y = capsuleHalf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ImGui::IsKeyDown(ImGuiKey_Space)) {
|
|
||||||
state.verticalVelocity = jumpStrength;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.verticalVelocity += gravity * deltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.verticalVelocity = std::clamp(state.verticalVelocity, -maxFall, maxFall);
|
|
||||||
velocity.y = state.verticalVelocity;
|
|
||||||
|
|
||||||
glm::vec3 rotation(state.pitch, state.yaw, 0.0f);
|
|
||||||
ctx.object->rotation = rotation;
|
|
||||||
ctx.SetRigidbodyRotation(rotation);
|
|
||||||
|
|
||||||
if (!ctx.SetRigidbodyVelocity(velocity)) {
|
|
||||||
ctx.object->position += velocity * deltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showDebug) {
|
|
||||||
state.debugVelocity = velocity;
|
|
||||||
state.debugGrounded = grounded;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spec(ScriptContext& /*ctx*/, float /*deltaTime*/) {}
|
|
||||||
void TestEditor(ScriptContext& /*ctx*/, float /*deltaTime*/) {}
|
|
||||||
|
|||||||
@@ -364,6 +364,54 @@ void Engine::renderLauncher() {
|
|||||||
renderNewProjectDialog();
|
renderNewProjectDialog();
|
||||||
if (projectManager.showOpenProjectDialog)
|
if (projectManager.showOpenProjectDialog)
|
||||||
renderOpenProjectDialog();
|
renderOpenProjectDialog();
|
||||||
|
|
||||||
|
if (projectLoadInProgress) {
|
||||||
|
float elapsed = static_cast<float>(glfwGetTime() - projectLoadStartTime);
|
||||||
|
if (elapsed > 0.15f) {
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||||
|
ImGui::SetNextWindowSize(io.DisplaySize);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.05f, 0.06f, 0.08f, 0.65f));
|
||||||
|
ImGui::Begin("ProjectLoadOverlay", nullptr,
|
||||||
|
ImGuiWindowFlags_NoTitleBar |
|
||||||
|
ImGuiWindowFlags_NoResize |
|
||||||
|
ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_NoDocking |
|
||||||
|
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||||
|
ImGuiWindowFlags_NoInputs);
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
ImVec2 center = ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
||||||
|
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(420, 160));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 16.0f);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(24.0f, 20.0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.12f, 0.13f, 0.18f, 0.98f));
|
||||||
|
ImGui::Begin("ProjectLoadCard", nullptr,
|
||||||
|
ImGuiWindowFlags_NoTitleBar |
|
||||||
|
ImGuiWindowFlags_NoResize |
|
||||||
|
ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_NoDocking |
|
||||||
|
ImGuiWindowFlags_NoCollapse |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings);
|
||||||
|
|
||||||
|
ImGui::TextColored(ImVec4(0.88f, 0.90f, 0.96f, 1.0f), "Loading project...");
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextDisabled("%s", projectLoadPath.c_str());
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Spinner("##project_load_spinner", 16.0f, 4, ImGui::GetColorU32(ImGuiCol_ButtonHovered));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BufferingBar("##project_load_bar", std::fmod(elapsed * 0.25f, 1.0f),
|
||||||
|
ImVec2(ImGui::GetContentRegionAvail().x - 40.0f, 8.0f),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Button),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_ButtonHovered));
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
|
|||||||
@@ -2624,23 +2624,41 @@ void Engine::renderDialogs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showCompilePopup) {
|
if (showCompilePopup) {
|
||||||
|
if (!compilePopupOpened) {
|
||||||
|
ImGui::OpenPopup("Script Compile");
|
||||||
|
compilePopupOpened = true;
|
||||||
|
}
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
ImVec2 center = ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
ImVec2 center = ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
||||||
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||||
ImGui::SetNextWindowSize(ImVec2(520, 240), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowSize(ImVec2(520, 260), ImGuiCond_FirstUseEver);
|
||||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoSavedSettings;
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||||
if (ImGui::Begin("Script Compile", &showCompilePopup, flags)) {
|
ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoSavedSettings;
|
||||||
|
bool allowClose = !compileInProgress;
|
||||||
|
if (ImGui::BeginPopupModal("Script Compile", allowClose ? &showCompilePopup : nullptr, flags)) {
|
||||||
ImGui::TextWrapped("%s", lastCompileStatus.c_str());
|
ImGui::TextWrapped("%s", lastCompileStatus.c_str());
|
||||||
|
float t = static_cast<float>(glfwGetTime());
|
||||||
|
float pulse = 0.5f + 0.5f * std::sin(t * 2.5f);
|
||||||
|
ImGui::ProgressBar(compileInProgress ? pulse : 1.0f, ImVec2(-1, 0),
|
||||||
|
compileInProgress ? "Working..." : "Done");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::BeginChild("CompileLog", ImVec2(0, -40), true);
|
ImGui::BeginChild("CompileLog", ImVec2(0, -40), true);
|
||||||
ImGui::TextUnformatted(lastCompileLog.c_str());
|
if (lastCompileLog.empty() && compileInProgress) {
|
||||||
|
ImGui::TextUnformatted("Waiting for compiler output...");
|
||||||
|
} else {
|
||||||
|
ImGui::TextUnformatted(lastCompileLog.c_str());
|
||||||
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
if (ImGui::Button("Close", ImVec2(80, 0))) {
|
if (allowClose && ImGui::Button("Close", ImVec2(80, 0))) {
|
||||||
showCompilePopup = false;
|
showCompilePopup = false;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
compilePopupOpened = false;
|
||||||
}
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
ImGui::End();
|
} else {
|
||||||
|
compilePopupOpened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showSaveSceneAsDialog) {
|
if (showSaveSceneAsDialog) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstdio>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -261,9 +262,23 @@ void Engine::renderGameViewportWindow() {
|
|||||||
ImGui::Begin("Game Viewport", &showGameViewport, ImGuiWindowFlags_NoScrollbar);
|
ImGui::Begin("Game Viewport", &showGameViewport, ImGuiWindowFlags_NoScrollbar);
|
||||||
|
|
||||||
bool windowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
|
bool windowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
|
||||||
ImVec2 avail = ImGui::GetContentRegionAvail();
|
struct GameResolutionOption {
|
||||||
int width = std::max(160, (int)avail.x);
|
const char* label;
|
||||||
int height = std::max(120, (int)avail.y);
|
int width;
|
||||||
|
int height;
|
||||||
|
bool useWindow;
|
||||||
|
bool custom;
|
||||||
|
};
|
||||||
|
static const std::array<GameResolutionOption, 5> kGameResolutions = {{
|
||||||
|
{ "Window", 0, 0, true, false },
|
||||||
|
{ "1920x1080 (1080p)", 1920, 1080, false, false },
|
||||||
|
{ "1280x720 (720p)", 1280, 720, false, false },
|
||||||
|
{ "2560x1440 (1440p)", 2560, 1440, false, false },
|
||||||
|
{ "Custom", 0, 0, false, true }
|
||||||
|
}};
|
||||||
|
if (gameViewportResolutionIndex < 0 || gameViewportResolutionIndex >= (int)kGameResolutions.size()) {
|
||||||
|
gameViewportResolutionIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
SceneObject* playerCam = nullptr;
|
SceneObject* playerCam = nullptr;
|
||||||
for (auto& obj : sceneObjects) {
|
for (auto& obj : sceneObjects) {
|
||||||
@@ -288,12 +303,69 @@ void Engine::renderGameViewportWindow() {
|
|||||||
ImGui::Checkbox("Post FX", &dummyToggle);
|
ImGui::Checkbox("Post FX", &dummyToggle);
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Text", &showUITextOverlay);
|
ImGui::Checkbox("Profiler", &showGameProfiler);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Canvas Guides", &showCanvasOverlay);
|
ImGui::Checkbox("Canvas Guides", &showCanvasOverlay);
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
const GameResolutionOption& resOption = kGameResolutions[gameViewportResolutionIndex];
|
||||||
|
ImGui::SetNextItemWidth(180.0f);
|
||||||
|
if (ImGui::BeginCombo("Resolution", resOption.label)) {
|
||||||
|
for (int i = 0; i < (int)kGameResolutions.size(); ++i) {
|
||||||
|
bool selected = (i == gameViewportResolutionIndex);
|
||||||
|
if (ImGui::Selectable(kGameResolutions[i].label, selected)) {
|
||||||
|
gameViewportResolutionIndex = i;
|
||||||
|
}
|
||||||
|
if (selected) ImGui::SetItemDefaultFocus();
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
if (kGameResolutions[gameViewportResolutionIndex].custom) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(90.0f);
|
||||||
|
ImGui::DragInt("W", &gameViewportCustomWidth, 1.0f, 64, 8192);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(90.0f);
|
||||||
|
ImGui::DragInt("H", &gameViewportCustomHeight, 1.0f, 64, 8192);
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Auto Fit", &gameViewportAutoFit);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginDisabled(gameViewportAutoFit);
|
||||||
|
float zoomPercent = gameViewportZoom * 100.0f;
|
||||||
|
ImGui::SetNextItemWidth(140.0f);
|
||||||
|
if (ImGui::SliderFloat("Zoom", &zoomPercent, 10.0f, 200.0f, "%.0f%%")) {
|
||||||
|
gameViewportZoom = zoomPercent / 100.0f;
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||||
|
int renderWidth = 0;
|
||||||
|
int renderHeight = 0;
|
||||||
|
if (kGameResolutions[gameViewportResolutionIndex].useWindow) {
|
||||||
|
renderWidth = std::max(160, (int)avail.x);
|
||||||
|
renderHeight = std::max(120, (int)avail.y);
|
||||||
|
} else if (kGameResolutions[gameViewportResolutionIndex].custom) {
|
||||||
|
renderWidth = std::clamp(gameViewportCustomWidth, 64, 8192);
|
||||||
|
renderHeight = std::clamp(gameViewportCustomHeight, 64, 8192);
|
||||||
|
} else {
|
||||||
|
renderWidth = kGameResolutions[gameViewportResolutionIndex].width;
|
||||||
|
renderHeight = kGameResolutions[gameViewportResolutionIndex].height;
|
||||||
|
}
|
||||||
|
float zoom = gameViewportZoom;
|
||||||
|
if (gameViewportAutoFit) {
|
||||||
|
if (kGameResolutions[gameViewportResolutionIndex].useWindow) {
|
||||||
|
zoom = 1.0f;
|
||||||
|
} else {
|
||||||
|
float fitX = (renderWidth > 0) ? (avail.x / (float)renderWidth) : 1.0f;
|
||||||
|
float fitY = (renderHeight > 0) ? (avail.y / (float)renderHeight) : 1.0f;
|
||||||
|
zoom = std::min(1.0f, std::min(fitX, fitY));
|
||||||
|
zoom = std::max(0.01f, zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (playerCam && postFxChanged) {
|
if (playerCam && postFxChanged) {
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
}
|
}
|
||||||
@@ -302,38 +374,68 @@ void Engine::renderGameViewportWindow() {
|
|||||||
gameViewCursorLocked = false;
|
gameViewCursorLocked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerCam && rendererInitialized) {
|
if (playerCam && rendererInitialized) {
|
||||||
unsigned int tex = renderer.renderScenePreview(
|
unsigned int tex = renderer.renderScenePreview(
|
||||||
makeCameraFromObject(*playerCam),
|
makeCameraFromObject(*playerCam),
|
||||||
sceneObjects,
|
sceneObjects,
|
||||||
width,
|
renderWidth,
|
||||||
height,
|
renderHeight,
|
||||||
playerCam->camera.fov,
|
playerCam->camera.fov,
|
||||||
playerCam->camera.nearClip,
|
playerCam->camera.nearClip,
|
||||||
playerCam->camera.farClip,
|
playerCam->camera.farClip,
|
||||||
playerCam->camera.applyPostFX
|
playerCam->camera.applyPostFX
|
||||||
);
|
);
|
||||||
|
|
||||||
ImGui::Image((void*)(intptr_t)tex, ImVec2((float)width, (float)height), ImVec2(0, 1), ImVec2(1, 0));
|
ImVec2 imageSize(std::max(1.0f, renderWidth * zoom), std::max(1.0f, renderHeight * zoom));
|
||||||
|
ImVec2 cursorPos = ImGui::GetCursorPos();
|
||||||
|
float offsetX = std::max(0.0f, (avail.x - imageSize.x) * 0.5f);
|
||||||
|
float offsetY = std::max(0.0f, (avail.y - imageSize.y) * 0.5f);
|
||||||
|
ImGui::SetCursorPos(ImVec2(cursorPos.x + offsetX, cursorPos.y + offsetY));
|
||||||
|
ImGui::Image((void*)(intptr_t)tex, imageSize, ImVec2(0, 1), ImVec2(1, 0));
|
||||||
bool imageHovered = ImGui::IsItemHovered();
|
bool imageHovered = ImGui::IsItemHovered();
|
||||||
ImVec2 imageMin = ImGui::GetItemRectMin();
|
ImVec2 imageMin = ImGui::GetItemRectMin();
|
||||||
ImVec2 imageMax = ImGui::GetItemRectMax();
|
ImVec2 imageMax = ImGui::GetItemRectMax();
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
float uiScaleX = (renderWidth > 0) ? (imageSize.x / (float)renderWidth) : 1.0f;
|
||||||
|
float uiScaleY = (renderHeight > 0) ? (imageSize.y / (float)renderHeight) : 1.0f;
|
||||||
if (showCanvasOverlay) {
|
if (showCanvasOverlay) {
|
||||||
ImVec2 pad(8.0f, 8.0f);
|
ImVec2 pad(8.0f, 8.0f);
|
||||||
ImVec2 tl(imageMin.x + pad.x, imageMin.y + pad.y);
|
ImVec2 tl(imageMin.x + pad.x, imageMin.y + pad.y);
|
||||||
ImVec2 br(imageMax.x - pad.x, imageMax.y - pad.y);
|
ImVec2 br(imageMax.x - pad.x, imageMax.y - pad.y);
|
||||||
drawList->AddRect(tl, br, IM_COL32(110, 170, 255, 180), 8.0f, 0, 2.0f);
|
drawList->AddRect(tl, br, IM_COL32(110, 170, 255, 180), 8.0f, 0, 2.0f);
|
||||||
}
|
}
|
||||||
if (showUITextOverlay) {
|
if (showGameProfiler) {
|
||||||
const char* textLabel = "Text Overlay";
|
float fps = ImGui::GetIO().Framerate;
|
||||||
ImVec2 textPos(imageMin.x + 16.0f, imageMin.y + 16.0f);
|
float frameMs = (fps > 0.0f) ? (1000.0f / fps) : 0.0f;
|
||||||
ImVec2 size = ImGui::CalcTextSize(textLabel);
|
int zoomPercent = (int)std::round(zoom * 100.0f);
|
||||||
ImVec2 bgPad(6.0f, 4.0f);
|
const Renderer::RenderStats& stats = renderer.getLastPreviewStats();
|
||||||
ImVec2 bgMin(textPos.x - bgPad.x, textPos.y - bgPad.y);
|
|
||||||
ImVec2 bgMax(textPos.x + size.x + bgPad.x, textPos.y + size.y + bgPad.y);
|
char line1[128];
|
||||||
drawList->AddRectFilled(bgMin, bgMax, IM_COL32(20, 20, 24, 200), 4.0f);
|
char line2[128];
|
||||||
drawList->AddText(textPos, IM_COL32(235, 235, 245, 255), textLabel);
|
char line3[128];
|
||||||
|
char line4[128];
|
||||||
|
std::snprintf(line1, sizeof(line1), "FPS: %.0f (%.1f ms)", fps, frameMs);
|
||||||
|
std::snprintf(line2, sizeof(line2), "Batches: %d", stats.drawCalls);
|
||||||
|
std::snprintf(line3, sizeof(line3), "Meshes: %d", stats.meshDraws);
|
||||||
|
std::snprintf(line4, sizeof(line4), "Render: %dx%d @ %d%%", renderWidth, renderHeight, zoomPercent);
|
||||||
|
|
||||||
|
const char* lines[] = { line1, line2, line3, line4 };
|
||||||
|
float lineHeight = ImGui::GetFontSize() + 2.0f;
|
||||||
|
float maxWidth = 0.0f;
|
||||||
|
for (const char* line : lines) {
|
||||||
|
ImVec2 size = ImGui::CalcTextSize(line);
|
||||||
|
maxWidth = std::max(maxWidth, size.x);
|
||||||
|
}
|
||||||
|
ImVec2 pad(8.0f, 6.0f);
|
||||||
|
ImVec2 panelMin(imageMin.x + 14.0f, imageMin.y + 14.0f);
|
||||||
|
ImVec2 panelMax(panelMin.x + maxWidth + pad.x * 2.0f,
|
||||||
|
panelMin.y + lineHeight * (float)(sizeof(lines) / sizeof(lines[0])) + pad.y * 2.0f);
|
||||||
|
drawList->AddRectFilled(panelMin, panelMax, IM_COL32(18, 18, 24, 210), 6.0f);
|
||||||
|
drawList->AddRect(panelMin, panelMax, IM_COL32(255, 255, 255, 40), 6.0f);
|
||||||
|
for (int i = 0; i < (int)(sizeof(lines) / sizeof(lines[0])); ++i) {
|
||||||
|
ImVec2 textPos(panelMin.x + pad.x, panelMin.y + pad.y + lineHeight * i);
|
||||||
|
drawList->AddText(textPos, IM_COL32(235, 235, 245, 255), lines[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool uiInteracting = false;
|
bool uiInteracting = false;
|
||||||
auto isUIType = [](ObjectType type) {
|
auto isUIType = [](ObjectType type) {
|
||||||
@@ -401,9 +503,9 @@ void Engine::renderGameViewportWindow() {
|
|||||||
*parentMin = regionMin;
|
*parentMin = regionMin;
|
||||||
*parentMax = regionMax;
|
*parentMax = regionMax;
|
||||||
}
|
}
|
||||||
ImVec2 size = ImVec2(std::max(1.0f, node->ui.size.x), std::max(1.0f, node->ui.size.y));
|
ImVec2 size = ImVec2(std::max(1.0f, node->ui.size.x * uiScaleX), std::max(1.0f, node->ui.size.y * uiScaleY));
|
||||||
ImVec2 anchorPoint = anchorToPoint(node->ui.anchor, regionMin, regionMax);
|
ImVec2 anchorPoint = anchorToPoint(node->ui.anchor, regionMin, regionMax);
|
||||||
ImVec2 pivot(anchorPoint.x + node->ui.position.x, anchorPoint.y + node->ui.position.y);
|
ImVec2 pivot(anchorPoint.x + node->ui.position.x * uiScaleX, anchorPoint.y + node->ui.position.y * uiScaleY);
|
||||||
ImVec2 pivotOffset = anchorToPivot(node->ui.anchor, size);
|
ImVec2 pivotOffset = anchorToPivot(node->ui.anchor, size);
|
||||||
regionMin = ImVec2(pivot.x - pivotOffset.x, pivot.y - pivotOffset.y);
|
regionMin = ImVec2(pivot.x - pivotOffset.x, pivot.y - pivotOffset.y);
|
||||||
regionMax = ImVec2(regionMin.x + size.x, regionMin.y + size.y);
|
regionMax = ImVec2(regionMin.x + size.x, regionMin.y + size.y);
|
||||||
@@ -590,7 +692,8 @@ void Engine::renderGameViewportWindow() {
|
|||||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||||
ImVec4 tint(obj.ui.color.r, obj.ui.color.g, obj.ui.color.b, obj.ui.color.a);
|
ImVec4 tint(obj.ui.color.r, obj.ui.color.g, obj.ui.color.b, obj.ui.color.a);
|
||||||
float scale = std::max(0.1f, obj.ui.textScale);
|
float scale = std::max(0.1f, obj.ui.textScale);
|
||||||
float fontSize = std::max(1.0f, ImGui::GetFontSize() * scale);
|
float scaleFactor = std::min(uiScaleX, uiScaleY);
|
||||||
|
float fontSize = std::max(1.0f, ImGui::GetFontSize() * scale * scaleFactor);
|
||||||
ImVec2 textPos = ImVec2(clippedMin.x + 4.0f, clippedMin.y + 2.0f);
|
ImVec2 textPos = ImVec2(clippedMin.x + 4.0f, clippedMin.y + 2.0f);
|
||||||
ImGui::PushClipRect(clippedMin, clippedMax, true);
|
ImGui::PushClipRect(clippedMin, clippedMax, true);
|
||||||
dl->AddText(ImGui::GetFont(), fontSize, textPos, ImGui::GetColorU32(tint), obj.ui.label.c_str());
|
dl->AddText(ImGui::GetFont(), fontSize, textPos, ImGui::GetColorU32(tint), obj.ui.label.c_str());
|
||||||
@@ -630,12 +733,15 @@ void Engine::renderGameViewportWindow() {
|
|||||||
DecomposeMatrix(model, pos, rot, scl);
|
DecomposeMatrix(model, pos, rot, scl);
|
||||||
(void)rot;
|
(void)rot;
|
||||||
ImVec2 newMin(imageMin.x + pos.x, imageMin.y + pos.y);
|
ImVec2 newMin(imageMin.x + pos.x, imageMin.y + pos.y);
|
||||||
ImVec2 newSize(std::max(1.0f, scl.x), std::max(1.0f, scl.y));
|
ImVec2 newSize(std::max(1.0f, scl.x), std::max(1.0f, scl.y));
|
||||||
ImVec2 anchorPoint = anchorToPoint(selected->ui.anchor, parentMin, parentMax);
|
ImVec2 anchorPoint = anchorToPoint(selected->ui.anchor, parentMin, parentMax);
|
||||||
ImVec2 pivotOffset = anchorToPivot(selected->ui.anchor, newSize);
|
ImVec2 pivotOffset = anchorToPivot(selected->ui.anchor, newSize);
|
||||||
ImVec2 pivot(newMin.x + pivotOffset.x, newMin.y + pivotOffset.y);
|
ImVec2 pivot(newMin.x + pivotOffset.x, newMin.y + pivotOffset.y);
|
||||||
selected->ui.position = glm::vec2(pivot.x - anchorPoint.x, pivot.y - anchorPoint.y);
|
float invScaleX = (uiScaleX > 0.0f) ? 1.0f / uiScaleX : 1.0f;
|
||||||
selected->ui.size = glm::vec2(newSize.x, newSize.y);
|
float invScaleY = (uiScaleY > 0.0f) ? 1.0f / uiScaleY : 1.0f;
|
||||||
|
selected->ui.position = glm::vec2((pivot.x - anchorPoint.x) * invScaleX,
|
||||||
|
(pivot.y - anchorPoint.y) * invScaleY);
|
||||||
|
selected->ui.size = glm::vec2(newSize.x * invScaleX, newSize.y * invScaleY);
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
gizmoUsed = true;
|
gizmoUsed = true;
|
||||||
}
|
}
|
||||||
@@ -656,7 +762,6 @@ void Engine::renderGameViewportWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gameViewportFocused = windowFocused && gameViewCursorLocked;
|
gameViewportFocused = windowFocused && gameViewCursorLocked;
|
||||||
ImGui::TextDisabled(gameViewCursorLocked ? "Camera captured (ESC to release)" : "Click to capture");
|
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextDisabled("No player camera found (Camera Type: Player).");
|
ImGui::TextDisabled("No player camera found (Camera Type: Player).");
|
||||||
gameViewportFocused = ImGui::IsWindowFocused();
|
gameViewportFocused = ImGui::IsWindowFocused();
|
||||||
|
|||||||
397
src/Engine.cpp
397
src/Engine.cpp
@@ -3,6 +3,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <chrono>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "ThirdParty/glm/gtc/constants.hpp"
|
#include "ThirdParty/glm/gtc/constants.hpp"
|
||||||
@@ -439,6 +440,7 @@ void Engine::run() {
|
|||||||
deltaTime = std::min(deltaTime, 1.0f / 30.0f);
|
deltaTime = std::min(deltaTime, 1.0f / 30.0f);
|
||||||
|
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
pollProjectLoad();
|
||||||
|
|
||||||
if (!showLauncher) {
|
if (!showLauncher) {
|
||||||
handleKeyboardShortcuts();
|
handleKeyboardShortcuts();
|
||||||
@@ -505,6 +507,10 @@ void Engine::run() {
|
|||||||
}
|
}
|
||||||
audio.update(sceneObjects, listenerCamera, audioShouldPlay);
|
audio.update(sceneObjects, listenerCamera, audioShouldPlay);
|
||||||
|
|
||||||
|
updateCompileJob();
|
||||||
|
updateAutoCompileScripts();
|
||||||
|
processAutoCompileQueue();
|
||||||
|
|
||||||
if (!showLauncher && projectManager.currentProject.isLoaded && rendererInitialized) {
|
if (!showLauncher && projectManager.currentProject.isLoaded && rendererInitialized) {
|
||||||
glm::mat4 view = camera.getViewMatrix();
|
glm::mat4 view = camera.getViewMatrix();
|
||||||
float aspect = static_cast<float>(viewportWidth) / static_cast<float>(viewportHeight);
|
float aspect = static_cast<float>(viewportWidth) / static_cast<float>(viewportHeight);
|
||||||
@@ -613,6 +619,10 @@ void Engine::shutdown() {
|
|||||||
saveCurrentScene();
|
saveCurrentScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (compileWorker.joinable()) {
|
||||||
|
compileWorker.join();
|
||||||
|
}
|
||||||
|
|
||||||
physics.onPlayStop();
|
physics.onPlayStop();
|
||||||
audio.onPlayStop();
|
audio.onPlayStop();
|
||||||
audio.shutdown();
|
audio.shutdown();
|
||||||
@@ -1022,6 +1032,112 @@ void Engine::updateScripts(float delta) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::queueAutoCompile(const fs::path& scriptPath, const fs::file_time_type& sourceTime) {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::path scriptAbs = fs::absolute(scriptPath, ec);
|
||||||
|
if (ec) scriptAbs = scriptPath;
|
||||||
|
std::string key = scriptAbs.lexically_normal().string();
|
||||||
|
if (!autoCompileQueued.insert(key).second) return;
|
||||||
|
|
||||||
|
autoCompileQueue.push_back(scriptAbs);
|
||||||
|
scriptLastAutoCompileTime[key] = sourceTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::updateAutoCompileScripts() {
|
||||||
|
if (!projectManager.currentProject.isLoaded) return;
|
||||||
|
if (showLauncher) return;
|
||||||
|
|
||||||
|
double now = glfwGetTime();
|
||||||
|
if (now - scriptAutoCompileLastCheck < scriptAutoCompileInterval) return;
|
||||||
|
scriptAutoCompileLastCheck = now;
|
||||||
|
|
||||||
|
fs::path configPath = projectManager.currentProject.scriptsConfigPath;
|
||||||
|
if (configPath.empty()) {
|
||||||
|
configPath = projectManager.currentProject.projectPath / "Scripts.modu";
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptBuildConfig config;
|
||||||
|
std::string error;
|
||||||
|
if (!scriptCompiler.loadConfig(configPath, config, error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
packageManager.applyToBuildConfig(config);
|
||||||
|
|
||||||
|
std::unordered_set<std::string> sources;
|
||||||
|
auto addSource = [&](const fs::path& path) {
|
||||||
|
if (path.empty()) return;
|
||||||
|
std::error_code ec;
|
||||||
|
fs::path absPath = fs::absolute(path, ec);
|
||||||
|
if (ec) absPath = path;
|
||||||
|
sources.insert(absPath.lexically_normal().string());
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& obj : sceneObjects) {
|
||||||
|
for (const auto& sc : obj.scripts) {
|
||||||
|
if (sc.path.empty()) continue;
|
||||||
|
addSource(sc.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path scriptsDir = config.scriptsDir;
|
||||||
|
if (!scriptsDir.is_absolute()) {
|
||||||
|
scriptsDir = projectManager.currentProject.projectPath / scriptsDir;
|
||||||
|
}
|
||||||
|
std::error_code dirEc;
|
||||||
|
if (fs::exists(scriptsDir, dirEc)) {
|
||||||
|
for (auto it = fs::recursive_directory_iterator(scriptsDir, dirEc);
|
||||||
|
it != fs::recursive_directory_iterator(); ++it) {
|
||||||
|
if (it->is_directory()) continue;
|
||||||
|
std::string ext = it->path().extension().string();
|
||||||
|
if (ext == ".cpp" || ext == ".cc" || ext == ".cxx" || ext == ".c") {
|
||||||
|
addSource(it->path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& sourceKey : sources) {
|
||||||
|
fs::path sourcePath = sourceKey;
|
||||||
|
std::error_code sourceEc;
|
||||||
|
if (!fs::exists(sourcePath, sourceEc)) continue;
|
||||||
|
auto sourceTime = fs::last_write_time(sourcePath, sourceEc);
|
||||||
|
if (sourceEc) continue;
|
||||||
|
|
||||||
|
ScriptBuildCommands commands;
|
||||||
|
if (!scriptCompiler.makeCommands(config, sourcePath, commands, error)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code binEc;
|
||||||
|
bool binaryExists = fs::exists(commands.binaryPath, binEc);
|
||||||
|
fs::file_time_type binaryTime{};
|
||||||
|
if (binaryExists && !binEc) {
|
||||||
|
binaryTime = fs::last_write_time(commands.binaryPath, binEc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needsCompile = !binaryExists || (!binEc && sourceTime > binaryTime);
|
||||||
|
if (!needsCompile) continue;
|
||||||
|
|
||||||
|
auto it = scriptLastAutoCompileTime.find(sourceKey);
|
||||||
|
if (it != scriptLastAutoCompileTime.end() && sourceTime <= it->second) continue;
|
||||||
|
|
||||||
|
queueAutoCompile(sourcePath, sourceTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::processAutoCompileQueue() {
|
||||||
|
if (compileInProgress) return;
|
||||||
|
if (autoCompileQueue.empty()) return;
|
||||||
|
|
||||||
|
fs::path next = autoCompileQueue.front();
|
||||||
|
autoCompileQueue.pop_front();
|
||||||
|
std::error_code ec;
|
||||||
|
fs::path absPath = fs::absolute(next, ec);
|
||||||
|
if (ec) absPath = next;
|
||||||
|
autoCompileQueued.erase(absPath.lexically_normal().string());
|
||||||
|
|
||||||
|
compileScriptFile(next);
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::updatePlayerController(float delta) {
|
void Engine::updatePlayerController(float delta) {
|
||||||
if (!isPlaying) return;
|
if (!isPlaying) return;
|
||||||
|
|
||||||
@@ -1327,49 +1443,98 @@ void Engine::updateHierarchyWorldTransforms() {
|
|||||||
|
|
||||||
#pragma region Project Lifecycle
|
#pragma region Project Lifecycle
|
||||||
void Engine::OpenProjectPath(const std::string& path) {
|
void Engine::OpenProjectPath(const std::string& path) {
|
||||||
try {
|
startProjectLoad(path);
|
||||||
if (projectManager.loadProject(path)) {
|
}
|
||||||
// Make sure project folders exist even for older/minimal projects
|
|
||||||
if (!fs::exists(projectManager.currentProject.assetsPath)) {
|
|
||||||
fs::create_directories(projectManager.currentProject.assetsPath);
|
|
||||||
}
|
|
||||||
if (!fs::exists(projectManager.currentProject.scenesPath)) {
|
|
||||||
fs::create_directories(projectManager.currentProject.scenesPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
packageManager.setProjectRoot(projectManager.currentProject.projectPath);
|
void Engine::startProjectLoad(const std::string& path) {
|
||||||
|
if (projectLoadInProgress) return;
|
||||||
|
projectManager.errorMessage.clear();
|
||||||
|
projectLoadInProgress = true;
|
||||||
|
projectLoadStartTime = glfwGetTime();
|
||||||
|
projectLoadPath = path;
|
||||||
|
showLauncher = true;
|
||||||
|
|
||||||
if (!initRenderer()) {
|
projectLoadFuture = std::async(std::launch::async, [path]() {
|
||||||
addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error);
|
ProjectLoadResult result;
|
||||||
showLauncher = true;
|
result.path = path;
|
||||||
return;
|
try {
|
||||||
|
Project project;
|
||||||
|
if (project.load(path)) {
|
||||||
|
result.success = true;
|
||||||
|
result.project = std::move(project);
|
||||||
|
} else {
|
||||||
|
result.error = "Failed to load project file";
|
||||||
}
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
if (!physics.isReady() && !physics.init()) {
|
result.error = std::string("Exception opening project: ") + e.what();
|
||||||
addConsoleMessage("Warning: PhysX failed to initialize; physics disabled for this session", ConsoleMessageType::Warning);
|
} catch (...) {
|
||||||
}
|
result.error = "Unknown exception opening project";
|
||||||
|
|
||||||
loadRecentScenes();
|
|
||||||
fs::path contentRoot = projectManager.currentProject.usesNewLayout
|
|
||||||
? projectManager.currentProject.assetsPath
|
|
||||||
: projectManager.currentProject.projectPath;
|
|
||||||
fileBrowser.setProjectRoot(contentRoot);
|
|
||||||
fileBrowser.currentPath = contentRoot;
|
|
||||||
fileBrowser.needsRefresh = true;
|
|
||||||
scriptEditorWindowsDirty = true;
|
|
||||||
scriptEditorWindows.clear();
|
|
||||||
showLauncher = false;
|
|
||||||
addConsoleMessage("Opened project: " + projectManager.currentProject.name, ConsoleMessageType::Info);
|
|
||||||
} else {
|
|
||||||
addConsoleMessage("Error opening project: " + projectManager.errorMessage, ConsoleMessageType::Error);
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
return result;
|
||||||
addConsoleMessage(std::string("Exception opening project: ") + e.what(), ConsoleMessageType::Error);
|
});
|
||||||
showLauncher = true;
|
}
|
||||||
} catch (...) {
|
|
||||||
addConsoleMessage("Unknown exception opening project", ConsoleMessageType::Error);
|
void Engine::pollProjectLoad() {
|
||||||
showLauncher = true;
|
if (!projectLoadInProgress) return;
|
||||||
|
if (!projectLoadFuture.valid()) {
|
||||||
|
projectLoadInProgress = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto state = projectLoadFuture.wait_for(std::chrono::milliseconds(0));
|
||||||
|
if (state == std::future_status::ready) {
|
||||||
|
ProjectLoadResult result = projectLoadFuture.get();
|
||||||
|
projectLoadInProgress = false;
|
||||||
|
finishProjectLoad(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::finishProjectLoad(ProjectLoadResult& result) {
|
||||||
|
if (!result.success) {
|
||||||
|
projectManager.errorMessage = result.error.empty() ? "Failed to load project file" : result.error;
|
||||||
|
addConsoleMessage("Error opening project: " + projectManager.errorMessage, ConsoleMessageType::Error);
|
||||||
|
showLauncher = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
projectManager.currentProject = std::move(result.project);
|
||||||
|
projectManager.addToRecentProjects(projectManager.currentProject.name, result.path);
|
||||||
|
|
||||||
|
// Make sure project folders exist even for older/minimal projects
|
||||||
|
if (!fs::exists(projectManager.currentProject.assetsPath)) {
|
||||||
|
fs::create_directories(projectManager.currentProject.assetsPath);
|
||||||
|
}
|
||||||
|
if (!fs::exists(projectManager.currentProject.scenesPath)) {
|
||||||
|
fs::create_directories(projectManager.currentProject.scenesPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
packageManager.setProjectRoot(projectManager.currentProject.projectPath);
|
||||||
|
|
||||||
|
if (!initRenderer()) {
|
||||||
|
addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error);
|
||||||
|
showLauncher = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!physics.isReady() && !physics.init()) {
|
||||||
|
addConsoleMessage("Warning: PhysX failed to initialize; physics disabled for this session", ConsoleMessageType::Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRecentScenes();
|
||||||
|
fs::path contentRoot = projectManager.currentProject.usesNewLayout
|
||||||
|
? projectManager.currentProject.assetsPath
|
||||||
|
: projectManager.currentProject.projectPath;
|
||||||
|
fileBrowser.setProjectRoot(contentRoot);
|
||||||
|
fileBrowser.currentPath = contentRoot;
|
||||||
|
fileBrowser.needsRefresh = true;
|
||||||
|
scriptEditorWindowsDirty = true;
|
||||||
|
scriptEditorWindows.clear();
|
||||||
|
scriptLastAutoCompileTime.clear();
|
||||||
|
autoCompileQueue.clear();
|
||||||
|
autoCompileQueued.clear();
|
||||||
|
scriptAutoCompileLastCheck = 0.0;
|
||||||
|
showLauncher = false;
|
||||||
|
addConsoleMessage("Opened project: " + projectManager.currentProject.name, ConsoleMessageType::Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::createNewProject(const char* name, const char* location) {
|
void Engine::createNewProject(const char* name, const char* location) {
|
||||||
@@ -1407,6 +1572,10 @@ void Engine::createNewProject(const char* name, const char* location) {
|
|||||||
fileBrowser.needsRefresh = true;
|
fileBrowser.needsRefresh = true;
|
||||||
scriptEditorWindowsDirty = true;
|
scriptEditorWindowsDirty = true;
|
||||||
scriptEditorWindows.clear();
|
scriptEditorWindows.clear();
|
||||||
|
scriptLastAutoCompileTime.clear();
|
||||||
|
autoCompileQueue.clear();
|
||||||
|
autoCompileQueued.clear();
|
||||||
|
scriptAutoCompileLastCheck = 0.0;
|
||||||
|
|
||||||
showLauncher = false;
|
showLauncher = false;
|
||||||
firstFrame = true;
|
firstFrame = true;
|
||||||
@@ -1882,70 +2051,120 @@ void Engine::compileScriptFile(const fs::path& scriptPath) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (compileInProgress) {
|
||||||
|
showCompilePopup = true;
|
||||||
|
lastCompileStatus = "Compile already in progress";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (compileWorker.joinable()) {
|
||||||
|
compileWorker.join();
|
||||||
|
}
|
||||||
|
|
||||||
showCompilePopup = true;
|
showCompilePopup = true;
|
||||||
|
compilePopupHideTime = 0.0;
|
||||||
lastCompileLog.clear();
|
lastCompileLog.clear();
|
||||||
lastCompileStatus = "Compiling " + scriptPath.filename().string();
|
lastCompileStatus = "Compiling " + scriptPath.filename().string();
|
||||||
|
lastCompileSuccess = false;
|
||||||
|
|
||||||
fs::path configPath = projectManager.currentProject.scriptsConfigPath;
|
fs::path configPath = projectManager.currentProject.scriptsConfigPath;
|
||||||
if (configPath.empty()) {
|
if (configPath.empty()) {
|
||||||
configPath = projectManager.currentProject.projectPath / "Scripts.modu";
|
configPath = projectManager.currentProject.projectPath / "Scripts.modu";
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptBuildConfig config;
|
compileInProgress = true;
|
||||||
std::string error;
|
compileResultReady = false;
|
||||||
if (!scriptCompiler.loadConfig(configPath, config, error)) {
|
compileWorker = std::thread([this, scriptPath, configPath]() {
|
||||||
lastCompileSuccess = false;
|
ScriptCompileJobResult result;
|
||||||
lastCompileLog = error;
|
result.scriptPath = scriptPath;
|
||||||
addConsoleMessage("Script config error: " + error, ConsoleMessageType::Error);
|
std::string error;
|
||||||
return;
|
ScriptBuildConfig config;
|
||||||
}
|
if (!scriptCompiler.loadConfig(configPath, config, error)) {
|
||||||
|
result.error = error;
|
||||||
packageManager.applyToBuildConfig(config);
|
} else {
|
||||||
|
packageManager.applyToBuildConfig(config);
|
||||||
ScriptBuildCommands commands;
|
ScriptBuildCommands commands;
|
||||||
if (!scriptCompiler.makeCommands(config, scriptPath, commands, error)) {
|
if (!scriptCompiler.makeCommands(config, scriptPath, commands, error)) {
|
||||||
lastCompileSuccess = false;
|
result.error = error;
|
||||||
lastCompileLog = error;
|
} else {
|
||||||
addConsoleMessage("Script build error: " + error, ConsoleMessageType::Error);
|
ScriptCompileOutput output;
|
||||||
return;
|
if (!scriptCompiler.compile(commands, output, error)) {
|
||||||
}
|
result.compileLog = output.compileLog;
|
||||||
|
result.linkLog = output.linkLog;
|
||||||
ScriptCompileOutput output;
|
result.error = error;
|
||||||
if (!scriptCompiler.compile(commands, output, error)) {
|
} else {
|
||||||
lastCompileSuccess = false;
|
result.success = true;
|
||||||
lastCompileStatus = "Compile failed";
|
result.compileLog = output.compileLog;
|
||||||
lastCompileLog = output.compileLog + output.linkLog + error;
|
result.linkLog = output.linkLog;
|
||||||
addConsoleMessage("Compile failed: " + error, ConsoleMessageType::Error);
|
result.binaryPath = commands.binaryPath;
|
||||||
if (!output.compileLog.empty()) addConsoleMessage(output.compileLog, ConsoleMessageType::Info);
|
result.compiledSource = fs::absolute(scriptPath).lexically_normal().string();
|
||||||
if (!output.linkLog.empty()) addConsoleMessage(output.linkLog, ConsoleMessageType::Info);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptRuntime.unloadAll();
|
|
||||||
|
|
||||||
lastCompileSuccess = true;
|
|
||||||
lastCompileStatus = "Reloading EngineRoot";
|
|
||||||
lastCompileLog = output.compileLog + output.linkLog;
|
|
||||||
addConsoleMessage("Compiled script -> " + commands.binaryPath.string(), ConsoleMessageType::Success);
|
|
||||||
if (!output.compileLog.empty()) addConsoleMessage(output.compileLog, ConsoleMessageType::Info);
|
|
||||||
if (!output.linkLog.empty()) addConsoleMessage(output.linkLog, ConsoleMessageType::Info);
|
|
||||||
|
|
||||||
// Update any ScriptComponents that point to this source with the freshly built binary path.
|
|
||||||
std::string compiledSource = fs::absolute(scriptPath).lexically_normal().string();
|
|
||||||
for (auto& obj : sceneObjects) {
|
|
||||||
for (auto& sc : obj.scripts) {
|
|
||||||
std::error_code ec;
|
|
||||||
fs::path scAbs = fs::absolute(sc.path, ec);
|
|
||||||
std::string scPathNorm = (ec ? fs::path(sc.path) : scAbs).lexically_normal().string();
|
|
||||||
if (scPathNorm == compiledSource) {
|
|
||||||
sc.lastBinaryPath = commands.binaryPath.string();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(compileMutex);
|
||||||
|
compileResult = std::move(result);
|
||||||
|
compileResultReady = true;
|
||||||
|
compileInProgress = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::updateCompileJob() {
|
||||||
|
if (compileResultReady) {
|
||||||
|
if (compileWorker.joinable()) {
|
||||||
|
compileWorker.join();
|
||||||
|
}
|
||||||
|
ScriptCompileJobResult result;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(compileMutex);
|
||||||
|
result = compileResult;
|
||||||
|
compileResultReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
lastCompileSuccess = false;
|
||||||
|
lastCompileStatus = "Compile failed";
|
||||||
|
lastCompileLog = result.compileLog + result.linkLog + result.error;
|
||||||
|
if (!result.error.empty()) {
|
||||||
|
addConsoleMessage("Compile failed: " + result.error, ConsoleMessageType::Error);
|
||||||
|
} else {
|
||||||
|
addConsoleMessage("Compile failed", ConsoleMessageType::Error);
|
||||||
|
}
|
||||||
|
if (!result.compileLog.empty()) addConsoleMessage(result.compileLog, ConsoleMessageType::Info);
|
||||||
|
if (!result.linkLog.empty()) addConsoleMessage(result.linkLog, ConsoleMessageType::Info);
|
||||||
|
} else {
|
||||||
|
scriptRuntime.unloadAll();
|
||||||
|
|
||||||
|
lastCompileSuccess = true;
|
||||||
|
lastCompileStatus = "Reloading ModuCore";
|
||||||
|
lastCompileLog = result.compileLog + result.linkLog;
|
||||||
|
addConsoleMessage("Compiled script -> " + result.binaryPath.string(), ConsoleMessageType::Success);
|
||||||
|
if (!result.compileLog.empty()) addConsoleMessage(result.compileLog, ConsoleMessageType::Info);
|
||||||
|
if (!result.linkLog.empty()) addConsoleMessage(result.linkLog, ConsoleMessageType::Info);
|
||||||
|
|
||||||
|
for (auto& obj : sceneObjects) {
|
||||||
|
for (auto& sc : obj.scripts) {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::path scAbs = fs::absolute(sc.path, ec);
|
||||||
|
std::string scPathNorm = (ec ? fs::path(sc.path) : scAbs).lexically_normal().string();
|
||||||
|
if (scPathNorm == result.compiledSource) {
|
||||||
|
sc.lastBinaryPath = result.binaryPath.string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptEditorWindowsDirty = true;
|
||||||
|
refreshScriptEditorWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
compilePopupHideTime = glfwGetTime() + 1.0;
|
||||||
|
showCompilePopup = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh scripted editor window registry in case new tabs were added by this build.
|
if (!compileInProgress && showCompilePopup && compilePopupHideTime > 0.0 &&
|
||||||
scriptEditorWindowsDirty = true;
|
glfwGetTime() >= compilePopupHideTime) {
|
||||||
refreshScriptEditorWindows();
|
showCompilePopup = false;
|
||||||
|
compilePopupOpened = false;
|
||||||
|
compilePopupHideTime = 0.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::refreshScriptEditorWindows() {
|
void Engine::refreshScriptEditorWindows() {
|
||||||
|
|||||||
51
src/Engine.h
51
src/Engine.h
@@ -14,6 +14,12 @@
|
|||||||
#include "PackageManager.h"
|
#include "PackageManager.h"
|
||||||
#include "../include/Window/Window.h"
|
#include "../include/Window/Window.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <atomic>
|
||||||
|
#include <deque>
|
||||||
|
#include <future>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
void window_size_callback(GLFWwindow* window, int width, int height);
|
void window_size_callback(GLFWwindow* window, int width, int height);
|
||||||
|
|
||||||
@@ -113,8 +119,13 @@ private:
|
|||||||
int previewCameraId = -1;
|
int previewCameraId = -1;
|
||||||
bool gameViewCursorLocked = false;
|
bool gameViewCursorLocked = false;
|
||||||
bool gameViewportFocused = false;
|
bool gameViewportFocused = false;
|
||||||
bool showUITextOverlay = false;
|
bool showGameProfiler = true;
|
||||||
bool showCanvasOverlay = false;
|
bool showCanvasOverlay = false;
|
||||||
|
int gameViewportResolutionIndex = 0;
|
||||||
|
int gameViewportCustomWidth = 1920;
|
||||||
|
int gameViewportCustomHeight = 1080;
|
||||||
|
float gameViewportZoom = 1.0f;
|
||||||
|
bool gameViewportAutoFit = true;
|
||||||
int activePlayerId = -1;
|
int activePlayerId = -1;
|
||||||
MeshBuilder meshBuilder;
|
MeshBuilder meshBuilder;
|
||||||
char meshBuilderPath[260] = "";
|
char meshBuilderPath[260] = "";
|
||||||
@@ -135,9 +146,40 @@ private:
|
|||||||
PhysicsSystem physics;
|
PhysicsSystem physics;
|
||||||
AudioSystem audio;
|
AudioSystem audio;
|
||||||
bool showCompilePopup = false;
|
bool showCompilePopup = false;
|
||||||
|
bool compilePopupOpened = false;
|
||||||
|
double compilePopupHideTime = 0.0;
|
||||||
bool lastCompileSuccess = false;
|
bool lastCompileSuccess = false;
|
||||||
std::string lastCompileStatus;
|
std::string lastCompileStatus;
|
||||||
std::string lastCompileLog;
|
std::string lastCompileLog;
|
||||||
|
struct ScriptCompileJobResult {
|
||||||
|
bool success = false;
|
||||||
|
fs::path scriptPath;
|
||||||
|
fs::path binaryPath;
|
||||||
|
std::string compiledSource;
|
||||||
|
std::string compileLog;
|
||||||
|
std::string linkLog;
|
||||||
|
std::string error;
|
||||||
|
};
|
||||||
|
std::atomic<bool> compileInProgress = false;
|
||||||
|
std::atomic<bool> compileResultReady = false;
|
||||||
|
std::thread compileWorker;
|
||||||
|
std::mutex compileMutex;
|
||||||
|
ScriptCompileJobResult compileResult;
|
||||||
|
std::unordered_map<std::string, fs::file_time_type> scriptLastAutoCompileTime;
|
||||||
|
std::deque<fs::path> autoCompileQueue;
|
||||||
|
std::unordered_set<std::string> autoCompileQueued;
|
||||||
|
double scriptAutoCompileLastCheck = 0.0;
|
||||||
|
double scriptAutoCompileInterval = 0.5;
|
||||||
|
struct ProjectLoadResult {
|
||||||
|
bool success = false;
|
||||||
|
Project project;
|
||||||
|
std::string error;
|
||||||
|
std::string path;
|
||||||
|
};
|
||||||
|
bool projectLoadInProgress = false;
|
||||||
|
double projectLoadStartTime = 0.0;
|
||||||
|
std::string projectLoadPath;
|
||||||
|
std::future<ProjectLoadResult> projectLoadFuture;
|
||||||
bool specMode = false;
|
bool specMode = false;
|
||||||
bool testMode = false;
|
bool testMode = false;
|
||||||
bool collisionWireframe = false;
|
bool collisionWireframe = false;
|
||||||
@@ -190,11 +232,18 @@ private:
|
|||||||
void renderViewport();
|
void renderViewport();
|
||||||
void renderGameViewportWindow();
|
void renderGameViewportWindow();
|
||||||
void renderDialogs();
|
void renderDialogs();
|
||||||
|
void updateCompileJob();
|
||||||
void renderProjectBrowserPanel();
|
void renderProjectBrowserPanel();
|
||||||
void renderScriptEditorWindows();
|
void renderScriptEditorWindows();
|
||||||
void refreshScriptEditorWindows();
|
void refreshScriptEditorWindows();
|
||||||
Camera makeCameraFromObject(const SceneObject& obj) const;
|
Camera makeCameraFromObject(const SceneObject& obj) const;
|
||||||
void compileScriptFile(const fs::path& scriptPath);
|
void compileScriptFile(const fs::path& scriptPath);
|
||||||
|
void updateAutoCompileScripts();
|
||||||
|
void processAutoCompileQueue();
|
||||||
|
void queueAutoCompile(const fs::path& scriptPath, const fs::file_time_type& sourceTime);
|
||||||
|
void startProjectLoad(const std::string& path);
|
||||||
|
void pollProjectLoad();
|
||||||
|
void finishProjectLoad(ProjectLoadResult& result);
|
||||||
void updateScripts(float delta);
|
void updateScripts(float delta);
|
||||||
void updatePlayerController(float delta);
|
void updatePlayerController(float delta);
|
||||||
void updateRigidbody2D(float delta);
|
void updateRigidbody2D(float delta);
|
||||||
|
|||||||
@@ -898,12 +898,36 @@ void Renderer::ensureQuad() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::drawFullscreenQuad() {
|
void Renderer::drawFullscreenQuad() {
|
||||||
|
recordFullscreenDraw();
|
||||||
if (quadVAO == 0) ensureQuad();
|
if (quadVAO == 0) ensureQuad();
|
||||||
glBindVertexArray(quadVAO);
|
glBindVertexArray(quadVAO);
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::resetStats(RenderStats& stats) {
|
||||||
|
stats.drawCalls = 0;
|
||||||
|
stats.meshDraws = 0;
|
||||||
|
stats.fullscreenDraws = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::recordDrawCall() {
|
||||||
|
if (!activeStats) return;
|
||||||
|
activeStats->drawCalls += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::recordMeshDraw() {
|
||||||
|
if (!activeStats) return;
|
||||||
|
activeStats->drawCalls += 1;
|
||||||
|
activeStats->meshDraws += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::recordFullscreenDraw() {
|
||||||
|
if (!activeStats) return;
|
||||||
|
activeStats->drawCalls += 1;
|
||||||
|
activeStats->fullscreenDraws += 1;
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::clearHistory() {
|
void Renderer::clearHistory() {
|
||||||
historyValid = false;
|
historyValid = false;
|
||||||
if (historyTarget.fbo != 0 && historyTarget.width > 0 && historyTarget.height > 0) {
|
if (historyTarget.fbo != 0 && historyTarget.width > 0 && historyTarget.height > 0) {
|
||||||
@@ -1303,6 +1327,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (meshToDraw) {
|
if (meshToDraw) {
|
||||||
|
recordMeshDraw();
|
||||||
meshToDraw->draw();
|
meshToDraw->draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1313,6 +1338,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
|||||||
(float)width / height,
|
(float)width / height,
|
||||||
nearPlane, farPlane);
|
nearPlane, farPlane);
|
||||||
|
|
||||||
|
recordDrawCall();
|
||||||
skybox->draw(glm::value_ptr(view), glm::value_ptr(proj));
|
skybox->draw(glm::value_ptr(view), glm::value_ptr(proj));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1474,6 +1500,8 @@ unsigned int Renderer::applyPostProcessing(const std::vector<SceneObject>& scene
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int selectedId, float fovDeg, float nearPlane, float farPlane, bool drawColliders) {
|
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int selectedId, float fovDeg, float nearPlane, float farPlane, bool drawColliders) {
|
||||||
|
resetStats(viewportStats);
|
||||||
|
activeStats = &viewportStats;
|
||||||
updateMirrorTargets(camera, sceneObjects, currentWidth, currentHeight, fovDeg, nearPlane, farPlane);
|
updateMirrorTargets(camera, sceneObjects, currentWidth, currentHeight, fovDeg, nearPlane, farPlane);
|
||||||
renderSceneInternal(camera, sceneObjects, currentWidth, currentHeight, true, fovDeg, nearPlane, farPlane, true);
|
renderSceneInternal(camera, sceneObjects, currentWidth, currentHeight, true, fovDeg, nearPlane, farPlane, true);
|
||||||
if (drawColliders) {
|
if (drawColliders) {
|
||||||
@@ -1485,11 +1513,17 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
|
|||||||
renderSelectionOutline(camera, sceneObjects, selectedId, fovDeg, nearPlane, farPlane);
|
renderSelectionOutline(camera, sceneObjects, selectedId, fovDeg, nearPlane, farPlane);
|
||||||
unsigned int result = applyPostProcessing(sceneObjects, viewportTexture, currentWidth, currentHeight, true);
|
unsigned int result = applyPostProcessing(sceneObjects, viewportTexture, currentWidth, currentHeight, true);
|
||||||
displayTexture = result ? result : viewportTexture;
|
displayTexture = result ? result : viewportTexture;
|
||||||
|
activeStats = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane, bool applyPostFX) {
|
unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane, bool applyPostFX) {
|
||||||
|
resetStats(previewStats);
|
||||||
|
activeStats = &previewStats;
|
||||||
ensureRenderTarget(previewTarget, width, height);
|
ensureRenderTarget(previewTarget, width, height);
|
||||||
if (previewTarget.fbo == 0) return 0;
|
if (previewTarget.fbo == 0) {
|
||||||
|
activeStats = nullptr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, previewTarget.fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, previewTarget.fbo);
|
||||||
glViewport(0, 0, width, height);
|
glViewport(0, 0, width, height);
|
||||||
@@ -1499,9 +1533,11 @@ unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vecto
|
|||||||
updateMirrorTargets(camera, sceneObjects, width, height, fovDeg, nearPlane, farPlane);
|
updateMirrorTargets(camera, sceneObjects, width, height, fovDeg, nearPlane, farPlane);
|
||||||
renderSceneInternal(camera, sceneObjects, width, height, true, fovDeg, nearPlane, farPlane, true);
|
renderSceneInternal(camera, sceneObjects, width, height, true, fovDeg, nearPlane, farPlane, true);
|
||||||
if (!applyPostFX) {
|
if (!applyPostFX) {
|
||||||
|
activeStats = nullptr;
|
||||||
return previewTarget.texture;
|
return previewTarget.texture;
|
||||||
}
|
}
|
||||||
unsigned int processed = applyPostProcessing(sceneObjects, previewTarget.texture, width, height, false);
|
unsigned int processed = applyPostProcessing(sceneObjects, previewTarget.texture, width, height, false);
|
||||||
|
activeStats = nullptr;
|
||||||
return processed ? processed : previewTarget.texture;
|
return processed ? processed : previewTarget.texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ public:
|
|||||||
class Camera;
|
class Camera;
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
|
public:
|
||||||
|
struct RenderStats {
|
||||||
|
int drawCalls = 0;
|
||||||
|
int meshDraws = 0;
|
||||||
|
int fullscreenDraws = 0;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned int framebuffer = 0, viewportTexture = 0, rbo = 0;
|
unsigned int framebuffer = 0, viewportTexture = 0, rbo = 0;
|
||||||
int currentWidth = 800, currentHeight = 600;
|
int currentWidth = 800, currentHeight = 600;
|
||||||
@@ -115,6 +122,9 @@ private:
|
|||||||
unsigned int displayTexture = 0;
|
unsigned int displayTexture = 0;
|
||||||
bool historyValid = false;
|
bool historyValid = false;
|
||||||
std::unordered_map<int, RenderTarget> mirrorTargets;
|
std::unordered_map<int, RenderTarget> mirrorTargets;
|
||||||
|
RenderStats viewportStats;
|
||||||
|
RenderStats previewStats;
|
||||||
|
RenderStats* activeStats = nullptr;
|
||||||
|
|
||||||
void setupFBO();
|
void setupFBO();
|
||||||
void ensureRenderTarget(RenderTarget& target, int w, int h);
|
void ensureRenderTarget(RenderTarget& target, int w, int h);
|
||||||
@@ -122,6 +132,10 @@ private:
|
|||||||
void updateMirrorTargets(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane);
|
void updateMirrorTargets(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane);
|
||||||
void ensureQuad();
|
void ensureQuad();
|
||||||
void drawFullscreenQuad();
|
void drawFullscreenQuad();
|
||||||
|
void resetStats(RenderStats& stats);
|
||||||
|
void recordDrawCall();
|
||||||
|
void recordMeshDraw();
|
||||||
|
void recordFullscreenDraw();
|
||||||
void clearHistory();
|
void clearHistory();
|
||||||
void clearTarget(RenderTarget& target);
|
void clearTarget(RenderTarget& target);
|
||||||
void renderSceneInternal(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane, bool drawMirrorObjects);
|
void renderSceneInternal(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane, bool drawMirrorObjects);
|
||||||
@@ -154,4 +168,6 @@ public:
|
|||||||
|
|
||||||
Skybox* getSkybox() { return skybox; }
|
Skybox* getSkybox() { return skybox; }
|
||||||
unsigned int getViewportTexture() const { return displayTexture ? displayTexture : viewportTexture; }
|
unsigned int getViewportTexture() const { return displayTexture ? displayTexture : viewportTexture; }
|
||||||
|
const RenderStats& getLastViewportStats() const { return viewportStats; }
|
||||||
|
const RenderStats& getLastPreviewStats() const { return previewStats; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "ScriptRuntime.h"
|
#include "ScriptRuntime.h"
|
||||||
#include "Engine.h"
|
#include "Engine.h"
|
||||||
#include "SceneObject.h"
|
#include "SceneObject.h"
|
||||||
|
#include "ThirdParty/imgui/imgui.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -195,6 +196,154 @@ void ScriptContext::GetPlanarYawPitchVectors(float pitchDeg, float yawDeg,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 ScriptContext::GetMoveInputWASD(float pitchDeg, float yawDeg) const {
|
||||||
|
glm::vec3 forward(0.0f);
|
||||||
|
glm::vec3 right(0.0f);
|
||||||
|
glm::vec3 move(0.0f);
|
||||||
|
GetPlanarYawPitchVectors(pitchDeg, yawDeg, forward, right);
|
||||||
|
if (ImGui::IsKeyDown(ImGuiKey_W)) move += forward;
|
||||||
|
if (ImGui::IsKeyDown(ImGuiKey_S)) move -= forward;
|
||||||
|
if (ImGui::IsKeyDown(ImGuiKey_D)) move += right;
|
||||||
|
if (ImGui::IsKeyDown(ImGuiKey_A)) move -= right;
|
||||||
|
if (glm::length(move) > 0.001f) move = glm::normalize(move);
|
||||||
|
return move;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptContext::ApplyMouseLook(float& pitchDeg, float& yawDeg, float sensitivity, float maxDelta,
|
||||||
|
float deltaTime, bool requireMouseButton) const {
|
||||||
|
if (requireMouseButton && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) return false;
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
glm::vec2 delta(io.MouseDelta.x, io.MouseDelta.y);
|
||||||
|
float len = glm::length(delta);
|
||||||
|
if (len > maxDelta) delta *= (maxDelta / len);
|
||||||
|
yawDeg -= delta.x * 50.0f * sensitivity * deltaTime;
|
||||||
|
pitchDeg -= delta.y * 50.0f * sensitivity * deltaTime;
|
||||||
|
pitchDeg = std::clamp(pitchDeg, -89.0f, 89.0f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptContext::IsSprintDown() const {
|
||||||
|
return ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptContext::IsJumpDown() const {
|
||||||
|
return ImGui::IsKeyDown(ImGuiKey_Space);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptContext::ResolveGround(float capsuleHalf, float probeExtra, float groundSnap, float verticalVelocity,
|
||||||
|
glm::vec3* outHitPos, bool* outHitGround) const {
|
||||||
|
if (!object) return false;
|
||||||
|
glm::vec3 hitPos(0.0f);
|
||||||
|
glm::vec3 hitNormal(0.0f, 1.0f, 0.0f);
|
||||||
|
float hitDist = 0.0f;
|
||||||
|
float probeDist = capsuleHalf + probeExtra;
|
||||||
|
glm::vec3 rayStart = object->position + glm::vec3(0.0f, 0.1f, 0.0f);
|
||||||
|
bool hitGround = RaycastClosest(rayStart, glm::vec3(0.0f, -1.0f, 0.0f), probeDist,
|
||||||
|
&hitPos, &hitNormal, &hitDist);
|
||||||
|
bool grounded = hitGround && hitNormal.y > 0.25f &&
|
||||||
|
hitDist <= capsuleHalf + groundSnap &&
|
||||||
|
verticalVelocity <= 0.35f;
|
||||||
|
if (!hitGround) {
|
||||||
|
grounded = object->position.y <= capsuleHalf + 0.12f && verticalVelocity <= 0.35f;
|
||||||
|
}
|
||||||
|
if (outHitPos) *outHitPos = hitPos;
|
||||||
|
if (outHitGround) *outHitGround = hitGround;
|
||||||
|
return grounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptContext::ApplyVelocity(const glm::vec3& velocity, float deltaTime) {
|
||||||
|
if (!object) return;
|
||||||
|
if (!SetRigidbodyVelocity(velocity)) {
|
||||||
|
object->position += velocity * deltaTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptContext::BindStandaloneMovementSettings(StandaloneMovementSettings& settings) {
|
||||||
|
AutoSetting("moveTuning", settings.moveTuning);
|
||||||
|
AutoSetting("lookTuning", settings.lookTuning);
|
||||||
|
AutoSetting("capsuleTuning", settings.capsuleTuning);
|
||||||
|
AutoSetting("gravityTuning", settings.gravityTuning);
|
||||||
|
AutoSetting("enableMouseLook", settings.enableMouseLook);
|
||||||
|
AutoSetting("requireMouseButton", settings.requireMouseButton);
|
||||||
|
AutoSetting("enforceCollider", settings.enforceCollider);
|
||||||
|
AutoSetting("enforceRigidbody", settings.enforceRigidbody);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptContext::DrawStandaloneMovementInspector(StandaloneMovementSettings& settings, bool* showDebug) {
|
||||||
|
BindStandaloneMovementSettings(settings);
|
||||||
|
ImGui::TextUnformatted("Standalone Movement Controller");
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::DragFloat3("Walk/Run/Jump", &settings.moveTuning.x, 0.05f, 0.0f, 25.0f, "%.2f");
|
||||||
|
ImGui::DragFloat2("Look Sens/Clamp", &settings.lookTuning.x, 0.01f, 0.0f, 500.0f, "%.2f");
|
||||||
|
ImGui::DragFloat3("Height/Radius/Snap", &settings.capsuleTuning.x, 0.02f, 0.0f, 5.0f, "%.2f");
|
||||||
|
ImGui::DragFloat3("Gravity/Probe/MaxFall", &settings.gravityTuning.x, 0.05f, -50.0f, 50.0f, "%.2f");
|
||||||
|
ImGui::Checkbox("Enable Mouse Look", &settings.enableMouseLook);
|
||||||
|
ImGui::Checkbox("Hold RMB to Look", &settings.requireMouseButton);
|
||||||
|
ImGui::Checkbox("Force Collider", &settings.enforceCollider);
|
||||||
|
ImGui::Checkbox("Force Rigidbody", &settings.enforceRigidbody);
|
||||||
|
if (showDebug) {
|
||||||
|
AutoSetting("showDebug", *showDebug);
|
||||||
|
ImGui::Checkbox("Show Debug", showDebug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptContext::TickStandaloneMovement(StandaloneMovementState& state, StandaloneMovementSettings& settings,
|
||||||
|
float deltaTime, StandaloneMovementDebug* debug) {
|
||||||
|
if (!object) return;
|
||||||
|
BindStandaloneMovementSettings(settings);
|
||||||
|
if (settings.enforceCollider) EnsureCapsuleCollider(settings.capsuleTuning.x, settings.capsuleTuning.y);
|
||||||
|
if (settings.enforceRigidbody) EnsureRigidbody(true, false);
|
||||||
|
|
||||||
|
const float walkSpeed = settings.moveTuning.x;
|
||||||
|
const float runSpeed = settings.moveTuning.y;
|
||||||
|
const float jumpStrength = settings.moveTuning.z;
|
||||||
|
const float lookSensitivity = settings.lookTuning.x;
|
||||||
|
const float maxMouseDelta = glm::max(5.0f, settings.lookTuning.y);
|
||||||
|
const float height = settings.capsuleTuning.x;
|
||||||
|
const float groundSnap = settings.capsuleTuning.z;
|
||||||
|
const float gravity = settings.gravityTuning.x;
|
||||||
|
const float probeExtra = settings.gravityTuning.y;
|
||||||
|
const float maxFall = glm::max(1.0f, settings.gravityTuning.z);
|
||||||
|
|
||||||
|
if (settings.enableMouseLook) {
|
||||||
|
ApplyMouseLook(state.pitch, state.yaw, lookSensitivity, maxMouseDelta, deltaTime, settings.requireMouseButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 move = GetMoveInputWASD(state.pitch, state.yaw);
|
||||||
|
float targetSpeed = IsSprintDown() ? runSpeed : walkSpeed;
|
||||||
|
glm::vec3 velocity = move * targetSpeed;
|
||||||
|
float capsuleHalf = std::max(0.1f, height * 0.5f);
|
||||||
|
|
||||||
|
glm::vec3 physVel;
|
||||||
|
bool havePhysVel = GetRigidbodyVelocity(physVel);
|
||||||
|
if (havePhysVel) state.verticalVelocity = physVel.y;
|
||||||
|
|
||||||
|
glm::vec3 hitPos(0.0f);
|
||||||
|
bool hitGround = false;
|
||||||
|
bool grounded = ResolveGround(capsuleHalf, probeExtra, groundSnap, state.verticalVelocity, &hitPos, &hitGround);
|
||||||
|
if (grounded) {
|
||||||
|
state.verticalVelocity = 0.0f;
|
||||||
|
if (!havePhysVel) {
|
||||||
|
object->position.y = hitGround ? std::max(object->position.y, hitPos.y + capsuleHalf) : capsuleHalf;
|
||||||
|
}
|
||||||
|
if (IsJumpDown()) state.verticalVelocity = jumpStrength;
|
||||||
|
} else {
|
||||||
|
state.verticalVelocity += gravity * deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.verticalVelocity = std::clamp(state.verticalVelocity, -maxFall, maxFall);
|
||||||
|
velocity.y = state.verticalVelocity;
|
||||||
|
glm::vec3 rotation(state.pitch, state.yaw, 0.0f);
|
||||||
|
SetRotation(rotation);
|
||||||
|
SetRigidbodyRotation(rotation);
|
||||||
|
ApplyVelocity(velocity, deltaTime);
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
debug->velocity = velocity;
|
||||||
|
debug->grounded = grounded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ScriptContext::IsUIButtonPressed() const {
|
bool ScriptContext::IsUIButtonPressed() const {
|
||||||
return object && object->type == ObjectType::UIButton && object->ui.buttonPressed;
|
return object && object->type == ObjectType::UIButton && object->ui.buttonPressed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,37 @@ struct ScriptContext {
|
|||||||
void SetRotation(const glm::vec3& rot);
|
void SetRotation(const glm::vec3& rot);
|
||||||
void SetScale(const glm::vec3& scl);
|
void SetScale(const glm::vec3& scl);
|
||||||
void GetPlanarYawPitchVectors(float pitchDeg, float yawDeg, glm::vec3& outForward, glm::vec3& outRight) const;
|
void GetPlanarYawPitchVectors(float pitchDeg, float yawDeg, glm::vec3& outForward, glm::vec3& outRight) const;
|
||||||
|
glm::vec3 GetMoveInputWASD(float pitchDeg, float yawDeg) const;
|
||||||
|
bool ApplyMouseLook(float& pitchDeg, float& yawDeg, float sensitivity, float maxDelta, float deltaTime,
|
||||||
|
bool requireMouseButton) const;
|
||||||
|
bool IsSprintDown() const;
|
||||||
|
bool IsJumpDown() const;
|
||||||
|
bool ResolveGround(float capsuleHalf, float probeExtra, float groundSnap, float verticalVelocity,
|
||||||
|
glm::vec3* outHitPos = nullptr, bool* outHitGround = nullptr) const;
|
||||||
|
void ApplyVelocity(const glm::vec3& velocity, float deltaTime);
|
||||||
|
struct StandaloneMovementSettings {
|
||||||
|
glm::vec3 moveTuning = glm::vec3(4.5f, 7.5f, 6.5f);
|
||||||
|
glm::vec3 lookTuning = glm::vec3(0.12f, 200.0f, 0.0f);
|
||||||
|
glm::vec3 capsuleTuning = glm::vec3(1.8f, 0.4f, 0.2f);
|
||||||
|
glm::vec3 gravityTuning = glm::vec3(-9.81f, 0.4f, 30.0f);
|
||||||
|
bool enableMouseLook = true;
|
||||||
|
bool requireMouseButton = false;
|
||||||
|
bool enforceCollider = true;
|
||||||
|
bool enforceRigidbody = true;
|
||||||
|
};
|
||||||
|
struct StandaloneMovementState {
|
||||||
|
float pitch = 0.0f;
|
||||||
|
float yaw = 0.0f;
|
||||||
|
float verticalVelocity = 0.0f;
|
||||||
|
};
|
||||||
|
struct StandaloneMovementDebug {
|
||||||
|
glm::vec3 velocity = glm::vec3(0.0f);
|
||||||
|
bool grounded = false;
|
||||||
|
};
|
||||||
|
void BindStandaloneMovementSettings(StandaloneMovementSettings& settings);
|
||||||
|
void DrawStandaloneMovementInspector(StandaloneMovementSettings& settings, bool* showDebug = nullptr);
|
||||||
|
void TickStandaloneMovement(StandaloneMovementState& state, StandaloneMovementSettings& settings,
|
||||||
|
float deltaTime, StandaloneMovementDebug* debug = nullptr);
|
||||||
// UI helpers
|
// UI helpers
|
||||||
bool IsUIButtonPressed() const;
|
bool IsUIButtonPressed() const;
|
||||||
bool IsUIInteractable() const;
|
bool IsUIInteractable() const;
|
||||||
|
|||||||
Reference in New Issue
Block a user