Actuators

This commit is contained in:
2026-04-27 06:55:05 +03:00
parent 37441aa8fd
commit fa49bb5005
13 changed files with 718 additions and 0 deletions

View File

@@ -55,6 +55,9 @@ set(EDITSCENE_SOURCES
ui/GoapRunnerEditor.cpp
ui/PathFollowingEditor.cpp
ui/GoapPlannerEditor.cpp
systems/ActuatorSystem.cpp
ui/ActuatorEditor.cpp
components/ActuatorModule.cpp
systems/PrefabSystem.cpp
ui/PrefabInstanceEditor.cpp
@@ -192,6 +195,8 @@ set(EDITSCENE_HEADERS
systems/GoapRunnerSystem.hpp
systems/PathFollowingSystem.hpp
systems/GoapPlannerSystem.hpp
components/Actuator.hpp
ui/ActuatorEditor.hpp
components/PrefabInstance.hpp
ui/PrefabInstanceEditor.hpp

View File

@@ -69,9 +69,11 @@
#include "systems/PrefabSystem.hpp"
#include "components/NavMesh.hpp"
#include "components/SmartObject.hpp"
#include "components/Actuator.hpp"
#include "components/GoapPlanner.hpp"
#include "components/GoapRunner.hpp"
#include "components/PathFollowing.hpp"
#include "systems/ActuatorSystem.hpp"
#include <OgreRTShaderSystem.h>
#include <imgui.h>
@@ -342,6 +344,10 @@ void EditorApp::setup()
// Wire up EditorApp for game mode detection
m_smartObjectSystem->setEditorApp(this);
// Setup Actuator system
m_actuatorSystem = std::make_unique<ActuatorSystem>(
m_world, m_sceneMgr, this, m_behaviorTreeSystem.get());
// Setup GOAP Runner system
m_goapRunnerSystem = std::make_unique<GoapRunnerSystem>(
m_world, m_sceneMgr, m_smartObjectSystem.get(),
@@ -601,6 +607,9 @@ void EditorApp::setupECS()
// Register Smart Object component
m_world.component<SmartObjectComponent>();
// Register Actuator component
m_world.component<ActuatorComponent>();
// Register GOAP Planner component
m_world.component<GoapPlannerComponent>();
@@ -808,6 +817,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
m_goapRunnerSystem->update(evt.timeSinceLastFrame);
}
/* --- Actuator system (player interaction prompts) --- */
if (m_actuatorSystem) {
m_actuatorSystem->update(evt.timeSinceLastFrame);
}
/* --- Dynamic physics (characters after static world) --- */
if (m_characterSystem) {

View File

@@ -39,6 +39,7 @@ class SmartObjectSystem;
class GoapRunnerSystem;
class PathFollowingSystem;
class GoapPlannerSystem;
class ActuatorSystem;
class EditorApp;
/**
@@ -228,6 +229,7 @@ private:
std::unique_ptr<GoapRunnerSystem> m_goapRunnerSystem;
std::unique_ptr<PathFollowingSystem> m_pathFollowingSystem;
std::unique_ptr<GoapPlannerSystem> m_goapPlannerSystem;
std::unique_ptr<ActuatorSystem> m_actuatorSystem;
// Game systems

View File

@@ -0,0 +1,44 @@
#ifndef EDITSCENE_ACTUATOR_HPP
#define EDITSCENE_ACTUATOR_HPP
#pragma once
#include <Ogre.h>
#include <vector>
#include <string>
/**
* Actuator component.
*
* An interactive object visible only to the player character.
* When the player is within radius + height range, an on-screen
* prompt appears and the action can be triggered with the action key.
*
* Unlike SmartObject, Actuators do not use pathfinding or path
* following — they are instantaneous interactions.
*/
struct ActuatorComponent {
// Interaction radius in XZ plane
float radius = 1.5f;
// Maximum height difference for interaction
float height = 1.8f;
// Names of GOAP actions (from ActionDatabase) that this actuator provides
std::vector<Ogre::String> actionNames;
// Runtime: cooldown timer (seconds remaining)
float cooldownTimer = 0.0f;
// Runtime: currently executing an action
bool isExecuting = false;
ActuatorComponent() = default;
explicit ActuatorComponent(float radius_, float height_)
: radius(radius_)
, height(height_)
{
}
};
#endif // EDITSCENE_ACTUATOR_HPP

View File

@@ -0,0 +1,19 @@
#include "Actuator.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/ActuatorEditor.hpp"
REGISTER_COMPONENT_GROUP("Actuator", "Game", ActuatorComponent, ActuatorEditor)
{
registry.registerComponent<ActuatorComponent>(
"Actuator", "Game", std::make_unique<ActuatorEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<ActuatorComponent>())
e.set<ActuatorComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<ActuatorComponent>())
e.remove<ActuatorComponent>();
});
}

