feat: add prefabs

This commit is contained in:
2026-10-18 17:51:56 +02:00
parent d21c2ccfd7
commit 7df65f52b5
14 changed files with 1154 additions and 494 deletions
+53
View File
@@ -9,7 +9,9 @@
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include <destrum/Graphics/Managers/Renderer2D.h>
#include <destrum/Input/InputManager.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Serialization/PrefabSerializer.h>
#include <destrum/Serialization/SceneSerializer.h>
#include <components/FirstPersonController.h>
@@ -120,6 +122,20 @@ void LightKeeper::drawDebugMenuBar()
saveCurrentScene();
}
ImGui::Separator();
ImGui::TextDisabled("Prefab");
ImGui::SetNextItemWidth(320.0f);
ImGui::InputText("Prefab path", prefabPath, sizeof(prefabPath));
const bool hasSelection = SceneManager::GetInstance().GetCurrentScene().GetSelectedObject() != nullptr;
if (ImGui::MenuItem("Save Selected as Prefab", nullptr, false, hasSelection)) {
saveSelectedPrefab();
}
if (ImGui::MenuItem("Instantiate Prefab")) {
instantiatePrefab();
}
if (!sceneStatus.empty()) {
ImGui::Separator();
ImGui::TextWrapped("%s", sceneStatus.c_str());
@@ -270,6 +286,43 @@ void LightKeeper::saveCurrentScene()
}
}
void LightKeeper::saveSelectedPrefab()
{
try {
const GameObject* selected = SceneManager::GetInstance().GetCurrentScene().GetSelectedObject();
if (selected == nullptr) {
sceneStatus = "Select an object to save as a prefab";
return;
}
const auto path = ResolveScenePath(prefabPath, m_params.exeDir);
const auto parent = path.parent_path();
if (!parent.empty()) {
std::filesystem::create_directories(parent);
}
sceneStatus = PrefabSerializer::Save(*selected, path)
? "Saved prefab \"" + selected->GetName() + "\" to " + path.string()
: "Failed to save prefab to " + path.string();
} catch (const std::exception& exception) {
sceneStatus = std::string{"Prefab save failed: "} + exception.what();
}
}
void LightKeeper::instantiatePrefab()
{
try {
const auto path = ResolveScenePath(prefabPath, m_params.exeDir);
const GameObject* instance = PrefabSerializer::Instantiate(
SceneManager::GetInstance().GetCurrentScene(), path.string());
sceneStatus = instance != nullptr
? "Instantiated \"" + instance->GetName() + "\" from " + path.string()
: "Failed to instantiate prefab " + path.string();
} catch (const std::exception& exception) {
sceneStatus = std::string{"Prefab instantiate failed: "} + exception.what();
}
}
void LightKeeper::loadScene()
{
try {
+39 -19
View File
@@ -31,6 +31,7 @@
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace {
// The visible ground plane is at y = -1
@@ -268,11 +269,12 @@ namespace {
float top;
};
void CreateWall(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources, const std::string& name,
std::vector<GameObject*> CreateWall(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources, const std::string& name,
const glm::vec3& floorCenter, float length, float height, bool alongX, MaterialID materialID,
const std::optional<WallOpening>& opening = std::nullopt)
{
constexpr float thickness = 0.3f;
std::vector<GameObject*> pieces;
const auto createPiece = [&](const std::string& suffix, float from, float to, float bottom, float top)
{
@@ -285,13 +287,14 @@ namespace {
const glm::vec3 size = alongX
? glm::vec3{to - from, top - bottom, thickness}
: glm::vec3{thickness, top - bottom, to - from};
CreateBox(scene, gfxDevice, resources, name + suffix, center, size, materialID, RigidbodyType::Static);
pieces.push_back(
CreateBox(scene, gfxDevice, resources, name + suffix, center, size, materialID, RigidbodyType::Static));
};
const float halfLength = length * 0.5f;
if (!opening) {
createPiece("", -halfLength, halfLength, 0.0f, height);
return;
return pieces;
}
const float openingStart = opening->offset - opening->width * 0.5f;
@@ -300,6 +303,7 @@ namespace {
createPiece("_Right", openingEnd, halfLength, 0.0f, height);
createPiece("_Below", openingStart, openingEnd, 0.0f, opening->bottom);
createPiece("_Above", openingStart, openingEnd, opening->top, height);
return pieces;
}
GameObject* CreateCeilingLight(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources,
@@ -336,26 +340,40 @@ namespace {
constexpr float doorOffset = -1.5f;
constexpr float doorWidth = 1.2f;
CreateBox(scene, gfxDevice, resources, "House_Floor", center + glm::vec3{0.0f, 0.03f, 0.0f},
{width, 0.06f, depth}, floorMaterial, RigidbodyType::Static);
GameObject* house = scene.CreateGameObject("House");
house->GetTransform().SetWorldPosition(center);
const auto addToHouse = [&](GameObject* object)
{
object->GetTransform().SetParent(&house->GetTransform(), true);
};
const auto addWallToHouse = [&](const std::vector<GameObject*>& pieces)
{
for (GameObject* piece : pieces) {
addToHouse(piece);
}
};
CreateWall(scene, gfxDevice, resources, "House_FrontWall", center + glm::vec3{0.0f, 0.0f, depth * 0.5f},
width + 0.3f, wallHeight, true, wallMaterial,
WallOpening{.offset = doorOffset, .width = doorWidth, .bottom = 0.0f, .top = 2.2f});
CreateWall(scene, gfxDevice, resources, "House_BackWall", center - glm::vec3{0.0f, 0.0f, depth * 0.5f},
width + 0.3f, wallHeight, true, wallMaterial,
WallOpening{.offset = 1.5f, .width = 1.4f, .bottom = 1.0f, .top = 2.0f});
CreateWall(scene, gfxDevice, resources, "House_LeftWall", center - glm::vec3{width * 0.5f, 0.0f, 0.0f},
depth - 0.3f, wallHeight, false, wallMaterial);
CreateWall(scene, gfxDevice, resources, "House_RightWall", center + glm::vec3{width * 0.5f, 0.0f, 0.0f},
depth - 0.3f, wallHeight, false, wallMaterial,
WallOpening{.offset = 0.0f, .width = 1.6f, .bottom = 1.0f, .top = 2.0f});
addToHouse(CreateBox(scene, gfxDevice, resources, "House_Floor", center + glm::vec3{0.0f, 0.03f, 0.0f},
{width, 0.06f, depth}, floorMaterial, RigidbodyType::Static));
CreateBox(scene, gfxDevice, resources, "House_Roof", center + glm::vec3{0.0f, wallHeight + 0.15f, 0.0f},
{width + 0.8f, 0.3f, depth + 0.8f}, roofMaterial, RigidbodyType::Static);
addWallToHouse(CreateWall(scene, gfxDevice, resources, "House_FrontWall", center + glm::vec3{0.0f, 0.0f, depth * 0.5f},
width + 0.3f, wallHeight, true, wallMaterial,
WallOpening{.offset = doorOffset, .width = doorWidth, .bottom = 0.0f, .top = 2.2f}));
addWallToHouse(CreateWall(scene, gfxDevice, resources, "House_BackWall", center - glm::vec3{0.0f, 0.0f, depth * 0.5f},
width + 0.3f, wallHeight, true, wallMaterial,
WallOpening{.offset = 1.5f, .width = 1.4f, .bottom = 1.0f, .top = 2.0f}));
addWallToHouse(CreateWall(scene, gfxDevice, resources, "House_LeftWall", center - glm::vec3{width * 0.5f, 0.0f, 0.0f},
depth - 0.3f, wallHeight, false, wallMaterial));
addWallToHouse(CreateWall(scene, gfxDevice, resources, "House_RightWall", center + glm::vec3{width * 0.5f, 0.0f, 0.0f},
depth - 0.3f, wallHeight, false, wallMaterial,
WallOpening{.offset = 0.0f, .width = 1.6f, .bottom = 1.0f, .top = 2.0f}));
const GameObject* ceilingLight =
addToHouse(CreateBox(scene, gfxDevice, resources, "House_Roof", center + glm::vec3{0.0f, wallHeight + 0.15f, 0.0f},
{width + 0.8f, 0.3f, depth + 0.8f}, roofMaterial, RigidbodyType::Static));
GameObject* ceilingLight =
CreateCeilingLight(scene, gfxDevice, resources, center + glm::vec3{0.0f, wallHeight - 0.06f, 0.0f});
addToHouse(ceilingLight);
// Inside, next to the door
Material switchMaterial = Material::SimpleColor(glm::vec3{0.9f}, "LightSwitchMaterial");
@@ -369,6 +387,7 @@ namespace {
auto* switchButton = lightSwitch->AddComponent<InteractableButton>();
switchButton->SetCycleColors(false);
switchButton->AddTarget(*ceilingLight);
addToHouse(lightSwitch);
GameObject* sofa = ModelSpawner::SpawnStaticModel(
scene, gfxDevice, resources, "game://sofa.glb", StaticModelOptions(),
@@ -377,6 +396,7 @@ namespace {
sofa->GetTransform().SetWorldPosition({-3.7, -1, -17.5});
sofa->GetTransform().SetWorldRotation({180, 0, 180});
sofa->GetTransform().SetWorldScale({3, 3, 3});
addToHouse(sofa);
}
void CreateFlightHelmet(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)