Event system

This commit is contained in:
2026-04-27 18:45:01 +03:00
parent b9cce0248a
commit 8507a3a501
17 changed files with 775 additions and 2 deletions

View File

@@ -58,6 +58,10 @@ set(EDITSCENE_SOURCES
systems/ActuatorSystem.cpp
ui/ActuatorEditor.cpp
components/ActuatorModule.cpp
systems/EventBus.cpp
systems/EventHandlerSystem.cpp
ui/EventHandlerEditor.cpp
components/EventHandlerModule.cpp
systems/PrefabSystem.cpp
ui/PrefabInstanceEditor.cpp
@@ -197,6 +201,10 @@ set(EDITSCENE_HEADERS
systems/GoapPlannerSystem.hpp
components/Actuator.hpp
ui/ActuatorEditor.hpp
systems/EventBus.hpp
components/EventHandler.hpp
systems/EventHandlerSystem.hpp
ui/EventHandlerEditor.hpp
components/PrefabInstance.hpp
ui/PrefabInstanceEditor.hpp

View File

@@ -74,6 +74,8 @@
#include "components/GoapRunner.hpp"
#include "components/PathFollowing.hpp"
#include "systems/ActuatorSystem.hpp"
#include "systems/EventHandlerSystem.hpp"
#include "components/EventHandler.hpp"
#include <OgreRTShaderSystem.h>
#include <imgui.h>
@@ -356,6 +358,10 @@ void EditorApp::setup()
m_actuatorSystem = std::make_unique<ActuatorSystem>(
m_world, m_sceneMgr, this, m_behaviorTreeSystem.get());
// Setup Event Handler system
m_eventHandlerSystem = std::make_unique<EventHandlerSystem>(
m_world, m_behaviorTreeSystem.get());
// Setup GOAP Runner system
m_goapRunnerSystem = std::make_unique<GoapRunnerSystem>(
m_world, m_sceneMgr, m_smartObjectSystem.get(),
@@ -618,6 +624,9 @@ void EditorApp::setupECS()
// Register Actuator component
m_world.component<ActuatorComponent>();
// Register Event Handler component
m_world.component<EventHandlerComponent>();
// Register GOAP Planner component
m_world.component<GoapPlannerComponent>();
@@ -830,6 +839,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
m_actuatorSystem->update(evt.timeSinceLastFrame);
}
/* --- Event Handler system (event-driven BTs) --- */
if (m_eventHandlerSystem) {
m_eventHandlerSystem->update(evt.timeSinceLastFrame);
}
/* --- Dynamic physics (characters after static world) --- */
if (m_characterSystem) {

View File

@@ -40,6 +40,7 @@ class GoapRunnerSystem;
class PathFollowingSystem;
class GoapPlannerSystem;
class ActuatorSystem;
class EventHandlerSystem;
class EditorApp;
/**
@@ -190,6 +191,10 @@ public:
{
return m_actuatorSystem.get();
}
EventHandlerSystem *getEventHandlerSystem() const
{
return m_eventHandlerSystem.get();
}
Ogre::ImGuiOverlay *getImGuiOverlay() const
{
return m_imguiOverlay;
@@ -234,6 +239,7 @@ private:
std::unique_ptr<PathFollowingSystem> m_pathFollowingSystem;
std::unique_ptr<GoapPlannerSystem> m_goapPlannerSystem;
std::unique_ptr<ActuatorSystem> m_actuatorSystem;
std::unique_ptr<EventHandlerSystem> m_eventHandlerSystem;
// Game systems

View File

@@ -73,7 +73,8 @@ struct BehaviorTreeNode {
type == "checkBit" || type == "setValue" ||
type == "checkValue" || type == "blackboardDump" ||
type == "delay" || type == "teleportToChild" ||
type == "disablePhysics" || type == "enablePhysics";
type == "disablePhysics" || type == "enablePhysics" ||
type == "sendEvent";
}
};

View File

@@ -0,0 +1,21 @@
#ifndef EDITSCENE_EVENT_HANDLER_HPP
#define EDITSCENE_EVENT_HANDLER_HPP
#pragma once
#include <Ogre.h>
/**
* Event-driven behavior tree handler component.
*
* When the specified event is received, the referenced GoapAction's
* behavior tree is executed for this entity. Event parameters are
* merged into the entity's GoapBlackboard before the tree runs and
* cleaned up when the tree completes.
*/
struct EventHandlerComponent {
Ogre::String eventName;
Ogre::String actionName;
bool enabled = true;
};
#endif // EDITSCENE_EVENT_HANDLER_HPP

View File

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

View File

@@ -188,9 +188,32 @@ Ogre::String GoapBlackboard::dump() const
")\n";
}
if (!stringValues.empty()) {
result += " String values:\n";
for (const auto &pair : stringValues)
result += " " + pair.first + " = " + pair.second + "\n";
}
return result;
}
void GoapBlackboard::merge(const GoapBlackboard &other)
{
// Merge bits
bits = (bits & ~other.mask) | (other.bits & other.mask);
mask |= other.mask;
// Merge values
for (const auto &pair : other.values)
values[pair.first] = pair.second;
for (const auto &pair : other.floatValues)
floatValues[pair.first] = pair.second;
for (const auto &pair : other.vec3Values)
vec3Values[pair.first] = pair.second;
for (const auto &pair : other.stringValues)
stringValues[pair.first] = pair.second;
}
std::vector<int> GoapBlackboard::getSetBits() const
{
std::vector<int> result;

View File

@@ -35,6 +35,9 @@ struct GoapBlackboard {
// Named Vector3 values — runtime character state
std::unordered_map<std::string, Ogre::Vector3> vec3Values;
// Named string values — event params, tags, etc.
std::unordered_map<std::string, Ogre::String> stringValues;
GoapBlackboard() = default;
/* --- Bit accessors --- */
@@ -148,6 +151,34 @@ struct GoapBlackboard {
vec3Values.erase(key);
}
/* --- String value accessors --- */
void setStringValue(const std::string &key, const Ogre::String &value)
{
stringValues[key] = value;
}
Ogre::String getStringValue(const std::string &key,
const Ogre::String &defaultValue = "") const
{
auto it = stringValues.find(key);
if (it != stringValues.end())
return it->second;
return defaultValue;
}
bool hasStringValue(const std::string &key) const
{
return stringValues.find(key) != stringValues.end();
}
void removeStringValue(const std::string &key)
{
stringValues.erase(key);
}
/* --- Merge another blackboard into this one --- */
void merge(const GoapBlackboard &other);
/* --- Generic scalar lookup (tries int then float) --- */
bool getScalarValue(const std::string &key, float &out) const;
@@ -171,6 +202,7 @@ struct GoapBlackboard {
values.clear();
floatValues.clear();
vec3Values.clear();
stringValues.clear();
}
Ogre::String dump() const;
@@ -191,7 +223,8 @@ struct GoapBlackboard {
bitmask == other.bitmask &&
values == other.values &&
floatValues == other.floatValues &&
vec3Values == other.vec3Values;
vec3Values == other.vec3Values &&
stringValues == other.stringValues;
}
bool operator!=(const GoapBlackboard &other) const

View File

@@ -2,6 +2,7 @@
#include "AnimationTreeSystem.hpp"
#include "CharacterSystem.hpp"
#include "SmartObjectSystem.hpp"
#include "EventBus.hpp"
#include "../components/BehaviorTree.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/GoapBlackboard.hpp"
@@ -16,6 +17,90 @@
static float g_epsilon = 0.0001f;
static bool parseValueString(const Ogre::String &str, int &outInt,
float &outFloat, Ogre::Vector3 &outVec3, int &type);
/** Parse "key=val,key2=val2" params into a GoapBlackboard.
* Values are auto-detected: int, float, vec3 (x,y,z), or quoted string. */
static void parseEventParams(const Ogre::String &str, GoapBlackboard &out)
{
if (str.empty())
return;
const char *s = str.c_str();
while (*s) {
// Skip whitespace
while (*s == ' ' || *s == '\t')
s++;
if (!*s)
break;
// Find key
const char *keyStart = s;
while (*s && *s != '=' && *s != ',')
s++;
Ogre::String key(keyStart, static_cast<size_t>(s - keyStart));
// Trim trailing spaces from key
while (!key.empty() &&
(key.back() == ' ' || key.back() == '\t'))
key.pop_back();
if (*s != '=') {
while (*s && *s != ',')
s++;
if (*s == ',')
s++;
continue;
}
s++; // skip '='
// Skip whitespace before value
while (*s == ' ' || *s == '\t')
s++;
// Find value end (next comma or end)
const char *valStart = s;
bool inQuotes = false;
while (*s && (*s != ',' || inQuotes)) {
if (*s == '"')
inQuotes = !inQuotes;
s++;
}
Ogre::String val(valStart, static_cast<size_t>(s - valStart));
// Trim trailing spaces from value
while (!val.empty() &&
(val.back() == ' ' || val.back() == '\t'))
val.pop_back();
// Strip quotes if present
if (val.size() >= 2 && val.front() == '"' &&
val.back() == '"') {
val = val.substr(1, val.size() - 2);
out.setStringValue(key, val);
} else {
// Try int/float/vec3
int iVal;
float fVal;
Ogre::Vector3 vVal;
int vType;
if (parseValueString(val, iVal, fVal, vVal, vType)) {
if (vType == 0)
out.setValue(key, iVal);
else if (vType == 1)
out.setFloatValue(key, fVal);
else
out.setVec3Value(key, vVal);
} else {
// Fallback to string
out.setStringValue(key, val);
}
}
if (*s == ',')
s++;
}
}
static bool parseBitIndex(const Ogre::String &str, int &out)
{
// Try numeric first
@@ -233,6 +318,15 @@ BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e,
return Status::success;
}
if (node.type == "sendEvent") {
if (isNewlyActive(state, &node)) {
GoapBlackboard params;
parseEventParams(node.params, params);
EventBus::getInstance().send(node.name, params);
}
return Status::success;
}
if (node.type == "delay") {
char key[32];
snprintf(key, sizeof(key), "%p", (void *)&node);

View File

@@ -0,0 +1,78 @@
#include "EventBus.hpp"
EventBus &EventBus::getInstance()
{
static EventBus instance;
return instance;
}
EventBus::ListenerId EventBus::subscribe(const Ogre::String &eventName,
Callback cb)
{
ListenerId id = m_nextId++;
m_listeners[eventName].push_back({ id, std::move(cb) });
m_idToEvent[id] = eventName;
return id;
}
void EventBus::unsubscribe(ListenerId id)
{
auto it = m_idToEvent.find(id);
if (it == m_idToEvent.end())
return;
const Ogre::String &eventName = it->second;
auto lit = m_listeners.find(eventName);
if (lit != m_listeners.end()) {
auto &vec = lit->second;
for (auto vit = vec.begin(); vit != vec.end(); ++vit) {
if (vit->id == id) {
vec.erase(vit);
break;
}
}
if (vec.empty())
m_listeners.erase(lit);
}
m_idToEvent.erase(it);
}
void EventBus::send(const Ogre::String &eventName,
const GoapBlackboard &params)
{
auto lit = m_listeners.find(eventName);
if (lit == m_listeners.end())
return;
// Copy the listener list in case a callback mutates subscriptions
auto listeners = lit->second;
for (const auto &listener : listeners) {
if (listener.callback)
listener.callback(eventName, params);
}
}
void EventBus::send(const Ogre::String &eventName,
const Ogre::String &paramName, int value)
{
GoapBlackboard params;
params.setValue(paramName, value);
send(eventName, params);
}
void EventBus::send(const Ogre::String &eventName,
const Ogre::String &paramName, float value)
{
GoapBlackboard params;
params.setFloatValue(paramName, value);
send(eventName, params);
}
void EventBus::send(const Ogre::String &eventName,
const Ogre::String &paramName,
const Ogre::Vector3 &value)
{
GoapBlackboard params;
params.setVec3Value(paramName, value);
send(eventName, params);
}

View File

@@ -0,0 +1,66 @@
#ifndef EDITSCENE_EVENT_BUS_HPP
#define EDITSCENE_EVENT_BUS_HPP
#pragma once
#include "../components/GoapBlackboard.hpp"
#include <Ogre.h>
#include <cstdint>
#include <functional>
#include <unordered_map>
#include <vector>
/**
* Global synchronous event bus.
*
* Subscribers register callbacks by event name. When an event is sent,
* all matching callbacks are invoked immediately in the send() call.
*
* Payload is a GoapBlackboard — reuses the existing int/float/vec3/bit/string
* storage with zero RTTI overhead.
*/
class EventBus {
public:
using ListenerId = uint64_t;
using Callback =
std::function<void(const Ogre::String &eventName,
const GoapBlackboard &params)>;
static EventBus &getInstance();
/** Subscribe to an event. Returns a handle for unsubscribe(). */
ListenerId subscribe(const Ogre::String &eventName, Callback cb);
/** Unsubscribe by handle. Safe to call with invalid id. */
void unsubscribe(ListenerId id);
/** Send an event with a GoapBlackboard payload. */
void send(const Ogre::String &eventName,
const GoapBlackboard &params = {});
/** Convenience: send event with a single int param. */
void send(const Ogre::String &eventName,
const Ogre::String &paramName, int value);
/** Convenience: send event with a single float param. */
void send(const Ogre::String &eventName,
const Ogre::String &paramName, float value);
/** Convenience: send event with a single Vec3 param. */
void send(const Ogre::String &eventName,
const Ogre::String &paramName,
const Ogre::Vector3 &value);
private:
EventBus() = default;
struct Listener {
ListenerId id;
Callback callback;
};
ListenerId m_nextId = 1;
std::unordered_map<Ogre::String, std::vector<Listener>> m_listeners;
std::unordered_map<ListenerId, Ogre::String> m_idToEvent;
};
#endif // EDITSCENE_EVENT_BUS_HPP

View File

@@ -0,0 +1,226 @@
#include "EventHandlerSystem.hpp"
#include "BehaviorTreeSystem.hpp"
#include "../components/EventHandler.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/GoapBlackboard.hpp"
#include <OgreLogManager.h>
EventHandlerSystem::EventHandlerSystem(flecs::world &world,
BehaviorTreeSystem *btSystem)
: m_world(world)
, m_btSystem(btSystem)
{
}
EventHandlerSystem::~EventHandlerSystem()
{
// Unsubscribe all remaining listeners
for (auto &pair : m_subscriptions) {
EventBus::getInstance().unsubscribe(pair.second);
}
m_subscriptions.clear();
}
void EventHandlerSystem::subscribeEntity(
flecs::entity e, const EventHandlerComponent &handler)
{
if (!handler.enabled || handler.eventName.empty())
return;
flecs::entity_t id = e.id();
if (m_subscriptions.find(id) != m_subscriptions.end())
return;
EventBus::ListenerId lid = EventBus::getInstance().subscribe(
handler.eventName,
[this, id, handler](const Ogre::String &,
const GoapBlackboard &params) {
this->onEvent(id, handler.eventName, params);
});
m_subscriptions[id] = lid;
}
void EventHandlerSystem::unsubscribeEntity(flecs::entity_t id)
{
auto it = m_subscriptions.find(id);
if (it == m_subscriptions.end())
return;
EventBus::getInstance().unsubscribe(it->second);
m_subscriptions.erase(it);
// If there is an active handler, abort it and clean up
auto activeIt = m_activeHandlers.find(id);
if (activeIt != m_activeHandlers.end()) {
auto keysIt = m_injectedKeys.find(id);
if (keysIt != m_injectedKeys.end()) {
flecs::entity e = m_world.entity(id);
if (e.is_alive())
removeParams(e, keysIt->second);
m_injectedKeys.erase(keysIt);
}
m_activeHandlers.erase(activeIt);
}
}
void EventHandlerSystem::onEvent(flecs::entity_t entityId,
const Ogre::String &eventName,
const GoapBlackboard &params)
{
(void)eventName;
flecs::entity e = m_world.entity(entityId);
if (!e.is_alive() || !e.has<EventHandlerComponent>())
return;
auto &handler = e.get<EventHandlerComponent>();
if (!handler.enabled || handler.actionName.empty())
return;
// If already handling an event, abort the previous one first
auto activeIt = m_activeHandlers.find(entityId);
if (activeIt != m_activeHandlers.end()) {
auto keysIt = m_injectedKeys.find(entityId);
if (keysIt != m_injectedKeys.end()) {
removeParams(e, keysIt->second);
m_injectedKeys.erase(keysIt);
}
m_activeHandlers.erase(activeIt);
}
// Start new handler
ActiveHandler ah;
ah.entityId = entityId;
ah.actionName = handler.actionName;
ah.eventParams = params;
ah.firstFrame = true;
m_activeHandlers[entityId] = ah;
// Inject params immediately so the first update() tick sees them
std::unordered_set<std::string> injected;
injectParams(e, params, injected);
m_injectedKeys[entityId] = std::move(injected);
}
void EventHandlerSystem::injectParams(
flecs::entity e, const GoapBlackboard &params,
std::unordered_set<std::string> &outKeys)
{
if (!e.has<GoapBlackboard>())
e.set<GoapBlackboard>({});
auto &bb = e.get_mut<GoapBlackboard>();
for (const auto &pair : params.values) {
bb.setValue(pair.first, pair.second);
outKeys.insert(pair.first);
}
for (const auto &pair : params.floatValues) {
bb.setFloatValue(pair.first, pair.second);
outKeys.insert(pair.first);
}
for (const auto &pair : params.vec3Values) {
bb.setVec3Value(pair.first, pair.second);
outKeys.insert(pair.first);
}
for (const auto &pair : params.stringValues) {
bb.setStringValue(pair.first, pair.second);
outKeys.insert(pair.first);
}
// Bits are not injected individually; they are part of the event
// payload semantics but merging bits globally is risky.
// Instead, event bits are NOT auto-injected. If needed, use
// setBit nodes inside the handler BT.
}
void EventHandlerSystem::removeParams(
flecs::entity e,
const std::unordered_set<std::string> &keys)
{
if (!e.has<GoapBlackboard>())
return;
auto &bb = e.get_mut<GoapBlackboard>();
for (const auto &key : keys) {
bb.removeValue(key);
bb.removeFloatValue(key);
bb.removeVec3Value(key);
bb.removeStringValue(key);
}
}
void EventHandlerSystem::update(float deltaTime)
{
if (!m_btSystem)
return;
// --- Sync subscriptions with current entities ---
std::unordered_set<flecs::entity_t> currentEntities;
m_world.query<EventHandlerComponent>().each(
[&](flecs::entity e, EventHandlerComponent &handler) {
currentEntities.insert(e.id());
if (handler.enabled) {
subscribeEntity(e, handler);
} else {
unsubscribeEntity(e.id());
}
});
// Unsubscribe entities that lost their component
std::vector<flecs::entity_t> toRemove;
for (auto &pair : m_subscriptions) {
if (currentEntities.find(pair.first) == currentEntities.end())
toRemove.push_back(pair.first);
}
for (flecs::entity_t id : toRemove)
unsubscribeEntity(id);
// --- Tick active handlers ---
std::vector<flecs::entity_t> completedHandlers;
for (auto &pair : m_activeHandlers) {
flecs::entity_t id = pair.first;
ActiveHandler &ah = pair.second;
// Look up the action in the database
ActionDatabase *db = nullptr;
m_world.query<ActionDatabase>().each(
[&](flecs::entity, ActionDatabase &database) {
if (!db)
db = &database;
});
if (!db) {
completedHandlers.push_back(id);
continue;
}
const GoapAction *action = db->findAction(ah.actionName);
if (!action) {
Ogre::LogManager::getSingleton().logMessage(
"[EventHandlerSystem] Action not found: " +
ah.actionName);
completedHandlers.push_back(id);
continue;
}
auto status = m_btSystem->evaluatePlayerAction(
id, action->behaviorTree, deltaTime, ah.firstFrame);
ah.firstFrame = false;
if (status != BehaviorTreeSystem::Status::running) {
completedHandlers.push_back(id);
}
}
// --- Clean up completed handlers ---
for (flecs::entity_t id : completedHandlers) {
flecs::entity e = m_world.entity(id);
auto keysIt = m_injectedKeys.find(id);
if (keysIt != m_injectedKeys.end()) {
if (e.is_alive())
removeParams(e, keysIt->second);
m_injectedKeys.erase(keysIt);
}
m_activeHandlers.erase(id);
}
}

View File

@@ -0,0 +1,64 @@
#ifndef EDITSCENE_EVENT_HANDLER_SYSTEM_HPP
#define EDITSCENE_EVENT_HANDLER_SYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "EventBus.hpp"
#include "../components/GoapBlackboard.hpp"
class BehaviorTreeSystem;
class EditorApp;
/**
* System that executes behavior trees in response to events.
*
* For each entity with EventHandlerComponent, subscribes to the
* specified event. When the event fires, copies parameters into the
* entity's GoapBlackboard and runs the referenced action's behavior
* tree. Cleans up injected parameters when the tree completes.
*/
class EventHandlerSystem {
public:
EventHandlerSystem(flecs::world &world, BehaviorTreeSystem *btSystem);
~EventHandlerSystem();
void update(float deltaTime);
private:
struct ActiveHandler {
flecs::entity_t entityId;
Ogre::String actionName;
GoapBlackboard eventParams;
bool firstFrame = true;
};
void subscribeEntity(flecs::entity e,
const class EventHandlerComponent &handler);
void unsubscribeEntity(flecs::entity_t id);
void onEvent(flecs::entity_t entityId,
const Ogre::String &eventName,
const GoapBlackboard &params);
void injectParams(flecs::entity e, const GoapBlackboard &params,
std::unordered_set<std::string> &outKeys);
void removeParams(flecs::entity e,
const std::unordered_set<std::string> &keys);
flecs::world &m_world;
BehaviorTreeSystem *m_btSystem;
// Per-entity event subscription
std::unordered_map<flecs::entity_t, EventBus::ListenerId> m_subscriptions;
// Per-entity active handler (one at a time per entity)
std::unordered_map<flecs::entity_t, ActiveHandler> m_activeHandlers;
// Keys injected into blackboard per active handler
std::unordered_map<flecs::entity_t, std::unordered_set<std::string>> m_injectedKeys;
};
#endif // EDITSCENE_EVENT_HANDLER_SYSTEM_HPP

View File

@@ -28,6 +28,7 @@
#include "../components/WaterPlane.hpp"
#include "../components/Sun.hpp"
#include "../components/Skybox.hpp"
#include "../components/EventHandler.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/ActionDebug.hpp"
#include "../components/SmartObject.hpp"
@@ -307,6 +308,9 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
if (entity.has<ActuatorComponent>()) {
json["actuator"] = serializeActuator(entity);
}
if (entity.has<EventHandlerComponent>()) {
json["eventHandler"] = serializeEventHandler(entity);
}
if (entity.has<GoapPlannerComponent>()) {
json["goapPlanner"] = serializeGoapPlanner(entity);
}
@@ -516,6 +520,9 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
if (json.contains("actuator")) {
deserializeActuator(entity, json["actuator"]);
}
if (json.contains("eventHandler")) {
deserializeEventHandler(entity, json["eventHandler"]);
}
if (json.contains("goapPlanner")) {
deserializeGoapPlanner(entity, json["goapPlanner"]);
}
@@ -800,6 +807,9 @@ void SceneSerializer::deserializeEntityComponents(
}
if (json.contains("actuator")) {
deserializeActuator(entity, json["actuator"]);
if (json.contains("eventHandler")) {
deserializeEventHandler(entity, json["eventHandler"]);
}
}
if (json.contains("goapPlanner")) {
deserializeGoapPlanner(entity, json["goapPlanner"]);
@@ -3700,3 +3710,24 @@ void SceneSerializer::deserializeActuator(flecs::entity entity,
}
entity.set<ActuatorComponent>(actuator);
}
nlohmann::json SceneSerializer::serializeEventHandler(flecs::entity entity)
{
const EventHandlerComponent &handler = entity.get<EventHandlerComponent>();
nlohmann::json json;
json["eventName"] = handler.eventName;
json["actionName"] = handler.actionName;
json["enabled"] = handler.enabled;
return json;
}
void SceneSerializer::deserializeEventHandler(flecs::entity entity,
const nlohmann::json &json)
{
EventHandlerComponent handler;
handler.eventName = json.value("eventName", handler.eventName);
handler.actionName = json.value("actionName", handler.actionName);
handler.enabled = json.value("enabled", handler.enabled);
entity.set<EventHandlerComponent>(handler);
}

View File

@@ -211,6 +211,7 @@ private:
nlohmann::json serializePathFollowing(flecs::entity entity);
nlohmann::json serializeSmartObject(flecs::entity entity);
nlohmann::json serializeActuator(flecs::entity entity);
nlohmann::json serializeEventHandler(flecs::entity entity);
nlohmann::json serializeGoapPlanner(flecs::entity entity);
nlohmann::json serializeBehaviorTree(flecs::entity entity);
void deserializeActionDatabase(flecs::entity entity,
@@ -223,6 +224,8 @@ private:
const nlohmann::json &json);
void deserializeActuator(flecs::entity entity,
const nlohmann::json &json);
void deserializeEventHandler(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,64 @@
#include "EventHandlerEditor.hpp"
#include "../components/ActionDatabase.hpp"
#include <imgui.h>
static ActionDatabase *findDatabase(flecs::entity entity)
{
auto world = entity.world();
ActionDatabase *db = nullptr;
world.query<ActionDatabase>().each(
[&](flecs::entity, ActionDatabase &database) {
if (!db)
db = &database;
});
return db;
}
bool EventHandlerEditor::renderComponent(flecs::entity entity,
EventHandlerComponent &handler)
{
bool modified = false;
ImGui::PushID("EventHandler");
char eventBuf[256];
snprintf(eventBuf, sizeof(eventBuf), "%s", handler.eventName.c_str());
if (ImGui::InputText("Event Name", eventBuf, sizeof(eventBuf))) {
handler.eventName = eventBuf;
modified = true;
}
if (ImGui::Checkbox("Enabled", &handler.enabled))
modified = true;
// Action selection from database
ActionDatabase *db = findDatabase(entity);
if (db && !db->actions.empty()) {
int selected = -1;
std::vector<const char *> names;
for (size_t i = 0; i < db->actions.size(); i++) {
names.push_back(db->actions[i].name.c_str());
if (handler.actionName == db->actions[i].name)
selected = static_cast<int>(i);
}
if (ImGui::Combo("Action", &selected, names.data(),
static_cast<int>(names.size()))) {
if (selected >= 0 &&
selected < static_cast<int>(names.size()))
handler.actionName = names[selected];
modified = true;
}
} else {
ImGui::TextDisabled("No actions in database");
char actionBuf[256];
snprintf(actionBuf, sizeof(actionBuf), "%s",
handler.actionName.c_str());
if (ImGui::InputText("Action Name", actionBuf,
sizeof(actionBuf))) {
handler.actionName = actionBuf;
modified = true;
}
}
ImGui::PopID();
return modified;
}

View File

@@ -0,0 +1,20 @@
#ifndef EDITSCENE_EVENT_HANDLER_EDITOR_HPP
#define EDITSCENE_EVENT_HANDLER_EDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/EventHandler.hpp"
class EventHandlerEditor : public ComponentEditor<EventHandlerComponent> {
public:
const char *getName() const override
{
return "Event Handler";
}
protected:
bool renderComponent(flecs::entity entity,
EventHandlerComponent &handler) override;
};
#endif // EDITSCENE_EVENT_HANDLER_EDITOR_HPP