From 08a6df4e681f8ced5fff82f92b9ea1117e520b6d Mon Sep 17 00:00:00 2001 From: Bram Verhulst Date: Wed, 12 Aug 2026 01:37:36 +0200 Subject: [PATCH] feat: add basic ah game (Kinda shitty, i should use components) --- Readme.md | 2 +- lightkeeper/include/Lightkeeper.h | 32 +- lightkeeper/src/Lightkeeper.cpp | 713 +++++++++--------------------- lightkeeper/src/main.cpp | 5 +- 4 files changed, 231 insertions(+), 521 deletions(-) diff --git a/Readme.md b/Readme.md index 6f27f75..2e44cd9 100644 --- a/Readme.md +++ b/Readme.md @@ -119,7 +119,7 @@ cmake --install build --config Release ## Demo app -`lightkeeper` is the current demo application. It creates an SDL window, initializes the Destrum app, loads assets, sets up a scene, renders meshes, skyboxes, and a skinned animated character. +`lightkeeper` is the current game application. It launches **Star Catcher**, a small playable loop built from the engine's scene and rendering systems. Move the blue catcher with `A`/`D` or the arrow keys, catch orange stars, and press `R` to restart after three misses. Press `Escape` to quit. ## Development notes diff --git a/lightkeeper/include/Lightkeeper.h b/lightkeeper/include/Lightkeeper.h index 0428234..32da01e 100644 --- a/lightkeeper/include/Lightkeeper.h +++ b/lightkeeper/include/Lightkeeper.h @@ -1,10 +1,10 @@ #ifndef LIGHTKEEPER_H #define LIGHTKEEPER_H -#include +#include +#include #include -#include #include #include "destrum/Graphics/Resources/Cubemap.h" @@ -21,19 +21,35 @@ public: void customFixedUpdate(float dt) override; void onWindowResize(int newWidth, int newHeight) override; + private: + struct FallingStar { + GameObject* object{}; + float speed{}; + }; + + void resetGame(); + void respawnStar(FallingStar& star, float minimumHeight); + void drawHud(); + Camera camera{glm::vec3(0.f, 0.f, -5.f), glm::vec3(0, 1, 0)}; - MeshID sphereMesh; - MaterialID sphereMaterial; + MeshID sphereMesh{NULL_MESH_ID}; + MaterialID playerMaterial{NULL_MATERIAL_ID}; + MaterialID starMaterial{NULL_MATERIAL_ID}; std::unique_ptr skyboxCubemap; - GameObject* capybara = nullptr; + GameObject* player{nullptr}; + std::vector stars; + std::mt19937 randomEngine{0xD35A1234u}; + std::uniform_real_distribution spawnX{-5.5f, 5.5f}; + std::uniform_real_distribution spawnHeight{0.0f, 2.0f}; + std::uniform_real_distribution spawnSpeed{2.8f, 4.2f}; - char scenePath[512]{"scenes/debug_scene.json"}; - char newSceneName[128]{"EmptyScene"}; - std::string sceneStatus; + int score{0}; + int misses{0}; + bool gameOver{false}; }; #endif //LIGHTKEEPER_H diff --git a/lightkeeper/src/Lightkeeper.cpp b/lightkeeper/src/Lightkeeper.cpp index a760a82..37a2e8e 100644 --- a/lightkeeper/src/Lightkeeper.cpp +++ b/lightkeeper/src/Lightkeeper.cpp @@ -1,52 +1,35 @@ #include "Lightkeeper.h" -#include -#include -#include "glm/gtx/transform.hpp" -#include "spdlog/spdlog.h" -#include -#include -#include -#include - -#include "destrum/Components/MeshRendererComponent.h" -#include "destrum/Components/Rotator.h" -#include "destrum/Components/Spinner.h" -#include "destrum/Components/OrbitAndSpin.h" -#include "destrum/ObjectModel/GameObject.h" -#include "destrum/Util/ModelDoc.h" -#include "destrum/Components/Animator.h" - - -#include -#include +#include +#include #include -#include -#include "imgui.h" -#include "destrum/Components/Physics/SphereCollider.h" -#include "destrum/Util/ModelDocUtils.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spdlog/spdlog.h" namespace { - std::filesystem::path ResolveScenePath( - std::string_view value, - const std::filesystem::path& exeDir) { - if (value.empty()) { - throw std::invalid_argument("Scene path cannot be empty"); - } - - if (value.find("://") != std::string_view::npos) { - return AssetFS::GetInstance().GetFullPath(value); - } - - const std::filesystem::path path{value}; - return path.is_absolute() ? path : exeDir / path; - } + constexpr int kStarCount = 3; + constexpr int kMaxMisses = 3; + constexpr float kPlayerY = -5.0f; + constexpr float kCatchY = -4.25f; + constexpr float kPlayerSpeed = 7.0f; + constexpr float kPlayerHalfWidth = 1.15f; } -LightKeeper::LightKeeper() : App() -{ -} +LightKeeper::LightKeeper() = default; LightKeeper::~LightKeeper() { @@ -63,461 +46,180 @@ void LightKeeper::customInit() resources.init(gfxDevice); renderer.init(gfxDevice, resources, m_params.renderSize); - const float aspectRatio = static_cast(m_params.renderSize.x) / static_cast(m_params.renderSize.y); - camera.setAspectRatio(aspectRatio); + camera.m_position = glm::vec3{0.0f, 0.0f, -10.0f}; + camera.SetRotation(glm::radians(90.0f), 0.0f); + camera.setAspectRatio( + static_cast(m_params.renderSize.x) / + static_cast(m_params.renderSize.y)); - ModelDoc::LoadOptions staticModelOptions{}; - staticModelOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; - staticModelOptions.loadMaterials = true; - staticModelOptions.loadSkeleton = false; - staticModelOptions.loadAnimations = false; + const auto skyboxID = + AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr"); + const auto vertShaderPath = + AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.vert"); + const auto fragShaderPath = + AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.frag"); - camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f))); - - auto& scene = SceneManager::GetInstance().CreateScene("Main"); - - // scene.Add(testCube); - - // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg"); - // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/mars.jpg"); - const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr"); - // - // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/test-skybox.png"); - // - const auto vertShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.vert"); - const auto fragShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.frag"); - // skyboxCubemap = std::make_unique(); - skyboxCubemap->LoadCubeMap(gfxDevice, resources, skyboxID.generic_string()); - skyboxCubemap->InitCubemapPipeline(gfxDevice, resources, vertShaderPath.generic_string(), fragShaderPath.generic_string()); + skyboxCubemap->LoadCubeMap(gfxDevice, resources, skyboxID); + skyboxCubemap->InitCubemapPipeline( + gfxDevice, + resources, + vertShaderPath.generic_string(), + fragShaderPath.generic_string()); skyboxCubemap->CreateCubeMap(gfxDevice, resources); - renderer.setSkyboxTexture(skyboxCubemap->GetCubeMapImageID()); - const auto planeObj = scene.CreateGameObject("GroundPlane"); - const auto planeMeshComp = planeObj->AddComponent(); + ModelDoc::LoadOptions modelOptions{}; + modelOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; + modelOptions.loadMaterials = true; + modelOptions.loadSkeleton = false; + modelOptions.loadAnimations = false; - auto planeModel = ModelDoc::LoadModel( - AssetFS::GetInstance().GetFullPath("game://plane.glb").generic_string(), - staticModelOptions - ); - ModelDocUtils::LogModelDocSummary(planeModel, "plane.glb"); - - const auto& planePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(planeModel, "game://plane.glb"); - const auto planeMeshID = resources.meshes().addMesh(gfxDevice, planePrimitive.mesh); - - const auto planeTexturePath = ModelDocUtils::PickTexturePath( - planeModel, - planePrimitive, - AssetFS::GetInstance().GetFullPath("game://grass.png") - ); - - const auto planeTextureID = resources.loadImageFromFile(gfxDevice, planeTexturePath); - const auto planeMaterialID = resources.materials().addMaterial({ - .baseColor = ModelDocUtils::GetImportedBaseColor( - planeModel, planePrimitive), - .textureFilteringMode = TextureFilteringMode::Nearest, - .diffuseTexture = planeTextureID, - .name = ModelDocUtils::GetImportedMaterialName( - planeModel, planePrimitive, "GroundPlaneMaterial"), - }); - - planeMeshComp->SetMeshID(planeMeshID); - planeMeshComp->SetMaterialID(planeMaterialID); - planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.0f, 0.f)); - planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f)); - planeObj->AddComponent(glm::vec3{10.0f, 0.5f, 10.0f}); - - auto* floorRb = planeObj->AddComponent(); - floorRb->SetType(RigidbodyType::Static); - scene.GetPhysics().RegisterGameObject(*planeObj); - - const auto CharObj = scene.CreateGameObject("Character"); - capybara = CharObj; - - ModelDoc::LoadOptions characterModelOptions{}; - characterModelOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; - characterModelOptions.loadMaterials = true; - characterModelOptions.loadSkeleton = true; - characterModelOptions.loadAnimations = true; - - // auto charModel = ModelDoc::LoadModel( - // AssetFS::GetInstance().GetFullPath("engine://cotw-capybara-male/source/capybara.fbx").generic_string(), - // characterModelOptions - // ); - // ModelDocUtils::LogModelDocSummary(charModel, "capybara.fbx"); - // - // const auto& charPrimitive = ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow( - // charModel, - // "engine://cotw-capybara-male/source/capybara.fbx" - // ); - // - // const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh); - // - // const auto charTexturePath = ModelDocUtils::PickTexturePath( - // charModel, - // charPrimitive, - // AssetFS::GetInstance().GetFullPath( - // "engine://cotw-capybara-male/textures/capybara_male_light_brown_dif.ddsc.DECA.RE.pngballs") - // ); - // - // const auto charTextureID = resources.loadImageFromFile(gfxDevice, charTexturePath); - // // const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg")); - // const auto charMaterialID = resources.materials().addMaterial({ - // .baseColor = ModelDocUtils::GetImportedBaseColor( - // charModel, charPrimitive), - // .diffuseTexture = charTextureID, - // .name = ModelDocUtils::GetImportedMaterialName( - // charModel, charPrimitive, "CharacterMaterial"), - // }); - // - // const auto charMeshComp = CharObj->AddComponent(); - // charMeshComp->SetMeshID(charMeshID); - // charMeshComp->SetMaterialID(charMaterialID); - // - // const auto animator = CharObj->AddComponent(); - // animator->setSkeleton(std::move(charModel.skeleton)); - // - // std::string firstAnimationName; - // - // for (auto& clip : charModel.animations) - // { - // spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration); - // - // if (firstAnimationName.empty()) - // firstAnimationName = clip.name; - // - // animator->addClip(std::make_shared(std::move(clip))); - // } - // - // if (!firstAnimationName.empty()) - // { - // spdlog::info("Playing animation: '{}'", firstAnimationName); - // animator->play(firstAnimationName); - // } - // - // animator->play("capybara_canter_fwd_01|capybara_canter_fwd_01|run"); - // - // CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f)); - // CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f); - - // ModelDoc::LoadOptions options{}; - // options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; - // - // auto model = ModelDoc::LoadModel( - // AssetFS::GetInstance() - // .GetFullPath("engine://multiple_object_test.fbx") - // .generic_string(), - // options - // ); - // - // ModelDocUtils::LogModelDocSummary(model, "engine://multiple_object_test.fbx"); - // - // auto root = std::make_shared("MultiMeshModel"); - // root->GetTransform().SetWorldPosition(glm::vec3(0.1f, 0.1f, 0.1f)); - // scene.Add(root); - // - // for (std::size_t i = 0; i < model.primitives.size(); ++i) { - // const auto &primitive = model.primitives[i]; - // - // const auto meshID = meshCache.addMesh(gfxDevice, primitive.mesh); - // - // const auto texturePath = ModelDocUtils::PickTexturePath( - // model, - // primitive, - // AssetFS::GetInstance().GetFullPath("engine://textures/white.png") - // ); - // - // const auto textureID = gfxDevice.loadImageFromFile(texturePath); - // - // const auto materialID = materialCache.addMaterial(gfxDevice, { - // .baseColor = ModelDocUtils::GetImportedBaseColor( - // model, primitive), - // .diffuseTexture = textureID, - // .name = ModelDocUtils::GetImportedMaterialName( - // model, - // primitive, - // primitive.mesh.name + "_Material" - // ), - // }); - // - // auto part = std::make_shared( - // primitive.mesh.name.empty() - // ? "Primitive_" + std::to_string(i) - // : primitive.mesh.name - // ); - // - // auto meshComp = part->AddComponent(); - // meshComp->SetMeshID(meshID); - // meshComp->SetMaterialID(materialID); - // - // scene.Add(part); - // } - - - // { - // const auto CharObj = scene.CreateGameObject("Character"); - // - // ModelDoc::LoadOptions characterOptions{}; - // characterOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; - // characterOptions.loadMaterials = true; - // characterOptions.loadSkeleton = true; - // characterOptions.loadAnimations = false; - // - // auto charModel = AssetManager::GetInstance().LoadModel("engine://characterMedium.fbx", characterOptions); - // - // const auto& charPrimitive = - // ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow( - // charModel, - // "engine://characterMedium.fbx" - // ); - // - // const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh); - // - // const auto charTextureID = resources.loadImageFromFile(gfxDevice, - // AssetFS::GetInstance().GetFullPath("engine://textures/criminalMaleA.png") - // ); - // - // const auto charMaterialID = resources.materials().addMaterial({ - // .baseColor = ModelDocUtils::GetImportedBaseColor( - // charModel, charPrimitive), - // .diffuseTexture = charTextureID, - // .name = ModelDocUtils::GetImportedMaterialName( - // charModel, - // charPrimitive, - // "CharacterMaterial" - // ), - // }); - // - // const auto charMeshComp = CharObj->AddComponent(); - // charMeshComp->SetMeshID(charMeshID); - // charMeshComp->SetMaterialID(charMaterialID); - // - // const auto animator = CharObj->AddComponent(); - // animator->setSkeleton(charModel.skeleton); - // - // auto runClips = AssetManager::GetInstance().LoadAnimationClips( - // "engine://run.fbx", - // charModel.skeleton - // ); - // - // for (auto& clip : runClips) - // { - // spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration); - // animator->addClip(std::make_shared(std::move(clip))); - // } - // - // if (!runClips.empty()) - // { - // animator->play("Root|Run"); - // } - // - // CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f)); - // CharObj->GetTransform().SetWorldPosition(glm::vec3(5, 0, 0)); - // } - // - // auto cubeModel = ModelDoc::LoadModel( - // AssetFS::GetInstance() - // .GetFullPath("engine://cube.fbx") - // .generic_string(), - // staticModelOptions - // ); - // ModelDocUtils::LogModelDocSummary(cubeModel, "cube.fbx"); - // - // const auto& cubePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(cubeModel, "game://cube.fbx"); - // - // - // const auto eliasTextuerPath = ModelDocUtils::PickTexturePath( - // cubeModel, - // cubePrimitive, - // AssetFS::GetInstance().GetFullPath("game://grass.png") - // ); - // - // const auto eliasTextueID = resources.loadImageFromFile(gfxDevice, eliasTextuerPath); - // const auto eliasMaterialID = resources.materials().addMaterial({ - // .baseColor = ModelDocUtils::GetImportedBaseColor( - // planeModel, planePrimitive), - // .textureFilteringMode = - // TextureFilteringMode::Anisotropic, - // .diffuseTexture = eliasTextueID, - // .name = ModelDocUtils::GetImportedMaterialName( - // planeModel, planePrimitive, "GroundPlaneMaterial"), - // }); - // - // const auto cubeMeshID = resources.meshes().addMesh(gfxDevice, cubePrimitive.mesh); - // // - // // for (int i{0}; i < 100; i++) - // // { - // // auto cube = std::make_shared("Cube"); - // // - // // cube->AddComponent(glm::vec3{0.5f}); - // // cube->AddComponent(); - // // - // // auto meshComp = cube->AddComponent(); - // // meshComp->SetMeshID(cubeMeshID); - // // meshComp->SetMaterialID(eliasMaterialID); - // // - // // cube->GetTransform().SetWorldPosition(glm::vec3(0.0f, i, 0.0f)); - // // cube->GetTransform().SetWorldScale(glm::vec3(0.005f)); - // // - // // scene.Add(cube); - // // scene.GetPhysics().RegisterGameObject(*cube); - // // } - // - // // const int cubeCount = 10; - // // const float spacing = 1.0f; - // // - // // for (int x = 0; x < cubeCount; x++) - // // { - // // for (int y = 0; y < 3; y++) - // // { - // // for (int z = 0; z < cubeCount; z++) - // // { - // // auto cube = std::make_shared("Cube"); - // // - // // cube->AddComponent(glm::vec3{0.5f}); - // // cube->AddComponent(); - // // - // // auto meshComp = cube->AddComponent(); - // // meshComp->SetMeshID(cubeMeshID); - // // meshComp->SetMaterialID(eliasMaterialID); - // // - // // cube->GetTransform().SetWorldPosition(glm::vec3( - // // (x - cubeCount / 2.0f) * spacing, - // // y * spacing + 0, - // // (z - cubeCount / 2.0f) * spacing - // // )); - // // - // // cube->GetTransform().SetWorldScale(glm::vec3(0.005f)); - // // - // // scene.Add(cube); - // // scene.GetPhysics().RegisterGameObject(*cube); - // // } - // // } - // // } - - - // { - auto sphereModel = AssetManager::GetInstance().LoadModel("engine://sphere.fbx", staticModelOptions); - ModelDocUtils::LogModelDocSummary(sphereModel, "sphere.fbx"); - // - const auto& spherePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(sphereModel, "game://sphere.fbx"); - // - // - // const auto sphereTexturePath = ModelDocUtils::PickTexturePath( - // sphereModel, - // spherePrimitive, - // AssetFS::GetInstance().GetFullPath("game://238882.png") - // ); - // - // const auto sphereTextureID = resources.loadImageFromFile(gfxDevice, sphereTexturePath); - // sphereMaterial = resources.materials().addMaterial({ - // .baseColor = ModelDocUtils::GetImportedBaseColor(planeModel, planePrimitive), - // .textureFilteringMode =TextureFilteringMode::Anisotropic, - // .diffuseTexture = sphereTextureID, - // .name = ModelDocUtils::GetImportedMaterialName( - // planeModel, planePrimitive, - // "GroundPlaneMaterial"), - // }); - // + const auto sphereModel = AssetManager::GetInstance().LoadModel( + "engine://sphere.fbx", + modelOptions); + const auto& spherePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow( + sphereModel, + "engine://sphere.fbx"); sphereMesh = resources.meshes().addMesh(gfxDevice, spherePrimitive.mesh); - // - // } - sphereMaterial = resources.materials().addSimpleColorMaterial({0.5f, 0.3f, 0.8f}, "Blue"); + playerMaterial = resources.materials().addSimpleColorMaterial( + {0.08f, 0.75f, 1.0f}, + "Catcher Blue"); + starMaterial = resources.materials().addSimpleColorMaterial( + {1.0f, 0.55f, 0.05f}, + "Falling Star Orange"); + auto& scene = SceneManager::GetInstance().CreateScene("Star Catcher"); + + player = scene.CreateGameObject("Catcher"); + auto* playerRenderer = player->AddComponent(); + playerRenderer->SetMeshID(sphereMesh); + playerRenderer->SetMaterialID(playerMaterial); + player->GetTransform().SetLocalPosition({0.0f, kPlayerY, 0.0f}); + player->GetTransform().SetLocalScale(glm::vec3{0.012f, 0.0035f, 0.009f}); + + stars.reserve(kStarCount); + for (int index = 0; index < kStarCount; ++index) { + auto* starObject = scene.CreateGameObject( + "Star " + std::to_string(index + 1)); + auto* starRenderer = starObject->AddComponent(); + starRenderer->SetMeshID(sphereMesh); + starRenderer->SetMaterialID(starMaterial); + starObject->GetTransform().SetLocalScale(glm::vec3{0.0045f}); + stars.push_back({starObject, 0.0f}); + } + + resetGame(); +} + +void LightKeeper::respawnStar(FallingStar& star, float minimumHeight) +{ + star.speed = spawnSpeed(randomEngine) + static_cast(score) * 0.08f; + star.object->SetActive(true); + star.object->GetTransform().SetLocalPosition({ + spawnX(randomEngine), + minimumHeight + spawnHeight(randomEngine), + 0.0f}); +} + +void LightKeeper::resetGame() +{ + score = 0; + misses = 0; + gameOver = false; + + if (player != nullptr) { + player->SetActive(true); + player->GetTransform().SetLocalPosition({0.0f, kPlayerY, 0.0f}); + } + + for (std::size_t index = 0; index < stars.size(); ++index) { + respawnStar(stars[index], 6.5f + static_cast(index) * 0.85f); + } } 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); + auto& input = InputManager::GetInstance(); + + if (input.WasKeyPressed(SDL_SCANCODE_ESCAPE)) { + isRunning = false; + return; + } + + if (input.WasKeyPressed(SDL_SCANCODE_R)) { + resetGame(); + } + + if (!gameOver) { + float moveDirection = 0.0f; + if (input.IsKeyDown(SDL_SCANCODE_A) || input.IsKeyDown(SDL_SCANCODE_LEFT)) { + moveDirection += 1.0f; + } + if (input.IsKeyDown(SDL_SCANCODE_D) || input.IsKeyDown(SDL_SCANCODE_RIGHT)) { + moveDirection -= 1.0f; + } + + glm::vec3 playerPosition = player->GetTransform().GetLocalPosition(); + playerPosition.x = std::clamp( + playerPosition.x + moveDirection * kPlayerSpeed * dt, + -5.8f, + 5.8f); + player->GetTransform().SetLocalPosition(playerPosition); + + for (auto& star : stars) { + glm::vec3 starPosition = star.object->GetTransform().GetLocalPosition(); + starPosition.y -= star.speed * dt; + star.object->GetTransform().SetLocalPosition(starPosition); + + if (starPosition.y > kCatchY) { + continue; + } + + const bool caught = std::abs(starPosition.x - playerPosition.x) <= + kPlayerHalfWidth; + if (caught) { + ++score; + } else { + ++misses; + if (misses >= kMaxMisses) { + gameOver = true; + } + } + + if (!gameOver) { + respawnStar(star, 7.0f); + } + } + } + SceneManager::GetInstance().Update(dt); SceneManager::GetInstance().LateUpdate(dt); + drawHud(); +} - if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_1)) - { - renderer.setRenderWireframe(!renderer.getRenderWireframe()); - } +void LightKeeper::drawHud() +{ + constexpr ImGuiWindowFlags flags = + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoNav; - ImGui::Begin("Test"); - if (ImGui::Button("SPawn ball")) - { - auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere"); - sphere->AddComponent(1.5f); - auto rb = sphere->AddComponent(); - rb->SetMass(1000); + ImGui::SetNextWindowPos({24.0f, 24.0f}, ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.65f); + ImGui::Begin("Star Catcher HUD", nullptr, flags); + ImGui::Text("STAR CATCHER"); + ImGui::Separator(); + ImGui::Text("Score %03d", score); + ImGui::Text("Lives %d", kMaxMisses - misses); + ImGui::TextDisabled("A/D or arrow keys to move"); - auto meshRenderComp = sphere->AddComponent(); - meshRenderComp->SetMaterialID(sphereMaterial); - meshRenderComp->SetMeshID(sphereMesh); - - sphere->GetTransform().SetWorldPosition((std::rand() % 2) - 0.5f, 100, (std::rand() % 2) - 0.5f); - sphere->GetTransform().SetWorldScale(glm::vec3{0.015f}); - - - SceneManager::GetInstance().GetCurrentScene().GetPhysics().RefreshGameObject(*sphere); - } - ImGui::End(); - - ImGui::Begin("Scene Debug"); - ImGui::InputText("Scene path", scenePath, sizeof(scenePath)); - ImGui::InputText("New scene name", newSceneName, sizeof(newSceneName)); - - if (ImGui::Button("Save Current Scene")) { - try { - const auto path = ResolveScenePath(scenePath, m_params.exeDir); - const auto parent = path.parent_path(); - if (!parent.empty()) { - std::filesystem::create_directories(parent); - } - - if (SceneSerializer::Save( - SceneManager::GetInstance().GetCurrentScene(), - path)) { - sceneStatus = "Saved scene to " + path.string(); - } else { - sceneStatus = "Failed to save scene to " + path.string(); - } - } catch (const std::exception& exception) { - sceneStatus = std::string{"Save failed: "} + exception.what(); - } - } - - ImGui::SameLine(); - if (ImGui::Button("Load Scene")) { - try { - const auto path = ResolveScenePath(scenePath, m_params.exeDir); - if (SceneSerializer::Load( - SceneManager::GetInstance().GetCurrentScene(), - path)) { - sceneStatus = "Loaded scene from " + path.string(); - } else { - sceneStatus = "Failed to load scene from " + path.string(); - } - } catch (const std::exception& exception) { - sceneStatus = std::string{"Load failed: "} + exception.what(); - } - } - - if (ImGui::Button("New Empty Scene")) { - try { - const std::string name = newSceneName[0] != '\0' - ? newSceneName - : "EmptyScene"; - auto& sceneManager = SceneManager::GetInstance(); - const int newSceneIndex = sceneManager.GetSceneCount(); - sceneManager.CreateScene(name); - sceneManager.SwitchScene(newSceneIndex); - sceneStatus = "Created and switched to scene '" + name + "'"; - } catch (const std::exception& exception) { - sceneStatus = std::string{"New scene failed: "} + exception.what(); - } - } - - if (!sceneStatus.empty()) { - ImGui::TextWrapped("%s", sceneStatus.c_str()); + if (gameOver) { + ImGui::Separator(); + ImGui::Text("GAME OVER"); + ImGui::Text("Press R to play again"); } ImGui::End(); } @@ -531,53 +233,41 @@ void LightKeeper::customDraw() renderer.beginDrawing(gfxDevice); - const RenderContext ctx{ + const GameRenderer::SceneData sceneData{ + .camera = camera, + .ambientColor = glm::vec3{0.1f}, + .ambientIntensity = 0.5f, + .fogColor = glm::vec3{0.02f, 0.03f, 0.08f}, + .fogDensity = 0.01f}; + const RenderContext context{ .renderer = renderer, .camera = camera, - .sceneData = { - .camera = camera, - .ambientColor = glm::vec3(0.1f), - .ambientIntensity = 0.5f, - .fogColor = glm::vec3(0.5f), - .fogDensity = 0.01f - } - }; + .sceneData = sceneData}; - SceneManager::GetInstance().Render(ctx); + SceneManager::GetInstance().Render(context); renderer.endDrawing(); - const auto& drawImage = renderer.getDrawImage(); - - renderer.draw( - cmd, gfxDevice, camera, GameRenderer::SceneData{ - camera, glm::vec3(0.1f), 0.5f, glm::vec3(0.5f), 0.01f - }); - + renderer.draw(cmd, gfxDevice, camera, sceneData); gfxDevice.endFrame( - cmd, drawImage, { - .clearColor = {{0.f, 0.f, 0.5f, 1.f}}, + cmd, + renderer.getDrawImage(), + { + .clearColor = {{0.005f, 0.008f, 0.03f, 1.0f}}, .drawImageBlitRect = glm::ivec4{}, - .imguiPass = &imguiPass, - }); + .imguiPass = &imguiPass}); } void LightKeeper::customCleanup() { - // auto device = gfxDevice.getDevice().device; - - // vkDeviceWaitIdle(device); - gfxDevice.waitIdle(); - SceneManager::GetInstance().Destroy(); + stars.clear(); + player = nullptr; - if (skyboxCubemap) - { + if (skyboxCubemap) { skyboxCubemap->cleanup(gfxDevice); skyboxCubemap.reset(); } - - renderer.cleanup(gfxDevice); } void LightKeeper::customFixedUpdate(float dt) @@ -587,7 +277,10 @@ void LightKeeper::customFixedUpdate(float dt) void LightKeeper::onWindowResize(int newWidth, int newHeight) { - renderer.resize(gfxDevice, glm::ivec2{newWidth, newHeight}); - const float aspectRatio = static_cast(newWidth) / static_cast(newHeight); - camera.setAspectRatio(aspectRatio); + if (newWidth <= 0 || newHeight <= 0) { + return; + } + + renderer.resize(gfxDevice, {newWidth, newHeight}); + camera.setAspectRatio(static_cast(newWidth) / static_cast(newHeight)); } diff --git a/lightkeeper/src/main.cpp b/lightkeeper/src/main.cpp index 5703bd7..45b8901 100644 --- a/lightkeeper/src/main.cpp +++ b/lightkeeper/src/main.cpp @@ -25,9 +25,10 @@ int main(int argc, char* argv[]) { app.init({ .windowSize = {1200, 800}, .renderSize = {1200, 800}, - .appName = "Destrum Engine", - .windowTitle = "Lightkeeper", + .appName = "Destrum Star Catcher", + .windowTitle = "Star Catcher", .exeDir = exeDir, + .showDebugUi = false, }); app.run(); app.cleanup();