View File

@@ -26,6 +26,11 @@ struct PlayerControllerComponent {
Ogre::String swimIdleState = "swim-idle";
Ogre::String swimState = "swim";
Ogre::String swimFastState = "swim-fast";
/* Actuator interaction settings */
float actuatorDistance = 25.0f;
float actuatorCooldown = 1.5f;
Ogre::Vector3 actuatorColor = Ogre::Vector3(0.0f, 0.4f, 1.0f);
};
#endif // EDITSCENE_PLAYERCONTROLLER_HPP

View File

@@ -0,0 +1,441 @@
#include "ActuatorSystem.hpp"
#include "../EditorApp.hpp"
#include "BehaviorTreeSystem.hpp"
#include "../components/Actuator.hpp"
#include "../components/PlayerController.hpp"
#include "../components/Transform.hpp"
#include "../components/Character.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/ActionDebug.hpp"
#include "../components/EntityName.hpp"
#include "../camera/EditorCamera.hpp"
#include <OgreCamera.h>
#include <OgreSceneNode.h>
#include <OgreLogManager.h>
#include <imgui.h>
#include <cmath>
ActuatorSystem::ActuatorSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr,
EditorApp *editorApp,
BehaviorTreeSystem *btSystem)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_editorApp(editorApp)
, m_btSystem(btSystem)
{
}
ActuatorSystem::~ActuatorSystem() = default;
Ogre::Vector2 ActuatorSystem::projectToScreen(const Ogre::Vector3 &worldPoint)
{
if (!m_editorApp)
return Ogre::Vector2(-1, -1);
EditorCamera *editorCam = m_editorApp->getEditorCamera();
if (!editorCam)
return Ogre::Vector2(-1, -1);
Ogre::Camera *camera = editorCam->getCamera();
if (!camera)
return Ogre::Vector2(-1, -1);
ImVec2 vpSize = ImGui::GetMainViewport()->Size;
float width = vpSize.x;
float height = vpSize.y;
// Convert to camera space
Ogre::Vector3 eyeSpacePoint = camera->getViewMatrix() * worldPoint;
// Project to clip space
Ogre::Vector3 clipSpacePoint =
camera->getProjectionMatrix() * eyeSpacePoint;
if (clipSpacePoint.z < 0.0f)
return Ogre::Vector2(-1, -1);
// Convert from clip space (-1 to 1) to screen space (0 to 1)
float screenX = (clipSpacePoint.x / 2.0f) + 0.5f;
float screenY = 1.0f - ((clipSpacePoint.y / 2.0f) + 0.5f);
// Map to actual pixel dimensions
return Ogre::Vector2(screenX * width, screenY * height);
}
bool ActuatorSystem::isInRange(const Ogre::Vector3 &charPos,
const Ogre::Vector3 &objPos, float radius,
float height)
{
Ogre::Vector3 diff = charPos - objPos;
float xzDist = std::sqrt(diff.x * diff.x + diff.z * diff.z);
if (xzDist > radius)
return false;
float yDiff = std::abs(diff.y);
if (yDiff > height)
return false;
return true;
}
void ActuatorSystem::executeAction(flecs::entity character,
flecs::entity actuatorEntity,
const Ogre::String &actionName)
{
if (!character.is_alive() || !actuatorEntity.is_alive())
return;
if (!actuatorEntity.has<ActuatorComponent>())
return;
auto &actuator = actuatorEntity.get_mut<ActuatorComponent>();
actuator.isExecuting = true;
m_executingActuatorId = actuatorEntity.id();
m_executingCharacterId = character.id();
// Set up ActionDebug on the character to run the behavior tree
if (!character.has<ActionDebug>()) {
character.set<ActionDebug>({});
}
auto &debug = character.get_mut<ActionDebug>();
debug.isRunning = true;
debug.runTimer = 0.0f;
debug.currentActionName = actionName;
debug.selectedActionName = actionName;
Ogre::LogManager::getSingleton().logMessage(
"[ActuatorSystem] Executing action: " + actionName);
}
bool ActuatorSystem::isActionComplete(flecs::entity character)
{
if (!m_btSystem || !character.is_alive())
return true;
if (!character.has<ActionDebug>())
return true;
auto &debug = character.get<ActionDebug>();
if (!debug.isRunning)
return true;
auto &btState = m_btSystem->getActionDebugState(character.id());
return btState.treeResult != BehaviorTreeSystem::Status::running;
}
void ActuatorSystem::drawActuatorMarkers(
const std::vector<ScreenActuator> &markers,
const ScreenActuator *target, const Ogre::String &labelText)
{
ImDrawList *drawList = ImGui::GetBackgroundDrawList();
if (!drawList)
return;
// Default circle color from player controller
ImVec4 defaultColorVec(0.0f, 0.4f, 1.0f, 1.0f);
m_world.query<PlayerControllerComponent>().each(
[&](flecs::entity, PlayerControllerComponent &pc) {
defaultColorVec = ImVec4(pc.actuatorColor.x,
pc.actuatorColor.y,
pc.actuatorColor.z, 1.0f);
});
ImColor defaultColor(defaultColorVec);
// Target is brighter
ImColor targetColor(
ImVec4(std::min(defaultColorVec.x + 0.2f, 1.0f),
std::min(defaultColorVec.y + 0.3f, 1.0f),
std::min(defaultColorVec.z + 0.0f, 1.0f), 1.0f));
for (const auto &marker : markers) {
bool isTarget = target && target->entity.id() == marker.entity.id();
float circleRadius = isTarget ? 12.0f : 8.0f;
ImColor circleCol = isTarget ? targetColor : defaultColor;
ImVec2 center(marker.screenPos.x, marker.screenPos.y);
drawList->AddCircleFilled(center, circleRadius, circleCol);
drawList->AddCircle(center, circleRadius,
IM_COL32(255, 255, 255, 180), 0, 2.0f);
}
// Draw label for target
if (target && !labelText.empty()) {
ImVec2 center(target->screenPos.x, target->screenPos.y);
float circleRadius = 12.0f;
ImVec2 textSize = ImGui::CalcTextSize(labelText.c_str());
ImVec2 textPos(center.x - (textSize.x * 0.5f),
center.y + circleRadius + 6.0f);
// Shadow
drawList->AddText(
ImVec2(textPos.x + 1, textPos.y + 1),
IM_COL32(0, 0, 0, 200), labelText.c_str());
// Text
drawList->AddText(textPos, IM_COL32(255, 255, 255, 255),
labelText.c_str());
}
}
void ActuatorSystem::drawActionMenu(flecs::entity actuatorEntity)
{
if (!actuatorEntity.is_alive() ||
!actuatorEntity.has<ActuatorComponent>())
return;
auto &actuator = actuatorEntity.get<ActuatorComponent>();
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing,
ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Appearing);
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_AlwaysAutoResize;
if (ImGui::Begin("Select Action", nullptr, flags)) {
ImGui::Text("Select an action:");
ImGui::Separator();
for (const auto &name : actuator.actionNames) {
if (name.empty())
continue;
if (ImGui::Button(name.c_str(),
ImVec2(ImGui::GetContentRegionAvail().x,
0))) {
m_pendingActionName = name;
}
}
ImGui::Separator();
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
m_menuOpen = false;
m_menuActuatorId = 0;
m_eHoldTime = 0.0f;
if (m_editorApp)
m_editorApp->setWindowGrab(true);
}
}
ImGui::End();
}
void ActuatorSystem::update(float deltaTime)
{
// Update cooldown timers for all actuators
m_world.query<ActuatorComponent>().each(
[&](flecs::entity, ActuatorComponent &actuator) {
if (actuator.cooldownTimer > 0.0f) {
actuator.cooldownTimer -= deltaTime;
if (actuator.cooldownTimer < 0.0f)
actuator.cooldownTimer = 0.0f;
}
if (actuator.cooldownTimer <= 0.0f && !actuator.isExecuting) {
// Fully reset
}
});
// Only run in game mode while playing
if (!m_editorApp ||
m_editorApp->getGameMode() != EditorApp::GameMode::Game ||
m_editorApp->getGamePlayState() !=
EditorApp::GamePlayState::Playing)
return;
// Check if an action is currently executing
if (m_executingActuatorId != 0) {
flecs::entity character = m_world.entity(m_executingCharacterId);
if (isActionComplete(character)) {
// Action finished - start cooldown
flecs::entity actuator = m_world.entity(m_executingActuatorId);
if (actuator.is_alive() && actuator.has<ActuatorComponent>()) {
auto &ac = actuator.get_mut<ActuatorComponent>();
ac.isExecuting = false;
// Get cooldown from player controller config
float cooldown = 1.5f;
m_world
.query<PlayerControllerComponent>()
.each([&](flecs::entity,
PlayerControllerComponent &pc) {
cooldown = pc.actuatorCooldown;
});
ac.cooldownTimer = cooldown;
}
m_executingActuatorId = 0;
m_executingCharacterId = 0;
// Clear ActionDebug on character
if (character.is_alive() && character.has<ActionDebug>()) {
auto &debug = character.get_mut<ActionDebug>();
debug.isRunning = false;
debug.currentActionName.clear();
}
} else {
// Action still running - don't draw any markers
return;
}
}
// Find player character
flecs::entity playerCharacter = flecs::entity::null();
float actuatorDistance = 25.0f;
float actuatorCooldown = 1.5f;
m_world.query<PlayerControllerComponent>().each(
[&](flecs::entity e, PlayerControllerComponent &pc) {
(void)e;
if (!playerCharacter.is_alive()) {
// Find character by name
m_world.query<EntityNameComponent>()
.each([&](flecs::entity ec,
EntityNameComponent &en) {
if (!playerCharacter.is_alive() &&
en.name == pc.targetCharacterName)
playerCharacter = ec;
});
actuatorDistance = pc.actuatorDistance;
actuatorCooldown = pc.actuatorCooldown;
}
});
if (!playerCharacter.is_alive() ||
!playerCharacter.has<TransformComponent>())
return;
Ogre::Vector3 charPos =
playerCharacter.get<TransformComponent>().node
->_getDerivedPosition();
// Collect actuators within distance
std::vector<ScreenActuator> visibleActuators;
std::vector<ScreenActuator> inRangeActuators;
m_world.query<ActuatorComponent, TransformComponent>()
.each([&](flecs::entity e, ActuatorComponent &actuator,
TransformComponent &trans) {
// Skip if on cooldown or executing
if (actuator.cooldownTimer > 0.0f || actuator.isExecuting)
return;
if (!trans.node)
return;
Ogre::Vector3 objPos = trans.node->_getDerivedPosition();
float dist = charPos.distance(objPos);
if (dist > actuatorDistance)
return;
Ogre::Vector2 screenPos = projectToScreen(objPos);
if (screenPos.x < 0)
return;
ScreenActuator sa;
sa.entity = e;
sa.screenPos = screenPos;
sa.distance = dist;
// Distance to screen center (X only for horizontal preference)
ImVec2 vpSize = ImGui::GetMainViewport()->Size;
sa.distToScreenCenter =
std::abs(screenPos.x - vpSize.x * 0.5f);
visibleActuators.push_back(sa);
if (isInRange(charPos, objPos, actuator.radius,
actuator.height)) {
inRangeActuators.push_back(sa);
}
});
// Determine target actuator for interaction
const ScreenActuator *target = nullptr;
if (!inRangeActuators.empty()) {
// Pick closest to screen X-center, then bottommost (largest Y)
size_t bestIdx = 0;
for (size_t i = 1; i < inRangeActuators.size(); i++) {
if (inRangeActuators[i].distToScreenCenter <
inRangeActuators[bestIdx].distToScreenCenter) {
bestIdx = i;
} else if (inRangeActuators[i].distToScreenCenter ==
inRangeActuators[bestIdx].distToScreenCenter) {
// Bottommost = larger Y
if (inRangeActuators[i].screenPos.y >
inRangeActuators[bestIdx].screenPos.y)
bestIdx = i;
}
}
target = &inRangeActuators[bestIdx];
}
// Build label text
Ogre::String labelText;
if (target && target->entity.is_alive() &&
target->entity.has<ActuatorComponent>()) {
auto &actuator = target->entity.get<ActuatorComponent>();
if (actuator.actionNames.size() == 1 &&
!actuator.actionNames[0].empty()) {
labelText = "E - " + actuator.actionNames[0];
} else if (actuator.actionNames.size() > 1) {
labelText = "E";
}
}
// Draw markers
drawActuatorMarkers(visibleActuators, target, labelText);
// Handle input
GameInputState &input = m_editorApp->getGameInputState();
// Handle menu state first
if (m_menuOpen) {
flecs::entity menuActuator = m_world.entity(m_menuActuatorId);
drawActionMenu(menuActuator);
if (!m_pendingActionName.empty()) {
// Action selected from menu
executeAction(playerCharacter, menuActuator,
m_pendingActionName);
m_pendingActionName.clear();
m_menuOpen = false;
m_menuActuatorId = 0;
m_eHoldTime = 0.0f;
m_eWasHeld = false;
if (m_editorApp)
m_editorApp->setWindowGrab(true);
}
// Also close menu if E is released without selection
if (!input.e && m_eWasHeld) {
m_menuOpen = false;
m_menuActuatorId = 0;
m_eHoldTime = 0.0f;
m_eWasHeld = false;
if (m_editorApp)
m_editorApp->setWindowGrab(true);
}
return;
}
// Handle E key
if (target && input.e) {
auto &actuator = target->entity.get<ActuatorComponent>();
m_eHoldTime += deltaTime;
if (actuator.actionNames.size() == 1 &&
!actuator.actionNames[0].empty()) {
// Single action: execute on press
if (input.ePressed) {
executeAction(playerCharacter, target->entity,
actuator.actionNames[0]);
m_eHoldTime = 0.0f;
}
} else if (actuator.actionNames.size() > 1) {
// Multiple actions: hold to open menu
if (m_eHoldTime > 0.3f && !m_menuOpen) {
m_menuOpen = true;
m_menuActuatorId = target->entity.id();
m_eWasHeld = true;
if (m_editorApp)
m_editorApp->setWindowGrab(false);
}
}
} else {
m_eHoldTime = 0.0f;
m_eWasHeld = false;
}
}

