feat: add Activateable in Lightkeeper
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
#include <components/Activatable.h>
|
||||
|
||||
void Activatable::Activate(GameObject& instigator)
|
||||
{
|
||||
if (!isEnabled() || IsBeingDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
OnActivate(instigator);
|
||||
OnActivated.Invoke(instigator);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#include <components/Door.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
Door::Door(GameObject& parent)
|
||||
: Activatable(parent, "Door")
|
||||
{
|
||||
}
|
||||
|
||||
void Door::Start()
|
||||
{
|
||||
const glm::vec3 currentPosition = GetGameObject()->GetTransform().GetLocalPosition();
|
||||
m_ClosedPosition = currentPosition - GetOffsetFor(m_OpenAmount);
|
||||
m_Started = true;
|
||||
}
|
||||
|
||||
void Door::Update(float dt)
|
||||
{
|
||||
if (!m_Started) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float targetAmount = m_IsOpen ? 1.0f : 0.0f;
|
||||
if (m_OpenAmount == targetAmount) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float step = m_OpenDuration > 0.0f ? dt / m_OpenDuration : 1.0f;
|
||||
m_OpenAmount = m_IsOpen
|
||||
? std::min(m_OpenAmount + step, targetAmount)
|
||||
: std::max(m_OpenAmount - step, targetAmount);
|
||||
|
||||
ApplyPosition();
|
||||
}
|
||||
|
||||
void Door::OnActivate(GameObject&)
|
||||
{
|
||||
if (m_Toggleable) {
|
||||
Toggle();
|
||||
} else {
|
||||
Open();
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 Door::GetOffsetFor(float openAmount) const
|
||||
{
|
||||
const float eased = openAmount * openAmount * (3.0f - 2.0f * openAmount);
|
||||
return m_OpenOffset * eased;
|
||||
}
|
||||
|
||||
void Door::ApplyPosition()
|
||||
{
|
||||
GetGameObject()->GetTransform().SetLocalPosition(m_ClosedPosition + GetOffsetFor(m_OpenAmount));
|
||||
}
|
||||
|
||||
nlohmann::json Door::Serialize() const
|
||||
{
|
||||
return {
|
||||
{"openOffset", {m_OpenOffset.x, m_OpenOffset.y, m_OpenOffset.z}},
|
||||
{"openDuration", m_OpenDuration},
|
||||
{"toggleable", m_Toggleable},
|
||||
{"open", m_IsOpen},
|
||||
{"openAmount", m_OpenAmount},
|
||||
};
|
||||
}
|
||||
|
||||
void Door::Deserialize(const nlohmann::json& data)
|
||||
{
|
||||
if (data.contains("openOffset")) {
|
||||
const auto& offset = data.at("openOffset");
|
||||
m_OpenOffset = {offset.at(0).get<float>(), offset.at(1).get<float>(), offset.at(2).get<float>()};
|
||||
}
|
||||
m_OpenDuration = std::max(data.value("openDuration", m_OpenDuration), 0.0f);
|
||||
m_Toggleable = data.value("toggleable", m_Toggleable);
|
||||
m_IsOpen = data.value("open", m_IsOpen);
|
||||
m_OpenAmount = std::clamp(data.value("openAmount", m_OpenAmount), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void Door::ImGuiInspector()
|
||||
{
|
||||
ImGui::Text("State: %s (%.0f%%)", m_IsOpen ? "Open" : "Closed", m_OpenAmount * 100.0f);
|
||||
if (ImGui::Button(m_IsOpen ? "Close" : "Open")) {
|
||||
Toggle();
|
||||
}
|
||||
|
||||
ImGui::DragFloat3("Open Offset", &m_OpenOffset.x, 0.05f);
|
||||
ImGui::DragFloat("Open Duration", &m_OpenDuration, 0.05f, 0.0f, 10.0f, "%.2f s");
|
||||
ImGui::Checkbox("Toggleable", &m_Toggleable);
|
||||
}
|
||||
@@ -183,8 +183,6 @@ bool FirstPersonController::CheckGrounded() const
|
||||
const float radius = m_Capsule->GetRadius();
|
||||
const float bottom = center.y - m_Capsule->GetHeight() * 0.5f;
|
||||
|
||||
// Rays start inside the capsule (ignoring it) so they are always above the floor, even when the solver
|
||||
// leaves the capsule sunk in a little. The outer rays catch standing on an edge.
|
||||
const float spread = radius * 0.7f;
|
||||
const std::array<glm::vec2, 5> offsets{
|
||||
glm::vec2{0.0f, 0.0f},
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
#include <components/InteractableButton.h>
|
||||
|
||||
#include <components/Activatable.h>
|
||||
|
||||
#include <destrum/Components/MeshRendererComponent.h>
|
||||
#include <destrum/Graphics/Renderer.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Util/GameState.h>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -38,6 +43,50 @@ void InteractableButton::Interact(GameObject& interactor)
|
||||
ApplyColor();
|
||||
|
||||
spdlog::info("{} pressed {} by {}", GetGameObject()->GetName(), m_PressCount, interactor.GetName());
|
||||
ActivateTargets(interactor);
|
||||
}
|
||||
|
||||
void InteractableButton::AddTarget(const GameObject& target)
|
||||
{
|
||||
if (target.GetId() == InvalidObjectId || std::ranges::find(m_Targets, target.GetId()) != m_Targets.end()) {
|
||||
return;
|
||||
}
|
||||
m_Targets.push_back(target.GetId());
|
||||
}
|
||||
|
||||
void InteractableButton::RemoveTarget(ObjectId target)
|
||||
{
|
||||
std::erase(m_Targets, target);
|
||||
}
|
||||
|
||||
void InteractableButton::ActivateTargets(GameObject& interactor)
|
||||
{
|
||||
const Scene* scene = GetGameObject()->GetScene();
|
||||
if (scene == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const ObjectId targetId : m_Targets) {
|
||||
GameObject* target = scene->FindGameObject(targetId);
|
||||
if (target == nullptr) {
|
||||
spdlog::warn("{} targets object {} which no longer exists", GetGameObject()->GetName(), targetId);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<Activatable*> activatables;
|
||||
for (const auto& component : target->GetComponents()) {
|
||||
if (auto* activatable = dynamic_cast<Activatable*>(component.get())) {
|
||||
activatables.push_back(activatable);
|
||||
}
|
||||
}
|
||||
|
||||
if (activatables.empty()) {
|
||||
spdlog::warn("{} targets {} which has nothing to activate", GetGameObject()->GetName(), target->GetName());
|
||||
}
|
||||
for (Activatable* activatable : activatables) {
|
||||
activatable->Activate(interactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InteractableButton::ApplyColor()
|
||||
@@ -59,6 +108,7 @@ nlohmann::json InteractableButton::Serialize() const
|
||||
return {
|
||||
{"colorIndex", m_ColorIndex},
|
||||
{"pressCount", m_PressCount},
|
||||
{"targets", m_Targets},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,6 +116,7 @@ void InteractableButton::Deserialize(const nlohmann::json& data)
|
||||
{
|
||||
m_ColorIndex = data.value("colorIndex", m_ColorIndex) % ButtonColors.size();
|
||||
m_PressCount = data.value("pressCount", m_PressCount);
|
||||
m_Targets = data.value("targets", std::vector<ObjectId>{});
|
||||
}
|
||||
|
||||
void InteractableButton::ImGuiInspector()
|
||||
@@ -74,4 +125,53 @@ void InteractableButton::ImGuiInspector()
|
||||
if (ImGui::Button("Press")) {
|
||||
Interact(*GetGameObject());
|
||||
}
|
||||
|
||||
DrawTargetsInspector();
|
||||
}
|
||||
|
||||
void InteractableButton::DrawTargetsInspector()
|
||||
{
|
||||
ImGui::SeparatorText("Targets");
|
||||
|
||||
const Scene* scene = GetGameObject()->GetScene();
|
||||
if (scene == nullptr) {
|
||||
ImGui::TextDisabled("Not in a scene");
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<ObjectId> targetToRemove;
|
||||
for (const ObjectId targetId : m_Targets) {
|
||||
const GameObject* target = scene->FindGameObject(targetId);
|
||||
ImGui::PushID(static_cast<int>(targetId));
|
||||
if (ImGui::SmallButton("x")) {
|
||||
targetToRemove = targetId;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (target != nullptr) {
|
||||
ImGui::TextUnformatted(target->GetName().c_str());
|
||||
} else {
|
||||
ImGui::TextDisabled("Missing object %llu", static_cast<unsigned long long>(targetId));
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (targetToRemove) {
|
||||
RemoveTarget(*targetToRemove);
|
||||
}
|
||||
|
||||
if (ImGui::BeginCombo("##AddTarget", "Add target...")) {
|
||||
for (const auto& object : scene->GetObjects()) {
|
||||
const bool alreadyTargeted = object != nullptr &&
|
||||
std::ranges::find(m_Targets, object->GetId()) != m_Targets.end();
|
||||
if (object == nullptr || object.get() == GetGameObject() || alreadyTargeted ||
|
||||
object->GetComponent<Activatable>() == nullptr) {
|
||||
continue;
|
||||
}
|
||||
ImGui::PushID(static_cast<int>(object->GetId()));
|
||||
if (ImGui::Selectable(object->GetName().c_str())) {
|
||||
AddTarget(*object);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <destrum/Util/ModelDocUtils.h>
|
||||
|
||||
#include <components/FirstPersonController.h>
|
||||
#include <components/Door.h>
|
||||
#include <components/InteractableButton.h>
|
||||
#include <components/TriggerLoggerComponent.h>
|
||||
|
||||
@@ -159,7 +160,32 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
void CreateButton(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
GameObject* CreateDoorway(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
{
|
||||
const MaterialID frameMaterial = AddPrototypeMaterial(gfxDevice, resources, "Dark", 0.9f);
|
||||
const MaterialID doorMaterial = AddPrototypeMaterial(gfxDevice, resources, "Purple", 0.5f);
|
||||
|
||||
constexpr float x = 4.0f;
|
||||
constexpr float z = -4.5f;
|
||||
constexpr float frameHeight = 2.8f;
|
||||
constexpr float doorWidth = 1.6f;
|
||||
constexpr float doorHeight = 2.4f;
|
||||
|
||||
for (const float side : {-1.0f, 1.0f}) {
|
||||
CreateBox(scene, gfxDevice, resources, side < 0.0f ? "DoorFrame_Left" : "DoorFrame_Right",
|
||||
{x, GroundHeight + frameHeight * 0.5f, z + side * (doorWidth * 0.5f + 0.2f)},
|
||||
{0.4f, frameHeight, 0.4f}, frameMaterial, RigidbodyType::Static);
|
||||
}
|
||||
CreateBox(scene, gfxDevice, resources, "DoorFrame_Top", {x, GroundHeight + frameHeight + 0.2f, z},
|
||||
{0.4f, 0.4f, doorWidth + 0.8f}, frameMaterial, RigidbodyType::Static);
|
||||
|
||||
GameObject* door = CreateBox(scene, gfxDevice, resources, "Door", {x, GroundHeight + doorHeight * 0.5f, z},
|
||||
{0.3f, doorHeight, doorWidth}, doorMaterial, RigidbodyType::Kinematic);
|
||||
door->AddComponent<Door>()->SetOpenOffset({0.0f, 0.0f, -doorWidth - 0.1f});
|
||||
return door;
|
||||
}
|
||||
|
||||
void CreateButton(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources, const GameObject& target)
|
||||
{
|
||||
const MaterialID pedestalMaterial = AddPrototypeMaterial(gfxDevice, resources, "Dark", 0.9f);
|
||||
CreateBox(scene, gfxDevice, resources, "ButtonPedestal", {1.5f, -0.45f, -4.5f}, {0.4f, 1.1f, 0.4f},
|
||||
@@ -172,7 +198,7 @@ namespace {
|
||||
|
||||
GameObject* button = CreateBox(scene, gfxDevice, resources, "Button", {1.5f, 0.15f, -4.5f}, {0.25f, 0.1f, 0.25f},
|
||||
buttonMaterialID, RigidbodyType::Static);
|
||||
button->AddComponent<InteractableButton>();
|
||||
button->AddComponent<InteractableButton>()->AddTarget(target);
|
||||
}
|
||||
|
||||
void CreateFlightHelmet(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
@@ -299,7 +325,8 @@ MainSceneObjects BuildMainScene(Scene& scene, GfxDevice& gfxDevice, RenderResour
|
||||
CreateTree(scene, gfxDevice, resources);
|
||||
CreateCapybara(scene, gfxDevice, resources);
|
||||
CreateTriggerZone(scene);
|
||||
CreateButton(scene, gfxDevice, resources);
|
||||
const GameObject* door = CreateDoorway(scene, gfxDevice, resources);
|
||||
CreateButton(scene, gfxDevice, resources, *door);
|
||||
CreatePlayer(scene, objects);
|
||||
LoadSpawnBallResources(gfxDevice, resources, objects);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user