Scene Script added. Hide verbose error message.

This commit is contained in:
2026-09-04 23:45:08 +03:00
parent b062d8331d
commit 4670aa7af9
17 changed files with 543 additions and 2 deletions
+39
View File
@@ -342,6 +342,45 @@ uses `EditorApp::getPlayerCharacterEntity()` to know which character performs th
action. Actions run through `BehaviorTreeSystem` and lock player input while
running (`PlayerControllerComponent::inputLocked`).
### SceneScriptComponent & SceneScriptSystem
Attaches a Lua script to a scene or prefab entity. Fields:
- `scriptPath` Lua file resolved from the `LuaScripts` resource group;
wins over `inlineScript` when set.
- `inlineScript` Lua source stored directly on the component. The editor
adder prefills it with a commented template subscribing to the load events.
`SceneScriptSystem` is a static helper (no per-frame update):
- `init(lua_State *)` stores the shared `m_lua` state; called once from
`EditorApp::setup()` after the Lua APIs are registered.
- `loadPendingScripts(world)` executes every not-yet-executed
`SceneScriptComponent` exactly once in that shared state. Dedupe keys: the
file path for `scriptPath`, `"inline:" + std::hash(source)` for inline
scripts. Scripts are never unloaded; a script that fails (open error, load
error or runtime error) is logged via `Ogre::LogManager` and NOT marked
executed, so the next load retries it.
- `sendSceneLoaded(scenePath)` sends `"scene_loaded"` with param `scenePath`;
`sendPrefabLoaded(prefabPath, entityId)` sends `"prefab_loaded"` with
params `prefabPath` and `entityId` (both via `EventBus` +
`editScene::EventParams`).
Wiring call sites (each calls `loadPendingScripts` then sends the event):
- `EditorApp::setup()` after Lua init, for the `startup_menu.json` loaded
earlier in game mode (the menu scene loads before the Lua state exists, so
its scripts run here, just before `game_start` is sent; their subscriptions
therefore see `scene_loaded` but miss the menu's `prefab_loaded` events).
- `EditorApp::startNewGame()` after `resolveInstances()`.
- `EditorApp::loadGame()` after `resolveInstances()`.
- `EditorUISystem::loadScene()` editor scene loads.
- `PrefabSystem::instantiatePrefab()` (in `SceneSerializer.cpp`) after a
prefab instance is created, sends `prefab_loaded`.
Lua binding: `ecs.get_component`/`set_component` with `"SceneScript"`
(`scriptPath`, `inlineScript`). Example: `lua-examples/scene_script_example.lua`.
### Save/Load
Game-mode saves are JSON files in the OS user-data directory (see
+6
View File
@@ -82,6 +82,9 @@ set(EDITSCENE_SOURCES
systems/EventHandlerSystem.cpp
ui/EventHandlerEditor.cpp
components/EventHandlerModule.cpp
systems/SceneScriptSystem.cpp
ui/SceneScriptEditor.cpp
components/SceneScriptModule.cpp
systems/PrefabSystem.cpp
ui/PrefabInstanceEditor.cpp
@@ -285,6 +288,9 @@ set(EDITSCENE_HEADERS
components/EventHandler.hpp
systems/EventHandlerSystem.hpp
ui/EventHandlerEditor.hpp
components/SceneScript.hpp
systems/SceneScriptSystem.hpp
ui/SceneScriptEditor.hpp
components/PrefabInstance.hpp
components/TerrainPrefabSpawner.hpp
ui/PrefabInstanceEditor.hpp
+28
View File
@@ -102,8 +102,10 @@
#include "systems/ActuatorSystem.hpp"
#include "systems/EventHandlerSystem.hpp"
#include "systems/EventBus.hpp"
#include "systems/SceneScriptSystem.hpp"
#include "systems/ItemSystem.hpp"
#include "components/EventHandler.hpp"
#include "components/SceneScript.hpp"
#include "components/Item.hpp"
#include "components/Inventory.hpp"
@@ -730,6 +732,10 @@ void EditorApp::setup()
std::make_unique<PlayerControllerSystem>(
m_world, m_sceneMgr, this);
/* Set when startup_menu.json loaded successfully; its scene
* scripts run after Lua init below. */
bool startupMenuLoaded = false;
if (m_gameMode == GameMode::Game) {
// Load startup menu scene configured in editor.
// This must happen before show() so the
@@ -741,6 +747,7 @@ void EditorApp::setup()
m_uiSystem.get())) {
PrefabSystem prefabSys(m_world, m_sceneMgr);
prefabSys.resolveInstances();
startupMenuLoaded = true;
Ogre::LogManager::getSingleton().logMessage(
"Game mode: startup_menu.json loaded");
} else {
@@ -812,6 +819,9 @@ void EditorApp::setup()
editScene::registerLuaItemApi(L);
editScene::registerLuaTerrainApi(L);
// Scene scripts execute in this shared Lua state.
SceneScriptSystem::init(L);
// Run late setup: load data.lua and initial scripts.
m_lua.lateSetup();
@@ -828,6 +838,17 @@ void EditorApp::setup()
}
if (m_gameMode == GameMode::Game) {
// Run pending scene scripts (from startup_menu.json) so
// their event subscriptions are active before
// scene_loaded and game_start are sent. The startup
// menu scene loads before the Lua state is initialized,
// so this happens here instead of at load time.
if (startupMenuLoaded) {
SceneScriptSystem::loadPendingScripts(m_world);
SceneScriptSystem::sendSceneLoaded(
"startup_menu.json");
}
// Queue "game_start" event after Lua scripts are loaded
// so Lua subscribers registered via ecs.subscribe_event()
// will receive the event.
@@ -951,6 +972,8 @@ void EditorApp::startNewGame(const Ogre::String &scenePath)
if (serializer.loadFromFile(scenePath, m_uiSystem.get())) {
PrefabSystem prefabSys(m_world, m_sceneMgr);
prefabSys.resolveInstances();
SceneScriptSystem::loadPendingScripts(m_world);
SceneScriptSystem::sendSceneLoaded(scenePath);
setGamePlayState(GamePlayState::Playing);
m_currentBaseScene = scenePath;
m_playTime = 0.0f;
@@ -1169,6 +1192,8 @@ void EditorApp::loadGame(const std::string &slotPath)
}
PrefabSystem prefabSys(m_world, m_sceneMgr);
prefabSys.resolveInstances();
SceneScriptSystem::loadPendingScripts(m_world);
SceneScriptSystem::sendSceneLoaded(baseScene);
/* Restore registries */
auto &registry = CharacterRegistry::getSingleton();
@@ -1461,6 +1486,9 @@ void EditorApp::setupECS()
// Register Event Handler component
m_world.component<EventHandlerComponent>();
// Register Scene Script component
m_world.component<SceneScriptComponent>();
// Register GOAP Planner component
m_world.component<GoapPlannerComponent>();
@@ -0,0 +1,28 @@
#ifndef EDITSCENE_SCENE_SCRIPT_HPP
#define EDITSCENE_SCENE_SCRIPT_HPP
#pragma once
#include <string>
/**
* Scene/prefab Lua script component.
*
* References a Lua script that is executed exactly once in the shared
* lua_State owned by EditorApp when the scene or prefab containing this
* entity is loaded. Scripts are never unloaded; their usual purpose is
* to subscribe event handlers (ecs.subscribe_event) specific to the
* scene or prefab.
*
* If scriptPath is non-empty it wins and the file is resolved in the
* "LuaScripts" OGRE resource group (same resolution as
* LuaState::luaLibraryLoader). Otherwise inlineScript is executed.
* Execution is deduplicated: by path for file scripts, by content hash
* for inline scripts. On a Lua error the script is NOT marked executed,
* so a later load can retry.
*/
struct SceneScriptComponent {
std::string scriptPath;
std::string inlineScript;
};
#endif // EDITSCENE_SCENE_SCRIPT_HPP
@@ -0,0 +1,52 @@
#include "SceneScript.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/SceneScriptEditor.hpp"
/**
* Template prefilled into inlineScript when the component is added in
* the editor. Shows the most common event handlers; everything is
* commented out so a fresh component executes as a no-op.
*/
static const char kSceneScriptTemplate[] =
"-- Scene script: executed once when this scene or prefab is loaded.\n"
"-- Lives in the shared Lua environment and is never unloaded.\n"
"-- Its purpose is to subscribe event handlers specific to this\n"
"-- scene/prefab. Uncomment the handlers you need:\n"
"\n"
"-- ecs.subscribe_event(\"scene_loaded\", function(event, params)\n"
"-- -- params.scenePath identifies the loaded scene\n"
"-- end)\n"
"\n"
"-- ecs.subscribe_event(\"prefab_loaded\", function(event, params)\n"
"-- -- params.prefabPath and params.entityId identify the instance\n"
"-- end)\n"
"\n"
"-- ecs.subscribe_event(\"game_start\", function(event, params) end)\n"
"-- ecs.subscribe_event(\"game_saved\", function(event, params) end)\n"
"-- ecs.subscribe_event(\"game_loaded\", function(event, params) end)\n"
"\n"
"-- Custom events:\n"
"-- local id = ecs.subscribe_event(\"my_event\", function(event, params) end)\n"
"-- ecs.unsubscribe_event(id)\n"
"-- ecs.send_event(\"my_event\", { key = \"value\" })\n";
REGISTER_COMPONENT_GROUP("Scene Script", "Game", SceneScriptComponent,
SceneScriptEditor)
{
registry.registerComponent<SceneScriptComponent>(
"Scene Script", "Game",
std::make_unique<SceneScriptEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<SceneScriptComponent>()) {
SceneScriptComponent script;
script.inlineScript = kSceneScriptTemplate;
e.set<SceneScriptComponent>(script);
}
},
// Remover
[](flecs::entity e) {
if (e.has<SceneScriptComponent>())
e.remove<SceneScriptComponent>();
});
}
@@ -0,0 +1,84 @@
-- =============================================================================
-- Scene Script Component Examples
-- =============================================================================
-- This file shows what a script attached to a SceneScript component typically
-- looks like. Put code like this either into the component's "Inline Script"
-- field or into a .lua file referenced by "Script Path" (resolved from the
-- "LuaScripts" resource group; the path wins when both fields are set).
--
-- Semantics:
-- - The script executes exactly ONCE, in the shared Lua state, when the
-- owning scene or prefab finishes loading.
-- - File scripts are deduplicated by path, inline scripts by content hash,
-- so reloading a scene does not re-run them.
-- - A script that raises a Lua error is logged and will be retried on the
-- next load.
-- - Scripts are never unloaded; subscriptions made here stay active.
--
-- Events sent by the engine (subscribe via ecs.subscribe_event):
-- "scene_loaded" - params.scenePath (string)
-- "prefab_loaded" - params.prefabPath (string), params.entityId (entity id)
-- Also useful: "game_start", "game_saved", "game_loaded", "scene_ready".
--
-- Caveat: scripts in startup_menu.json execute right before "game_start" is
-- sent, so they can rely on "scene_loaded" but will miss the "prefab_loaded"
-- events of prefabs instantiated while the menu scene was loading.
-- =============================================================================
print("Scene script executed")
-- =============================================================================
-- Reacting to scene_loaded
-- =============================================================================
ecs.subscribe_event("scene_loaded", function(event, params)
local path = params and params.scenePath or "?"
print("scene_loaded: " .. path)
-- Per-scene setup can go here, e.g.:
-- if path:match("town%.json$") then ... end
end)
-- =============================================================================
-- Reacting to prefab_loaded
-- =============================================================================
ecs.subscribe_event("prefab_loaded", function(event, params)
if not params then
return
end
print("prefab_loaded: " .. tostring(params.prefabPath) ..
" entity=" .. tostring(params.entityId))
-- The instantiated root entity is available as params.entityId, e.g.:
-- if ecs.has_component(params.entityId, "Transform") then
-- local t = ecs.get_component(params.entityId, "Transform")
-- print(" at " .. t.position[1] .. ", " .. t.position[3])
-- end
end)
-- =============================================================================
-- Other useful game events
-- =============================================================================
ecs.subscribe_event("game_start", function(event, params)
print("game_start: the player pressed New Game on the startup menu")
end)
ecs.subscribe_event("game_saved", function(event, params)
print("game_saved")
end)
ecs.subscribe_event("game_loaded", function(event, params)
print("game_loaded")
end)
-- =============================================================================
-- Custom events still work the same way
-- =============================================================================
ecs.subscribe_event("my_custom_event", function(event, params)
print("my_custom_event received")
end)
ecs.send_event("my_custom_event", { source = "scene_script_example" })
print("Scene script example completed")
@@ -54,6 +54,7 @@
#include "components/GoapRunner.hpp"
#include "components/PathFollowing.hpp"
#include "components/EventHandler.hpp"
#include "components/SceneScript.hpp"
#include "components/Item.hpp"
#include "components/Inventory.hpp"
#include "components/GeneratedPhysicsTag.hpp"
@@ -759,6 +760,20 @@ static void registerAllComponents()
c.enabled = lua_toboolean(L, -1) != 0;
lua_pop(L, 1););
// --- SceneScript ---
REGISTER_COMPONENT(
SceneScriptComponent, "SceneScript",
lua_pushstring(L, c.scriptPath.c_str());
lua_setfield(L, -2, "scriptPath");
lua_pushstring(L, c.inlineScript.c_str());
lua_setfield(L, -2, "inlineScript");
, if (lua_getfield(L, idx, "scriptPath"), lua_isstring(L, -1))
c.scriptPath = lua_tostring(L, -1);
lua_pop(L, 1);
if (lua_getfield(L, idx, "inlineScript"), lua_isstring(L, -1))
c.inlineScript = lua_tostring(L, -1);
lua_pop(L, 1););
// --- PathFollowing ---
REGISTER_COMPONENT(
PathFollowingComponent, "PathFollowing",
@@ -26,8 +26,11 @@ void BuoyancySystem::update(float deltaTime)
});
if (!waterPhysics) {
Ogre::LogManager::getSingleton().logMessage(
"BuoyancySystem: No WaterPhysics component found");
if (m_debugEnabled && !m_loggedMissingWater) {
Ogre::LogManager::getSingleton().logMessage(
"BuoyancySystem: No WaterPhysics component found");
m_loggedMissingWater = true;
}
return;
}
@@ -83,6 +83,9 @@ private:
// Debug mode
bool m_debugEnabled = false;
// Set once the missing-WaterPhysics message has been logged
bool m_loggedMissingWater = false;
// Camera position for water detection area
Ogre::Vector3 m_cameraPosition = Ogre::Vector3::ZERO;
@@ -58,6 +58,7 @@
#include "BuoyancySystem.hpp"
#include "NavMeshSystem.hpp"
#include "NormalDebugSystem.hpp"
#include "SceneScriptSystem.hpp"
#include <imgui.h>
#include <algorithm>
#include <filesystem>
@@ -1348,6 +1349,8 @@ void EditorUISystem::loadScene(const std::string &filepath)
if (m_serializer->loadFromFile(filepath, this)) {
m_navigationPanel.setBookmarks(m_serializer->getBookmarks());
SceneScriptSystem::loadPendingScripts(m_world);
SceneScriptSystem::sendSceneLoaded(filepath);
Ogre::LogManager::getSingleton().logMessage(
"Scene loaded from: " + filepath);
} else {
@@ -0,0 +1,116 @@
#include "SceneScriptSystem.hpp"
#include "EventBus.hpp"
#include "../components/SceneScript.hpp"
#include "../components/EventParams.hpp"
#include <lua.hpp>
#include <OgreLogManager.h>
#include <OgreResourceGroupManager.h>
#include <OgreDataStream.h>
#include <OgreException.h>
#include <functional>
#include <unordered_set>
/* Shared Lua state owned by EditorApp; set once via init(). */
static lua_State *s_luaState = nullptr;
/* Keys of scripts that executed successfully (path, or "inline:<hash>"). */
static std::unordered_set<std::string> s_executedScripts;
void SceneScriptSystem::init(lua_State *L)
{
s_luaState = L;
}
bool SceneScriptSystem::isInitialized()
{
return s_luaState != nullptr;
}
static bool executeScriptSource(const std::string &source,
const std::string &chunkName)
{
if (luaL_loadbufferx(s_luaState, source.c_str(), source.size(),
chunkName.c_str(), "t") != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< "SceneScriptSystem: failed to load " << chunkName
<< ": " << lua_tostring(s_luaState, -1);
lua_pop(s_luaState, 1);
return false;
}
if (lua_pcall(s_luaState, 0, 0, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< "SceneScriptSystem: error running " << chunkName
<< ": " << lua_tostring(s_luaState, -1);
lua_pop(s_luaState, 1);
return false;
}
return true;
}
void SceneScriptSystem::loadPendingScripts(flecs::world &world)
{
if (!s_luaState)
return;
world.query<SceneScriptComponent>().each(
[&](flecs::entity entity, SceneScriptComponent &script) {
std::string key, source, chunkName;
if (!script.scriptPath.empty()) {
key = script.scriptPath;
if (s_executedScripts.count(key))
return;
try {
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::
getSingleton()
.openResource(
script.scriptPath,
"LuaScripts");
source = stream->getAsString();
} catch (const Ogre::Exception &e) {
Ogre::LogManager::getSingleton()
.stream()
<< "SceneScriptSystem: cannot open "
<< script.scriptPath << ": "
<< e.getDescription();
return;
}
chunkName = "@" + script.scriptPath;
} else {
if (script.inlineScript.empty())
return;
key = "inline:" + std::to_string(
std::hash<std::string>()(
script.inlineScript));
if (s_executedScripts.count(key))
return;
source = script.inlineScript;
chunkName = "@" + key;
}
if (executeScriptSource(source, chunkName)) {
s_executedScripts.insert(key);
Ogre::LogManager::getSingleton().stream()
<< "SceneScriptSystem: executed "
<< chunkName << " (entity "
<< entity.id() << ")";
}
});
}
void SceneScriptSystem::sendSceneLoaded(const std::string &scenePath)
{
editScene::EventParams params;
params.setString("scenePath", scenePath);
EventBus::getInstance().send("scene_loaded", params);
}
void SceneScriptSystem::sendPrefabLoaded(const std::string &prefabPath,
uint64_t entityId)
{
editScene::EventParams params;
params.setString("prefabPath", prefabPath);
params.setEntityId("entityId", entityId);
EventBus::getInstance().send("prefab_loaded", params);
}
@@ -0,0 +1,46 @@
#ifndef EDITSCENE_SCENE_SCRIPT_SYSTEM_HPP
#define EDITSCENE_SCENE_SCRIPT_SYSTEM_HPP
#pragma once
#include <flecs.h>
#include <cstdint>
#include <string>
struct lua_State;
/**
* Executes SceneScriptComponent scripts and sends the scene/prefab
* load events.
*
* init() stores the shared lua_State owned by EditorApp and must be
* called once after the Lua APIs are registered.
* loadPendingScripts() runs every SceneScriptComponent script that has
* not been executed yet (dedupe: by path for file scripts, by content
* hash for inline scripts) and is called from every scene/prefab load
* site. Scripts are never unloaded; a script that fails with a Lua
* error is logged and NOT marked executed so a later load can retry.
*
* Events sent via the global EventBus:
* "scene_loaded" - param scenePath (string)
* "prefab_loaded" - params prefabPath (string), entityId (entity id)
*/
class SceneScriptSystem {
public:
/** Store the shared lua_State. Called once from EditorApp::setup(). */
static void init(lua_State *L);
/** True after init() — scripts can only execute when initialized. */
static bool isInitialized();
/** Execute all not-yet-executed SceneScriptComponent scripts. */
static void loadPendingScripts(flecs::world &world);
/** Send "scene_loaded" with named param scenePath. */
static void sendSceneLoaded(const std::string &scenePath);
/** Send "prefab_loaded" with named params prefabPath and entityId. */
static void sendPrefabLoaded(const std::string &prefabPath,
uint64_t entityId);
};
#endif // EDITSCENE_SCENE_SCRIPT_SYSTEM_HPP
@@ -40,6 +40,7 @@
#include "../components/Sun.hpp"
#include "../components/Skybox.hpp"
#include "../components/EventHandler.hpp"
#include "../components/SceneScript.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/ActionDebug.hpp"
#include "../components/SmartObject.hpp"
@@ -54,6 +55,7 @@
#include "../components/NavMesh.hpp"
#include "../components/PrefabInstance.hpp"
#include "EditorUISystem.hpp"
#include "SceneScriptSystem.hpp"
#include <random>
#include <fstream>
#include <iostream>
@@ -405,6 +407,9 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
if (entity.has<EventHandlerComponent>()) {
json["eventHandler"] = serializeEventHandler(entity);
}
if (entity.has<SceneScriptComponent>()) {
json["sceneScript"] = serializeSceneScript(entity);
}
if (entity.has<GoapPlannerComponent>()) {
json["goapPlanner"] = serializeGoapPlanner(entity);
}
@@ -644,6 +649,9 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
if (json.contains("eventHandler")) {
deserializeEventHandler(entity, json["eventHandler"]);
}
if (json.contains("sceneScript")) {
deserializeSceneScript(entity, json["sceneScript"]);
}
if (json.contains("goapPlanner")) {
deserializeGoapPlanner(entity, json["goapPlanner"]);
}
@@ -956,6 +964,9 @@ void SceneSerializer::deserializeEntityComponents(
deserializeEventHandler(entity, json["eventHandler"]);
}
}
if (json.contains("sceneScript")) {
deserializeSceneScript(entity, json["sceneScript"]);
}
if (json.contains("goapPlanner")) {
deserializeGoapPlanner(entity, json["goapPlanner"]);
}
@@ -1121,6 +1132,13 @@ bool SceneSerializer::instantiatePrefab(flecs::entity instanceEntity,
// Restore main scene entity map
m_entityMap = savedMap;
/* Run any scene scripts contained in the prefab subtree
* (pending-only, so repeat instantiations are cheap), then
* notify listeners that this instance finished loading. */
SceneScriptSystem::loadPendingScripts(m_world);
SceneScriptSystem::sendPrefabLoaded(filepath,
instanceEntity.id());
return true;
} catch (const std::exception &e) {
m_lastError =
@@ -4131,6 +4149,24 @@ void SceneSerializer::deserializeEventHandler(flecs::entity entity,
entity.set<EventHandlerComponent>(handler);
}
nlohmann::json SceneSerializer::serializeSceneScript(flecs::entity entity)
{
const SceneScriptComponent &script = entity.get<SceneScriptComponent>();
nlohmann::json json;
json["scriptPath"] = script.scriptPath;
json["inlineScript"] = script.inlineScript;
return json;
}
void SceneSerializer::deserializeSceneScript(flecs::entity entity,
const nlohmann::json &json)
{
SceneScriptComponent script;
script.scriptPath = json.value("scriptPath", "");
script.inlineScript = json.value("inlineScript", "");
entity.set<SceneScriptComponent>(script);
}
nlohmann::json SceneSerializer::serializeItem(flecs::entity entity)
{
const ItemComponent &item = entity.get<ItemComponent>();
@@ -277,6 +277,7 @@ private:
nlohmann::json serializeSmartObject(flecs::entity entity);
nlohmann::json serializeActuator(flecs::entity entity);
nlohmann::json serializeEventHandler(flecs::entity entity);
nlohmann::json serializeSceneScript(flecs::entity entity);
nlohmann::json serializeGoapPlanner(flecs::entity entity);
nlohmann::json serializeGoapRunner(flecs::entity entity);
nlohmann::json serializeBehaviorTree(flecs::entity entity);
@@ -291,6 +292,8 @@ private:
const nlohmann::json &json);
void deserializeEventHandler(flecs::entity entity,
const nlohmann::json &json);
void deserializeSceneScript(flecs::entity entity,
const nlohmann::json &json);
void deserializeGoapPlanner(flecs::entity entity,
const nlohmann::json &json);
void deserializeGoapRunner(flecs::entity entity,
@@ -965,6 +965,32 @@ static int testEventHandlerComponent(lua_State *L)
return 0;
}
// ---------------------------------------------------------------------------
// Test 29b: SceneScript component
// ---------------------------------------------------------------------------
static int testSceneScriptComponent(lua_State *L)
{
TEST("SceneScript component");
bool ok = runLua(
L,
"local id = ecs.create_entity();"
"ecs.set_component(id, 'SceneScript', {"
" scriptPath = 'scripts/my_scene.lua',"
" inlineScript = 'print(1)'"
"});"
"local s = ecs.get_component(id, 'SceneScript');"
"assert(s ~= nil, 'SceneScript should exist');"
"assert(s.scriptPath == 'scripts/my_scene.lua', 'wrong scriptPath');"
"assert(s.inlineScript == 'print(1)', 'wrong inlineScript')");
if (!ok)
FAIL("SceneScript component assertion failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 30: GoapBlackboard component
// ---------------------------------------------------------------------------
@@ -1917,6 +1943,7 @@ int main()
failures += testSmartObjectComponent(L);
failures += testActuatorComponent(L);
failures += testEventHandlerComponent(L);
failures += testSceneScriptComponent(L);
failures += testGoapBlackboardComponent(L);
failures += testPrefabInstanceComponent(L);
failures += testCellGridComponent(L);
@@ -0,0 +1,32 @@
#include "SceneScriptEditor.hpp"
#include <imgui.h>
bool SceneScriptEditor::renderComponent(flecs::entity entity,
SceneScriptComponent &script)
{
bool modified = false;
ImGui::PushID("SceneScript");
(void)entity;
char pathBuf[512];
snprintf(pathBuf, sizeof(pathBuf), "%s", script.scriptPath.c_str());
if (ImGui::InputText("Script Path", pathBuf, sizeof(pathBuf))) {
script.scriptPath = pathBuf;
modified = true;
}
ImGui::TextDisabled(
"File in the LuaScripts resource group; overrides the inline script");
static char scriptBuf[16384];
snprintf(scriptBuf, sizeof(scriptBuf), "%s",
script.inlineScript.c_str());
if (ImGui::InputTextMultiline("Inline Script", scriptBuf,
sizeof(scriptBuf),
ImVec2(-1.0f, 240.0f))) {
script.inlineScript = scriptBuf;
modified = true;
}
ImGui::PopID();
return modified;
}
@@ -0,0 +1,20 @@
#ifndef EDITSCENE_SCENE_SCRIPT_EDITOR_HPP
#define EDITSCENE_SCENE_SCRIPT_EDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/SceneScript.hpp"
class SceneScriptEditor : public ComponentEditor<SceneScriptComponent> {
public:
const char *getName() const override
{
return "Scene Script";
}
protected:
bool renderComponent(flecs::entity entity,
SceneScriptComponent &script) override;
};
#endif // EDITSCENE_SCENE_SCRIPT_EDITOR_HPP