View File

@@ -0,0 +1,66 @@
#ifndef EDITSCENE_ACTUATOR_SYSTEM_HPP
#define EDITSCENE_ACTUATOR_SYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
#include <vector>
#include <string>
class EditorApp;
class BehaviorTreeSystem;
/**
* System that handles player interaction with Actuator entities.
*
* In game mode:
* - Draws on-screen circle markers for actuators within range
* - Shows "E - ActionName" (or just "E" for multi-action) when in reach
* - Handles E press / hold for action activation
* - Manages per-actuator cooldowns
*/
class ActuatorSystem {
public:
ActuatorSystem(flecs::world &world, Ogre::SceneManager *sceneMgr,
EditorApp *editorApp, BehaviorTreeSystem *btSystem);
~ActuatorSystem();
void update(float deltaTime);
private:
struct ScreenActuator {
flecs::entity entity;
Ogre::Vector2 screenPos;
float distance;
float distToScreenCenter;
};
Ogre::Vector2 projectToScreen(const Ogre::Vector3 &worldPoint);
bool isInRange(const Ogre::Vector3 &charPos,
const Ogre::Vector3 &objPos, float radius, float height);
void executeAction(flecs::entity character, flecs::entity actuatorEntity,
const Ogre::String &actionName);
bool isActionComplete(flecs::entity character);
void drawActuatorMarkers(const std::vector<ScreenActuator> &markers,
const ScreenActuator *target,
const Ogre::String &labelText);
void drawActionMenu(flecs::entity actuatorEntity);
flecs::world &m_world;
Ogre::SceneManager *m_sceneMgr;
EditorApp *m_editorApp;
BehaviorTreeSystem *m_btSystem;
// Multi-action menu state
bool m_menuOpen = false;
flecs::entity_t m_menuActuatorId = 0;
float m_eHoldTime = 0.0f;
bool m_eWasHeld = false;
Ogre::String m_pendingActionName;
// Currently executing action state
flecs::entity_t m_executingActuatorId = 0;
flecs::entity_t m_executingCharacterId = 0;
};
#endif // EDITSCENE_ACTUATOR_SYSTEM_HPP

