feat: add new fancy reflection

This commit is contained in:
2026-11-01 23:12:00 +01:00
parent 08acd84404
commit 6a85d17899
58 changed files with 380 additions and 1055 deletions
+1 -3
View File
@@ -13,9 +13,7 @@ public:
void OnTriggerEnter(GameObject* other) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void SetSpawnOffset(const glm::vec3& offset) { m_SpawnOffset = offset; }
[[nodiscard]] glm::vec3 GetSpawnPoint() const;
+1 -4
View File
@@ -16,10 +16,7 @@ public:
void LateUpdate(float dt) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ResolveReferences(const ObjectMap& objects) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void SetPlayer(const GameObject& player) { m_Player = ObjectRef(player); }
void SetCrosshair(const GameObject& crosshair) { m_Crosshair = ObjectRef(crosshair); }
@@ -20,8 +20,8 @@ public:
void Start() override;
void Update(float dt) override;
void FixedUpdate(float fixedDt) override;
void Reflect(FieldVisitor& v) override;
void ImGuiInspector() override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
[[nodiscard]] float GetYaw() const { return m_Yaw; }
+2 -3
View File
@@ -11,9 +11,8 @@ public:
void Start() override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void OnFieldsEdited() override;
void SetOn(bool on);
[[nodiscard]] bool IsOn() const { return m_On; }
@@ -14,8 +14,7 @@ public:
void Start() override;
void Update(float dt) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void Reflect(FieldVisitor& v) override;
void ImGuiInspector() override;
void SetTravelOffset(const glm::vec3& offset) { m_TravelOffset = offset; }
@@ -14,6 +14,7 @@ public:
void Start() override;
void FixedUpdate(float fixedDt) override;
void Reflect(FieldVisitor& v) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
@@ -10,9 +10,7 @@ public:
std::string GetTypeName() const override { return "PrintComponent"; }
void Start() override;
void Update(float dt) override;
void ImGuiInspector() override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json&) override;
void Reflect(FieldVisitor& v) override;
void SetMessage(std::string_view message)
{
@@ -11,8 +11,7 @@ public:
void OnTriggerEnter(GameObject* other) override;
void OnTriggerExit(GameObject* other) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json&) override;
void Reflect(FieldVisitor& v) override;
int triggerEnterCount{0};
int triggerExitCount{0};
+1 -3
View File
@@ -9,9 +9,7 @@ public:
std::string GetTypeName() const override { return "WinGoal"; }
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
[[nodiscard]] bool IsReached() const { return m_Reached; }
void Reset() { m_Reached = false; }
+1 -4
View File
@@ -13,10 +13,7 @@ public:
void LateUpdate(float dt) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ResolveReferences(const ObjectMap& objects) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void SetGoal(const GameObject& goal) { m_Goal = ObjectRef(goal); }
void SetScreen(const GameObject& screen) { m_Screen = ObjectRef(screen); }
+3 -15
View File
@@ -1,12 +1,10 @@
#include <components/Checkpoint.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Serialization/Reflect.h>
#include <components/PlayerRespawn.h>
#include <imgui.h>
#include <glm/gtc/type_ptr.hpp>
#include <spdlog/spdlog.h>
Checkpoint::Checkpoint(GameObject& parent)
@@ -35,17 +33,7 @@ void Checkpoint::OnTriggerEnter(GameObject* other)
spdlog::info("{} reached checkpoint {}", other->GetName(), GetGameObject()->GetName());
}
nlohmann::json Checkpoint::Serialize() const
void Checkpoint::Reflect(FieldVisitor& v)
{
return {{"spawnOffset", m_SpawnOffset}};
}
void Checkpoint::Deserialize(const nlohmann::json& data)
{
m_SpawnOffset = data.value("spawnOffset", m_SpawnOffset);
}
void Checkpoint::ImGuiInspector()
{
ImGui::DragFloat3("Spawn offset", glm::value_ptr(m_SpawnOffset), 0.05f);
v.Field("spawnOffset", m_SpawnOffset, {.speed = 0.05f});
}
+9 -48
View File
@@ -6,10 +6,7 @@
#include <destrum/Components/UI/UIGraphic.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Util/ImGuiUtils.h>
#include <imgui.h>
#include <destrum/Serialization/Reflect.h>
namespace {
void TintHierarchy(GameObject& object, const glm::vec4& color)
@@ -59,49 +56,13 @@ void CrosshairUI::LateUpdate(float)
TintHierarchy(*crosshair, focused ? m_FocusedColor : m_Color);
}
nlohmann::json CrosshairUI::Serialize() const
void CrosshairUI::Reflect(FieldVisitor& v)
{
return {
{"player", m_Player},
{"crosshair", m_Crosshair},
{"focusIndicator", m_FocusIndicator},
{"color", m_Color},
{"focusedColor", m_FocusedColor},
{"size", m_Size},
{"focusedSize", m_FocusedSize},
};
}
void CrosshairUI::Deserialize(const nlohmann::json& data)
{
m_Player = data.value("player", ObjectRef{});
m_Crosshair = data.value("crosshair", ObjectRef{});
m_FocusIndicator = data.value("focusIndicator", ObjectRef{});
m_Color = data.value("color", m_Color);
m_FocusedColor = data.value("focusedColor", m_FocusedColor);
m_Size = data.value("size", m_Size);
m_FocusedSize = data.value("focusedSize", m_FocusedSize);
}
void CrosshairUI::ResolveReferences(const ObjectMap& objects)
{
m_Player.Resolve(objects);
m_Crosshair.Resolve(objects);
m_FocusIndicator.Resolve(objects);
}
void CrosshairUI::ImGuiInspector()
{
if (const Scene* scene = GetGameObject()->GetScene()) {
ImGuiUtils::DrawObjectRefPicker("Player", m_Player, *scene, [](const GameObject& object) {
return object.GetComponent<FirstPersonController>() != nullptr;
});
ImGuiUtils::DrawObjectRefPicker("Crosshair", m_Crosshair, *scene);
ImGuiUtils::DrawObjectRefPicker("Focus Indicator", m_FocusIndicator, *scene);
}
ImGui::ColorEdit4("Color", &m_Color.x);
ImGui::ColorEdit4("Focused Color", &m_FocusedColor.x);
ImGui::DragFloat("Size", &m_Size, 0.5f, 0.0f, 500.0f);
ImGui::DragFloat("Focused Size", &m_FocusedSize, 0.5f, 0.0f, 500.0f);
v.Field("player", m_Player, {.filter = [](const GameObject& object) { return object.GetComponent<FirstPersonController>() != nullptr; }});
v.Field("crosshair", m_Crosshair);
v.Field("focusIndicator", m_FocusIndicator);
v.Field("color", m_Color, {.color = true});
v.Field("focusedColor", m_FocusedColor, {.color = true});
v.Field("size", m_Size, {.speed = 0.5f, .min = 0.0, .max = 500.0});
v.Field("focusedSize", m_FocusedSize, {.speed = 0.5f, .min = 0.0, .max = 500.0});
}
@@ -10,6 +10,7 @@
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Transform.h>
#include <destrum/Physics/PhysicsWorld.h>
#include <destrum/Serialization/Reflect.h>
#include <destrum/Util/DeltaTime.h>
#include <array>
@@ -274,70 +275,40 @@ glm::vec3 FirstPersonController::GetEyePosition() const
return position + glm::vec3{0.0f, m_EyeHeight, 0.0f};
}
void FirstPersonController::Reflect(FieldVisitor& v)
{
v.Field("walkSpeed", m_WalkSpeed, {.speed = 0.1f, .min = 0.0, .max = 100.0});
v.Field("sprintSpeed", m_SprintSpeed, {.speed = 0.1f, .min = 0.0, .max = 100.0});
v.Field("groundAcceleration", m_GroundAcceleration, {.speed = 0.5f, .min = 0.0, .max = 500.0});
v.Field("airAcceleration", m_AirAcceleration, {.speed = 0.5f, .min = 0.0, .max = 500.0});
v.Field("jumpHeight", m_JumpHeight, {.speed = 0.01f, .min = 0.0, .max = 10.0});
v.Field("gravity", m_Gravity, {.speed = 0.1f, .min = 0.0, .max = 100.0});
v.Field("fallGravityMultiplier", m_FallGravityMultiplier, {.speed = 0.01f, .min = 1.0, .max = 5.0});
v.Field("coyoteTime", m_CoyoteTime, {.speed = 0.005f, .min = 0.0, .max = 1.0});
v.Field("jumpBufferTime", m_JumpBufferTime, {.speed = 0.005f, .min = 0.0, .max = 1.0});
v.Field("interactDistance", m_InteractDistance, {.speed = 0.05f, .min = 0.0, .max = 20.0});
v.Field("maxSlopeAngle", m_MaxSlopeAngle, {.min = 0.0, .max = glm::radians(89.0), .angle = true});
v.Field("groundStickSpeed", m_GroundStickSpeed, {.speed = 0.05f, .min = 0.0, .max = 20.0});
v.Field("mouseSensitivity", m_MouseSensitivity, {.speed = 0.0001f, .min = 0.0, .max = 0.1, .format = "%.4f"});
v.Field("eyeHeight", m_EyeHeight, {.speed = 0.01f, .min = -2.0, .max = 2.0});
v.Field("yaw", m_Yaw, {.angle = true, .readOnly = true});
v.Field("pitch", m_Pitch, {.angle = true, .readOnly = true});
}
void FirstPersonController::ImGuiInspector()
{
Component::ImGuiInspector();
ImGui::DragFloat("Walk Speed", &m_WalkSpeed, 0.1f, 0.0f, 100.0f);
ImGui::DragFloat("Sprint Speed", &m_SprintSpeed, 0.1f, 0.0f, 100.0f);
ImGui::DragFloat("Ground Acceleration", &m_GroundAcceleration, 0.5f, 0.0f, 500.0f);
ImGui::DragFloat("Air Acceleration", &m_AirAcceleration, 0.5f, 0.0f, 500.0f);
ImGui::DragFloat("Jump Height", &m_JumpHeight, 0.01f, 0.0f, 10.0f);
ImGui::DragFloat("Gravity", &m_Gravity, 0.1f, 0.0f, 100.0f);
ImGui::DragFloat("Fall Gravity Multiplier", &m_FallGravityMultiplier, 0.01f, 1.0f, 5.0f);
ImGui::DragFloat("Coyote Time", &m_CoyoteTime, 0.005f, 0.0f, 1.0f);
ImGui::DragFloat("Jump Buffer Time", &m_JumpBufferTime, 0.005f, 0.0f, 1.0f);
ImGui::DragFloat("Interact Distance", &m_InteractDistance, 0.05f, 0.0f, 20.0f);
ImGui::SliderAngle("Max Slope Angle", &m_MaxSlopeAngle, 0.0f, 89.0f);
ImGui::DragFloat("Ground Stick Speed", &m_GroundStickSpeed, 0.05f, 0.0f, 20.0f);
ImGui::DragFloat("Mouse Sensitivity", &m_MouseSensitivity, 0.0001f, 0.0f, 0.1f);
ImGui::DragFloat("Eye Height", &m_EyeHeight, 0.01f, -2.0f, 2.0f);
ImGui::Text("Grounded: %s", m_Grounded ? "yes" : "no");
ImGui::Text("Ground slope: %.1f deg", glm::degrees(std::acos(glm::clamp(m_GroundNormal.y, -1.0f, 1.0f))));
ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw));
ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch));
if (bool possessed = m_Possessed; ImGui::Checkbox("Possessed", &possessed)) {
SetPossessed(possessed);
}
}
nlohmann::json FirstPersonController::Serialize() const
{
return {
{"walkSpeed", m_WalkSpeed},
{"sprintSpeed", m_SprintSpeed},
{"groundAcceleration", m_GroundAcceleration},
{"airAcceleration", m_AirAcceleration},
{"jumpHeight", m_JumpHeight},
{"gravity", m_Gravity},
{"fallGravityMultiplier", m_FallGravityMultiplier},
{"coyoteTime", m_CoyoteTime},
{"jumpBufferTime", m_JumpBufferTime},
{"interactDistance", m_InteractDistance},
{"maxSlopeAngle", m_MaxSlopeAngle},
{"groundStickSpeed", m_GroundStickSpeed},
{"mouseSensitivity", m_MouseSensitivity},
{"eyeHeight", m_EyeHeight},
{"yaw", m_Yaw},
{"pitch", m_Pitch},
};
}
void FirstPersonController::Deserialize(const nlohmann::json& data)
{
m_WalkSpeed = data.value("walkSpeed", data.value("movementSpeed", m_WalkSpeed));
m_SprintSpeed = data.value("sprintSpeed", m_SprintSpeed);
m_GroundAcceleration = data.value("groundAcceleration", m_GroundAcceleration);
m_AirAcceleration = data.value("airAcceleration", m_AirAcceleration);
m_JumpHeight = data.value("jumpHeight", m_JumpHeight);
m_Gravity = data.value("gravity", m_Gravity);
m_FallGravityMultiplier = data.value("fallGravityMultiplier", m_FallGravityMultiplier);
m_CoyoteTime = data.value("coyoteTime", m_CoyoteTime);
m_JumpBufferTime = data.value("jumpBufferTime", m_JumpBufferTime);
m_InteractDistance = data.value("interactDistance", m_InteractDistance);
m_MaxSlopeAngle = data.value("maxSlopeAngle", m_MaxSlopeAngle);
m_GroundStickSpeed = data.value("groundStickSpeed", m_GroundStickSpeed);
m_MouseSensitivity = data.value("mouseSensitivity", m_MouseSensitivity);
m_EyeHeight = data.value("eyeHeight", m_EyeHeight);
m_Yaw = data.value("yaw", m_Yaw);
m_Pitch = data.value("pitch", m_Pitch);
Component::Deserialize(data);
if (!data.contains("walkSpeed") && data.contains("movementSpeed")) {
m_WalkSpeed = data.at("movementSpeed").get<float>();
}
}
+5 -13
View File
@@ -4,10 +4,9 @@
#include <destrum/Components/MeshRendererComponent.h>
#include <destrum/Graphics/Renderer.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Serialization/Reflect.h>
#include <destrum/Util/GameState.h>
#include <imgui.h>
LightToggle::LightToggle(GameObject& parent)
: Activatable(parent, "LightToggle")
{
@@ -50,19 +49,12 @@ void LightToggle::Apply()
renderer.updateMaterialGPU(meshRenderer->GetMaterialID());
}
nlohmann::json LightToggle::Serialize() const
void LightToggle::Reflect(FieldVisitor& v)
{
return {{"on", m_On}};
v.Field("on", m_On);
}
void LightToggle::Deserialize(const nlohmann::json& data)
void LightToggle::OnFieldsEdited()
{
m_On = data.value("on", m_On);
}
void LightToggle::ImGuiInspector()
{
if (bool on = m_On; ImGui::Checkbox("On", &on)) {
SetOn(on);
}
Apply();
}
+7 -19
View File
@@ -4,7 +4,7 @@
#include <cmath>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Serialization/Reflect.h>
#include <imgui.h>
@@ -50,28 +50,16 @@ glm::vec3 MovingPlatform::GetOffsetAt(float progress) const
return m_TravelOffset * eased;
}
nlohmann::json MovingPlatform::Serialize() const
void MovingPlatform::Reflect(FieldVisitor& v)
{
return {
{"travelOffset", m_TravelOffset},
{"travelDuration", m_TravelDuration},
{"pauseDuration", m_PauseDuration},
{"elapsedTime", m_ElapsedTime},
};
}
void MovingPlatform::Deserialize(const nlohmann::json& data)
{
m_TravelOffset = data.value("travelOffset", m_TravelOffset);
m_TravelDuration = std::max(data.value("travelDuration", m_TravelDuration), 0.01f);
m_PauseDuration = std::max(data.value("pauseDuration", m_PauseDuration), 0.0f);
m_ElapsedTime = std::max(data.value("elapsedTime", m_ElapsedTime), 0.0f);
v.Field("travelOffset", m_TravelOffset, {.speed = 0.05f});
v.Field("travelDuration", m_TravelDuration, {.speed = 0.05f, .min = 0.01, .format = "%.2f s"});
v.Field("pauseDuration", m_PauseDuration, {.speed = 0.05f, .min = 0.0, .format = "%.2f s"});
v.Field("elapsedTime", m_ElapsedTime, {.min = 0.0, .hidden = true});
}
void MovingPlatform::ImGuiInspector()
{
ImGui::Text("Progress: %.0f%%", GetTravelProgress() * 100.0f);
ImGui::DragFloat3("Travel Offset", &m_TravelOffset.x, 0.05f);
ImGui::DragFloat("Travel Duration", &m_TravelDuration, 0.05f, 0.01f, 60.0f, "%.2f s");
ImGui::DragFloat("Pause Duration", &m_PauseDuration, 0.05f, 0.0f, 60.0f, "%.2f s");
Component::ImGuiInspector();
}
+13 -4
View File
@@ -3,6 +3,7 @@
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Serialization/Reflect.h>
#include <components/FirstPersonController.h>
@@ -46,9 +47,15 @@ void PlayerRespawn::Respawn()
}
}
void PlayerRespawn::Reflect(FieldVisitor& v)
{
v.Field("killHeight", m_KillHeight);
}
// The respawn point is only saved once set, until then Start uses the spawn position
nlohmann::json PlayerRespawn::Serialize() const
{
nlohmann::json data{{"killHeight", m_KillHeight}};
nlohmann::json data = Component::Serialize();
if (m_HasRespawnPoint) {
data["respawnPoint"] = m_RespawnPoint;
}
@@ -57,7 +64,7 @@ nlohmann::json PlayerRespawn::Serialize() const
void PlayerRespawn::Deserialize(const nlohmann::json& data)
{
m_KillHeight = data.value("killHeight", m_KillHeight);
Component::Deserialize(data);
if (data.contains("respawnPoint")) {
SetRespawnPoint(data.at("respawnPoint").get<glm::vec3>());
}
@@ -65,8 +72,10 @@ void PlayerRespawn::Deserialize(const nlohmann::json& data)
void PlayerRespawn::ImGuiInspector()
{
ImGui::DragFloat("Kill height", &m_KillHeight, 0.1f);
ImGui::DragFloat3("Respawn point", glm::value_ptr(m_RespawnPoint), 0.1f);
Component::ImGuiInspector();
if (ImGui::DragFloat3("Respawn Point", glm::value_ptr(m_RespawnPoint), 0.1f)) {
m_HasRespawnPoint = true;
}
if (ImGui::Button("Respawn now")) {
Respawn();
}
+4 -15
View File
@@ -2,6 +2,8 @@
#include "spdlog/spdlog.h"
#include <destrum/Serialization/Reflect.h>
PrintComponent::PrintComponent(GameObject& parent) : Component(parent, "PrintComponent")
{
}
@@ -15,20 +17,7 @@ void PrintComponent::Update(float dt)
spdlog::debug(m_message);
}
void PrintComponent::ImGuiInspector()
void PrintComponent::Reflect(FieldVisitor& v)
{
Component::ImGuiInspector();
}
nlohmann::json PrintComponent::Serialize() const
{
return {{"message", m_message}};
}
void PrintComponent::Deserialize(const nlohmann::json& basic_jsons)
{
if (basic_jsons.contains("message"))
{
m_message = basic_jsons.at("message").get<std::string>();
}
v.Field("message", m_message);
}
@@ -2,6 +2,7 @@
#include "spdlog/spdlog.h"
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Serialization/Reflect.h>
TriggerLoggerComponent::TriggerLoggerComponent(GameObject& parent)
: Component(parent, "TriggerLoggerComponent") {}
@@ -28,18 +29,8 @@ void TriggerLoggerComponent::OnTriggerExit(GameObject* other) {
triggerExitCount);
}
nlohmann::json TriggerLoggerComponent::Serialize() const {
return {
{"triggerEnterCount", triggerEnterCount},
{"triggerExitCount", triggerExitCount}
};
void TriggerLoggerComponent::Reflect(FieldVisitor& v) {
v.Field("triggerEnterCount", triggerEnterCount, {.readOnly = true});
v.Field("triggerExitCount", triggerExitCount, {.readOnly = true});
v.Field("lastOtherName", lastOtherName, {.readOnly = true, .transient = true});
}
void TriggerLoggerComponent::Deserialize(const nlohmann::json& data) {
if (data.contains("triggerEnterCount")) {
triggerEnterCount = data.at("triggerEnterCount").get<int>();
}
if (data.contains("triggerExitCount")) {
triggerExitCount = data.at("triggerExitCount").get<int>();
}
}
+3 -13
View File
@@ -1,8 +1,8 @@
#include <components/WinGoal.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Serialization/Reflect.h>
#include <imgui.h>
#include <spdlog/spdlog.h>
WinGoal::WinGoal(GameObject& parent)
@@ -18,17 +18,7 @@ void WinGoal::OnActivate(GameObject& instigator)
m_Reached = true;
}
nlohmann::json WinGoal::Serialize() const
void WinGoal::Reflect(FieldVisitor& v)
{
return {{"reached", m_Reached}};
}
void WinGoal::Deserialize(const nlohmann::json& data)
{
m_Reached = data.value("reached", m_Reached);
}
void WinGoal::ImGuiInspector()
{
ImGui::Checkbox("Reached", &m_Reached);
v.Field("reached", m_Reached);
}
+4 -31
View File
@@ -4,7 +4,7 @@
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Util/ImGuiUtils.h>
#include <destrum/Serialization/Reflect.h>
WinScreenUI::WinScreenUI(GameObject& parent)
: Component(parent, "WinScreenUI")
@@ -28,35 +28,8 @@ void WinScreenUI::LateUpdate(float)
screen->SetActive(winGoal != nullptr && winGoal->IsReached());
}
nlohmann::json WinScreenUI::Serialize() const
void WinScreenUI::Reflect(FieldVisitor& v)
{
return {
{"goal", m_Goal},
{"screen", m_Screen},
};
}
void WinScreenUI::Deserialize(const nlohmann::json& data)
{
m_Goal = data.value("goal", ObjectRef{});
m_Screen = data.value("screen", ObjectRef{});
}
void WinScreenUI::ResolveReferences(const ObjectMap& objects)
{
m_Goal.Resolve(objects);
m_Screen.Resolve(objects);
}
void WinScreenUI::ImGuiInspector()
{
const Scene* scene = GetGameObject()->GetScene();
if (scene == nullptr) {
return;
}
ImGuiUtils::DrawObjectRefPicker("Goal", m_Goal, *scene, [](const GameObject& object) {
return object.GetComponent<WinGoal>() != nullptr;
});
ImGuiUtils::DrawObjectRefPicker("Screen", m_Screen, *scene);
v.Field("goal", m_Goal, {.filter = [](const GameObject& object) { return object.GetComponent<WinGoal>() != nullptr; }});
v.Field("screen", m_Screen);
}