feat: add Activateable in Lightkeeper
This commit is contained in:
@@ -64,6 +64,7 @@ public:
|
|||||||
[[nodiscard]] const std::vector<std::shared_ptr<GameObject>>& GetObjects() const {
|
[[nodiscard]] const std::vector<std::shared_ptr<GameObject>>& GetObjects() const {
|
||||||
return m_objects;
|
return m_objects;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] GameObject* FindGameObject(ObjectId id) const;
|
||||||
|
|
||||||
void SetRegisterBindings(std::function<void()> registerBindings) {
|
void SetRegisterBindings(std::function<void()> registerBindings) {
|
||||||
m_registerBindings = std::move(registerBindings);
|
m_registerBindings = std::move(registerBindings);
|
||||||
|
|||||||
@@ -205,6 +205,20 @@ CameraComponent* Scene::GetCurrentCamera() const {
|
|||||||
return cameras.empty() ? nullptr : cameras.front();
|
return cameras.empty() ? nullptr : cameras.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameObject* Scene::FindGameObject(ObjectId id) const {
|
||||||
|
if (id == InvalidObjectId) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
for (const auto* objects : {&m_objects, &m_pendingAdditions}) {
|
||||||
|
for (const auto& object : *objects) {
|
||||||
|
if (object != nullptr && !object->IsBeingDestroyed() && object->GetId() == id) {
|
||||||
|
return object.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<CameraComponent*> Scene::GetCameras() const {
|
std::vector<CameraComponent*> Scene::GetCameras() const {
|
||||||
std::vector<CameraComponent*> cameras;
|
std::vector<CameraComponent*> cameras;
|
||||||
for (const auto& object : m_objects) {
|
for (const auto& object : m_objects) {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ set(GAME_SRC
|
|||||||
src/scenes/Experiments.cpp
|
src/scenes/Experiments.cpp
|
||||||
|
|
||||||
src/components/GameComponentRegistry.cpp
|
src/components/GameComponentRegistry.cpp
|
||||||
|
src/components/Activatable.cpp
|
||||||
|
src/components/Door.cpp
|
||||||
src/components/FirstPersonController.cpp
|
src/components/FirstPersonController.cpp
|
||||||
src/components/Interactable.cpp
|
src/components/Interactable.cpp
|
||||||
src/components/InteractableButton.cpp
|
src/components/InteractableButton.cpp
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef LIGHTKEEPER_ACTIVATABLE_H
|
||||||
|
#define LIGHTKEEPER_ACTIVATABLE_H
|
||||||
|
|
||||||
|
#include <destrum/Event.h>
|
||||||
|
#include <destrum/ObjectModel/Component.h>
|
||||||
|
|
||||||
|
class Activatable : public Component {
|
||||||
|
public:
|
||||||
|
void Activate(GameObject& instigator);
|
||||||
|
|
||||||
|
Event<GameObject&> OnActivated;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Activatable(GameObject& parent, const std::string& name)
|
||||||
|
: Component(parent, name) {}
|
||||||
|
|
||||||
|
virtual void OnActivate(GameObject& instigator) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LIGHTKEEPER_ACTIVATABLE_H
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#ifndef LIGHTKEEPER_DOOR_H
|
||||||
|
#define LIGHTKEEPER_DOOR_H
|
||||||
|
|
||||||
|
#include <components/Activatable.h>
|
||||||
|
|
||||||
|
#include <glm/vec3.hpp>
|
||||||
|
|
||||||
|
class Door final : public Activatable {
|
||||||
|
public:
|
||||||
|
explicit Door(GameObject& parent);
|
||||||
|
|
||||||
|
std::string GetTypeName() const override { return "Door"; }
|
||||||
|
|
||||||
|
void Start() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
|
||||||
|
nlohmann::json Serialize() const override;
|
||||||
|
void Deserialize(const nlohmann::json& data) override;
|
||||||
|
void ImGuiInspector() override;
|
||||||
|
|
||||||
|
void Open() { m_IsOpen = true; }
|
||||||
|
void Close() { m_IsOpen = false; }
|
||||||
|
void Toggle() { m_IsOpen = !m_IsOpen; }
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsOpen() const { return m_IsOpen; }
|
||||||
|
[[nodiscard]] float GetOpenAmount() const { return m_OpenAmount; }
|
||||||
|
|
||||||
|
void SetOpenOffset(const glm::vec3& offset) { m_OpenOffset = offset; }
|
||||||
|
void SetOpenDuration(float seconds) { m_OpenDuration = seconds; }
|
||||||
|
void SetToggleable(bool toggleable) { m_Toggleable = toggleable; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void OnActivate(GameObject& instigator) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] glm::vec3 GetOffsetFor(float openAmount) const;
|
||||||
|
void ApplyPosition();
|
||||||
|
|
||||||
|
glm::vec3 m_OpenOffset{0.0f, -2.4f, 0.0f};
|
||||||
|
float m_OpenDuration{1.0f};
|
||||||
|
bool m_Toggleable{true};
|
||||||
|
|
||||||
|
bool m_IsOpen{false};
|
||||||
|
float m_OpenAmount{0.0f};
|
||||||
|
|
||||||
|
glm::vec3 m_ClosedPosition{0.0f};
|
||||||
|
bool m_Started{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LIGHTKEEPER_DOOR_H
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
#ifndef LIGHTKEEPER_GAMECOMPONENTLIST_H
|
#ifndef LIGHTKEEPER_GAMECOMPONENTLIST_H
|
||||||
#define LIGHTKEEPER_GAMECOMPONENTLIST_H
|
#define LIGHTKEEPER_GAMECOMPONENTLIST_H
|
||||||
|
|
||||||
|
#include <components/Door.h>
|
||||||
#include <components/FirstPersonController.h>
|
#include <components/FirstPersonController.h>
|
||||||
#include <components/InteractableButton.h>
|
#include <components/InteractableButton.h>
|
||||||
#include <components/PrintComponent.h>
|
#include <components/PrintComponent.h>
|
||||||
#include <components/TriggerLoggerComponent.h>
|
#include <components/TriggerLoggerComponent.h>
|
||||||
|
|
||||||
#define LIGHTKEEPER_GAME_COMPONENTS(X) \
|
#define LIGHTKEEPER_GAME_COMPONENTS(X) \
|
||||||
|
X(Door) \
|
||||||
X(FirstPersonController) \
|
X(FirstPersonController) \
|
||||||
X(InteractableButton) \
|
X(InteractableButton) \
|
||||||
X(PrintComponent) \
|
X(PrintComponent) \
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#include <components/Interactable.h>
|
#include <components/Interactable.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <destrum/ObjectModel/ObjectId.h>
|
||||||
|
|
||||||
class InteractableButton final : public Interactable {
|
class InteractableButton final : public Interactable {
|
||||||
public:
|
public:
|
||||||
@@ -21,11 +24,19 @@ public:
|
|||||||
[[nodiscard]] std::size_t GetColorIndex() const { return m_ColorIndex; }
|
[[nodiscard]] std::size_t GetColorIndex() const { return m_ColorIndex; }
|
||||||
[[nodiscard]] int GetPressCount() const { return m_PressCount; }
|
[[nodiscard]] int GetPressCount() const { return m_PressCount; }
|
||||||
|
|
||||||
|
// Every Activatable on a target object is activated when the button is pressed
|
||||||
|
void AddTarget(const GameObject& target);
|
||||||
|
void RemoveTarget(ObjectId target);
|
||||||
|
[[nodiscard]] const std::vector<ObjectId>& GetTargets() const { return m_Targets; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ApplyColor();
|
void ApplyColor();
|
||||||
|
void ActivateTargets(GameObject& interactor);
|
||||||
|
void DrawTargetsInspector();
|
||||||
|
|
||||||
std::size_t m_ColorIndex{0};
|
std::size_t m_ColorIndex{0};
|
||||||
int m_PressCount{0};
|
int m_PressCount{0};
|
||||||
|
std::vector<ObjectId> m_Targets;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LIGHTKEEPER_INTERACTABLEBUTTON_H
|
#endif // LIGHTKEEPER_INTERACTABLEBUTTON_H
|
||||||
|
|||||||
@@ -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 radius = m_Capsule->GetRadius();
|
||||||
const float bottom = center.y - m_Capsule->GetHeight() * 0.5f;
|
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 float spread = radius * 0.7f;
|
||||||
const std::array<glm::vec2, 5> offsets{
|
const std::array<glm::vec2, 5> offsets{
|
||||||
glm::vec2{0.0f, 0.0f},
|
glm::vec2{0.0f, 0.0f},
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
#include <components/InteractableButton.h>
|
#include <components/InteractableButton.h>
|
||||||
|
|
||||||
|
#include <components/Activatable.h>
|
||||||
|
|
||||||
#include <destrum/Components/MeshRendererComponent.h>
|
#include <destrum/Components/MeshRendererComponent.h>
|
||||||
#include <destrum/Graphics/Renderer.h>
|
#include <destrum/Graphics/Renderer.h>
|
||||||
#include <destrum/ObjectModel/GameObject.h>
|
#include <destrum/ObjectModel/GameObject.h>
|
||||||
|
#include <destrum/Scene/Scene.h>
|
||||||
#include <destrum/Util/GameState.h>
|
#include <destrum/Util/GameState.h>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
@@ -38,6 +43,50 @@ void InteractableButton::Interact(GameObject& interactor)
|
|||||||
ApplyColor();
|
ApplyColor();
|
||||||
|
|
||||||
spdlog::info("{} pressed {} by {}", GetGameObject()->GetName(), m_PressCount, interactor.GetName());
|
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()
|
void InteractableButton::ApplyColor()
|
||||||
@@ -59,6 +108,7 @@ nlohmann::json InteractableButton::Serialize() const
|
|||||||
return {
|
return {
|
||||||
{"colorIndex", m_ColorIndex},
|
{"colorIndex", m_ColorIndex},
|
||||||
{"pressCount", m_PressCount},
|
{"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_ColorIndex = data.value("colorIndex", m_ColorIndex) % ButtonColors.size();
|
||||||
m_PressCount = data.value("pressCount", m_PressCount);
|
m_PressCount = data.value("pressCount", m_PressCount);
|
||||||
|
m_Targets = data.value("targets", std::vector<ObjectId>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
void InteractableButton::ImGuiInspector()
|
void InteractableButton::ImGuiInspector()
|
||||||
@@ -74,4 +125,53 @@ void InteractableButton::ImGuiInspector()
|
|||||||
if (ImGui::Button("Press")) {
|
if (ImGui::Button("Press")) {
|
||||||
Interact(*GetGameObject());
|
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 <destrum/Util/ModelDocUtils.h>
|
||||||
|
|
||||||
#include <components/FirstPersonController.h>
|
#include <components/FirstPersonController.h>
|
||||||
|
#include <components/Door.h>
|
||||||
#include <components/InteractableButton.h>
|
#include <components/InteractableButton.h>
|
||||||
#include <components/TriggerLoggerComponent.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);
|
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},
|
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},
|
GameObject* button = CreateBox(scene, gfxDevice, resources, "Button", {1.5f, 0.15f, -4.5f}, {0.25f, 0.1f, 0.25f},
|
||||||
buttonMaterialID, RigidbodyType::Static);
|
buttonMaterialID, RigidbodyType::Static);
|
||||||
button->AddComponent<InteractableButton>();
|
button->AddComponent<InteractableButton>()->AddTarget(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateFlightHelmet(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
void CreateFlightHelmet(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||||
@@ -299,7 +325,8 @@ MainSceneObjects BuildMainScene(Scene& scene, GfxDevice& gfxDevice, RenderResour
|
|||||||
CreateTree(scene, gfxDevice, resources);
|
CreateTree(scene, gfxDevice, resources);
|
||||||
CreateCapybara(scene, gfxDevice, resources);
|
CreateCapybara(scene, gfxDevice, resources);
|
||||||
CreateTriggerZone(scene);
|
CreateTriggerZone(scene);
|
||||||
CreateButton(scene, gfxDevice, resources);
|
const GameObject* door = CreateDoorway(scene, gfxDevice, resources);
|
||||||
|
CreateButton(scene, gfxDevice, resources, *door);
|
||||||
CreatePlayer(scene, objects);
|
CreatePlayer(scene, objects);
|
||||||
LoadSpawnBallResources(gfxDevice, resources, objects);
|
LoadSpawnBallResources(gfxDevice, resources, objects);
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,25 @@ namespace {
|
|||||||
"destroying a parent must mark descendants");
|
"destroying a parent must mark descendants");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testSceneFindGameObject()
|
||||||
|
{
|
||||||
|
Scene& scene = SceneManager::GetInstance().CreateScene("find-object");
|
||||||
|
GameObject* pending = scene.CreateGameObject("pending");
|
||||||
|
check(scene.FindGameObject(pending->GetId()) == pending, "objects must be found while still pending");
|
||||||
|
|
||||||
|
scene.CommitPendingAdditions();
|
||||||
|
GameObject* committed = scene.CreateGameObject("committed");
|
||||||
|
scene.CommitPendingAdditions();
|
||||||
|
check(scene.FindGameObject(committed->GetId()) == committed, "committed objects must be found by id");
|
||||||
|
check(scene.FindGameObject(InvalidObjectId) == nullptr, "the invalid id must never match");
|
||||||
|
|
||||||
|
const ObjectId destroyedId = committed->GetId();
|
||||||
|
committed->Destroy();
|
||||||
|
check(scene.FindGameObject(destroyedId) == nullptr, "objects being destroyed must not be found");
|
||||||
|
|
||||||
|
SceneManager::GetInstance().Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
void testSceneRoundTrip()
|
void testSceneRoundTrip()
|
||||||
{
|
{
|
||||||
Scene& scene = SceneManager::GetInstance().CreateScene("round-trip");
|
Scene& scene = SceneManager::GetInstance().CreateScene("round-trip");
|
||||||
@@ -1198,6 +1217,7 @@ namespace {
|
|||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
testTransforms();
|
testTransforms();
|
||||||
|
testSceneFindGameObject();
|
||||||
testSceneRoundTrip();
|
testSceneRoundTrip();
|
||||||
testEventMutation();
|
testEventMutation();
|
||||||
testEventRemoval();
|
testEventRemoval();
|
||||||
|
|||||||
Reference in New Issue
Block a user