View File

@@ -31,6 +31,7 @@
#include "../components/ActionDatabase.hpp"
#include "../components/ActionDebug.hpp"
#include "../components/SmartObject.hpp"
#include "../components/Actuator.hpp"
#include "../components/GoapPlanner.hpp"
#include "../components/PathFollowing.hpp"
#include "../components/BehaviorTree.hpp"
@@ -303,6 +304,9 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
if (entity.has<SmartObjectComponent>()) {
json["smartObject"] = serializeSmartObject(entity);
}
if (entity.has<ActuatorComponent>()) {
json["actuator"] = serializeActuator(entity);
}
if (entity.has<GoapPlannerComponent>()) {
json["goapPlanner"] = serializeGoapPlanner(entity);
}
@@ -509,6 +513,9 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
if (json.contains("smartObject")) {
deserializeSmartObject(entity, json["smartObject"]);
}
if (json.contains("actuator")) {
deserializeActuator(entity, json["actuator"]);
}
if (json.contains("goapPlanner")) {
deserializeGoapPlanner(entity, json["goapPlanner"]);
}
@@ -791,6 +798,9 @@ void SceneSerializer::deserializeEntityComponents(
if (json.contains("smartObject")) {
deserializeSmartObject(entity, json["smartObject"]);
}
if (json.contains("actuator")) {
deserializeActuator(entity, json["actuator"]);
}
if (json.contains("goapPlanner")) {
deserializeGoapPlanner(entity, json["goapPlanner"]);
}
@@ -3641,3 +3651,30 @@ void SceneSerializer::deserializeNavMeshGeometrySource(
src.include = json.value("include", true);
entity.set<NavMeshGeometrySource>(src);
}
nlohmann::json SceneSerializer::serializeActuator(flecs::entity entity)
{
const ActuatorComponent &actuator = entity.get<ActuatorComponent>();
nlohmann::json json;
json["radius"] = actuator.radius;
json["height"] = actuator.height;
json["actionNames"] = actuator.actionNames;
return json;
}
void SceneSerializer::deserializeActuator(flecs::entity entity,
const nlohmann::json &json)
{
ActuatorComponent actuator;
actuator.radius = json.value("radius", 1.5f);
actuator.height = json.value("height", 1.8f);
if (json.contains("actionNames") && json["actionNames"].is_array()) {
actuator.actionNames.clear();
for (const auto &name : json["actionNames"]) {
if (name.is_string())
actuator.actionNames.push_back(name);
}
}
entity.set<ActuatorComponent>(actuator);
}

View File

@@ -210,6 +210,7 @@ private:
nlohmann::json serializeActionDebug(flecs::entity entity);
nlohmann::json serializePathFollowing(flecs::entity entity);
nlohmann::json serializeSmartObject(flecs::entity entity);
nlohmann::json serializeActuator(flecs::entity entity);
nlohmann::json serializeGoapPlanner(flecs::entity entity);
nlohmann::json serializeBehaviorTree(flecs::entity entity);
void deserializeActionDatabase(flecs::entity entity,
@@ -220,6 +221,8 @@ private:
const nlohmann::json &json);
void deserializeSmartObject(flecs::entity entity,
const nlohmann::json &json);
void deserializeActuator(flecs::entity entity,
const nlohmann::json &json);
void deserializeGoapPlanner(flecs::entity entity,
const nlohmann::json &json);
void deserializeBehaviorTree(flecs::entity entity,

View File

@@ -0,0 +1,45 @@
#include "ActuatorEditor.hpp"
#include <imgui.h>
bool ActuatorEditor::renderComponent(flecs::entity entity,
ActuatorComponent &actuator)
{
(void)entity;
bool modified = false;
modified |= ImGui::DragFloat("Radius", &actuator.radius, 0.1f,
0.1f, 100.0f);
modified |= ImGui::DragFloat("Height", &actuator.height, 0.1f,
0.1f, 100.0f);
ImGui::Separator();
ImGui::Text("Actions:");
int removeIdx = -1;
for (int i = 0; i < (int)actuator.actionNames.size(); i++) {
ImGui::PushID(i);
char buf[256];
snprintf(buf, sizeof(buf), "%s",
actuator.actionNames[i].c_str());
if (ImGui::InputText("##name", buf, sizeof(buf))) {
actuator.actionNames[i] = buf;
modified = true;
}
ImGui::SameLine();
if (ImGui::SmallButton("X"))
removeIdx = i;
ImGui::PopID();
}
if (removeIdx >= 0) {
actuator.actionNames.erase(
actuator.actionNames.begin() + removeIdx);
modified = true;
}
if (ImGui::Button("Add Action")) {
actuator.actionNames.push_back("");
modified = true;
}
return modified;
}

View File

@@ -0,0 +1,20 @@
#ifndef EDITSCENE_ACTUATOR_EDITOR_HPP
#define EDITSCENE_ACTUATOR_EDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/Actuator.hpp"
class ActuatorEditor : public ComponentEditor<ActuatorComponent> {
public:
const char *getName() const override
{
return "Actuator";
}
protected:
bool renderComponent(flecs::entity entity,
ActuatorComponent &actuator) override;
};
#endif // EDITSCENE_ACTUATOR_EDITOR_HPP

View File

@@ -107,6 +107,23 @@ bool PlayerControllerEditor::renderComponent(flecs::entity entity,
modified = true;
}
ImGui::Separator();
ImGui::Text("Actuator Interaction");
if (ImGui::DragFloat("Actuator Distance", &pc.actuatorDistance,
0.5f, 1.0f, 200.0f))
modified = true;
if (ImGui::DragFloat("Actuator Cooldown", &pc.actuatorCooldown,
0.1f, 0.0f, 10.0f))
modified = true;
float color[3] = { pc.actuatorColor.x, pc.actuatorColor.y,
pc.actuatorColor.z };
if (ImGui::ColorEdit3("Actuator Color", color)) {
pc.actuatorColor = Ogre::Vector3(color[0], color[1],
color[2]);
modified = true;
}
ImGui::Unindent();
}