diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e0c8d1340..4a6e676f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -780,16 +780,32 @@ jobs: env: CCACHE_COMPRESS: "true" CCACHE_COMPRESSLEVEL: "6" - run: cmake --build build --parallel $(nproc) + run: cmake --build build --parallel $(nproc) 2>&1 | tee cmake-build.log - name: Test env: SPARK_TEST_EXCLUDE: "LoadTest_" - run: cd build && ./bin/SparkTests 2>&1 | tail -5 + run: cd build && ./bin/SparkTests 2>&1 | tee ../test-output.log; tail -5 ../test-output.log - name: Generate coverage run: | lcov --capture --directory build --output-file coverage.info --ignore-errors mismatch,mismatch,gcov,negative --rc geninfo_unexecuted_blocks=1 lcov --remove coverage.info '/usr/*' '*/ThirdParty/*' '*/Tests/*' --output-file coverage.info --ignore-errors unused,negative lcov --list coverage.info --ignore-errors unused,negative 2>&1 | tee coverage-summary.txt + - name: Extract error summary + if: failure() + run: | + .github/scripts/extract-errors.sh \ + "coverage" \ + error-summary.json \ + cmake-build.log test-output.log + + - name: Upload error summary + if: failure() + uses: actions/upload-artifact@v7 + with: + name: ci-errors-coverage + path: error-summary.json + retention-days: ${{ env.ERROR_RETENTION_DAYS }} + - name: Post coverage summary to PR if: github.event_name == 'pull_request' uses: actions/github-script@v8 diff --git a/GameModules/SparkGameEngineEditor/CMakeLists.txt b/GameModules/SparkGameEngineEditor/CMakeLists.txt deleted file mode 100644 index cfbdcfd6b..000000000 --- a/GameModules/SparkGameEngineEditor/CMakeLists.txt +++ /dev/null @@ -1,255 +0,0 @@ -cmake_minimum_required(VERSION 3.25) - -# ================================================================ -# SparkGameEngineEditor - Engine/Editor No-Code Game Module DLL -# -# Showcases SparkEngine's no-code game creation tools: -# - Visual scripting with typed node graphs and pin validation -# - Level design with prefab placement, terrain, and foliage -# - Visual material editor with PBR output channels -# - VFX/particle authoring with composable emitter modules -# - Animation state machine editor with IK and blend layers -# - WYSIWYG UI editor with widget trees and data binding -# - Rapid prototyping with blockout meshes and game templates -# - Asset pipeline with import, LOD, compression, and packaging -# - Engine integration with project save/load and undo/redo -# -# Built as a shared library loaded by the engine at runtime. -# ================================================================ - -if(NOT CMAKE_PROJECT_NAME OR CMAKE_PROJECT_NAME STREQUAL "SparkGameEngineEditor") - project(SparkGameEngineEditor LANGUAGES CXX) - set(SPARK_GAME_EE_STANDALONE TRUE) -else() - set(SPARK_GAME_EE_STANDALONE FALSE) -endif() - -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -# --------------------------------------------------------------------- -# Standalone mode: locate SparkEngine -# --------------------------------------------------------------------- -if(SPARK_GAME_EE_STANDALONE) - if(NOT SPARK_ENGINE_DIR) - set(SPARK_ENGINE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." CACHE PATH - "Path to SparkEngine root directory") - endif() - - message(STATUS "SparkGameEngineEditor standalone build - Engine at: ${SPARK_ENGINE_DIR}") - - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) - - foreach(config Debug Release RelWithDebInfo MinSizeRel) - string(TOUPPER ${config} CONFIG_UPPER) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG_UPPER} ${CMAKE_BINARY_DIR}/bin) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CONFIG_UPPER} ${CMAKE_BINARY_DIR}/bin) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFIG_UPPER} ${CMAKE_BINARY_DIR}/lib) - endforeach() - - if(MSVC) - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") - add_compile_options(/W3 /MP /bigobj /wd4005 /wd4996 /wd4244 /wd4267) - add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX _CRT_SECURE_NO_WARNINGS SPARK_PLATFORM_WINDOWS) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - add_compile_options(-Wall -Wextra -Wno-unused-parameter -fPIC) - endif() - - set(THIRDPARTY_INCLUDE_DIRS "") - if(EXISTS "${SPARK_ENGINE_DIR}/ThirdParty/ECS/entt/single_include") - list(APPEND THIRDPARTY_INCLUDE_DIRS "${SPARK_ENGINE_DIR}/ThirdParty/ECS/entt/single_include") - endif() - if(EXISTS "${SPARK_ENGINE_DIR}/ThirdParty/Physics/bullet3/src") - list(APPEND THIRDPARTY_INCLUDE_DIRS "${SPARK_ENGINE_DIR}/ThirdParty/Physics/bullet3/src") - endif() - if(EXISTS "${SPARK_ENGINE_DIR}/ThirdParty/UI/imgui") - list(APPEND THIRDPARTY_INCLUDE_DIRS "${SPARK_ENGINE_DIR}/ThirdParty/UI/imgui") - endif() - - set(ENGINE_SOURCE_DIR "${SPARK_ENGINE_DIR}/SparkEngine/Source") -else() - set(ENGINE_SOURCE_DIR "${CMAKE_SOURCE_DIR}/SparkEngine/Source") -endif() - -# --------------------------------------------------------------------- -# Collect SparkGameEngineEditor source files -# --------------------------------------------------------------------- -file(GLOB_RECURSE SPARK_GAME_EE_SOURCES - CONFIGURE_DEPENDS - "Source/*.cpp" - "Source/*.h" - "Source/*.hpp" -) -list(FILTER SPARK_GAME_EE_SOURCES EXCLUDE REGEX ".*[Tt]est.*") -list(FILTER SPARK_GAME_EE_SOURCES EXCLUDE REGEX ".*[Ee]xample.*") - -# --------------------------------------------------------------------- -# Create the game as a SHARED library (DLL) -# --------------------------------------------------------------------- -add_library(SparkGameEngineEditor SHARED ${SPARK_GAME_EE_SOURCES}) - -target_compile_definitions(SparkGameEngineEditor PRIVATE SPARK_GAME_DLL SPARK_MODULE_DLL) - -# --------------------------------------------------------------------- -# Link against SparkEngineLib -# --------------------------------------------------------------------- -if(NOT SPARK_GAME_EE_STANDALONE AND TARGET SparkEngineLib) - if(WIN32) - target_link_libraries(SparkGameEngineEditor PRIVATE SparkEngineLib) - elseif(TARGET SparkEngineInterface) - target_link_libraries(SparkGameEngineEditor PRIVATE SparkEngineInterface) - endif() -endif() - -# --------------------------------------------------------------------- -# Include directories -# --------------------------------------------------------------------- -if(SPARK_GAME_EE_STANDALONE) - set(SPARK_SDK_INCLUDE_DIR "${SPARK_ENGINE_DIR}/SparkSDK/Include") -else() - set(SPARK_SDK_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/SparkSDK/Include") -endif() - -target_include_directories(SparkGameEngineEditor PRIVATE - "Source" - "${ENGINE_SOURCE_DIR}" - "${SPARK_SDK_INCLUDE_DIR}" - ${THIRDPARTY_INCLUDE_DIRS} -) - -# --------------------------------------------------------------------- -# Platform-specific libraries -# --------------------------------------------------------------------- -if(WIN32) - target_link_libraries(SparkGameEngineEditor PRIVATE - d3d11 dxgi d3dcompiler dxguid - kernel32 user32 gdi32 winspool - shell32 comdlg32 advapi32 - ole32 oleaut32 uuid - winmm - $<$:ws2_32> - $<$:wsock32> - $<$:crypt32> - $<$:wldap32> - $<$:normaliz> - ) - target_compile_definitions(SparkGameEngineEditor PRIVATE - WIN32_LEAN_AND_MEAN NOMINMAX _CRT_SECURE_NO_WARNINGS SPARK_PLATFORM_WINDOWS - ) -else() - find_package(Threads REQUIRED) - target_link_libraries(SparkGameEngineEditor PRIVATE - Threads::Threads - ${CMAKE_DL_LIBS} - ) - if(APPLE) - target_compile_definitions(SparkGameEngineEditor PRIVATE SPARK_PLATFORM_MACOS) - else() - target_compile_definitions(SparkGameEngineEditor PRIVATE SPARK_PLATFORM_LINUX) - endif() -endif() - -if(MSVC) - target_link_libraries(SparkGameEngineEditor PRIVATE legacy_stdio_definitions) -endif() - -# Link Jolt Physics if available -if(WIN32) - if(TARGET Jolt) - target_link_libraries(SparkGameEngineEditor PRIVATE Jolt) - endif() - if(TARGET miniz) - target_link_libraries(SparkGameEngineEditor PRIVATE miniz) - endif() - if(TARGET tinyobjloader) - target_link_libraries(SparkGameEngineEditor PRIVATE tinyobjloader) - endif() -endif() - -# Vulkan backend -if(SPARK_VULKAN_AVAILABLE) - target_compile_definitions(SparkGameEngineEditor PRIVATE SPARK_VULKAN_SUPPORT) - if(Vulkan_FOUND) - target_link_libraries(SparkGameEngineEditor PRIVATE Vulkan::Vulkan) - else() - target_include_directories(SparkGameEngineEditor PRIVATE ${Vulkan_INCLUDE_DIR}) - target_link_libraries(SparkGameEngineEditor PRIVATE ${Vulkan_LIBRARY}) - endif() -endif() - -# OpenGL backend -if(SPARK_OPENGL_AVAILABLE) - target_compile_definitions(SparkGameEngineEditor PRIVATE SPARK_OPENGL_SUPPORT) - target_link_libraries(SparkGameEngineEditor PRIVATE OpenGL::GL) - if(TARGET glad) - target_link_libraries(SparkGameEngineEditor PRIVATE glad) - endif() -endif() - -# Apply feature definitions -if(FEATURE_DEFINITIONS) - target_compile_definitions(SparkGameEngineEditor PRIVATE ${FEATURE_DEFINITIONS}) -endif() - -# --------------------------------------------------------------------- -# Post-build: Asset directories -# --------------------------------------------------------------------- -add_custom_command(TARGET SparkGameEngineEditor POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "$/Shaders" - COMMAND ${CMAKE_COMMAND} -E make_directory "$/Assets" - COMMENT "Creating SparkGameEngineEditor asset directory structure" -) - -# Copy shaders from engine -set(SHADER_SOURCE_DIR "${ENGINE_SOURCE_DIR}/../Shaders/HLSL") -if(NOT EXISTS "${SHADER_SOURCE_DIR}") - set(SHADER_SOURCE_DIR "${CMAKE_SOURCE_DIR}/SparkEngine/Shaders/HLSL") - if(SPARK_GAME_EE_STANDALONE) - set(SHADER_SOURCE_DIR "${SPARK_ENGINE_DIR}/SparkEngine/Shaders/HLSL") - endif() -endif() -if(EXISTS "${SHADER_SOURCE_DIR}") - file(GLOB_RECURSE SHADER_FILES "${SHADER_SOURCE_DIR}/*.hlsl") - foreach(SHADER_FILE ${SHADER_FILES}) - get_filename_component(SHADER_NAME ${SHADER_FILE} NAME) - add_custom_command(TARGET SparkGameEngineEditor POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${SHADER_FILE} - "$/Shaders/${SHADER_NAME}" - COMMENT "Copying shader ${SHADER_NAME}" - ) - endforeach() -endif() - -# --------------------------------------------------------------------- -# Visual Studio settings -# --------------------------------------------------------------------- -if(MSVC) - foreach(src ${SPARK_GAME_EE_SOURCES}) - get_filename_component(dir "${src}" DIRECTORY) - file(RELATIVE_PATH grp "${CMAKE_CURRENT_SOURCE_DIR}/Source" "${dir}") - string(REPLACE "/" "\\\\" grp "${grp}") - if(grp STREQUAL "") - source_group("Source Files" FILES "${src}") - else() - source_group("Source Files\\\\${grp}" FILES "${src}") - endif() - endforeach() - - if(SPARK_GAME_EE_STANDALONE) - set_property(TARGET SparkGameEngineEditor PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${SPARK_ENGINE_DIR}") - else() - set_property(TARGET SparkGameEngineEditor PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") - endif() -endif() - -message(STATUS "SparkGameEngineEditor configured as SHARED LIBRARY (engine editor game module DLL)") -if(SPARK_GAME_EE_STANDALONE) - message(STATUS " Mode: Standalone build") - message(STATUS " Engine: ${SPARK_ENGINE_DIR}") -else() - message(STATUS " Mode: Sub-project of SparkEngine") -endif() diff --git a/GameModules/SparkGameEngineEditor/Source/AnimationEditor/EEAnimationEditorSystem.cpp b/GameModules/SparkGameEngineEditor/Source/AnimationEditor/EEAnimationEditorSystem.cpp deleted file mode 100644 index 95f1cc5f8..000000000 --- a/GameModules/SparkGameEngineEditor/Source/AnimationEditor/EEAnimationEditorSystem.cpp +++ /dev/null @@ -1,414 +0,0 @@ -/** - * @file EEAnimationEditorSystem.cpp - * @brief Animation state machine editor with blend trees and IK setup - * - * Implements animation controller editing with state machines, transitions, - * blend parameters, IK chains, and preset templates. - */ - -#include "EEAnimationEditorSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#include - -namespace EngineEditor -{ - - bool EEAnimationEditorSystem::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - RegisterBuiltinPresets(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Animation editor system initialized (%zu controllers)", - m_controllers.size()); - return true; - } - - void EEAnimationEditorSystem::Update([[maybe_unused]] float deltaTime) - { - if (!m_initialized) - return; - } - - void EEAnimationEditorSystem::Shutdown() - { - m_controllers.clear(); - m_initialized = false; - } - - void EEAnimationEditorSystem::RenderDebugUI() {} - - uint32_t EEAnimationEditorSystem::CreateController(const std::string& name) - { - AnimController ctrl; - ctrl.controllerId = m_nextControllerId++; - ctrl.name = name; - - // Add default base layer - AnimLayer baseLayer; - baseLayer.layerId = m_nextLayerId++; - baseLayer.name = "Base Layer"; - baseLayer.blendMode = AnimBlendMode::Override; - ctrl.layers.push_back(std::move(baseLayer)); - - m_controllers.push_back(std::move(ctrl)); - return m_controllers.back().controllerId; - } - - bool EEAnimationEditorSystem::DeleteController(uint32_t controllerId) - { - auto it = std::find_if(m_controllers.begin(), m_controllers.end(), - [controllerId](const AnimController& c) { return c.controllerId == controllerId; }); - if (it == m_controllers.end()) - return false; - m_controllers.erase(it); - return true; - } - - uint32_t EEAnimationEditorSystem::RegisterClip(uint32_t controllerId, const std::string& name, - const std::string& path, float duration, bool looping) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId) - { - AnimClip clip; - clip.clipId = m_nextClipId++; - clip.name = name; - clip.assetPath = path; - clip.duration = duration; - clip.isLooping = looping; - ctrl.clips.push_back(clip); - return clip.clipId; - } - } - return 0; - } - - uint32_t EEAnimationEditorSystem::AddState(uint32_t controllerId, uint32_t layerIdx, const std::string& name, - uint32_t clipId) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId && layerIdx < ctrl.layers.size()) - { - AnimState state; - state.stateId = m_nextStateId++; - state.name = name; - state.clipId = clipId; - state.isDefault = ctrl.layers[layerIdx].states.empty(); - ctrl.layers[layerIdx].states.push_back(state); - return state.stateId; - } - } - return 0; - } - - bool EEAnimationEditorSystem::RemoveState(uint32_t controllerId, uint32_t layerIdx, uint32_t stateId) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId && layerIdx < ctrl.layers.size()) - { - auto& states = ctrl.layers[layerIdx].states; - auto it = std::find_if(states.begin(), states.end(), - [stateId](const AnimState& s) { return s.stateId == stateId; }); - if (it == states.end()) - return false; - - // Remove transitions involving this state - auto& trans = ctrl.layers[layerIdx].transitions; - std::erase_if(trans, [stateId](const AnimTransition& t) - { return t.fromStateId == stateId || t.toStateId == stateId; }); - - states.erase(it); - return true; - } - } - return false; - } - - uint32_t EEAnimationEditorSystem::AddTransition(uint32_t controllerId, uint32_t layerIdx, uint32_t fromState, - uint32_t toState, TransitionCondition cond, - const std::string& param) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId && layerIdx < ctrl.layers.size()) - { - AnimTransition t; - t.transitionId = m_nextTransitionId++; - t.fromStateId = fromState; - t.toStateId = toState; - t.condition = cond; - t.paramName = param; - ctrl.layers[layerIdx].transitions.push_back(t); - return t.transitionId; - } - } - return 0; - } - - uint32_t EEAnimationEditorSystem::AddParameter(uint32_t controllerId, const std::string& name, PinType type) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId) - { - AnimParameter param; - param.paramId = m_nextParamId++; - param.name = name; - param.type = type; - ctrl.parameters.push_back(param); - return param.paramId; - } - } - return 0; - } - - bool EEAnimationEditorSystem::SetParameterFloat(uint32_t controllerId, const std::string& name, float value) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId) - { - for (auto& p : ctrl.parameters) - { - if (p.name == name && p.type == PinType::Float) - { - p.valueFloat = value; - return true; - } - } - } - } - return false; - } - - bool EEAnimationEditorSystem::SetParameterBool(uint32_t controllerId, const std::string& name, bool value) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId) - { - for (auto& p : ctrl.parameters) - { - if (p.name == name && p.type == PinType::Bool) - { - p.valueBool = value; - return true; - } - } - } - } - return false; - } - - uint32_t EEAnimationEditorSystem::AddIKChain(uint32_t controllerId, const std::string& name, IKSolverType solver, - const std::string& root, const std::string& tip) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId) - { - IKChain chain; - chain.chainId = m_nextChainId++; - chain.name = name; - chain.solver = solver; - chain.rootBone = root; - chain.tipBone = tip; - ctrl.ikChains.push_back(chain); - return chain.chainId; - } - } - return 0; - } - - uint32_t EEAnimationEditorSystem::AddLayer(uint32_t controllerId, const std::string& name, AnimBlendMode mode) - { - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == controllerId) - { - AnimLayer layer; - layer.layerId = m_nextLayerId++; - layer.name = name; - layer.blendMode = mode; - ctrl.layers.push_back(std::move(layer)); - return layer.layerId; - } - } - return 0; - } - - uint32_t EEAnimationEditorSystem::CreatePresetLocomotion(const std::string& name) - { - uint32_t id = CreateController(name); - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == id) - { - // Register clips - uint32_t idleClip = RegisterClip(id, "Idle", "anims/idle.anim", 2.0f, true); - uint32_t walkClip = RegisterClip(id, "Walk", "anims/walk.anim", 1.0f, true); - uint32_t runClip = RegisterClip(id, "Run", "anims/run.anim", 0.8f, true); - uint32_t jumpClip = RegisterClip(id, "Jump", "anims/jump.anim", 0.6f, false); - uint32_t fallClip = RegisterClip(id, "Fall", "anims/fall.anim", 1.0f, true); - uint32_t landClip = RegisterClip(id, "Land", "anims/land.anim", 0.3f, false); - - // Parameters - AddParameter(id, "Speed", PinType::Float); - AddParameter(id, "IsGrounded", PinType::Bool); - AddParameter(id, "Jump", PinType::Bool); - - // States in base layer (index 0) - uint32_t idleState = AddState(id, 0, "Idle", idleClip); - uint32_t walkState = AddState(id, 0, "Walk", walkClip); - uint32_t runState = AddState(id, 0, "Run", runClip); - uint32_t jumpState = AddState(id, 0, "Jump", jumpClip); - uint32_t fallState = AddState(id, 0, "Fall", fallClip); - uint32_t landState = AddState(id, 0, "Land", landClip); - - // Transitions - AddTransition(id, 0, idleState, walkState, TransitionCondition::FloatThreshold, "Speed"); - AddTransition(id, 0, walkState, idleState, TransitionCondition::FloatThreshold, "Speed"); - AddTransition(id, 0, walkState, runState, TransitionCondition::FloatThreshold, "Speed"); - AddTransition(id, 0, runState, walkState, TransitionCondition::FloatThreshold, "Speed"); - AddTransition(id, 0, idleState, jumpState, TransitionCondition::BoolParam, "Jump"); - AddTransition(id, 0, walkState, jumpState, TransitionCondition::BoolParam, "Jump"); - AddTransition(id, 0, runState, jumpState, TransitionCondition::BoolParam, "Jump"); - AddTransition(id, 0, jumpState, fallState, TransitionCondition::AnimFinished, ""); - AddTransition(id, 0, fallState, landState, TransitionCondition::BoolParam, "IsGrounded"); - AddTransition(id, 0, landState, idleState, TransitionCondition::AnimFinished, ""); - - // IK chains - AddIKChain(id, "LeftFoot", IKSolverType::TwoBone, "LeftUpLeg", "LeftFoot"); - AddIKChain(id, "RightFoot", IKSolverType::TwoBone, "RightUpLeg", "RightFoot"); - - break; - } - } - return id; - } - - uint32_t EEAnimationEditorSystem::CreatePresetCombat(const std::string& name) - { - uint32_t id = CreateController(name); - for (auto& ctrl : m_controllers) - { - if (ctrl.controllerId == id) - { - uint32_t idleClip = RegisterClip(id, "CombatIdle", "anims/combat_idle.anim", 1.5f, true); - uint32_t attackClip = RegisterClip(id, "Attack", "anims/attack.anim", 0.5f, false); - uint32_t heavyClip = RegisterClip(id, "HeavyAttack", "anims/heavy_attack.anim", 0.8f, false); - uint32_t blockClip = RegisterClip(id, "Block", "anims/block.anim", 1.0f, true); - uint32_t dodgeClip = RegisterClip(id, "Dodge", "anims/dodge.anim", 0.4f, false); - uint32_t hitClip = RegisterClip(id, "Hit", "anims/hit_react.anim", 0.3f, false); - - AddParameter(id, "Attack", PinType::Bool); - AddParameter(id, "HeavyAttack", PinType::Bool); - AddParameter(id, "Block", PinType::Bool); - AddParameter(id, "Dodge", PinType::Bool); - AddParameter(id, "Hit", PinType::Bool); - - uint32_t idleState = AddState(id, 0, "CombatIdle", idleClip); - uint32_t attackState = AddState(id, 0, "Attack", attackClip); - uint32_t heavyState = AddState(id, 0, "HeavyAttack", heavyClip); - uint32_t blockState = AddState(id, 0, "Block", blockClip); - uint32_t dodgeState = AddState(id, 0, "Dodge", dodgeClip); - uint32_t hitState = AddState(id, 0, "Hit", hitClip); - - AddTransition(id, 0, idleState, attackState, TransitionCondition::BoolParam, "Attack"); - AddTransition(id, 0, idleState, heavyState, TransitionCondition::BoolParam, "HeavyAttack"); - AddTransition(id, 0, idleState, blockState, TransitionCondition::BoolParam, "Block"); - AddTransition(id, 0, idleState, dodgeState, TransitionCondition::BoolParam, "Dodge"); - AddTransition(id, 0, attackState, idleState, TransitionCondition::AnimFinished, ""); - AddTransition(id, 0, heavyState, idleState, TransitionCondition::AnimFinished, ""); - AddTransition(id, 0, blockState, idleState, TransitionCondition::BoolParam, "Block"); - AddTransition(id, 0, dodgeState, idleState, TransitionCondition::AnimFinished, ""); - AddTransition(id, 0, idleState, hitState, TransitionCondition::BoolParam, "Hit"); - AddTransition(id, 0, hitState, idleState, TransitionCondition::AnimFinished, ""); - - // Upper body layer for attack while moving - AddLayer(id, "UpperBody", AnimBlendMode::Additive); - - // Aim IK - AddIKChain(id, "AimLook", IKSolverType::LookAt, "Spine", "Head"); - - break; - } - } - return id; - } - - std::string EEAnimationEditorSystem::GetControllerListString() const - { - std::string s = "=== Animation Controllers ===\n"; - for (const auto& c : m_controllers) - { - s += " [" + std::to_string(c.controllerId) + "] " + c.name; - s += " (layers:" + std::to_string(c.layers.size()); - s += " clips:" + std::to_string(c.clips.size()); - s += " params:" + std::to_string(c.parameters.size()); - s += " ik:" + std::to_string(c.ikChains.size()) + ")\n"; - } - if (m_controllers.empty()) - s += " (none)\n"; - return s; - } - - std::string EEAnimationEditorSystem::GetControllerDetailString(uint32_t controllerId) const - { - for (const auto& c : m_controllers) - { - if (c.controllerId == controllerId) - { - std::string s = "=== Controller: " + c.name + " ===\n"; - s += "Clips: " + std::to_string(c.clips.size()) + "\n"; - for (const auto& clip : c.clips) - s += " " + clip.name + " (" + std::to_string(clip.duration) + "s" + - (clip.isLooping ? ", loop" : "") + ")\n"; - - s += "Parameters: " + std::to_string(c.parameters.size()) + "\n"; - for (const auto& p : c.parameters) - s += " " + p.name + " (type=" + std::to_string(static_cast(p.type)) + ")\n"; - - for (size_t i = 0; i < c.layers.size(); i++) - { - const auto& l = c.layers[i]; - s += "Layer " + std::to_string(i) + ": " + l.name + - " (blend=" + std::to_string(static_cast(l.blendMode)) + ")\n"; - s += " States: " + std::to_string(l.states.size()) + "\n"; - for (const auto& st : l.states) - s += " [" + std::to_string(st.stateId) + "] " + st.name + - (st.isDefault ? " [DEFAULT]" : "") + "\n"; - s += " Transitions: " + std::to_string(l.transitions.size()) + "\n"; - } - - s += "IK Chains: " + std::to_string(c.ikChains.size()) + "\n"; - for (const auto& ik : c.ikChains) - s += " " + ik.name + " (" + std::to_string(static_cast(ik.solver)) + " " + ik.rootBone + - " -> " + ik.tipBone + ")\n"; - return s; - } - } - return "Controller not found"; - } - - std::string EEAnimationEditorSystem::GetIKSolverCatalog() const - { - return "IK Solvers: TwoBone, FABRIK, CCD, LookAt, SplineIK\n"; - } - - void EEAnimationEditorSystem::RegisterBuiltinPresets() - { - CreatePresetLocomotion("AC_Locomotion"); - CreatePresetCombat("AC_Combat"); - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/AnimationEditor/EEAnimationEditorSystem.h b/GameModules/SparkGameEngineEditor/Source/AnimationEditor/EEAnimationEditorSystem.h deleted file mode 100644 index eb0ddf69c..000000000 --- a/GameModules/SparkGameEngineEditor/Source/AnimationEditor/EEAnimationEditorSystem.h +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @file EEAnimationEditorSystem.h - * @brief Animation state machine editor with blend trees and IK setup - * @author Spark Engine Team - * @date 2026 - * - * Provides visual editing of animation state machines, blend trees with - * multi-parameter blending, IK chain configuration, animation layer - * management, and event/notify tracks. - */ - -#pragma once - -#include "Spark/IEngineContext.h" -#include "Enums/EngineEditorEnums.h" - -#include -#include -#include - -namespace EngineEditor -{ - - /// @brief An animation clip reference - struct AnimClip - { - uint32_t clipId = 0; - std::string name; - std::string assetPath; - float duration = 1.0f; - bool isLooping = false; - bool isAdditive = false; - }; - - /// @brief A state in the animation state machine - struct AnimState - { - uint32_t stateId = 0; - std::string name; - uint32_t clipId = 0; ///< Main clip for this state - float playRate = 1.0f; - float posX = 0.0f; ///< Graph canvas position - float posY = 0.0f; - bool isDefault = false; ///< Entry state - }; - - /// @brief A transition between two states - struct AnimTransition - { - uint32_t transitionId = 0; - uint32_t fromStateId = 0; - uint32_t toStateId = 0; - TransitionCondition condition = TransitionCondition::BoolParam; - std::string paramName; - float threshold = 0.0f; - float blendDuration = 0.25f; - bool hasExitTime = false; - float exitTime = 0.9f; - }; - - /// @brief An animation parameter used in transitions and blend trees - struct AnimParameter - { - uint32_t paramId = 0; - std::string name; - PinType type = PinType::Float; ///< Bool, Int, Float, or Exec (trigger) - float valueFloat = 0.0f; - int valueInt = 0; - bool valueBool = false; - }; - - /// @brief IK chain configuration - struct IKChain - { - uint32_t chainId = 0; - std::string name; - IKSolverType solver = IKSolverType::TwoBone; - std::string rootBone; - std::string tipBone; - std::string targetBone; - float weight = 1.0f; - bool isEnabled = true; - }; - - /// @brief An animation layer for layered blending - struct AnimLayer - { - uint32_t layerId = 0; - std::string name; - AnimBlendMode blendMode = AnimBlendMode::Override; - float weight = 1.0f; - std::string boneMask; ///< Bone mask name (e.g., "UpperBody") - std::vector states; - std::vector transitions; - }; - - /// @brief A complete animation controller - struct AnimController - { - uint32_t controllerId = 0; - std::string name; - std::vector layers; - std::vector parameters; - std::vector ikChains; - std::vector clips; - }; - - /** - * @brief Animation state machine editor for no-code animation setup - * - * Manages animation controllers with visual state machines, transitions, - * blend parameters, IK chains, and layered blending. - */ - class EEAnimationEditorSystem - { - public: - EEAnimationEditorSystem() = default; - ~EEAnimationEditorSystem() = default; - - bool Initialize(Spark::IEngineContext* context); - void Update(float deltaTime); - void Shutdown(); - void RenderDebugUI(); - - // Controller management - uint32_t CreateController(const std::string& name); - bool DeleteController(uint32_t controllerId); - - // Clip management - uint32_t RegisterClip(uint32_t controllerId, const std::string& name, const std::string& path, float duration, - bool looping); - - // State machine editing - uint32_t AddState(uint32_t controllerId, uint32_t layerIdx, const std::string& name, uint32_t clipId); - bool RemoveState(uint32_t controllerId, uint32_t layerIdx, uint32_t stateId); - uint32_t AddTransition(uint32_t controllerId, uint32_t layerIdx, uint32_t fromState, uint32_t toState, - TransitionCondition cond, const std::string& param); - - // Parameters - uint32_t AddParameter(uint32_t controllerId, const std::string& name, PinType type); - bool SetParameterFloat(uint32_t controllerId, const std::string& name, float value); - bool SetParameterBool(uint32_t controllerId, const std::string& name, bool value); - - // IK - uint32_t AddIKChain(uint32_t controllerId, const std::string& name, IKSolverType solver, - const std::string& root, const std::string& tip); - - // Layers - uint32_t AddLayer(uint32_t controllerId, const std::string& name, AnimBlendMode mode); - - // Presets - uint32_t CreatePresetLocomotion(const std::string& name); - uint32_t CreatePresetCombat(const std::string& name); - - // Queries - size_t GetControllerCount() const { return m_controllers.size(); } - std::string GetControllerListString() const; - std::string GetControllerDetailString(uint32_t controllerId) const; - std::string GetIKSolverCatalog() const; - - private: - void RegisterBuiltinPresets(); - - Spark::IEngineContext* m_context{nullptr}; - std::vector m_controllers; - uint32_t m_nextControllerId{1}; - uint32_t m_nextClipId{1}; - uint32_t m_nextStateId{1}; - uint32_t m_nextTransitionId{1}; - uint32_t m_nextParamId{1}; - uint32_t m_nextChainId{1}; - uint32_t m_nextLayerId{1}; - bool m_initialized{false}; - }; - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/AssetPipeline/EEAssetPipelineSystem.cpp b/GameModules/SparkGameEngineEditor/Source/AssetPipeline/EEAssetPipelineSystem.cpp deleted file mode 100644 index f14ecc0fa..000000000 --- a/GameModules/SparkGameEngineEditor/Source/AssetPipeline/EEAssetPipelineSystem.cpp +++ /dev/null @@ -1,371 +0,0 @@ -/** - * @file EEAssetPipelineSystem.cpp - * @brief Asset import, processing, LOD generation, and packaging - * - * Implements the full asset pipeline with format import, validation, - * optimization, LOD generation, texture compression, and packaging. - */ - -#include "EEAssetPipelineSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#include - -namespace EngineEditor -{ - - bool EEAssetPipelineSystem::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - RegisterDefaultRules(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Asset pipeline system initialized (%zu rules)", m_rules.size()); - return true; - } - - void EEAssetPipelineSystem::Update([[maybe_unused]] float deltaTime) - { - if (!m_initialized) - return; - } - - void EEAssetPipelineSystem::Shutdown() - { - m_assets.clear(); - m_lodConfigs.clear(); - m_textureSettings.clear(); - m_rules.clear(); - m_initialized = false; - } - - void EEAssetPipelineSystem::RenderDebugUI() {} - - uint32_t EEAssetPipelineSystem::ImportAsset(const std::string& sourcePath, AssetFormat format) - { - PipelineAsset asset; - asset.assetId = m_nextAssetId++; - asset.sourcePath = sourcePath; - asset.format = format; - asset.currentStage = PipelineStage::Import; - - // Extract name from path - size_t lastSlash = sourcePath.find_last_of("/\\"); - asset.name = (lastSlash != std::string::npos) ? sourcePath.substr(lastSlash + 1) : sourcePath; - - // Simulated file sizes by format - switch (format) - { - case AssetFormat::FBX: - asset.fileSizeBytes = 2048000; - break; - case AssetFormat::GLTF: - asset.fileSizeBytes = 1536000; - break; - case AssetFormat::OBJ: - asset.fileSizeBytes = 1024000; - break; - case AssetFormat::PNG: - asset.fileSizeBytes = 4096000; - break; - case AssetFormat::TGA: - asset.fileSizeBytes = 8192000; - break; - case AssetFormat::HDR: - asset.fileSizeBytes = 16384000; - break; - case AssetFormat::WAV: - asset.fileSizeBytes = 5120000; - break; - case AssetFormat::OGG: - asset.fileSizeBytes = 512000; - break; - case AssetFormat::TTF: - asset.fileSizeBytes = 256000; - break; - case AssetFormat::JSON: - asset.fileSizeBytes = 8192; - break; - default: - asset.fileSizeBytes = 1024000; - break; - } - - m_assets.push_back(asset); - return asset.assetId; - } - - bool EEAssetPipelineSystem::ProcessAsset(uint32_t assetId) - { - for (auto& asset : m_assets) - { - if (asset.assetId == assetId) - { - if (!ValidateAsset(asset)) - return false; - if (!OptimizeAsset(asset)) - return false; - if (!CompressAsset(asset)) - return false; - - asset.currentStage = PipelineStage::Package; - asset.isProcessed = true; - return true; - } - } - return false; - } - - bool EEAssetPipelineSystem::ProcessAll() - { - bool allOk = true; - for (auto& asset : m_assets) - { - if (!asset.isProcessed) - { - if (!ProcessAsset(asset.assetId)) - allOk = false; - } - } - return allOk; - } - - bool EEAssetPipelineSystem::RemoveAsset(uint32_t assetId) - { - auto it = std::find_if(m_assets.begin(), m_assets.end(), - [assetId](const PipelineAsset& a) { return a.assetId == assetId; }); - if (it == m_assets.end()) - return false; - - // Remove associated LOD and texture configs - std::erase_if(m_lodConfigs, [assetId](const LODConfig& l) { return l.assetId == assetId; }); - std::erase_if(m_textureSettings, [assetId](const TextureSettings& t) { return t.assetId == assetId; }); - - m_assets.erase(it); - return true; - } - - uint32_t EEAssetPipelineSystem::ConfigureLOD(uint32_t assetId, LODStrategy strategy, uint32_t lodCount) - { - LODConfig config; - config.lodId = m_nextLodId++; - config.assetId = assetId; - config.strategy = strategy; - config.lodCount = lodCount; - m_lodConfigs.push_back(config); - return config.lodId; - } - - bool EEAssetPipelineSystem::GenerateLODs(uint32_t assetId) - { - for (const auto& config : m_lodConfigs) - { - if (config.assetId == assetId) - { - // LOD generation dispatched to mesh processing system - return true; - } - } - return false; - } - - uint32_t EEAssetPipelineSystem::ConfigureTexture(uint32_t assetId, bool mipmaps, uint32_t maxRes, bool compress) - { - TextureSettings settings; - settings.settingsId = m_nextTexSettingsId++; - settings.assetId = assetId; - settings.generateMipmaps = mipmaps; - settings.maxResolution = maxRes; - settings.compressBC = compress; - m_textureSettings.push_back(settings); - return settings.settingsId; - } - - uint32_t EEAssetPipelineSystem::AddImportRule(const std::string& name, AssetFormat format, - const std::string& pattern) - { - ImportRule rule; - rule.ruleId = m_nextRuleId++; - rule.name = name; - rule.format = format; - rule.sourcePattern = pattern; - m_rules.push_back(rule); - return rule.ruleId; - } - - bool EEAssetPipelineSystem::RemoveImportRule(uint32_t ruleId) - { - auto it = - std::find_if(m_rules.begin(), m_rules.end(), [ruleId](const ImportRule& r) { return r.ruleId == ruleId; }); - if (it == m_rules.end()) - return false; - m_rules.erase(it); - return true; - } - - bool EEAssetPipelineSystem::PackageAssets([[maybe_unused]] const std::string& outputDir) - { - for (auto& asset : m_assets) - { - if (!asset.isProcessed) - { - if (!ProcessAsset(asset.assetId)) - return false; - } - asset.currentStage = PipelineStage::Deploy; - } - return true; - } - - size_t EEAssetPipelineSystem::GetProcessedCount() const - { - size_t count = 0; - for (const auto& a : m_assets) - { - if (a.isProcessed) - count++; - } - return count; - } - - uint64_t EEAssetPipelineSystem::GetTotalSizeBytes() const - { - uint64_t total = 0; - for (const auto& a : m_assets) - total += a.isProcessed ? a.processedSizeBytes : a.fileSizeBytes; - return total; - } - - std::string EEAssetPipelineSystem::GetAssetListString() const - { - std::string s = "=== Asset Pipeline (" + std::to_string(m_assets.size()) + " assets) ===\n"; - for (const auto& a : m_assets) - { - s += " [" + std::to_string(a.assetId) + "] " + a.name; - s += " (fmt=" + std::to_string(static_cast(a.format)); - s += " stage=" + std::to_string(static_cast(a.currentStage)); - s += " " + std::to_string(a.fileSizeBytes / 1024) + "KB"; - if (a.isProcessed) - s += " -> " + std::to_string(a.processedSizeBytes / 1024) + "KB"; - if (a.hasErrors) - s += " ERROR"; - s += ")\n"; - } - if (m_assets.empty()) - s += " (none)\n"; - return s; - } - - std::string EEAssetPipelineSystem::GetAssetDetailString(uint32_t assetId) const - { - for (const auto& a : m_assets) - { - if (a.assetId == assetId) - { - std::string s = "=== Asset: " + a.name + " ===\n"; - s += "Source: " + a.sourcePath + "\n"; - s += "Format: " + std::to_string(static_cast(a.format)) + "\n"; - s += "Stage: " + std::to_string(static_cast(a.currentStage)) + "\n"; - s += "Size: " + std::to_string(a.fileSizeBytes / 1024) + " KB\n"; - if (a.isProcessed) - s += "Processed: " + std::to_string(a.processedSizeBytes / 1024) + " KB\n"; - if (a.hasErrors) - s += "Error: " + a.errorMessage + "\n"; - return s; - } - } - return "Asset not found"; - } - - std::string EEAssetPipelineSystem::GetRuleListString() const - { - std::string s = "=== Import Rules ===\n"; - for (const auto& r : m_rules) - { - s += " [" + std::to_string(r.ruleId) + "] " + r.name; - s += " (fmt=" + std::to_string(static_cast(r.format)); - s += " pattern=\"" + r.sourcePattern + "\""; - if (r.autoImport) - s += " auto"; - if (r.generateLODs) - s += " +LOD"; - s += ")\n"; - } - if (m_rules.empty()) - s += " (none)\n"; - return s; - } - - std::string EEAssetPipelineSystem::GetPipelineStatusString() const - { - std::string s = "=== Pipeline Status ===\n"; - s += "Assets: " + std::to_string(m_assets.size()) + "\n"; - s += "Processed: " + std::to_string(GetProcessedCount()) + "/" + std::to_string(m_assets.size()) + "\n"; - s += "Total size: " + std::to_string(GetTotalSizeBytes() / (1024 * 1024)) + " MB\n"; - s += "LOD configs: " + std::to_string(m_lodConfigs.size()) + "\n"; - s += "Texture configs: " + std::to_string(m_textureSettings.size()) + "\n"; - s += "Import rules: " + std::to_string(m_rules.size()) + "\n"; - return s; - } - - bool EEAssetPipelineSystem::ValidateAsset(PipelineAsset& asset) - { - asset.currentStage = PipelineStage::Validate; - // Validate format-specific requirements - if (asset.sourcePath.empty()) - { - asset.hasErrors = true; - asset.errorMessage = "Empty source path"; - return false; - } - return true; - } - - bool EEAssetPipelineSystem::OptimizeAsset(PipelineAsset& asset) - { - asset.currentStage = PipelineStage::Optimize; - // Mesh optimization: vertex dedup, index optimization - // Texture optimization: resize, format conversion - asset.processedSizeBytes = static_cast(asset.fileSizeBytes * 0.7); - return true; - } - - bool EEAssetPipelineSystem::CompressAsset(PipelineAsset& asset) - { - asset.currentStage = PipelineStage::Compress; - // BC compression for textures, mesh quantization for geometry - asset.processedSizeBytes = static_cast(asset.processedSizeBytes * 0.5); - return true; - } - - void EEAssetPipelineSystem::RegisterDefaultRules() - { - uint32_t id = 1; - auto add = [&](const std::string& name, AssetFormat fmt, const std::string& pattern, bool lods, bool optimize) - { - ImportRule r; - r.ruleId = id++; - r.name = name; - r.format = fmt; - r.sourcePattern = pattern; - r.generateLODs = lods; - r.optimizeMesh = optimize; - m_rules.push_back(std::move(r)); - }; - - add("Character Models", AssetFormat::FBX, "Characters/*.fbx", true, true); - add("Prop Models", AssetFormat::FBX, "Props/*.fbx", true, true); - add("Environment", AssetFormat::GLTF, "Environment/*.gltf", true, true); - add("Textures", AssetFormat::PNG, "Textures/*.png", false, false); - add("HDR Skyboxes", AssetFormat::HDR, "Skyboxes/*.hdr", false, false); - add("Audio SFX", AssetFormat::WAV, "Audio/SFX/*.wav", false, false); - add("Audio Music", AssetFormat::OGG, "Audio/Music/*.ogg", false, false); - add("Fonts", AssetFormat::TTF, "Fonts/*.ttf", false, false); - add("Config Data", AssetFormat::JSON, "Data/*.json", false, false); - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/AssetPipeline/EEAssetPipelineSystem.h b/GameModules/SparkGameEngineEditor/Source/AssetPipeline/EEAssetPipelineSystem.h deleted file mode 100644 index 2f8ad50ee..000000000 --- a/GameModules/SparkGameEngineEditor/Source/AssetPipeline/EEAssetPipelineSystem.h +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @file EEAssetPipelineSystem.h - * @brief Asset import, processing, LOD generation, and packaging - * @author Spark Engine Team - * @date 2026 - * - * Manages the full asset pipeline: format import (FBX, glTF, OBJ, PNG, etc.), - * validation, optimization, LOD generation, texture compression, and - * packaging for deployment. - */ - -#pragma once - -#include "Spark/IEngineContext.h" -#include "Enums/EngineEditorEnums.h" - -#include -#include -#include - -namespace EngineEditor -{ - - /// @brief An asset in the pipeline - struct PipelineAsset - { - uint32_t assetId = 0; - std::string name; - std::string sourcePath; - std::string outputPath; - AssetFormat format = AssetFormat::FBX; - PipelineStage currentStage = PipelineStage::Import; - bool isProcessed = false; - bool hasErrors = false; - std::string errorMessage; - uint64_t fileSizeBytes = 0; - uint64_t processedSizeBytes = 0; - }; - - /// @brief LOD configuration for a mesh asset - struct LODConfig - { - uint32_t lodId = 0; - uint32_t assetId = 0; - LODStrategy strategy = LODStrategy::ScreenSize; - uint32_t lodCount = 4; - float reductionPerLevel = 0.5f; ///< Triangle reduction factor per LOD - float screenSizeLOD0 = 1.0f; - float screenSizeLOD1 = 0.5f; - float screenSizeLOD2 = 0.25f; - float screenSizeLOD3 = 0.1f; - }; - - /// @brief Texture compression settings - struct TextureSettings - { - uint32_t settingsId = 0; - uint32_t assetId = 0; - bool generateMipmaps = true; - uint32_t maxResolution = 4096; - bool compressBC = true; ///< Use BC1/BC3/BC5/BC7 compression - bool sRGB = true; - bool isNormalMap = false; - }; - - /// @brief An import rule mapping source format to processing options - struct ImportRule - { - uint32_t ruleId = 0; - std::string name; - AssetFormat format = AssetFormat::FBX; - std::string sourcePattern; ///< Glob pattern (e.g., "Characters/*.fbx") - bool autoImport = true; - bool generateLODs = true; - bool optimizeMesh = true; - float uniformScale = 1.0f; - }; - - /** - * @brief Asset pipeline system for no-code asset management - * - * Manages asset import, validation, optimization, LOD generation, - * texture processing, and packaging. - */ - class EEAssetPipelineSystem - { - public: - EEAssetPipelineSystem() = default; - ~EEAssetPipelineSystem() = default; - - bool Initialize(Spark::IEngineContext* context); - void Update(float deltaTime); - void Shutdown(); - void RenderDebugUI(); - - // Asset management - uint32_t ImportAsset(const std::string& sourcePath, AssetFormat format); - bool ProcessAsset(uint32_t assetId); - bool ProcessAll(); - bool RemoveAsset(uint32_t assetId); - - // LOD configuration - uint32_t ConfigureLOD(uint32_t assetId, LODStrategy strategy, uint32_t lodCount); - bool GenerateLODs(uint32_t assetId); - - // Texture settings - uint32_t ConfigureTexture(uint32_t assetId, bool mipmaps, uint32_t maxRes, bool compress); - - // Import rules - uint32_t AddImportRule(const std::string& name, AssetFormat format, const std::string& pattern); - bool RemoveImportRule(uint32_t ruleId); - - // Packaging - bool PackageAssets(const std::string& outputDir); - - // Queries - size_t GetAssetCount() const { return m_assets.size(); } - size_t GetRuleCount() const { return m_rules.size(); } - size_t GetProcessedCount() const; - uint64_t GetTotalSizeBytes() const; - std::string GetAssetListString() const; - std::string GetAssetDetailString(uint32_t assetId) const; - std::string GetRuleListString() const; - std::string GetPipelineStatusString() const; - - private: - void RegisterDefaultRules(); - bool ValidateAsset(PipelineAsset& asset); - bool OptimizeAsset(PipelineAsset& asset); - bool CompressAsset(PipelineAsset& asset); - - Spark::IEngineContext* m_context{nullptr}; - std::vector m_assets; - std::vector m_lodConfigs; - std::vector m_textureSettings; - std::vector m_rules; - uint32_t m_nextAssetId{1}; - uint32_t m_nextLodId{1}; - uint32_t m_nextTexSettingsId{1}; - uint32_t m_nextRuleId{1}; - bool m_initialized{false}; - }; - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/Core/EEEngineSystems.cpp b/GameModules/SparkGameEngineEditor/Source/Core/EEEngineSystems.cpp deleted file mode 100644 index e13aa7efd..000000000 --- a/GameModules/SparkGameEngineEditor/Source/Core/EEEngineSystems.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/** - * @file EEEngineSystems.cpp - * @brief Wires engine editor tools into engine subsystems - * - * Registers editor project data with save system, event bus, and - * undo/redo history tracking. - */ - -#include "EEEngineSystems.h" -#include "Enums/EngineEditorEnums.h" - -#include "Engine/SaveSystem/SaveSystem.h" -#include "Engine/Events/EventSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -namespace EngineEditor -{ - - EEEngineSystems::~EEEngineSystems() - { - if (m_initialized) - Shutdown(); - } - - bool EEEngineSystems::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - - RegisterSaveSerializers(); - SubscribeToEvents(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Engine editor integration initialized"); - Spark::SimpleConsole::GetInstance().LogInfo("[EngineEditor] Engine systems wired (save, events, undo)"); - return true; - } - - void EEEngineSystems::Update([[maybe_unused]] float deltaTime) - { - if (!m_initialized) - return; - } - - void EEEngineSystems::Shutdown() - { - if (!m_initialized) - return; - - m_eventHandles.clear(); - m_undoStack.clear(); - m_redoStack.clear(); - m_context = nullptr; - m_initialized = false; - } - - void EEEngineSystems::RenderDebugUI() {} - - std::string EEEngineSystems::NewProject(const std::string& name) - { - m_projectName = name; - m_undoStack.clear(); - m_redoStack.clear(); - RecordAction("Created project '" + name + "'"); - return "Project '" + name + "' created"; - } - - std::string EEEngineSystems::SaveProject(const std::string& slotName) - { - auto* saveSystem = m_context->GetSaveSystem(); - if (!saveSystem) - return "Save system not available"; - - return "Save to slot '" + slotName + "' requested (save system wired)"; - } - - std::string EEEngineSystems::LoadProject(const std::string& slotName) - { - auto* saveSystem = m_context->GetSaveSystem(); - if (!saveSystem) - return "Save system not available"; - - if (!saveSystem->SaveExists(slotName)) - return "No save found in slot '" + slotName + "'"; - - return "Load from slot '" + slotName + "' requested (save system wired)"; - } - - std::string EEEngineSystems::GetProjectStatus() const - { - std::string s = "=== Project Status ===\n"; - s += "Name: " + m_projectName + "\n"; - s += "Undo stack: " + std::to_string(m_undoStack.size()) + "\n"; - s += "Redo stack: " + std::to_string(m_redoStack.size()) + "\n"; - return s; - } - - void EEEngineSystems::RecordAction(const std::string& description) - { - m_undoStack.push_back(description); - m_redoStack.clear(); - - if (m_undoStack.size() > 100) - m_undoStack.erase(m_undoStack.begin()); - } - - bool EEEngineSystems::Undo() - { - if (m_undoStack.empty()) - return false; - - m_redoStack.push_back(m_undoStack.back()); - m_undoStack.pop_back(); - return true; - } - - bool EEEngineSystems::Redo() - { - if (m_redoStack.empty()) - return false; - - m_undoStack.push_back(m_redoStack.back()); - m_redoStack.pop_back(); - return true; - } - - std::string EEEngineSystems::GetUndoHistoryString() const - { - std::string s = "=== Undo History ===\n"; - size_t start = m_undoStack.size() > 10 ? m_undoStack.size() - 10 : 0; - for (size_t i = start; i < m_undoStack.size(); i++) - s += " " + std::to_string(i + 1) + ". " + m_undoStack[i] + "\n"; - if (m_undoStack.empty()) - s += " (empty)\n"; - if (!m_redoStack.empty()) - s += "Redo available: " + std::to_string(m_redoStack.size()) + " actions\n"; - return s; - } - - void EEEngineSystems::RegisterSaveSerializers() - { - auto* saveSystem = m_context->GetSaveSystem(); - if (!saveSystem) - return; - - auto& registry = Spark::ComponentSerializerRegistry::GetInstance(); - - auto registerPlaceholder = [&](const std::string& typeName) - { - registry.Register( - typeName, - [typeName](const void*) -> Spark::SerializedComponent - { - Spark::SerializedComponent sc; - sc.typeName = typeName; - sc.properties["placeholder"] = typeName; - return sc; - }, - []([[maybe_unused]] World& world, [[maybe_unused]] EntityID entity, - [[maybe_unused]] const Spark::SerializedComponent& data) {}); - }; - - registerPlaceholder("EEProjectState"); // Project name, settings - registerPlaceholder("EEVisualScriptData"); // Script graphs and variables - registerPlaceholder("EELevelDesignData"); // Placed instances, terrain - registerPlaceholder("EEMaterialData"); // Material graphs - registerPlaceholder("EEVFXData"); // VFX assets and emitters - registerPlaceholder("EEAnimControllerData"); // Animation controllers - registerPlaceholder("EEUIScreenData"); // UI screen layouts - registerPlaceholder("EEPrototypeData"); // Blockouts and rules - registerPlaceholder("EEAssetPipelineData"); // Asset pipeline state - - saveSystem->SetMaxAutoSaves(3); - - SPARK_LOG_INFO(Spark::LogCategory::Game, "Engine editor registered 9 save serializers"); - Spark::SimpleConsole::GetInstance().LogInfo("[EngineEditor] Registered 9 save serializers"); - } - - void EEEngineSystems::SubscribeToEvents() - { - auto* eventBus = m_context->GetEventBus(); - if (!eventBus) - return; - - // Track weather changes for VFX preview updates - m_eventHandles.push_back(eventBus->Subscribe( - [](const Spark::WeatherChangedEvent& evt) - { - Spark::SimpleConsole::GetInstance().LogInfo("[EngineEditor] Weather changed to type " + - std::to_string(evt.newType) + " — updating VFX previews"); - })); - - // Track time-of-day for lighting preview - m_eventHandles.push_back(eventBus->Subscribe( - [](const Spark::TimeOfDayChangedEvent& evt) - { - if (evt.currentHour >= 6.0f && evt.previousHour < 6.0f) - { - Spark::SimpleConsole::GetInstance().LogInfo("[EngineEditor] Dawn — updating lighting preview"); - } - else if (evt.currentHour >= 20.0f && evt.previousHour < 20.0f) - { - Spark::SimpleConsole::GetInstance().LogInfo("[EngineEditor] Dusk — updating lighting preview"); - } - })); - - SPARK_LOG_INFO(Spark::LogCategory::Game, "Engine editor subscribed to 2 engine events"); - Spark::SimpleConsole::GetInstance().LogInfo("[EngineEditor] Subscribed to 2 engine events"); - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/Core/EEEngineSystems.h b/GameModules/SparkGameEngineEditor/Source/Core/EEEngineSystems.h deleted file mode 100644 index 0ff253be0..000000000 --- a/GameModules/SparkGameEngineEditor/Source/Core/EEEngineSystems.h +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @file EEEngineSystems.h - * @brief Wires engine editor tools into engine subsystems - * @author Spark Engine Team - * @date 2026 - * - * EEEngineSystems registers editor tool data with engine infrastructure: - * save serializers for editor projects, event bus subscriptions for editor - * state changes, and undo/redo history tracking. - */ - -#pragma once - -#include "Spark/IEngineContext.h" -#include "Utils/EventBus.h" - -#include -#include - -namespace EngineEditor -{ - - /** - * @brief Bridges engine editor tools with engine subsystems - * - * Constructed and owned by SparkGameEngineEditorModule. On Initialize() it - * registers editor project data with save and event systems. - */ - class EEEngineSystems - { - public: - EEEngineSystems() = default; - ~EEEngineSystems(); - - bool Initialize(Spark::IEngineContext* context); - void Update(float deltaTime); - void Shutdown(); - void RenderDebugUI(); - - // Project management - std::string NewProject(const std::string& name); - std::string SaveProject(const std::string& slotName); - std::string LoadProject(const std::string& slotName); - std::string GetProjectStatus() const; - - // Undo/Redo - void RecordAction(const std::string& description); - bool Undo(); - bool Redo(); - size_t GetUndoStackSize() const { return m_undoStack.size(); } - size_t GetRedoStackSize() const { return m_redoStack.size(); } - std::string GetUndoHistoryString() const; - - private: - void RegisterSaveSerializers(); - void SubscribeToEvents(); - - Spark::IEngineContext* m_context = nullptr; - bool m_initialized = false; - std::string m_projectName = "Untitled"; - std::vector m_undoStack; - std::vector m_redoStack; - std::vector m_eventHandles; - }; - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/Core/Main.cpp b/GameModules/SparkGameEngineEditor/Source/Core/Main.cpp deleted file mode 100644 index 0c88849e0..000000000 --- a/GameModules/SparkGameEngineEditor/Source/Core/Main.cpp +++ /dev/null @@ -1,607 +0,0 @@ -/** - * @file Main.cpp - * @brief SparkGameEngineEditor DLL - IModule implementation and exports - * - * Implements the SparkGameEngineEditorModule class and exports the CreateModule/ - * DestroyModule factory functions for the engine's ModuleManager. - */ - -#include "SparkGameEngineEditor.h" -#include "EEEngineSystems.h" -#include "VisualScripting/EEVisualScriptingSystem.h" -#include "LevelDesign/EELevelDesignSystem.h" -#include "MaterialEditor/EEMaterialEditorSystem.h" -#include "VFXEditor/EEVFXEditorSystem.h" -#include "AnimationEditor/EEAnimationEditorSystem.h" -#include "UIEditor/EEUIEditorSystem.h" -#include "Prototyping/EEPrototypingSystem.h" -#include "AssetPipeline/EEAssetPipelineSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#ifdef SPARK_PLATFORM_WINDOWS -#include - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) -{ - switch (reason) - { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hModule); - break; - case DLL_PROCESS_DETACH: - break; - } - return TRUE; -} -#endif - -// ============================================================================= -// Module exports -// ============================================================================= - -SPARK_IMPLEMENT_MODULE(SparkGameEngineEditorModule) - -// ============================================================================= -// SparkGameEngineEditorModule implementation -// ============================================================================= - -SparkGameEngineEditorModule::SparkGameEngineEditorModule() = default; - -SparkGameEngineEditorModule::~SparkGameEngineEditorModule() -{ - if (m_initialized) - OnUnload(); -} - -Spark::ModuleInfo SparkGameEngineEditorModule::GetModuleInfo() const -{ - Spark::ModuleInfo info{}; - info.name = "Spark Engine Editor - No-Code Game Creation"; - info.version = "1.0.0"; - info.sdkVersion = SPARK_SDK_VERSION; - info.loadOrder = 1009; - return info; -} - -bool SparkGameEngineEditorModule::OnLoad(Spark::IEngineContext* context) -{ - if (!context) - return false; - - m_context = context; - - auto& console = Spark::SimpleConsole::GetInstance(); - console.LogInfo("[EngineEditor] Loading Spark Engine Editor module..."); - SPARK_LOG_INFO(Spark::LogCategory::Game, "Engine Editor module loading - initializing 9 subsystems"); - - // 1. Visual scripting (node graph, pin types, compilation) - m_visualScripting = std::make_unique(); - if (!m_visualScripting->Initialize(context)) - { - console.LogError("[EngineEditor] Failed to initialize visual scripting system"); - return false; - } - - // 2. Level design (prefabs, terrain, foliage, splines) - m_levelDesign = std::make_unique(); - if (!m_levelDesign->Initialize(context)) - { - console.LogError("[EngineEditor] Failed to initialize level design system"); - return false; - } - - // 3. Material editor (node graph, PBR, shading models) - m_materialEditor = std::make_unique(); - if (!m_materialEditor->Initialize(context)) - { - console.LogError("[EngineEditor] Failed to initialize material editor system"); - return false; - } - - // 4. VFX editor (emitters, modules, presets) - m_vfxEditor = std::make_unique(); - if (!m_vfxEditor->Initialize(context)) - { - console.LogError("[EngineEditor] Failed to initialize VFX editor system"); - return false; - } - - // 5. Animation editor (state machines, IK, layers) - m_animationEditor = std::make_unique(); - if (!m_animationEditor->Initialize(context)) - { - console.LogError("[EngineEditor] Failed to initialize animation editor system"); - return false; - } - - // 6. UI editor (WYSIWYG, widgets, anchors, data binding) - m_uiEditor = std::make_unique(); - if (!m_uiEditor->Initialize(context)) - { - console.LogError("[EngineEditor] Failed to initialize UI editor system"); - return false; - } - - // 7. Prototyping (blockout, templates, gameplay rules, play-test) - m_prototyping = std::make_unique(); - if (!m_prototyping->Initialize(context)) - { - console.LogError("[EngineEditor] Failed to initialize prototyping system"); - return false; - } - - // 8. Asset pipeline (import, LOD, compression, packaging) - m_assetPipeline = std::make_unique(); - if (!m_assetPipeline->Initialize(context)) - { - console.LogError("[EngineEditor] Failed to initialize asset pipeline system"); - return false; - } - - // 9. Engine systems integration (save, events, undo/redo) - m_engineSystems = std::make_unique(); - if (!m_engineSystems->Initialize(context)) - { - console.LogWarning("[EngineEditor] Engine systems integration partially failed (non-fatal)"); - } - - RegisterConsoleCommands(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Engine Editor module loaded successfully - 9 subsystems active"); - console.LogInfo("[EngineEditor] Module loaded successfully (9 subsystems)"); - console.LogInfo("[EngineEditor] Scripts: " + std::to_string(m_visualScripting->GetGraphCount()) + - " | Prefabs: " + std::to_string(m_levelDesign->GetPrefabCount()) + - " | Materials: " + std::to_string(m_materialEditor->GetMaterialCount()) + - " | VFX: " + std::to_string(m_vfxEditor->GetVFXCount()) + - " | AnimCtrl: " + std::to_string(m_animationEditor->GetControllerCount()) + - " | Screens: " + std::to_string(m_uiEditor->GetScreenCount()) + - " | Templates: " + std::to_string(m_prototyping->GetTemplateCount()) + - " | Assets: " + std::to_string(m_assetPipeline->GetAssetCount())); - return true; -} - -void SparkGameEngineEditorModule::OnUnload() -{ - if (!m_initialized) - return; - - auto& console = Spark::SimpleConsole::GetInstance(); - console.LogInfo("[EngineEditor] Unloading Spark Engine Editor module..."); - SPARK_LOG_INFO(Spark::LogCategory::Game, "Engine Editor module shutting down"); - - // Shutdown in reverse initialization order - if (m_engineSystems) - { - m_engineSystems->Shutdown(); - m_engineSystems.reset(); - } - if (m_assetPipeline) - { - m_assetPipeline->Shutdown(); - m_assetPipeline.reset(); - } - if (m_prototyping) - { - m_prototyping->Shutdown(); - m_prototyping.reset(); - } - if (m_uiEditor) - { - m_uiEditor->Shutdown(); - m_uiEditor.reset(); - } - if (m_animationEditor) - { - m_animationEditor->Shutdown(); - m_animationEditor.reset(); - } - if (m_vfxEditor) - { - m_vfxEditor->Shutdown(); - m_vfxEditor.reset(); - } - if (m_materialEditor) - { - m_materialEditor->Shutdown(); - m_materialEditor.reset(); - } - if (m_levelDesign) - { - m_levelDesign->Shutdown(); - m_levelDesign.reset(); - } - if (m_visualScripting) - { - m_visualScripting->Shutdown(); - m_visualScripting.reset(); - } - - m_context = nullptr; - m_initialized = false; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Engine Editor module unloaded"); - console.LogInfo("[EngineEditor] Module unloaded"); -} - -void SparkGameEngineEditorModule::OnUpdate(float deltaTime) -{ - if (!m_initialized || m_paused) - return; - - m_visualScripting->Update(deltaTime); - m_levelDesign->Update(deltaTime); - m_materialEditor->Update(deltaTime); - m_vfxEditor->Update(deltaTime); - m_animationEditor->Update(deltaTime); - m_uiEditor->Update(deltaTime); - m_prototyping->Update(deltaTime); - m_assetPipeline->Update(deltaTime); - m_engineSystems->Update(deltaTime); -} - -void SparkGameEngineEditorModule::OnFixedUpdate([[maybe_unused]] float fixedDeltaTime) -{ - if (!m_initialized || m_paused) - return; -} - -void SparkGameEngineEditorModule::OnRender() -{ - if (!m_initialized) - return; -} - -void SparkGameEngineEditorModule::OnResize([[maybe_unused]] int width, [[maybe_unused]] int height) {} - -void SparkGameEngineEditorModule::OnPause() -{ - m_paused = true; -} - -void SparkGameEngineEditorModule::OnResume() -{ - m_paused = false; -} - -void SparkGameEngineEditorModule::OnImGui() -{ - if (!m_initialized) - return; - - m_visualScripting->RenderDebugUI(); - m_levelDesign->RenderDebugUI(); - m_materialEditor->RenderDebugUI(); - m_vfxEditor->RenderDebugUI(); - m_animationEditor->RenderDebugUI(); - m_uiEditor->RenderDebugUI(); - m_prototyping->RenderDebugUI(); - m_assetPipeline->RenderDebugUI(); - m_engineSystems->RenderDebugUI(); -} - -void SparkGameEngineEditorModule::RegisterConsoleCommands() -{ - auto& console = Spark::SimpleConsole::GetInstance(); - - // --- Status --- - console.RegisterCommand( - "ee_status", - [this](const std::vector&) -> std::string - { - std::string s = "=== Spark Engine Editor Status ===\n"; - s += "Scripts: " + std::to_string(m_visualScripting->GetGraphCount()) + " graphs (" + - std::to_string(m_visualScripting->GetNodeTemplateCount()) + " node types)\n"; - s += "Level: " + std::to_string(m_levelDesign->GetInstanceCount()) + " instances, " + - std::to_string(m_levelDesign->GetPrefabCount()) + " prefabs\n"; - s += "Materials: " + std::to_string(m_materialEditor->GetMaterialCount()) + "\n"; - s += "VFX: " + std::to_string(m_vfxEditor->GetVFXCount()) + "\n"; - s += "AnimCtrl: " + std::to_string(m_animationEditor->GetControllerCount()) + "\n"; - s += "UI Screens: " + std::to_string(m_uiEditor->GetScreenCount()) + "\n"; - s += "Blockouts: " + std::to_string(m_prototyping->GetBlockoutCount()) + - " | Templates: " + std::to_string(m_prototyping->GetTemplateCount()) + "\n"; - s += "Assets: " + std::to_string(m_assetPipeline->GetAssetCount()) + " (" + - std::to_string(m_assetPipeline->GetProcessedCount()) + " processed)\n"; - s += "Undo: " + std::to_string(m_engineSystems->GetUndoStackSize()) + - " | Redo: " + std::to_string(m_engineSystems->GetRedoStackSize()) + "\n"; - return s; - }, - "Show engine editor module status", "EngineEditor"); - - // --- Visual Scripting --- - console.RegisterCommand( - "ee_scripts", [this](const std::vector&) -> std::string - { return m_visualScripting->GetGraphListString(); }, "List visual script graphs", "EngineEditor"); - - console.RegisterCommand( - "ee_nodes", [this](const std::vector&) -> std::string - { return m_visualScripting->GetNodeCatalogString(); }, "Show visual scripting node catalog", "EngineEditor"); - - console.RegisterCommand( - "ee_new_script", - [this](const std::vector& args) -> std::string - { - std::string name = args.empty() ? "NewScript" : args[0]; - uint32_t id = m_visualScripting->CreateGraph(name); - m_engineSystems->RecordAction("Created script '" + name + "'"); - return "Script '" + name + "' created (id=" + std::to_string(id) + ")"; - }, - "Create a new visual script", "EngineEditor"); - - console.RegisterCommand( - "ee_compile_scripts", [this](const std::vector&) -> std::string - { return m_visualScripting->CompileAll() ? "All scripts compiled" : "Compilation errors"; }, - "Compile all visual scripts", "EngineEditor"); - - // --- Level Design --- - console.RegisterCommand( - "ee_prefabs", [this](const std::vector&) -> std::string - { return m_levelDesign->GetPrefabCatalogString(); }, "Show prefab catalog", "EngineEditor"); - - console.RegisterCommand( - "ee_instances", [this](const std::vector&) -> std::string - { return m_levelDesign->GetInstanceListString(); }, "List placed prefab instances", "EngineEditor"); - - console.RegisterCommand( - "ee_place", - [this](const std::vector& args) -> std::string - { - if (args.size() < 4) - return "Usage: ee_place "; - try - { - uint32_t pid = static_cast(std::stoi(args[0])); - float x = std::stof(args[1]); - float y = std::stof(args[2]); - float z = std::stof(args[3]); - uint32_t id = m_levelDesign->PlacePrefab(pid, x, y, z); - if (id > 0) - { - m_engineSystems->RecordAction("Placed prefab " + args[0]); - return "Placed (id=" + std::to_string(id) + ")"; - } - return "Invalid prefab ID"; - } - catch (...) - { - return "Invalid arguments"; - } - }, - "Place a prefab", "EngineEditor", "ee_place "); - - console.RegisterCommand( - "ee_splines", [this](const std::vector&) -> std::string - { return m_levelDesign->GetSplineListString(); }, "List spline paths", "EngineEditor"); - - console.RegisterCommand( - "ee_tool_status", [this](const std::vector&) -> std::string - { return m_levelDesign->GetToolStatusString(); }, "Show level design tool status", "EngineEditor"); - - // --- Material Editor --- - console.RegisterCommand( - "ee_materials", [this](const std::vector&) -> std::string - { return m_materialEditor->GetMaterialListString(); }, "List materials", "EngineEditor"); - - console.RegisterCommand( - "ee_new_material", - [this](const std::vector& args) -> std::string - { - std::string name = args.empty() ? "NewMaterial" : args[0]; - uint32_t id = m_materialEditor->CreateMaterial(name, EngineEditor::ShadingModel::DefaultLit); - m_engineSystems->RecordAction("Created material '" + name + "'"); - return "Material '" + name + "' created (id=" + std::to_string(id) + ")"; - }, - "Create a new material", "EngineEditor"); - - console.RegisterCommand( - "ee_mat_nodes", [this](const std::vector&) -> std::string - { return m_materialEditor->GetNodeCatalogString(); }, "Show material node types", "EngineEditor"); - - // --- VFX Editor --- - console.RegisterCommand( - "ee_vfx", [this](const std::vector&) -> std::string { return m_vfxEditor->GetVFXListString(); }, - "List VFX assets", "EngineEditor"); - - console.RegisterCommand( - "ee_new_vfx", - [this](const std::vector& args) -> std::string - { - if (args.size() < 2) - return "Usage: ee_new_vfx "; - uint32_t id = m_vfxEditor->CreateVFX(args[0], args[1]); - m_engineSystems->RecordAction("Created VFX '" + args[0] + "'"); - return "VFX '" + args[0] + "' created (id=" + std::to_string(id) + ")"; - }, - "Create a new VFX asset", "EngineEditor", "ee_new_vfx "); - - console.RegisterCommand( - "ee_vfx_play", - [this](const std::vector& args) -> std::string - { - if (args.empty()) - return "Usage: ee_vfx_play "; - try - { - uint32_t id = static_cast(std::stoi(args[0])); - return m_vfxEditor->PlayVFX(id) ? "Playing VFX" : "Invalid VFX ID"; - } - catch (...) - { - return "Invalid ID"; - } - }, - "Play a VFX preview", "EngineEditor"); - - // --- Animation Editor --- - console.RegisterCommand( - "ee_anims", [this](const std::vector&) -> std::string - { return m_animationEditor->GetControllerListString(); }, "List animation controllers", "EngineEditor"); - - console.RegisterCommand( - "ee_new_anim", - [this](const std::vector& args) -> std::string - { - std::string name = args.empty() ? "NewController" : args[0]; - uint32_t id = m_animationEditor->CreateController(name); - m_engineSystems->RecordAction("Created anim controller '" + name + "'"); - return "Controller '" + name + "' created (id=" + std::to_string(id) + ")"; - }, - "Create a new animation controller", "EngineEditor"); - - console.RegisterCommand( - "ee_ik_solvers", [this](const std::vector&) -> std::string - { return m_animationEditor->GetIKSolverCatalog(); }, "Show IK solver types", "EngineEditor"); - - // --- UI Editor --- - console.RegisterCommand( - "ee_screens", [this](const std::vector&) -> std::string - { return m_uiEditor->GetScreenListString(); }, "List UI screens", "EngineEditor"); - - console.RegisterCommand( - "ee_new_screen", - [this](const std::vector& args) -> std::string - { - if (args.size() < 2) - return "Usage: ee_new_screen "; - uint32_t id = m_uiEditor->CreateScreen(args[0], args[1]); - m_engineSystems->RecordAction("Created UI screen '" + args[0] + "'"); - return "Screen '" + args[0] + "' created (id=" + std::to_string(id) + ")"; - }, - "Create a new UI screen", "EngineEditor", "ee_new_screen "); - - console.RegisterCommand( - "ee_widgets", [this](const std::vector&) -> std::string - { return m_uiEditor->GetWidgetCatalogString(); }, "Show widget types", "EngineEditor"); - - // --- Prototyping --- - console.RegisterCommand( - "ee_templates", [this](const std::vector&) -> std::string - { return m_prototyping->GetTemplateListString(); }, "List game templates", "EngineEditor"); - - console.RegisterCommand( - "ee_blockouts", [this](const std::vector&) -> std::string - { return m_prototyping->GetBlockoutListString(); }, "List blockout primitives", "EngineEditor"); - - console.RegisterCommand( - "ee_blockout", - [this](const std::vector& args) -> std::string - { - if (args.size() < 4) - return "Usage: ee_blockout "; - try - { - auto shape = static_cast(std::stoi(args[0])); - float x = std::stof(args[1]); - float y = std::stof(args[2]); - float z = std::stof(args[3]); - uint32_t id = m_prototyping->PlaceBlockout(shape, x, y, z); - m_engineSystems->RecordAction("Placed blockout"); - return "Blockout placed (id=" + std::to_string(id) + ")"; - } - catch (...) - { - return "Invalid arguments"; - } - }, - "Place a blockout primitive", "EngineEditor", "ee_blockout "); - - console.RegisterCommand( - "ee_rules", [this](const std::vector&) -> std::string - { return m_prototyping->GetRuleListString(); }, "List gameplay rules", "EngineEditor"); - - console.RegisterCommand( - "ee_play", - [this](const std::vector&) -> std::string - { - if (m_prototyping->IsPlaying()) - { - m_prototyping->StopPlayTest(); - return "Play-test stopped"; - } - m_prototyping->StartPlayTest(); - return "Play-test started"; - }, - "Toggle play-test mode", "EngineEditor"); - - // --- Asset Pipeline --- - console.RegisterCommand( - "ee_assets", [this](const std::vector&) -> std::string - { return m_assetPipeline->GetAssetListString(); }, "List pipeline assets", "EngineEditor"); - - console.RegisterCommand( - "ee_import", - [this](const std::vector& args) -> std::string - { - if (args.size() < 2) - return "Usage: ee_import "; - try - { - auto fmt = static_cast(std::stoi(args[1])); - uint32_t id = m_assetPipeline->ImportAsset(args[0], fmt); - m_engineSystems->RecordAction("Imported asset '" + args[0] + "'"); - return "Imported (id=" + std::to_string(id) + ")"; - } - catch (...) - { - return "Invalid arguments"; - } - }, - "Import an asset", "EngineEditor", "ee_import "); - - console.RegisterCommand( - "ee_process_all", [this](const std::vector&) -> std::string - { return m_assetPipeline->ProcessAll() ? "All assets processed" : "Processing errors"; }, - "Process all unprocessed assets", "EngineEditor"); - - console.RegisterCommand( - "ee_pipeline", [this](const std::vector&) -> std::string - { return m_assetPipeline->GetPipelineStatusString(); }, "Show asset pipeline status", "EngineEditor"); - - console.RegisterCommand( - "ee_import_rules", [this](const std::vector&) -> std::string - { return m_assetPipeline->GetRuleListString(); }, "List import rules", "EngineEditor"); - - // --- Project / Undo --- - console.RegisterCommand( - "ee_project", [this](const std::vector&) -> std::string - { return m_engineSystems->GetProjectStatus(); }, "Show project status", "EngineEditor"); - - console.RegisterCommand( - "ee_new_project", - [this](const std::vector& args) -> std::string - { - std::string name = args.empty() ? "NewProject" : args[0]; - return m_engineSystems->NewProject(name); - }, - "Create a new project", "EngineEditor"); - - console.RegisterCommand( - "ee_save", - [this](const std::vector& args) -> std::string - { - std::string slot = args.empty() ? "editor_slot1" : args[0]; - return m_engineSystems->SaveProject(slot); - }, - "Save project", "EngineEditor"); - - console.RegisterCommand( - "ee_load", - [this](const std::vector& args) -> std::string - { - std::string slot = args.empty() ? "editor_slot1" : args[0]; - return m_engineSystems->LoadProject(slot); - }, - "Load project", "EngineEditor"); - - console.RegisterCommand( - "ee_undo", [this](const std::vector&) -> std::string - { return m_engineSystems->Undo() ? "Undone" : "Nothing to undo"; }, "Undo last action", "EngineEditor"); - - console.RegisterCommand( - "ee_redo", [this](const std::vector&) -> std::string - { return m_engineSystems->Redo() ? "Redone" : "Nothing to redo"; }, "Redo last undone action", "EngineEditor"); - - console.RegisterCommand( - "ee_history", [this](const std::vector&) -> std::string - { return m_engineSystems->GetUndoHistoryString(); }, "Show undo history", "EngineEditor"); -} diff --git a/GameModules/SparkGameEngineEditor/Source/Core/SparkGameEngineEditor.h b/GameModules/SparkGameEngineEditor/Source/Core/SparkGameEngineEditor.h deleted file mode 100644 index 51e2ca0f5..000000000 --- a/GameModules/SparkGameEngineEditor/Source/Core/SparkGameEngineEditor.h +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @file SparkGameEngineEditor.h - * @brief Engine/editor no-code game creation showcase module - * @author Spark Engine Team - * @date 2026 - * - * SparkGameEngineEditor is a game module that showcases SparkEngine's no-code - * game creation pipeline: visual scripting graphs, level design tools, - * material editor, VFX authoring, animation state machine editor, WYSIWYG - * UI editor, rapid prototyping templates, and asset pipeline management. - * - * Implements the Spark::IModule interface for the module system. - */ - -#pragma once - -#include "Spark/SparkSDK.h" -#include - -namespace EngineEditor -{ - class EEVisualScriptingSystem; - class EELevelDesignSystem; - class EEMaterialEditorSystem; - class EEVFXEditorSystem; - class EEAnimationEditorSystem; - class EEUIEditorSystem; - class EEPrototypingSystem; - class EEAssetPipelineSystem; - class EEEngineSystems; -} // namespace EngineEditor - -/** - * @brief Game module showcasing no-code game creation tools on SparkEngine - * - * Wires up 9 subsystems covering visual scripting, level design, material - * editing, VFX authoring, animation editing, UI layout, rapid prototyping, - * asset pipeline, and engine integration. - */ -class SparkGameEngineEditorModule : public Spark::IModule -{ - public: - SparkGameEngineEditorModule(); - ~SparkGameEngineEditorModule() override; - - // --- Spark::IModule interface --- - Spark::ModuleInfo GetModuleInfo() const override; - bool OnLoad(Spark::IEngineContext* context) override; - void OnUnload() override; - void OnUpdate(float deltaTime) override; - void OnFixedUpdate(float fixedDeltaTime) override; - void OnRender() override; - void OnResize(int width, int height) override; - void OnPause() override; - void OnResume() override; - void OnImGui() override; - - private: - void RegisterConsoleCommands(); - - Spark::IEngineContext* m_context{nullptr}; - bool m_initialized{false}; - bool m_paused{false}; - - std::unique_ptr m_visualScripting; - std::unique_ptr m_levelDesign; - std::unique_ptr m_materialEditor; - std::unique_ptr m_vfxEditor; - std::unique_ptr m_animationEditor; - std::unique_ptr m_uiEditor; - std::unique_ptr m_prototyping; - std::unique_ptr m_assetPipeline; - std::unique_ptr m_engineSystems; -}; - -// Module exports -extern "C" -{ - SPARK_MODULE_API Spark::IModule* CreateModule(); - SPARK_MODULE_API void DestroyModule(Spark::IModule* mod); -} diff --git a/GameModules/SparkGameEngineEditor/Source/Enums/EngineEditorEnums.h b/GameModules/SparkGameEngineEditor/Source/Enums/EngineEditorEnums.h deleted file mode 100644 index 09fe7cdd9..000000000 --- a/GameModules/SparkGameEngineEditor/Source/Enums/EngineEditorEnums.h +++ /dev/null @@ -1,384 +0,0 @@ -/** - * @file EngineEditorEnums.h - * @brief Enumerations for engine/editor no-code game creation tools - * @author Spark Engine Team - * @date 2026 - * - * Contains all enum types used by the engine editor game module: visual - * scripting node types, level design tools, material parameters, VFX - * emitter shapes, animation blend modes, UI widget types, prototyping - * templates, and asset pipeline stages. - */ - -#pragma once - -#include - -namespace EngineEditor -{ - - // ===================================================================== - // Visual Scripting - // ===================================================================== - - /// @brief Categories of visual scripting nodes - enum class NodeCategory : uint8_t - { - Event = 0, ///< Entry points (OnBeginPlay, OnTick, OnCollision) - Flow = 1, ///< Branch, Sequence, ForLoop, WhileLoop, Switch - Math = 2, ///< Add, Multiply, Lerp, Clamp, Sin, Cos - Logic = 3, ///< And, Or, Not, Compare, Select - Variable = 4, ///< Get, Set, local/member variables - Function = 5, ///< CallFunction, pure functions, macros - Transform = 6, ///< GetPosition, SetRotation, LookAt, MoveToward - Physics = 7, ///< Raycast, AddForce, SetVelocity, Overlap - Input = 8, ///< GetAxis, IsKeyDown, GetMouseDelta - Animation = 9, ///< PlayMontage, SetBlendParam, GetBoneTransform - Audio = 10, ///< PlaySound, StopSound, SetVolume, SetPitch - AI = 11, ///< MoveTo, FindPath, GetPerception, SetBehavior - UI = 12, ///< ShowWidget, HideWidget, SetText, BindEvent - Networking = 13, ///< RPC, Replicate, IsServer, IsClient - Debug = 14, ///< PrintString, DrawDebugLine, Breakpoint - Count = 15 - }; - - /// @brief Pin data types for visual scripting connections - enum class PinType : uint8_t - { - Exec = 0, ///< Execution flow (white arrow) - Bool = 1, - Int = 2, - Float = 3, - String = 4, - Vector3 = 5, - Rotator = 6, - Color = 7, - Entity = 8, ///< ECS entity reference - Component = 9, ///< Component reference - Array = 10, - Map = 11, - Wildcard = 12, ///< Any type (auto-cast) - Count = 13 - }; - - // ===================================================================== - // Level Design - // ===================================================================== - - /// @brief Level design tool modes - enum class LevelTool : uint8_t - { - Select = 0, - Translate = 1, - Rotate = 2, - Scale = 3, - PrefabPlace = 4, - TerrainSculpt = 5, - TerrainPaint = 6, - FoliagePaint = 7, - SplinePath = 8, - VolumeEdit = 9, - LightPlace = 10, - DecalPlace = 11, - Count = 12 - }; - - /// @brief Terrain brush shapes - enum class BrushShape : uint8_t - { - Circle = 0, - Square = 1, - Smooth = 2, - Noise = 3, - Flatten = 4, - Erode = 5, - Count = 6 - }; - - /// @brief Terrain layer types for painting - enum class TerrainLayer : uint8_t - { - Grass = 0, - Dirt = 1, - Rock = 2, - Sand = 3, - Snow = 4, - Mud = 5, - Gravel = 6, - Moss = 7, - Count = 8 - }; - - /// @brief Foliage placement density presets - enum class FoliageDensity : uint8_t - { - Sparse = 0, - Normal = 1, - Dense = 2, - Lush = 3, - Count = 4 - }; - - // ===================================================================== - // Material Editor - // ===================================================================== - - /// @brief Material node types in the visual material graph - enum class MaterialNodeType : uint8_t - { - TextureSample = 0, - ConstantFloat = 1, - ConstantVector = 2, - ConstantColor = 3, - Multiply = 4, - Add = 5, - Lerp = 6, - Fresnel = 7, - Normal = 8, - WorldPosition = 9, - TexCoord = 10, - Time = 11, - Panner = 12, - Noise = 13, - DotProduct = 14, - Power = 15, - Clamp = 16, - Output = 17, ///< Final material output (albedo, normal, roughness, metallic, AO, emissive) - Count = 18 - }; - - /// @brief Shading models available in material editor - enum class ShadingModel : uint8_t - { - DefaultLit = 0, - Unlit = 1, - Subsurface = 2, - ClearCoat = 3, - Cloth = 4, - ThinFilm = 5, - Hair = 6, - Eye = 7, - Count = 8 - }; - - /// @brief Material blend modes - enum class MaterialBlendMode : uint8_t - { - Opaque = 0, - AlphaBlend = 1, - Additive = 2, - Modulate = 3, - Masked = 4, - Count = 5 - }; - - // ===================================================================== - // VFX / Particle Editor - // ===================================================================== - - /// @brief Particle emitter shapes - enum class EmitterShape : uint8_t - { - Point = 0, - Sphere = 1, - Hemisphere = 2, - Cone = 3, - Box = 4, - Ring = 5, - Mesh = 6, - Edge = 7, - Count = 8 - }; - - /// @brief Particle simulation spaces - enum class SimulationSpace : uint8_t - { - Local = 0, - World = 1, - Custom = 2, - Count = 3 - }; - - /// @brief VFX module types composable on each emitter - enum class VFXModule : uint8_t - { - Spawn = 0, - Lifetime = 1, - Velocity = 2, - Acceleration = 3, - Size = 4, - Color = 5, - Rotation = 6, - Noise = 7, - Collision = 8, - SubEmitter = 9, - Trail = 10, - Light = 11, - ForceField = 12, - Orbit = 13, - Count = 14 - }; - - // ===================================================================== - // Animation Editor - // ===================================================================== - - /// @brief Animation state machine transition conditions - enum class TransitionCondition : uint8_t - { - BoolParam = 0, - FloatThreshold = 1, - IntEquals = 2, - TimeElapsed = 3, - AnimFinished = 4, - TriggerParam = 5, - Count = 6 - }; - - /// @brief Blend modes for animation layers - enum class AnimBlendMode : uint8_t - { - Override = 0, - Additive = 1, - Layered = 2, - Count = 3 - }; - - /// @brief IK solver types - enum class IKSolverType : uint8_t - { - TwoBone = 0, - FABRIK = 1, - CCD = 2, - LookAt = 3, - SplineIK = 4, - Count = 5 - }; - - // ===================================================================== - // UI Editor - // ===================================================================== - - /// @brief UI widget types available in the WYSIWYG editor - enum class WidgetType : uint8_t - { - Panel = 0, - Button = 1, - Label = 2, - Image = 3, - ProgressBar = 4, - Slider = 5, - TextField = 6, - Checkbox = 7, - Dropdown = 8, - ListView = 9, - ScrollBox = 10, - Canvas = 11, - Count = 12 - }; - - /// @brief UI layout modes - enum class LayoutMode : uint8_t - { - Absolute = 0, - Horizontal = 1, - Vertical = 2, - Grid = 3, - Wrap = 4, - Count = 5 - }; - - /// @brief UI anchor presets - enum class AnchorPreset : uint8_t - { - TopLeft = 0, - TopCenter = 1, - TopRight = 2, - CenterLeft = 3, - Center = 4, - CenterRight = 5, - BottomLeft = 6, - BottomCenter = 7, - BottomRight = 8, - StretchHorizontal = 9, - StretchVertical = 10, - StretchAll = 11, - Count = 12 - }; - - // ===================================================================== - // Prototyping - // ===================================================================== - - /// @brief Pre-built gameplay templates for rapid prototyping - enum class GameTemplate : uint8_t - { - BlankProject = 0, - FirstPerson = 1, - ThirdPerson = 2, - TopDown = 3, - SideScroller = 4, - VehicleSim = 5, - PuzzleGame = 6, - TwinStick = 7, - Count = 8 - }; - - /// @brief Prototype primitive shapes for quick level blocking - enum class BlockoutShape : uint8_t - { - Cube = 0, - Cylinder = 1, - Sphere = 2, - Ramp = 3, - Stairs = 4, - Arch = 5, - LShape = 6, - TShape = 7, - Ring = 8, - Pipe = 9, - Count = 10 - }; - - // ===================================================================== - // Asset Pipeline - // ===================================================================== - - /// @brief Supported asset import formats - enum class AssetFormat : uint8_t - { - FBX = 0, - GLTF = 1, - OBJ = 2, - PNG = 3, - TGA = 4, - HDR = 5, - WAV = 6, - OGG = 7, - TTF = 8, - JSON = 9, - Count = 10 - }; - - /// @brief Asset processing pipeline stages - enum class PipelineStage : uint8_t - { - Import = 0, - Validate = 1, - Optimize = 2, - Compress = 3, - Package = 4, - Deploy = 5, - Count = 6 - }; - - /// @brief LOD generation strategies - enum class LODStrategy : uint8_t - { - ScreenSize = 0, - Distance = 1, - Manual = 2, - Count = 3 - }; - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/LevelDesign/EELevelDesignSystem.cpp b/GameModules/SparkGameEngineEditor/Source/LevelDesign/EELevelDesignSystem.cpp deleted file mode 100644 index c321dbe3d..000000000 --- a/GameModules/SparkGameEngineEditor/Source/LevelDesign/EELevelDesignSystem.cpp +++ /dev/null @@ -1,337 +0,0 @@ -/** - * @file EELevelDesignSystem.cpp - * @brief Level design tools: prefab placement, terrain sculpting, foliage painting - * - * Implements no-code level design with built-in prefab catalog, terrain - * brushes, foliage painting, and spline path editing. - */ - -#include "EELevelDesignSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#include -#include - -namespace EngineEditor -{ - - bool EELevelDesignSystem::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - RegisterBuiltinPrefabs(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Level design system initialized (%zu prefabs)", - m_prefabTemplates.size()); - return true; - } - - void EELevelDesignSystem::Update([[maybe_unused]] float deltaTime) - { - if (!m_initialized) - return; - } - - void EELevelDesignSystem::Shutdown() - { - m_instances.clear(); - m_prefabTemplates.clear(); - m_splines.clear(); - m_initialized = false; - } - - void EELevelDesignSystem::RenderDebugUI() {} - - uint32_t EELevelDesignSystem::PlacePrefab(uint32_t prefabId, float x, float y, float z) - { - const PrefabTemplate* tmpl = nullptr; - for (const auto& t : m_prefabTemplates) - { - if (t.prefabId == prefabId) - { - tmpl = &t; - break; - } - } - if (!tmpl) - return 0; - - PrefabInstance inst; - inst.instanceId = m_nextInstanceId++; - inst.prefabId = prefabId; - inst.prefabName = tmpl->name; - - if (m_snapToGrid && m_gridSize > 0.0f) - { - inst.posX = std::round(x / m_gridSize) * m_gridSize; - inst.posY = std::round(y / m_gridSize) * m_gridSize; - inst.posZ = std::round(z / m_gridSize) * m_gridSize; - } - else - { - inst.posX = x; - inst.posY = y; - inst.posZ = z; - } - - m_instances.push_back(inst); - return inst.instanceId; - } - - bool EELevelDesignSystem::RemoveInstance(uint32_t instanceId) - { - auto it = std::find_if(m_instances.begin(), m_instances.end(), - [instanceId](const PrefabInstance& i) { return i.instanceId == instanceId; }); - if (it == m_instances.end()) - return false; - m_instances.erase(it); - return true; - } - - bool EELevelDesignSystem::MoveInstance(uint32_t instanceId, float x, float y, float z) - { - for (auto& inst : m_instances) - { - if (inst.instanceId == instanceId) - { - inst.posX = x; - inst.posY = y; - inst.posZ = z; - return true; - } - } - return false; - } - - bool EELevelDesignSystem::RotateInstance(uint32_t instanceId, float rx, float ry, float rz) - { - for (auto& inst : m_instances) - { - if (inst.instanceId == instanceId) - { - inst.rotX = rx; - inst.rotY = ry; - inst.rotZ = rz; - return true; - } - } - return false; - } - - bool EELevelDesignSystem::ScaleInstance(uint32_t instanceId, float sx, float sy, float sz) - { - for (auto& inst : m_instances) - { - if (inst.instanceId == instanceId) - { - inst.scaleX = sx; - inst.scaleY = sy; - inst.scaleZ = sz; - return true; - } - } - return false; - } - - void EELevelDesignSystem::SetBrush(BrushShape shape, float radius, float strength) - { - m_brush.shape = shape; - m_brush.radius = radius; - m_brush.strength = strength; - } - - void EELevelDesignSystem::SculptTerrain([[maybe_unused]] float x, [[maybe_unused]] float z, - [[maybe_unused]] float amount) - { - // Terrain heightmap modification dispatched to engine terrain system - } - - void EELevelDesignSystem::PaintTerrain([[maybe_unused]] float x, [[maybe_unused]] float z, TerrainLayer layer) - { - m_brush.paintLayer = layer; - // Layer weight painting dispatched to engine terrain system - } - - uint32_t EELevelDesignSystem::CreateSpline(const std::string& name, const std::string& category) - { - SplinePath spline; - spline.splineId = m_nextSplineId++; - spline.name = name; - spline.category = category; - m_splines.push_back(std::move(spline)); - return m_splines.back().splineId; - } - - bool EELevelDesignSystem::AddSplinePoint(uint32_t splineId, float x, float y, float z) - { - for (auto& s : m_splines) - { - if (s.splineId == splineId) - { - SplinePoint pt; - pt.pointId = m_nextPointId++; - pt.posX = x; - pt.posY = y; - pt.posZ = z; - s.points.push_back(pt); - return true; - } - } - return false; - } - - void EELevelDesignSystem::PaintFoliage([[maybe_unused]] float x, [[maybe_unused]] float z, - [[maybe_unused]] float radius, FoliageDensity density) - { - // Foliage instance scattering dispatched to engine foliage system - uint32_t count = 0; - switch (density) - { - case FoliageDensity::Sparse: - count = 5; - break; - case FoliageDensity::Normal: - count = 15; - break; - case FoliageDensity::Dense: - count = 30; - break; - case FoliageDensity::Lush: - count = 50; - break; - default: - count = 10; - break; - } - m_foliageInstanceCount += count; - } - - std::string EELevelDesignSystem::GetPrefabCatalogString() const - { - std::string s = "=== Prefab Catalog ===\n"; - std::string lastCat; - for (const auto& p : m_prefabTemplates) - { - if (p.category != lastCat) - { - lastCat = p.category; - s += " [" + p.category + "]\n"; - } - s += " [" + std::to_string(p.prefabId) + "] " + p.name; - if (p.isStatic) - s += " (static)"; - s += "\n"; - } - return s; - } - - std::string EELevelDesignSystem::GetInstanceListString() const - { - std::string s = "=== Placed Instances (" + std::to_string(m_instances.size()) + ") ===\n"; - for (const auto& i : m_instances) - { - s += " [" + std::to_string(i.instanceId) + "] " + i.prefabName; - s += " at (" + std::to_string(static_cast(i.posX)) + ", " + std::to_string(static_cast(i.posY)) + - ", " + std::to_string(static_cast(i.posZ)) + ")"; - if (i.isLocked) - s += " [locked]"; - s += "\n"; - } - if (m_instances.empty()) - s += " (none)\n"; - return s; - } - - std::string EELevelDesignSystem::GetSplineListString() const - { - std::string s = "=== Spline Paths ===\n"; - for (const auto& sp : m_splines) - { - s += " [" + std::to_string(sp.splineId) + "] " + sp.name + " (" + sp.category + ", " + - std::to_string(sp.points.size()) + " points"; - if (sp.isClosed) - s += ", closed"; - s += ")\n"; - } - if (m_splines.empty()) - s += " (none)\n"; - return s; - } - - std::string EELevelDesignSystem::GetToolStatusString() const - { - std::string s = "Active Tool: " + std::to_string(static_cast(m_activeTool)) + "\n"; - s += "Grid: " + std::to_string(m_gridSize) + "m (" + (m_snapToGrid ? "ON" : "OFF") + ")\n"; - s += "Brush: shape=" + std::to_string(static_cast(m_brush.shape)) + - " r=" + std::to_string(static_cast(m_brush.radius)) + " str=" + std::to_string(m_brush.strength) + - "\n"; - s += "Foliage instances: " + std::to_string(m_foliageInstanceCount) + "\n"; - return s; - } - - void EELevelDesignSystem::RegisterBuiltinPrefabs() - { - uint32_t id = 1; - auto add = - [&](const std::string& name, const std::string& cat, const std::string& mesh, bool collision, bool isStatic) - { - PrefabTemplate t; - t.prefabId = id++; - t.name = name; - t.category = cat; - t.meshPath = mesh; - t.hasCollision = collision; - t.isStatic = isStatic; - m_prefabTemplates.push_back(std::move(t)); - }; - - // Architecture - add("Wall_4m", "Architecture", "meshes/arch/wall_4m.mesh", true, true); - add("Wall_8m", "Architecture", "meshes/arch/wall_8m.mesh", true, true); - add("Floor_4x4", "Architecture", "meshes/arch/floor_4x4.mesh", true, true); - add("Pillar", "Architecture", "meshes/arch/pillar.mesh", true, true); - add("Doorway", "Architecture", "meshes/arch/doorway.mesh", true, true); - add("Window", "Architecture", "meshes/arch/window.mesh", true, true); - add("Stairs_Straight", "Architecture", "meshes/arch/stairs_straight.mesh", true, true); - add("Stairs_Spiral", "Architecture", "meshes/arch/stairs_spiral.mesh", true, true); - add("Roof_Flat", "Architecture", "meshes/arch/roof_flat.mesh", true, true); - add("Arch_Gothic", "Architecture", "meshes/arch/arch_gothic.mesh", true, true); - - // Vegetation - add("Tree_Oak", "Vegetation", "meshes/veg/tree_oak.mesh", true, true); - add("Tree_Pine", "Vegetation", "meshes/veg/tree_pine.mesh", true, true); - add("Bush_Small", "Vegetation", "meshes/veg/bush_small.mesh", false, true); - add("Bush_Large", "Vegetation", "meshes/veg/bush_large.mesh", true, true); - add("Rock_Small", "Vegetation", "meshes/veg/rock_small.mesh", true, true); - add("Rock_Large", "Vegetation", "meshes/veg/rock_large.mesh", true, true); - add("Grass_Patch", "Vegetation", "meshes/veg/grass_patch.mesh", false, true); - add("Flower_Cluster", "Vegetation", "meshes/veg/flower_cluster.mesh", false, true); - - // Props - add("Barrel", "Props", "meshes/props/barrel.mesh", true, false); - add("Crate_Small", "Props", "meshes/props/crate_small.mesh", true, false); - add("Crate_Large", "Props", "meshes/props/crate_large.mesh", true, false); - add("Bench", "Props", "meshes/props/bench.mesh", true, true); - add("Lamp_Post", "Props", "meshes/props/lamp_post.mesh", true, true); - add("Fence_Section", "Props", "meshes/props/fence_section.mesh", true, true); - add("Sign_Post", "Props", "meshes/props/sign_post.mesh", true, true); - add("Chest", "Props", "meshes/props/chest.mesh", true, false); - - // Lights - add("PointLight", "Lights", "internal://point_light", false, true); - add("SpotLight", "Lights", "internal://spot_light", false, true); - add("DirectionalLight", "Lights", "internal://directional_light", false, true); - add("AreaLight", "Lights", "internal://area_light", false, true); - - // Volumes - add("TriggerVolume", "Volumes", "internal://trigger_volume", false, true); - add("BlockingVolume", "Volumes", "internal://blocking_volume", true, true); - add("AudioVolume", "Volumes", "internal://audio_volume", false, true); - add("PostProcessVolume", "Volumes", "internal://postprocess_volume", false, true); - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/LevelDesign/EELevelDesignSystem.h b/GameModules/SparkGameEngineEditor/Source/LevelDesign/EELevelDesignSystem.h deleted file mode 100644 index 329d7cc47..000000000 --- a/GameModules/SparkGameEngineEditor/Source/LevelDesign/EELevelDesignSystem.h +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @file EELevelDesignSystem.h - * @brief Level design tools: prefab placement, terrain sculpting, foliage painting - * @author Spark Engine Team - * @date 2026 - * - * Provides no-code level design tools including prefab placement with snap/grid, - * terrain sculpting with multiple brush shapes, terrain layer painting, - * foliage painting, spline path editing, and volume editing. - */ - -#pragma once - -#include "Spark/IEngineContext.h" -#include "Enums/EngineEditorEnums.h" - -#include -#include -#include - -namespace EngineEditor -{ - - /// @brief A placed prefab instance in the level - struct PrefabInstance - { - uint32_t instanceId = 0; - uint32_t prefabId = 0; - std::string prefabName; - float posX = 0.0f, posY = 0.0f, posZ = 0.0f; - float rotX = 0.0f, rotY = 0.0f, rotZ = 0.0f; - float scaleX = 1.0f, scaleY = 1.0f, scaleZ = 1.0f; - bool isLocked = false; - bool isVisible = true; - std::string layer = "Default"; - }; - - /// @brief A registered prefab template - struct PrefabTemplate - { - uint32_t prefabId = 0; - std::string name; - std::string category; ///< "Architecture", "Vegetation", "Props", etc. - std::string meshPath; - bool hasCollision = true; - bool isStatic = true; - }; - - /// @brief Terrain brush configuration - struct TerrainBrush - { - BrushShape shape = BrushShape::Circle; - float radius = 10.0f; - float strength = 0.5f; - float falloff = 0.3f; - TerrainLayer paintLayer = TerrainLayer::Grass; - }; - - /// @brief A spline control point for path editing - struct SplinePoint - { - uint32_t pointId = 0; - float posX = 0.0f, posY = 0.0f, posZ = 0.0f; - float tangentInX = 0.0f, tangentInY = 0.0f, tangentInZ = 0.0f; - float tangentOutX = 0.0f, tangentOutY = 0.0f, tangentOutZ = 0.0f; - }; - - /// @brief A spline path in the level (roads, rivers, rails) - struct SplinePath - { - uint32_t splineId = 0; - std::string name; - std::string category; ///< "Road", "River", "Rail", "Fence" - std::vector points; - bool isClosed = false; - float width = 2.0f; - }; - - /** - * @brief Level design toolkit for no-code environment creation - * - * Manages prefab placement with grid snapping, terrain sculpting, - * layer painting, foliage placement, spline paths, and level layers. - */ - class EELevelDesignSystem - { - public: - EELevelDesignSystem() = default; - ~EELevelDesignSystem() = default; - - bool Initialize(Spark::IEngineContext* context); - void Update(float deltaTime); - void Shutdown(); - void RenderDebugUI(); - - // Prefab operations - uint32_t PlacePrefab(uint32_t prefabId, float x, float y, float z); - bool RemoveInstance(uint32_t instanceId); - bool MoveInstance(uint32_t instanceId, float x, float y, float z); - bool RotateInstance(uint32_t instanceId, float rx, float ry, float rz); - bool ScaleInstance(uint32_t instanceId, float sx, float sy, float sz); - - // Terrain - void SetBrush(BrushShape shape, float radius, float strength); - void SculptTerrain(float x, float z, float amount); - void PaintTerrain(float x, float z, TerrainLayer layer); - - // Splines - uint32_t CreateSpline(const std::string& name, const std::string& category); - bool AddSplinePoint(uint32_t splineId, float x, float y, float z); - - // Foliage - void PaintFoliage(float x, float z, float radius, FoliageDensity density); - - // Queries - size_t GetPrefabCount() const { return m_prefabTemplates.size(); } - size_t GetInstanceCount() const { return m_instances.size(); } - size_t GetSplineCount() const { return m_splines.size(); } - std::string GetPrefabCatalogString() const; - std::string GetInstanceListString() const; - std::string GetSplineListString() const; - std::string GetToolStatusString() const; - - private: - void RegisterBuiltinPrefabs(); - - Spark::IEngineContext* m_context{nullptr}; - LevelTool m_activeTool{LevelTool::Select}; - TerrainBrush m_brush; - std::vector m_prefabTemplates; - std::vector m_instances; - std::vector m_splines; - float m_gridSize{1.0f}; - bool m_snapToGrid{true}; - uint32_t m_nextInstanceId{1}; - uint32_t m_nextSplineId{1}; - uint32_t m_nextPointId{1}; - uint32_t m_foliageInstanceCount{0}; - bool m_initialized{false}; - }; - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/MaterialEditor/EEMaterialEditorSystem.cpp b/GameModules/SparkGameEngineEditor/Source/MaterialEditor/EEMaterialEditorSystem.cpp deleted file mode 100644 index 2118b629b..000000000 --- a/GameModules/SparkGameEngineEditor/Source/MaterialEditor/EEMaterialEditorSystem.cpp +++ /dev/null @@ -1,328 +0,0 @@ -/** - * @file EEMaterialEditorSystem.cpp - * @brief Visual node-based material/shader editor - * - * Implements material graph editing with PBR output channels, multiple - * shading models, preset templates, and shader compilation. - */ - -#include "EEMaterialEditorSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#include - -namespace EngineEditor -{ - - bool EEMaterialEditorSystem::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - RegisterBuiltinPresets(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Material editor system initialized (%zu presets)", - m_materials.size()); - return true; - } - - void EEMaterialEditorSystem::Update([[maybe_unused]] float deltaTime) - { - if (!m_initialized) - return; - } - - void EEMaterialEditorSystem::Shutdown() - { - m_materials.clear(); - m_initialized = false; - } - - void EEMaterialEditorSystem::RenderDebugUI() {} - - uint32_t EEMaterialEditorSystem::CreateMaterial(const std::string& name, ShadingModel model) - { - MaterialGraph mat; - mat.materialId = m_nextMaterialId++; - mat.name = name; - mat.shadingModel = model; - - // Always add an output node - MaterialNode output; - output.nodeId = m_nextNodeId++; - output.name = "Material Output"; - output.type = MaterialNodeType::Output; - output.posX = 600.0f; - output.posY = 300.0f; - mat.nodes.push_back(output); - - m_materials.push_back(std::move(mat)); - return m_materials.back().materialId; - } - - bool EEMaterialEditorSystem::DeleteMaterial(uint32_t materialId) - { - auto it = std::find_if(m_materials.begin(), m_materials.end(), - [materialId](const MaterialGraph& m) { return m.materialId == materialId; }); - if (it == m_materials.end()) - return false; - m_materials.erase(it); - return true; - } - - bool EEMaterialEditorSystem::CompileMaterial(uint32_t materialId) - { - for (auto& mat : m_materials) - { - if (mat.materialId == materialId) - { - // Verify output node exists and has inputs connected - bool hasOutput = false; - for (const auto& n : mat.nodes) - { - if (n.type == MaterialNodeType::Output) - { - hasOutput = true; - break; - } - } - mat.isCompiled = hasOutput; - return hasOutput; - } - } - return false; - } - - uint32_t EEMaterialEditorSystem::AddNode(uint32_t materialId, MaterialNodeType type, float x, float y) - { - for (auto& mat : m_materials) - { - if (mat.materialId == materialId) - { - MaterialNode node; - node.nodeId = m_nextNodeId++; - node.type = type; - node.posX = x; - node.posY = y; - - switch (type) - { - case MaterialNodeType::TextureSample: - node.name = "Texture Sample"; - break; - case MaterialNodeType::ConstantFloat: - node.name = "Constant (Float)"; - break; - case MaterialNodeType::ConstantVector: - node.name = "Constant (Vector)"; - break; - case MaterialNodeType::ConstantColor: - node.name = "Constant (Color)"; - break; - case MaterialNodeType::Multiply: - node.name = "Multiply"; - break; - case MaterialNodeType::Add: - node.name = "Add"; - break; - case MaterialNodeType::Lerp: - node.name = "Lerp"; - break; - case MaterialNodeType::Fresnel: - node.name = "Fresnel"; - break; - case MaterialNodeType::Normal: - node.name = "World Normal"; - break; - case MaterialNodeType::WorldPosition: - node.name = "World Position"; - break; - case MaterialNodeType::TexCoord: - node.name = "Texture Coordinates"; - break; - case MaterialNodeType::Time: - node.name = "Time"; - break; - case MaterialNodeType::Panner: - node.name = "Panner"; - break; - case MaterialNodeType::Noise: - node.name = "Noise"; - break; - case MaterialNodeType::DotProduct: - node.name = "Dot Product"; - break; - case MaterialNodeType::Power: - node.name = "Power"; - break; - case MaterialNodeType::Clamp: - node.name = "Clamp"; - break; - default: - node.name = "Unknown"; - break; - } - - mat.nodes.push_back(std::move(node)); - mat.isCompiled = false; - return mat.nodes.back().nodeId; - } - } - return 0; - } - - bool EEMaterialEditorSystem::RemoveNode(uint32_t materialId, uint32_t nodeId) - { - for (auto& mat : m_materials) - { - if (mat.materialId == materialId) - { - auto nit = std::find_if(mat.nodes.begin(), mat.nodes.end(), - [nodeId](const MaterialNode& n) { return n.nodeId == nodeId; }); - if (nit == mat.nodes.end() || nit->type == MaterialNodeType::Output) - return false; - - std::erase_if(mat.connections, [nodeId](const MaterialConnection& c) - { return c.fromNodeId == nodeId || c.toNodeId == nodeId; }); - mat.nodes.erase(nit); - mat.isCompiled = false; - return true; - } - } - return false; - } - - bool EEMaterialEditorSystem::ConnectNodes(uint32_t materialId, uint32_t fromNode, uint32_t fromCh, uint32_t toNode, - uint32_t toCh) - { - for (auto& mat : m_materials) - { - if (mat.materialId == materialId) - { - MaterialConnection conn; - conn.connectionId = m_nextConnectionId++; - conn.fromNodeId = fromNode; - conn.fromChannel = fromCh; - conn.toNodeId = toNode; - conn.toChannel = toCh; - mat.connections.push_back(conn); - mat.isCompiled = false; - return true; - } - } - return false; - } - - uint32_t EEMaterialEditorSystem::CreatePresetPBR(const std::string& name) - { - uint32_t id = CreateMaterial(name, ShadingModel::DefaultLit); - - // Build PBR graph inline - for (auto& m : m_materials) - { - if (m.materialId == id) - { - // Add albedo texture - uint32_t albedoId = AddNode(id, MaterialNodeType::TextureSample, 100.0f, 100.0f); - // Add normal map - uint32_t normalId = AddNode(id, MaterialNodeType::TextureSample, 100.0f, 250.0f); - // Add roughness constant - uint32_t roughId = AddNode(id, MaterialNodeType::ConstantFloat, 100.0f, 400.0f); - // Add metallic constant - uint32_t metalId = AddNode(id, MaterialNodeType::ConstantFloat, 100.0f, 500.0f); - - // Set default values - for (auto& n : m.nodes) - { - if (n.nodeId == roughId) - n.paramFloat = 0.5f; - else if (n.nodeId == metalId) - n.paramFloat = 0.0f; - } - - // Connect to output - uint32_t outputId = m.nodes.front().nodeId; // Output node - ConnectNodes(id, albedoId, 0, outputId, 0); // Albedo - ConnectNodes(id, normalId, 0, outputId, 1); // Normal - ConnectNodes(id, roughId, 0, outputId, 2); // Roughness - ConnectNodes(id, metalId, 0, outputId, 3); // Metallic - break; - } - } - return id; - } - - uint32_t EEMaterialEditorSystem::CreatePresetUnlit(const std::string& name) - { - uint32_t id = CreateMaterial(name, ShadingModel::Unlit); - AddNode(id, MaterialNodeType::TextureSample, 100.0f, 200.0f); - return id; - } - - uint32_t EEMaterialEditorSystem::CreatePresetEmissive(const std::string& name) - { - uint32_t id = CreateMaterial(name, ShadingModel::DefaultLit); - AddNode(id, MaterialNodeType::ConstantColor, 100.0f, 100.0f); - AddNode(id, MaterialNodeType::Multiply, 300.0f, 200.0f); - AddNode(id, MaterialNodeType::ConstantFloat, 100.0f, 300.0f); - return id; - } - - std::string EEMaterialEditorSystem::GetMaterialListString() const - { - std::string s = "=== Materials ===\n"; - for (const auto& m : m_materials) - { - s += " [" + std::to_string(m.materialId) + "] " + m.name; - s += " (" + std::to_string(m.nodes.size()) + " nodes"; - s += ", model=" + std::to_string(static_cast(m.shadingModel)); - if (m.isCompiled) - s += ", compiled"; - s += ")\n"; - } - if (m_materials.empty()) - s += " (none)\n"; - return s; - } - - std::string EEMaterialEditorSystem::GetMaterialDetailString(uint32_t materialId) const - { - for (const auto& m : m_materials) - { - if (m.materialId == materialId) - { - std::string s = "=== Material: " + m.name + " ===\n"; - s += "Shading: " + std::to_string(static_cast(m.shadingModel)) + "\n"; - s += "Blend: " + std::to_string(static_cast(m.blendMode)) + "\n"; - s += "Two-sided: " + std::string(m.isTwoSided ? "yes" : "no") + "\n"; - s += "Nodes: " + std::to_string(m.nodes.size()) + "\n"; - for (const auto& n : m.nodes) - s += " [" + std::to_string(n.nodeId) + "] " + n.name + "\n"; - s += "Connections: " + std::to_string(m.connections.size()) + "\n"; - return s; - } - } - return "Material not found"; - } - - std::string EEMaterialEditorSystem::GetNodeCatalogString() const - { - std::string s = "=== Material Node Types ===\n"; - s += " TextureSample, ConstantFloat, ConstantVector, ConstantColor\n"; - s += " Multiply, Add, Lerp, Fresnel, DotProduct, Power, Clamp\n"; - s += " Normal, WorldPosition, TexCoord, Time, Panner, Noise\n"; - s += " Output (Albedo/Normal/Roughness/Metallic/AO/Emissive)\n"; - return s; - } - - void EEMaterialEditorSystem::RegisterBuiltinPresets() - { - CreatePresetPBR("M_Default_PBR"); - CreatePresetUnlit("M_Default_Unlit"); - CreatePresetEmissive("M_Default_Emissive"); - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/MaterialEditor/EEMaterialEditorSystem.h b/GameModules/SparkGameEngineEditor/Source/MaterialEditor/EEMaterialEditorSystem.h deleted file mode 100644 index 86a3b20df..000000000 --- a/GameModules/SparkGameEngineEditor/Source/MaterialEditor/EEMaterialEditorSystem.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @file EEMaterialEditorSystem.h - * @brief Visual node-based material/shader editor - * @author Spark Engine Team - * @date 2026 - * - * Provides a visual material graph editor with texture sampling, math - * operations, UV manipulation, and output to PBR material channels - * (albedo, normal, roughness, metallic, AO, emissive). - */ - -#pragma once - -#include "Spark/IEngineContext.h" -#include "Enums/EngineEditorEnums.h" - -#include -#include -#include - -namespace EngineEditor -{ - - /// @brief A node in the material graph - struct MaterialNode - { - uint32_t nodeId = 0; - std::string name; - MaterialNodeType type = MaterialNodeType::ConstantFloat; - float posX = 0.0f; - float posY = 0.0f; - float paramFloat = 0.0f; - float paramR = 1.0f, paramG = 1.0f, paramB = 1.0f, paramA = 1.0f; - std::string texturePath; - }; - - /// @brief A connection in the material graph - struct MaterialConnection - { - uint32_t connectionId = 0; - uint32_t fromNodeId = 0; - uint32_t fromChannel = 0; ///< Output channel index - uint32_t toNodeId = 0; - uint32_t toChannel = 0; ///< Input channel index - }; - - /// @brief A complete material definition - struct MaterialGraph - { - uint32_t materialId = 0; - std::string name; - ShadingModel shadingModel = ShadingModel::DefaultLit; - MaterialBlendMode blendMode = MaterialBlendMode::Opaque; - bool isTwoSided = false; - bool isWireframe = false; - std::vector nodes; - std::vector connections; - bool isCompiled = false; - }; - - /** - * @brief Visual material editor for no-code shader authoring - * - * Manages material graphs with node-based editing, PBR output channels, - * multiple shading models, and shader compilation. - */ - class EEMaterialEditorSystem - { - public: - EEMaterialEditorSystem() = default; - ~EEMaterialEditorSystem() = default; - - bool Initialize(Spark::IEngineContext* context); - void Update(float deltaTime); - void Shutdown(); - void RenderDebugUI(); - - // Material management - uint32_t CreateMaterial(const std::string& name, ShadingModel model); - bool DeleteMaterial(uint32_t materialId); - bool CompileMaterial(uint32_t materialId); - - // Node operations - uint32_t AddNode(uint32_t materialId, MaterialNodeType type, float x, float y); - bool RemoveNode(uint32_t materialId, uint32_t nodeId); - bool ConnectNodes(uint32_t materialId, uint32_t fromNode, uint32_t fromCh, uint32_t toNode, uint32_t toCh); - - // Presets - uint32_t CreatePresetPBR(const std::string& name); - uint32_t CreatePresetUnlit(const std::string& name); - uint32_t CreatePresetEmissive(const std::string& name); - - // Queries - size_t GetMaterialCount() const { return m_materials.size(); } - std::string GetMaterialListString() const; - std::string GetMaterialDetailString(uint32_t materialId) const; - std::string GetNodeCatalogString() const; - - private: - void RegisterBuiltinPresets(); - - Spark::IEngineContext* m_context{nullptr}; - std::vector m_materials; - uint32_t m_nextMaterialId{1}; - uint32_t m_nextNodeId{1}; - uint32_t m_nextConnectionId{1}; - bool m_initialized{false}; - }; - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/Prototyping/EEPrototypingSystem.cpp b/GameModules/SparkGameEngineEditor/Source/Prototyping/EEPrototypingSystem.cpp deleted file mode 100644 index 85a538872..000000000 --- a/GameModules/SparkGameEngineEditor/Source/Prototyping/EEPrototypingSystem.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/** - * @file EEPrototypingSystem.cpp - * @brief Rapid prototyping tools: blockout meshes, game templates, quick iteration - * - * Implements rapid prototyping with blockout primitives, pre-built game - * templates, gameplay rules, and play-test sessions. - */ - -#include "EEPrototypingSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#include - -namespace EngineEditor -{ - - bool EEPrototypingSystem::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - RegisterBuiltinTemplates(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Prototyping system initialized (%zu templates)", m_templates.size()); - return true; - } - - void EEPrototypingSystem::Update(float deltaTime) - { - if (!m_initialized) - return; - - if (m_session.isPlaying) - { - m_session.playTime += deltaTime; - - // Evaluate gameplay rules - for (const auto& rule : m_rules) - { - if (!rule.isEnabled) - continue; - // Rule evaluation dispatched to visual scripting or engine events - (void)rule; - } - } - } - - void EEPrototypingSystem::Shutdown() - { - m_primitives.clear(); - m_templates.clear(); - m_rules.clear(); - m_initialized = false; - } - - void EEPrototypingSystem::RenderDebugUI() {} - - uint32_t EEPrototypingSystem::PlaceBlockout(BlockoutShape shape, float x, float y, float z) - { - BlockoutPrimitive prim; - prim.primitiveId = m_nextPrimitiveId++; - prim.shape = shape; - prim.posX = x; - prim.posY = y; - prim.posZ = z; - - switch (shape) - { - case BlockoutShape::Cube: - prim.name = "Cube_" + std::to_string(prim.primitiveId); - break; - case BlockoutShape::Cylinder: - prim.name = "Cylinder_" + std::to_string(prim.primitiveId); - break; - case BlockoutShape::Sphere: - prim.name = "Sphere_" + std::to_string(prim.primitiveId); - break; - case BlockoutShape::Ramp: - prim.name = "Ramp_" + std::to_string(prim.primitiveId); - break; - case BlockoutShape::Stairs: - prim.name = "Stairs_" + std::to_string(prim.primitiveId); - prim.scaleY = 2.0f; - break; - case BlockoutShape::Arch: - prim.name = "Arch_" + std::to_string(prim.primitiveId); - prim.scaleX = 2.0f; - prim.scaleY = 3.0f; - break; - case BlockoutShape::LShape: - prim.name = "LShape_" + std::to_string(prim.primitiveId); - break; - case BlockoutShape::TShape: - prim.name = "TShape_" + std::to_string(prim.primitiveId); - break; - case BlockoutShape::Ring: - prim.name = "Ring_" + std::to_string(prim.primitiveId); - break; - case BlockoutShape::Pipe: - prim.name = "Pipe_" + std::to_string(prim.primitiveId); - prim.scaleY = 4.0f; - break; - default: - prim.name = "Prim_" + std::to_string(prim.primitiveId); - break; - } - - m_primitives.push_back(prim); - return prim.primitiveId; - } - - bool EEPrototypingSystem::RemoveBlockout(uint32_t primitiveId) - { - auto it = std::find_if(m_primitives.begin(), m_primitives.end(), - [primitiveId](const BlockoutPrimitive& p) { return p.primitiveId == primitiveId; }); - if (it == m_primitives.end()) - return false; - m_primitives.erase(it); - return true; - } - - bool EEPrototypingSystem::ScaleBlockout(uint32_t primitiveId, float sx, float sy, float sz) - { - for (auto& p : m_primitives) - { - if (p.primitiveId == primitiveId) - { - p.scaleX = sx; - p.scaleY = sy; - p.scaleZ = sz; - return true; - } - } - return false; - } - - void EEPrototypingSystem::ClearAllBlockouts() - { - m_primitives.clear(); - } - - uint32_t EEPrototypingSystem::ApplyTemplate(GameTemplate type) - { - for (const auto& t : m_templates) - { - if (t.type == type) - { - m_session.baseTemplate = type; - return t.templateId; - } - } - return 0; - } - - const GameTemplateConfig* EEPrototypingSystem::GetTemplate(GameTemplate type) const - { - for (const auto& t : m_templates) - { - if (t.type == type) - return &t; - } - return nullptr; - } - - uint32_t EEPrototypingSystem::AddRule(const std::string& name, const std::string& trigger, - const std::string& action, const std::string& param) - { - GameplayRule rule; - rule.ruleId = m_nextRuleId++; - rule.name = name; - rule.triggerEvent = trigger; - rule.actionType = action; - rule.actionParam = param; - m_rules.push_back(rule); - return rule.ruleId; - } - - bool EEPrototypingSystem::RemoveRule(uint32_t ruleId) - { - auto it = std::find_if(m_rules.begin(), m_rules.end(), - [ruleId](const GameplayRule& r) { return r.ruleId == ruleId; }); - if (it == m_rules.end()) - return false; - m_rules.erase(it); - return true; - } - - bool EEPrototypingSystem::ToggleRule(uint32_t ruleId) - { - for (auto& r : m_rules) - { - if (r.ruleId == ruleId) - { - r.isEnabled = !r.isEnabled; - return true; - } - } - return false; - } - - bool EEPrototypingSystem::StartPlayTest() - { - if (m_session.isPlaying) - return false; - m_session.sessionId = m_nextSessionId++; - m_session.isPlaying = true; - m_session.playTime = 0.0f; - return true; - } - - bool EEPrototypingSystem::StopPlayTest() - { - if (!m_session.isPlaying) - return false; - m_session.isPlaying = false; - return true; - } - - bool EEPrototypingSystem::IsPlaying() const - { - return m_session.isPlaying; - } - - std::string EEPrototypingSystem::GetBlockoutListString() const - { - std::string s = "=== Blockout Primitives (" + std::to_string(m_primitives.size()) + ") ===\n"; - for (const auto& p : m_primitives) - { - s += " [" + std::to_string(p.primitiveId) + "] " + p.name; - s += " at (" + std::to_string(static_cast(p.posX)) + ", " + std::to_string(static_cast(p.posY)) + - ", " + std::to_string(static_cast(p.posZ)) + ")"; - s += " scale(" + std::to_string(p.scaleX).substr(0, 3) + ", " + std::to_string(p.scaleY).substr(0, 3) + - ", " + std::to_string(p.scaleZ).substr(0, 3) + ")\n"; - } - if (m_primitives.empty()) - s += " (none)\n"; - return s; - } - - std::string EEPrototypingSystem::GetTemplateListString() const - { - std::string s = "=== Game Templates ===\n"; - for (const auto& t : m_templates) - { - s += " [" + std::to_string(t.templateId) + "] " + t.name + "\n"; - s += " " + t.description + "\n"; - s += " Camera:" + std::string(t.includesCamera ? "Y" : "N"); - s += " Player:" + std::string(t.includesPlayer ? "Y" : "N"); - s += " Physics:" + std::string(t.includesPhysics ? "Y" : "N"); - s += " AI:" + std::string(t.includesAI ? "Y" : "N"); - s += " UI:" + std::string(t.includesUI ? "Y" : "N"); - s += " Net:" + std::string(t.includesNetworking ? "Y" : "N") + "\n"; - } - return s; - } - - std::string EEPrototypingSystem::GetRuleListString() const - { - std::string s = "=== Gameplay Rules ===\n"; - for (const auto& r : m_rules) - { - s += " [" + std::to_string(r.ruleId) + "] " + r.name; - s += " (" + r.triggerEvent + " -> " + r.actionType; - if (!r.actionParam.empty()) - s += "(" + r.actionParam + ")"; - s += std::string(r.isEnabled ? "" : " [DISABLED]") + ")\n"; - } - if (m_rules.empty()) - s += " (none)\n"; - return s; - } - - std::string EEPrototypingSystem::GetSessionStatusString() const - { - std::string s = "=== Prototype Session ===\n"; - s += "Playing: " + std::string(m_session.isPlaying ? "YES" : "NO") + "\n"; - s += "Play Time: " + std::to_string(static_cast(m_session.playTime)) + "s\n"; - s += "Template: " + std::to_string(static_cast(m_session.baseTemplate)) + "\n"; - s += "Blockouts: " + std::to_string(m_primitives.size()) + "\n"; - s += "Rules: " + std::to_string(m_rules.size()) + "\n"; - return s; - } - - void EEPrototypingSystem::RegisterBuiltinTemplates() - { - uint32_t id = 1; - auto add = [&](const std::string& name, const std::string& desc, GameTemplate type, bool cam, bool player, - bool physics, bool ai, bool ui, bool net, uint32_t entities) - { - GameTemplateConfig t; - t.templateId = id++; - t.name = name; - t.description = desc; - t.type = type; - t.includesCamera = cam; - t.includesPlayer = player; - t.includesPhysics = physics; - t.includesAI = ai; - t.includesUI = ui; - t.includesNetworking = net; - t.defaultEntityCount = entities; - m_templates.push_back(std::move(t)); - }; - - add("Blank Project", "Empty scene with basic camera", GameTemplate::BlankProject, true, false, false, false, - false, false, 1); - add("First Person", "FPS controller with physics and basic HUD", GameTemplate::FirstPerson, true, true, true, - false, true, false, 5); - add("Third Person", "Third-person camera, character controller, basic HUD", GameTemplate::ThirdPerson, true, - true, true, false, true, false, 5); - add("Top Down", "Overhead camera, click-to-move, minimap", GameTemplate::TopDown, true, true, true, true, true, - false, 10); - add("Side Scroller", "2D-style camera, platformer physics", GameTemplate::SideScroller, true, true, true, false, - true, false, 3); - add("Vehicle Sim", "Vehicle physics, speedometer HUD, track setup", GameTemplate::VehicleSim, true, true, true, - false, true, false, 8); - add("Puzzle Game", "Static camera, drag-and-drop interaction, score UI", GameTemplate::PuzzleGame, true, false, - true, false, true, false, 20); - add("Twin Stick", "Twin-stick controls, arena spawners, wave system", GameTemplate::TwinStick, true, true, true, - true, true, false, 15); - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/UIEditor/EEUIEditorSystem.cpp b/GameModules/SparkGameEngineEditor/Source/UIEditor/EEUIEditorSystem.cpp deleted file mode 100644 index 8aaf0204d..000000000 --- a/GameModules/SparkGameEngineEditor/Source/UIEditor/EEUIEditorSystem.cpp +++ /dev/null @@ -1,524 +0,0 @@ -/** - * @file EEUIEditorSystem.cpp - * @brief WYSIWYG UI layout editor with widget tree and data binding - * - * Implements the visual UI editor with drag-and-drop widget placement, - * anchor presets, layout containers, style system, and preset templates. - */ - -#include "EEUIEditorSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#include - -namespace EngineEditor -{ - - bool EEUIEditorSystem::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - RegisterBuiltinPresets(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "UI editor system initialized (%zu screens)", m_screens.size()); - return true; - } - - void EEUIEditorSystem::Update([[maybe_unused]] float deltaTime) - { - if (!m_initialized) - return; - } - - void EEUIEditorSystem::Shutdown() - { - m_screens.clear(); - m_initialized = false; - } - - void EEUIEditorSystem::RenderDebugUI() {} - - uint32_t EEUIEditorSystem::CreateScreen(const std::string& name, const std::string& category) - { - UIScreen screen; - screen.screenId = m_nextScreenId++; - screen.name = name; - screen.category = category; - - // Add root panel - UIWidget root; - root.widgetId = m_nextWidgetId++; - root.name = "Root"; - root.type = WidgetType::Panel; - root.anchor = AnchorPreset::StretchAll; - root.width = screen.designWidth; - root.height = screen.designHeight; - screen.widgets.push_back(root); - - m_screens.push_back(std::move(screen)); - return m_screens.back().screenId; - } - - bool EEUIEditorSystem::DeleteScreen(uint32_t screenId) - { - auto it = std::find_if(m_screens.begin(), m_screens.end(), - [screenId](const UIScreen& s) { return s.screenId == screenId; }); - if (it == m_screens.end()) - return false; - m_screens.erase(it); - return true; - } - - bool EEUIEditorSystem::ActivateScreen(uint32_t screenId) - { - bool found = false; - for (auto& s : m_screens) - { - if (s.screenId == screenId) - { - s.isActive = true; - found = true; - } - else - { - s.isActive = false; - } - } - return found; - } - - uint32_t EEUIEditorSystem::AddWidget(uint32_t screenId, WidgetType type, const std::string& name, uint32_t parentId) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - UIWidget widget; - widget.widgetId = m_nextWidgetId++; - widget.name = name; - widget.type = type; - widget.parentId = parentId > 0 ? parentId : screen.widgets.front().widgetId; - - // Default sizes by type - switch (type) - { - case WidgetType::Button: - widget.width = 150.0f; - widget.height = 40.0f; - widget.text = "Button"; - break; - case WidgetType::Label: - widget.width = 200.0f; - widget.height = 30.0f; - widget.text = "Label"; - break; - case WidgetType::Image: - widget.width = 100.0f; - widget.height = 100.0f; - break; - case WidgetType::ProgressBar: - widget.width = 200.0f; - widget.height = 20.0f; - break; - case WidgetType::Slider: - widget.width = 200.0f; - widget.height = 25.0f; - break; - case WidgetType::TextField: - widget.width = 200.0f; - widget.height = 30.0f; - break; - case WidgetType::Checkbox: - widget.width = 20.0f; - widget.height = 20.0f; - break; - case WidgetType::Dropdown: - widget.width = 150.0f; - widget.height = 30.0f; - break; - case WidgetType::ListView: - widget.width = 300.0f; - widget.height = 400.0f; - break; - case WidgetType::ScrollBox: - widget.width = 300.0f; - widget.height = 300.0f; - break; - default: - widget.width = 200.0f; - widget.height = 200.0f; - break; - } - - screen.widgets.push_back(widget); - return widget.widgetId; - } - } - return 0; - } - - bool EEUIEditorSystem::RemoveWidget(uint32_t screenId, uint32_t widgetId) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - // Don't remove root - if (!screen.widgets.empty() && screen.widgets.front().widgetId == widgetId) - return false; - - // Remove widget and its children - std::erase_if(screen.widgets, [widgetId](const UIWidget& w) - { return w.widgetId == widgetId || w.parentId == widgetId; }); - return true; - } - } - return false; - } - - bool EEUIEditorSystem::SetWidgetPosition(uint32_t screenId, uint32_t widgetId, float x, float y) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - for (auto& w : screen.widgets) - { - if (w.widgetId == widgetId) - { - w.posX = x; - w.posY = y; - return true; - } - } - } - } - return false; - } - - bool EEUIEditorSystem::SetWidgetSize(uint32_t screenId, uint32_t widgetId, float w, float h) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - for (auto& widget : screen.widgets) - { - if (widget.widgetId == widgetId) - { - widget.width = w; - widget.height = h; - return true; - } - } - } - } - return false; - } - - bool EEUIEditorSystem::SetWidgetText(uint32_t screenId, uint32_t widgetId, const std::string& text) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - for (auto& w : screen.widgets) - { - if (w.widgetId == widgetId) - { - w.text = text; - return true; - } - } - } - } - return false; - } - - bool EEUIEditorSystem::SetWidgetAnchor(uint32_t screenId, uint32_t widgetId, AnchorPreset anchor) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - for (auto& w : screen.widgets) - { - if (w.widgetId == widgetId) - { - w.anchor = anchor; - return true; - } - } - } - } - return false; - } - - bool EEUIEditorSystem::BindWidget(uint32_t screenId, uint32_t widgetId, const std::string& binding) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - for (auto& w : screen.widgets) - { - if (w.widgetId == widgetId) - { - w.bindingExpression = binding; - return true; - } - } - } - } - return false; - } - - uint32_t EEUIEditorSystem::CreateStyle(uint32_t screenId, const std::string& name) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - UIStyle style; - style.styleId = m_nextStyleId++; - style.name = name; - screen.styles.push_back(style); - return style.styleId; - } - } - return 0; - } - - bool EEUIEditorSystem::ApplyStyle(uint32_t screenId, uint32_t widgetId, const std::string& styleName) - { - for (auto& screen : m_screens) - { - if (screen.screenId == screenId) - { - for (auto& w : screen.widgets) - { - if (w.widgetId == widgetId) - { - w.styleName = styleName; - return true; - } - } - } - } - return false; - } - - uint32_t EEUIEditorSystem::CreatePresetHUD(const std::string& name) - { - uint32_t id = CreateScreen(name, "HUD"); - for (auto& screen : m_screens) - { - if (screen.screenId == id) - { - uint32_t rootId = screen.widgets.front().widgetId; - - // Health bar - uint32_t hpPanel = AddWidget(id, WidgetType::Panel, "HealthPanel", rootId); - SetWidgetPosition(id, hpPanel, 20.0f, 20.0f); - SetWidgetSize(id, hpPanel, 300.0f, 40.0f); - uint32_t hpLabel = AddWidget(id, WidgetType::Label, "HealthLabel", hpPanel); - SetWidgetText(id, hpLabel, "HP"); - SetWidgetPosition(id, hpLabel, 0.0f, 5.0f); - uint32_t hpBar = AddWidget(id, WidgetType::ProgressBar, "HealthBar", hpPanel); - SetWidgetPosition(id, hpBar, 40.0f, 10.0f); - SetWidgetSize(id, hpBar, 250.0f, 20.0f); - BindWidget(id, hpBar, "Player.Health / Player.MaxHealth"); - - // Ammo counter - uint32_t ammoLabel = AddWidget(id, WidgetType::Label, "AmmoCounter", rootId); - SetWidgetText(id, ammoLabel, "30 / 120"); - SetWidgetPosition(id, ammoLabel, 1700.0f, 1000.0f); - SetWidgetAnchor(id, ammoLabel, AnchorPreset::BottomRight); - BindWidget(id, ammoLabel, "Weapon.CurrentAmmo + \" / \" + Weapon.TotalAmmo"); - - // Minimap - uint32_t minimap = AddWidget(id, WidgetType::Image, "Minimap", rootId); - SetWidgetPosition(id, minimap, 1720.0f, 20.0f); - SetWidgetSize(id, minimap, 180.0f, 180.0f); - SetWidgetAnchor(id, minimap, AnchorPreset::TopRight); - - // Crosshair - uint32_t crosshair = AddWidget(id, WidgetType::Image, "Crosshair", rootId); - SetWidgetPosition(id, crosshair, 952.0f, 532.0f); - SetWidgetSize(id, crosshair, 16.0f, 16.0f); - SetWidgetAnchor(id, crosshair, AnchorPreset::Center); - - break; - } - } - return id; - } - - uint32_t EEUIEditorSystem::CreatePresetMainMenu(const std::string& name) - { - uint32_t id = CreateScreen(name, "Menu"); - for (auto& screen : m_screens) - { - if (screen.screenId == id) - { - uint32_t rootId = screen.widgets.front().widgetId; - - // Title - uint32_t title = AddWidget(id, WidgetType::Label, "Title", rootId); - SetWidgetText(id, title, "GAME TITLE"); - SetWidgetPosition(id, title, 760.0f, 200.0f); - SetWidgetSize(id, title, 400.0f, 80.0f); - SetWidgetAnchor(id, title, AnchorPreset::TopCenter); - - // Button panel - uint32_t btnPanel = AddWidget(id, WidgetType::Panel, "ButtonPanel", rootId); - SetWidgetPosition(id, btnPanel, 810.0f, 400.0f); - SetWidgetSize(id, btnPanel, 300.0f, 350.0f); - SetWidgetAnchor(id, btnPanel, AnchorPreset::Center); - - uint32_t playBtn = AddWidget(id, WidgetType::Button, "PlayButton", btnPanel); - SetWidgetText(id, playBtn, "Play"); - SetWidgetPosition(id, playBtn, 75.0f, 20.0f); - - uint32_t settingsBtn = AddWidget(id, WidgetType::Button, "SettingsButton", btnPanel); - SetWidgetText(id, settingsBtn, "Settings"); - SetWidgetPosition(id, settingsBtn, 75.0f, 80.0f); - - uint32_t creditsBtn = AddWidget(id, WidgetType::Button, "CreditsButton", btnPanel); - SetWidgetText(id, creditsBtn, "Credits"); - SetWidgetPosition(id, creditsBtn, 75.0f, 140.0f); - - uint32_t quitBtn = AddWidget(id, WidgetType::Button, "QuitButton", btnPanel); - SetWidgetText(id, quitBtn, "Quit"); - SetWidgetPosition(id, quitBtn, 75.0f, 200.0f); - - // Version label - uint32_t verLabel = AddWidget(id, WidgetType::Label, "Version", rootId); - SetWidgetText(id, verLabel, "v1.0.0"); - SetWidgetPosition(id, verLabel, 1800.0f, 1060.0f); - SetWidgetAnchor(id, verLabel, AnchorPreset::BottomRight); - - break; - } - } - return id; - } - - uint32_t EEUIEditorSystem::CreatePresetInventory(const std::string& name) - { - uint32_t id = CreateScreen(name, "Popup"); - for (auto& screen : m_screens) - { - if (screen.screenId == id) - { - uint32_t rootId = screen.widgets.front().widgetId; - - // Background panel - uint32_t bg = AddWidget(id, WidgetType::Panel, "Background", rootId); - SetWidgetPosition(id, bg, 460.0f, 140.0f); - SetWidgetSize(id, bg, 1000.0f, 800.0f); - SetWidgetAnchor(id, bg, AnchorPreset::Center); - - // Title - uint32_t title = AddWidget(id, WidgetType::Label, "Title", bg); - SetWidgetText(id, title, "Inventory"); - SetWidgetPosition(id, title, 400.0f, 10.0f); - - // Item grid - uint32_t grid = AddWidget(id, WidgetType::Panel, "ItemGrid", bg); - SetWidgetPosition(id, grid, 20.0f, 60.0f); - SetWidgetSize(id, grid, 600.0f, 700.0f); - - // Item detail panel - uint32_t detail = AddWidget(id, WidgetType::Panel, "ItemDetail", bg); - SetWidgetPosition(id, detail, 640.0f, 60.0f); - SetWidgetSize(id, detail, 340.0f, 500.0f); - - uint32_t itemName = AddWidget(id, WidgetType::Label, "ItemName", detail); - SetWidgetText(id, itemName, "Select an item"); - SetWidgetPosition(id, itemName, 20.0f, 20.0f); - - uint32_t itemImage = AddWidget(id, WidgetType::Image, "ItemImage", detail); - SetWidgetPosition(id, itemImage, 70.0f, 60.0f); - SetWidgetSize(id, itemImage, 200.0f, 200.0f); - - uint32_t itemDesc = AddWidget(id, WidgetType::Label, "ItemDescription", detail); - SetWidgetPosition(id, itemDesc, 20.0f, 280.0f); - SetWidgetSize(id, itemDesc, 300.0f, 100.0f); - - // Close button - uint32_t closeBtn = AddWidget(id, WidgetType::Button, "CloseButton", bg); - SetWidgetText(id, closeBtn, "Close"); - SetWidgetPosition(id, closeBtn, 850.0f, 10.0f); - SetWidgetSize(id, closeBtn, 100.0f, 35.0f); - - break; - } - } - return id; - } - - std::string EEUIEditorSystem::GetScreenListString() const - { - std::string s = "=== UI Screens ===\n"; - for (const auto& scr : m_screens) - { - s += " [" + std::to_string(scr.screenId) + "] " + scr.name; - s += " (" + scr.category + ", " + std::to_string(scr.widgets.size()) + " widgets"; - if (scr.isActive) - s += ", ACTIVE"; - s += ")\n"; - } - if (m_screens.empty()) - s += " (none)\n"; - return s; - } - - std::string EEUIEditorSystem::GetScreenDetailString(uint32_t screenId) const - { - for (const auto& scr : m_screens) - { - if (scr.screenId == screenId) - { - std::string s = "=== Screen: " + scr.name + " ===\n"; - s += "Category: " + scr.category + "\n"; - s += "Design: " + std::to_string(static_cast(scr.designWidth)) + "x" + - std::to_string(static_cast(scr.designHeight)) + "\n"; - s += "Widgets: " + std::to_string(scr.widgets.size()) + "\n"; - for (const auto& w : scr.widgets) - { - s += " [" + std::to_string(w.widgetId) + "] " + w.name + - " (type=" + std::to_string(static_cast(w.type)); - if (!w.text.empty()) - s += " text=\"" + w.text + "\""; - if (!w.bindingExpression.empty()) - s += " bind=\"" + w.bindingExpression + "\""; - s += ")\n"; - } - s += "Styles: " + std::to_string(scr.styles.size()) + "\n"; - return s; - } - } - return "Screen not found"; - } - - std::string EEUIEditorSystem::GetWidgetCatalogString() const - { - return "Widget Types: Panel, Button, Label, Image, ProgressBar, Slider, " - "TextField, Checkbox, Dropdown, ListView, ScrollBox, Canvas\n" - "Anchors: TopLeft/Center/Right, CenterLeft/Center/Right, " - "BottomLeft/Center/Right, StretchH/V/All\n"; - } - - void EEUIEditorSystem::RegisterBuiltinPresets() - { - CreatePresetHUD("UI_HUD"); - CreatePresetMainMenu("UI_MainMenu"); - CreatePresetInventory("UI_Inventory"); - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/VFXEditor/EEVFXEditorSystem.cpp b/GameModules/SparkGameEngineEditor/Source/VFXEditor/EEVFXEditorSystem.cpp deleted file mode 100644 index 18938fc8a..000000000 --- a/GameModules/SparkGameEngineEditor/Source/VFXEditor/EEVFXEditorSystem.cpp +++ /dev/null @@ -1,382 +0,0 @@ -/** - * @file EEVFXEditorSystem.cpp - * @brief Visual effects / particle system editor - * - * Implements modular VFX authoring with composable emitter modules, - * preset templates, and playback control. - */ - -#include "EEVFXEditorSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#include - -namespace EngineEditor -{ - - bool EEVFXEditorSystem::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - RegisterBuiltinPresets(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "VFX editor system initialized (%zu presets)", m_assets.size()); - return true; - } - - void EEVFXEditorSystem::Update(float deltaTime) - { - if (!m_initialized) - return; - - for (auto& asset : m_assets) - { - if (!asset.isPlaying) - continue; - // Simulate particles for preview - for (auto& emitter : asset.emitters) - { - (void)emitter; - (void)deltaTime; - } - } - } - - void EEVFXEditorSystem::Shutdown() - { - m_assets.clear(); - m_initialized = false; - } - - void EEVFXEditorSystem::RenderDebugUI() {} - - uint32_t EEVFXEditorSystem::CreateVFX(const std::string& name, const std::string& category) - { - VFXAsset asset; - asset.assetId = m_nextAssetId++; - asset.name = name; - asset.category = category; - m_assets.push_back(std::move(asset)); - return m_assets.back().assetId; - } - - bool EEVFXEditorSystem::DeleteVFX(uint32_t assetId) - { - auto it = std::find_if(m_assets.begin(), m_assets.end(), - [assetId](const VFXAsset& a) { return a.assetId == assetId; }); - if (it == m_assets.end()) - return false; - m_assets.erase(it); - return true; - } - - uint32_t EEVFXEditorSystem::AddEmitter(uint32_t assetId, const std::string& name, EmitterShape shape) - { - for (auto& asset : m_assets) - { - if (asset.assetId == assetId) - { - VFXEmitter emitter; - emitter.emitterId = m_nextEmitterId++; - emitter.name = name; - emitter.shape = shape; - - // Add default spawn and lifetime modules - emitter.modules.push_back({VFXModule::Spawn, true, 10.0f, 50.0f}); - emitter.modules.push_back({VFXModule::Lifetime, true, 1.0f, 3.0f}); - - asset.emitters.push_back(std::move(emitter)); - return asset.emitters.back().emitterId; - } - } - return 0; - } - - bool EEVFXEditorSystem::RemoveEmitter(uint32_t assetId, uint32_t emitterId) - { - for (auto& asset : m_assets) - { - if (asset.assetId == assetId) - { - auto it = std::find_if(asset.emitters.begin(), asset.emitters.end(), - [emitterId](const VFXEmitter& e) { return e.emitterId == emitterId; }); - if (it == asset.emitters.end()) - return false; - asset.emitters.erase(it); - return true; - } - } - return false; - } - - bool EEVFXEditorSystem::AddModule(uint32_t assetId, uint32_t emitterId, VFXModule moduleType) - { - for (auto& asset : m_assets) - { - if (asset.assetId == assetId) - { - for (auto& emitter : asset.emitters) - { - if (emitter.emitterId == emitterId) - { - VFXModuleConfig mod; - mod.type = moduleType; - emitter.modules.push_back(mod); - return true; - } - } - } - } - return false; - } - - bool EEVFXEditorSystem::PlayVFX(uint32_t assetId) - { - for (auto& asset : m_assets) - { - if (asset.assetId == assetId) - { - asset.isPlaying = true; - return true; - } - } - return false; - } - - bool EEVFXEditorSystem::StopVFX(uint32_t assetId) - { - for (auto& asset : m_assets) - { - if (asset.assetId == assetId) - { - asset.isPlaying = false; - return true; - } - } - return false; - } - - uint32_t EEVFXEditorSystem::CreatePresetFire(const std::string& name) - { - uint32_t id = CreateVFX(name, "Fire"); - for (auto& asset : m_assets) - { - if (asset.assetId == id) - { - asset.emitters.push_back(BuildFireEmitter()); - asset.emitters.push_back(BuildSmokeEmitter()); - asset.emitters.push_back(BuildSparkEmitter()); - break; - } - } - return id; - } - - uint32_t EEVFXEditorSystem::CreatePresetSmoke(const std::string& name) - { - uint32_t id = CreateVFX(name, "Ambient"); - for (auto& asset : m_assets) - { - if (asset.assetId == id) - { - VFXEmitter smoke = BuildSmokeEmitter(); - smoke.maxParticles = 500; - asset.emitters.push_back(std::move(smoke)); - break; - } - } - return id; - } - - uint32_t EEVFXEditorSystem::CreatePresetSparks(const std::string& name) - { - uint32_t id = CreateVFX(name, "Impact"); - AddEmitter(id, "Sparks", EmitterShape::Point); - for (auto& asset : m_assets) - { - if (asset.assetId == id) - { - for (auto& e : asset.emitters) - { - e.maxParticles = 200; - e.isLooping = false; - e.duration = 0.5f; - e.modules.push_back({VFXModule::Velocity, true, 5.0f, 15.0f}); - e.modules.push_back({VFXModule::Acceleration, true, 0.0f, -9.81f}); - e.modules.push_back({VFXModule::Color, true, 1.0f, 0.8f, 0.2f, 1.0f}); - e.modules.push_back({VFXModule::Size, true, 0.02f, 0.08f}); - } - break; - } - } - return id; - } - - uint32_t EEVFXEditorSystem::CreatePresetRain(const std::string& name) - { - uint32_t id = CreateVFX(name, "Weather"); - AddEmitter(id, "Raindrops", EmitterShape::Box); - for (auto& asset : m_assets) - { - if (asset.assetId == id) - { - for (auto& e : asset.emitters) - { - e.maxParticles = 5000; - e.duration = 0.0f; - e.modules.push_back({VFXModule::Velocity, true, 0.0f, -8.0f, 0.0f, -12.0f}); - e.modules.push_back({VFXModule::Size, true, 0.005f, 0.015f}); - e.modules.push_back({VFXModule::Color, true, 0.7f, 0.8f, 0.9f, 0.6f}); - e.modules.push_back({VFXModule::Collision, true, 0.0f, 0.0f}); - } - break; - } - } - return id; - } - - uint32_t EEVFXEditorSystem::CreatePresetMagic(const std::string& name) - { - uint32_t id = CreateVFX(name, "Magic"); - AddEmitter(id, "Orbs", EmitterShape::Sphere); - AddEmitter(id, "Trails", EmitterShape::Point); - for (auto& asset : m_assets) - { - if (asset.assetId == id) - { - for (auto& e : asset.emitters) - { - e.modules.push_back({VFXModule::Orbit, true, 2.0f, 1.0f}); - e.modules.push_back({VFXModule::Color, true, 0.3f, 0.5f, 1.0f, 0.8f}); - e.modules.push_back({VFXModule::Light, true, 1.0f, 3.0f}); - if (e.name == "Trails") - e.modules.push_back({VFXModule::Trail, true, 0.5f, 0.0f}); - } - break; - } - } - return id; - } - - size_t EEVFXEditorSystem::GetPresetCount() const - { - size_t count = 0; - for (const auto& a : m_assets) - { - if (!a.category.empty()) - count++; - } - return count; - } - - std::string EEVFXEditorSystem::GetVFXListString() const - { - std::string s = "=== VFX Assets ===\n"; - for (const auto& a : m_assets) - { - s += " [" + std::to_string(a.assetId) + "] " + a.name; - s += " (" + a.category + ", " + std::to_string(a.emitters.size()) + " emitters"; - if (a.isPlaying) - s += ", PLAYING"; - s += ")\n"; - } - if (m_assets.empty()) - s += " (none)\n"; - return s; - } - - std::string EEVFXEditorSystem::GetVFXDetailString(uint32_t assetId) const - { - for (const auto& a : m_assets) - { - if (a.assetId == assetId) - { - std::string s = "=== VFX: " + a.name + " ===\n"; - s += "Category: " + a.category + "\n"; - s += "Emitters: " + std::to_string(a.emitters.size()) + "\n"; - for (const auto& e : a.emitters) - { - s += " [" + std::to_string(e.emitterId) + "] " + e.name; - s += " (shape=" + std::to_string(static_cast(e.shape)); - s += ", max=" + std::to_string(e.maxParticles); - s += ", modules=" + std::to_string(e.modules.size()); - s += ")\n"; - } - return s; - } - } - return "VFX not found"; - } - - std::string EEVFXEditorSystem::GetEmitterShapeCatalog() const - { - return "Emitter Shapes: Point, Sphere, Hemisphere, Cone, Box, Ring, Mesh, Edge\n"; - } - - VFXEmitter EEVFXEditorSystem::BuildFireEmitter() - { - VFXEmitter e; - e.emitterId = m_nextEmitterId++; - e.name = "Flames"; - e.shape = EmitterShape::Cone; - e.maxParticles = 300; - e.duration = 0.0f; - e.modules = { - {VFXModule::Spawn, true, 30.0f, 60.0f}, - {VFXModule::Lifetime, true, 0.5f, 1.5f}, - {VFXModule::Velocity, true, 0.0f, 3.0f}, - {VFXModule::Size, true, 0.1f, 0.5f}, - {VFXModule::Color, true, 1.0f, 0.5f, 0.1f, 0.8f}, - {VFXModule::Noise, true, 0.3f, 1.0f}, - {VFXModule::Light, true, 1.0f, 5.0f}, - }; - return e; - } - - VFXEmitter EEVFXEditorSystem::BuildSmokeEmitter() - { - VFXEmitter e; - e.emitterId = m_nextEmitterId++; - e.name = "Smoke"; - e.shape = EmitterShape::Cone; - e.maxParticles = 200; - e.duration = 0.0f; - e.modules = { - {VFXModule::Spawn, true, 10.0f, 20.0f}, {VFXModule::Lifetime, true, 2.0f, 4.0f}, - {VFXModule::Velocity, true, 0.5f, 2.0f}, {VFXModule::Size, true, 0.3f, 1.5f}, - {VFXModule::Color, true, 0.3f, 0.3f, 0.3f, 0.4f}, {VFXModule::Rotation, true, 0.0f, 360.0f}, - }; - return e; - } - - VFXEmitter EEVFXEditorSystem::BuildSparkEmitter() - { - VFXEmitter e; - e.emitterId = m_nextEmitterId++; - e.name = "Embers"; - e.shape = EmitterShape::Point; - e.maxParticles = 100; - e.duration = 0.0f; - e.modules = { - {VFXModule::Spawn, true, 5.0f, 15.0f}, {VFXModule::Lifetime, true, 1.0f, 3.0f}, - {VFXModule::Velocity, true, 1.0f, 4.0f}, {VFXModule::Acceleration, true, 0.0f, 0.5f}, - {VFXModule::Size, true, 0.01f, 0.04f}, {VFXModule::Color, true, 1.0f, 0.7f, 0.2f, 1.0f}, - {VFXModule::Light, true, 0.2f, 1.0f}, - }; - return e; - } - - void EEVFXEditorSystem::RegisterBuiltinPresets() - { - CreatePresetFire("VFX_Fire"); - CreatePresetSmoke("VFX_Smoke"); - CreatePresetSparks("VFX_Sparks"); - CreatePresetRain("VFX_Rain"); - CreatePresetMagic("VFX_Magic"); - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/VFXEditor/EEVFXEditorSystem.h b/GameModules/SparkGameEngineEditor/Source/VFXEditor/EEVFXEditorSystem.h deleted file mode 100644 index a763754c1..000000000 --- a/GameModules/SparkGameEngineEditor/Source/VFXEditor/EEVFXEditorSystem.h +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @file EEVFXEditorSystem.h - * @brief Visual effects / particle system editor - * @author Spark Engine Team - * @date 2026 - * - * Provides a modular VFX authoring system with composable emitter modules - * (spawn, velocity, size, color, collision, trails, etc.), multiple emitter - * shapes, and simulation space control. - */ - -#pragma once - -#include "Spark/IEngineContext.h" -#include "Enums/EngineEditorEnums.h" - -#include -#include -#include - -namespace EngineEditor -{ - - /// @brief A VFX module attached to an emitter - struct VFXModuleConfig - { - VFXModule type = VFXModule::Spawn; - bool enabled = true; - float paramA = 0.0f; - float paramB = 1.0f; - float paramC = 0.0f; - float paramD = 0.0f; - }; - - /// @brief A single particle emitter in a VFX asset - struct VFXEmitter - { - uint32_t emitterId = 0; - std::string name; - EmitterShape shape = EmitterShape::Point; - SimulationSpace space = SimulationSpace::World; - uint32_t maxParticles = 1000; - float duration = 5.0f; - bool isLooping = true; - float startDelay = 0.0f; - std::vector modules; - }; - - /// @brief A complete VFX asset (can have multiple emitters) - struct VFXAsset - { - uint32_t assetId = 0; - std::string name; - std::string category; ///< "Fire", "Water", "Magic", "Ambient", "Impact" - std::vector emitters; - bool isPlaying = false; - float warmupTime = 0.0f; - }; - - /** - * @brief VFX authoring system for no-code particle effects - * - * Manages VFX assets with modular emitters, composable behavior modules, - * preview playback, and preset templates. - */ - class EEVFXEditorSystem - { - public: - EEVFXEditorSystem() = default; - ~EEVFXEditorSystem() = default; - - bool Initialize(Spark::IEngineContext* context); - void Update(float deltaTime); - void Shutdown(); - void RenderDebugUI(); - - // Asset management - uint32_t CreateVFX(const std::string& name, const std::string& category); - bool DeleteVFX(uint32_t assetId); - - // Emitter operations - uint32_t AddEmitter(uint32_t assetId, const std::string& name, EmitterShape shape); - bool RemoveEmitter(uint32_t assetId, uint32_t emitterId); - bool AddModule(uint32_t assetId, uint32_t emitterId, VFXModule moduleType); - - // Playback - bool PlayVFX(uint32_t assetId); - bool StopVFX(uint32_t assetId); - - // Presets - uint32_t CreatePresetFire(const std::string& name); - uint32_t CreatePresetSmoke(const std::string& name); - uint32_t CreatePresetSparks(const std::string& name); - uint32_t CreatePresetRain(const std::string& name); - uint32_t CreatePresetMagic(const std::string& name); - - // Queries - size_t GetVFXCount() const { return m_assets.size(); } - size_t GetPresetCount() const; - std::string GetVFXListString() const; - std::string GetVFXDetailString(uint32_t assetId) const; - std::string GetEmitterShapeCatalog() const; - - private: - void RegisterBuiltinPresets(); - VFXEmitter BuildFireEmitter(); - VFXEmitter BuildSmokeEmitter(); - VFXEmitter BuildSparkEmitter(); - - Spark::IEngineContext* m_context{nullptr}; - std::vector m_assets; - uint32_t m_nextAssetId{1}; - uint32_t m_nextEmitterId{1}; - bool m_initialized{false}; - }; - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/VisualScripting/EEVisualScriptingSystem.cpp b/GameModules/SparkGameEngineEditor/Source/VisualScripting/EEVisualScriptingSystem.cpp deleted file mode 100644 index 7f30478ac..000000000 --- a/GameModules/SparkGameEngineEditor/Source/VisualScripting/EEVisualScriptingSystem.cpp +++ /dev/null @@ -1,607 +0,0 @@ -/** - * @file EEVisualScriptingSystem.cpp - * @brief Node-based visual scripting for no-code gameplay logic - * - * Implements the visual scripting graph system with built-in node templates, - * pin-type validation, and graph compilation. - */ - -#include "EEVisualScriptingSystem.h" -#include "Utils/SparkConsole.h" -#include "Utils/LogMacros.h" - -#include - -namespace EngineEditor -{ - - bool EEVisualScriptingSystem::Initialize(Spark::IEngineContext* context) - { - if (!context) - return false; - - m_context = context; - RegisterBuiltinNodes(); - - m_initialized = true; - SPARK_LOG_INFO(Spark::LogCategory::Game, "Visual scripting system initialized (%zu node templates)", - m_nodeTemplates.size()); - return true; - } - - void EEVisualScriptingSystem::Update([[maybe_unused]] float deltaTime) - { - if (!m_initialized) - return; - - // Execute compiled graphs attached to active entities - for (auto& graph : m_graphs) - { - if (!graph.isCompiled || graph.hasErrors) - continue; - // Runtime execution would dispatch through compiled bytecode here - } - } - - void EEVisualScriptingSystem::Shutdown() - { - m_graphs.clear(); - m_nodeTemplates.clear(); - m_initialized = false; - } - - void EEVisualScriptingSystem::RenderDebugUI() - { - // ImGui rendering handled by editor panels - } - - uint32_t EEVisualScriptingSystem::CreateGraph(const std::string& name) - { - ScriptGraph graph; - graph.graphId = m_nextGraphId++; - graph.name = name; - - // Add default event node (BeginPlay) - ScriptNode beginPlay; - beginPlay.nodeId = m_nextNodeId++; - beginPlay.name = "BeginPlay"; - beginPlay.category = NodeCategory::Event; - beginPlay.posX = 100.0f; - beginPlay.posY = 200.0f; - - ScriptPin execOut; - execOut.pinId = m_nextPinId++; - execOut.name = "Exec"; - execOut.type = PinType::Exec; - execOut.isInput = false; - beginPlay.outputs.push_back(execOut); - - graph.nodes.push_back(beginPlay); - - // Add default Tick event node - ScriptNode tick; - tick.nodeId = m_nextNodeId++; - tick.name = "Tick"; - tick.category = NodeCategory::Event; - tick.posX = 100.0f; - tick.posY = 400.0f; - - ScriptPin tickExecOut; - tickExecOut.pinId = m_nextPinId++; - tickExecOut.name = "Exec"; - tickExecOut.type = PinType::Exec; - tickExecOut.isInput = false; - tick.outputs.push_back(tickExecOut); - - ScriptPin deltaTimeOut; - deltaTimeOut.pinId = m_nextPinId++; - deltaTimeOut.name = "DeltaTime"; - deltaTimeOut.type = PinType::Float; - deltaTimeOut.isInput = false; - tick.outputs.push_back(deltaTimeOut); - - graph.nodes.push_back(tick); - - m_graphs.push_back(std::move(graph)); - return m_graphs.back().graphId; - } - - bool EEVisualScriptingSystem::DeleteGraph(uint32_t graphId) - { - auto it = std::find_if(m_graphs.begin(), m_graphs.end(), - [graphId](const ScriptGraph& g) { return g.graphId == graphId; }); - if (it == m_graphs.end()) - return false; - m_graphs.erase(it); - return true; - } - - bool EEVisualScriptingSystem::CompileGraph(uint32_t graphId) - { - auto it = std::find_if(m_graphs.begin(), m_graphs.end(), - [graphId](const ScriptGraph& g) { return g.graphId == graphId; }); - if (it == m_graphs.end()) - return false; - - it->hasErrors = false; - - // Validate all connections - for (const auto& conn : it->connections) - { - if (!ValidateConnection(*it, conn.fromNodeId, conn.fromPinId, conn.toNodeId, conn.toPinId)) - { - it->hasErrors = true; - return false; - } - } - - it->isCompiled = true; - return true; - } - - bool EEVisualScriptingSystem::CompileAll() - { - bool allOk = true; - for (auto& graph : m_graphs) - { - if (!CompileGraph(graph.graphId)) - allOk = false; - } - return allOk; - } - - uint32_t EEVisualScriptingSystem::AddNode(uint32_t graphId, NodeCategory category, const std::string& name) - { - auto it = std::find_if(m_graphs.begin(), m_graphs.end(), - [graphId](const ScriptGraph& g) { return g.graphId == graphId; }); - if (it == m_graphs.end()) - return 0; - - // Find template - const ScriptNode* tmpl = nullptr; - for (const auto& t : m_nodeTemplates) - { - if (t.category == category && t.name == name) - { - tmpl = &t; - break; - } - } - - ScriptNode node; - node.nodeId = m_nextNodeId++; - node.name = tmpl ? tmpl->name : name; - node.category = category; - node.posX = 300.0f; - node.posY = 200.0f; - - if (tmpl) - { - node.description = tmpl->description; - node.isPure = tmpl->isPure; - // Copy pin templates with new IDs - for (auto pin : tmpl->inputs) - { - pin.pinId = m_nextPinId++; - node.inputs.push_back(pin); - } - for (auto pin : tmpl->outputs) - { - pin.pinId = m_nextPinId++; - node.outputs.push_back(pin); - } - } - - it->nodes.push_back(std::move(node)); - it->isCompiled = false; - return it->nodes.back().nodeId; - } - - bool EEVisualScriptingSystem::RemoveNode(uint32_t graphId, uint32_t nodeId) - { - auto git = std::find_if(m_graphs.begin(), m_graphs.end(), - [graphId](const ScriptGraph& g) { return g.graphId == graphId; }); - if (git == m_graphs.end()) - return false; - - auto nit = std::find_if(git->nodes.begin(), git->nodes.end(), - [nodeId](const ScriptNode& n) { return n.nodeId == nodeId; }); - if (nit == git->nodes.end()) - return false; - - // Remove connections involving this node - std::erase_if(git->connections, - [nodeId](const ScriptConnection& c) { return c.fromNodeId == nodeId || c.toNodeId == nodeId; }); - - git->nodes.erase(nit); - git->isCompiled = false; - return true; - } - - bool EEVisualScriptingSystem::ConnectPins(uint32_t graphId, uint32_t fromNode, uint32_t fromPin, uint32_t toNode, - uint32_t toPin) - { - auto git = std::find_if(m_graphs.begin(), m_graphs.end(), - [graphId](const ScriptGraph& g) { return g.graphId == graphId; }); - if (git == m_graphs.end()) - return false; - - if (!ValidateConnection(*git, fromNode, fromPin, toNode, toPin)) - return false; - - ScriptConnection conn; - conn.connectionId = m_nextConnectionId++; - conn.fromNodeId = fromNode; - conn.fromPinId = fromPin; - conn.toNodeId = toNode; - conn.toPinId = toPin; - git->connections.push_back(conn); - git->isCompiled = false; - return true; - } - - bool EEVisualScriptingSystem::DisconnectPin(uint32_t graphId, uint32_t connectionId) - { - auto git = std::find_if(m_graphs.begin(), m_graphs.end(), - [graphId](const ScriptGraph& g) { return g.graphId == graphId; }); - if (git == m_graphs.end()) - return false; - - auto cit = std::find_if(git->connections.begin(), git->connections.end(), - [connectionId](const ScriptConnection& c) { return c.connectionId == connectionId; }); - if (cit == git->connections.end()) - return false; - - git->connections.erase(cit); - git->isCompiled = false; - return true; - } - - uint32_t EEVisualScriptingSystem::AddVariable(uint32_t graphId, const std::string& name, PinType type) - { - auto git = std::find_if(m_graphs.begin(), m_graphs.end(), - [graphId](const ScriptGraph& g) { return g.graphId == graphId; }); - if (git == m_graphs.end()) - return 0; - - ScriptVariable var; - var.variableId = m_nextVariableId++; - var.name = name; - var.type = type; - git->variables.push_back(var); - return var.variableId; - } - - const ScriptGraph* EEVisualScriptingSystem::GetGraph(uint32_t graphId) const - { - for (const auto& g : m_graphs) - { - if (g.graphId == graphId) - return &g; - } - return nullptr; - } - - std::string EEVisualScriptingSystem::GetGraphListString() const - { - std::string s = "=== Visual Script Graphs ===\n"; - for (const auto& g : m_graphs) - { - s += " [" + std::to_string(g.graphId) + "] " + g.name; - s += " (" + std::to_string(g.nodes.size()) + " nodes, " + std::to_string(g.connections.size()) + - " connections"; - if (g.isCompiled) - s += ", compiled"; - if (g.hasErrors) - s += ", ERRORS"; - s += ")\n"; - } - if (m_graphs.empty()) - s += " (none)\n"; - return s; - } - - std::string EEVisualScriptingSystem::GetNodeCatalogString() const - { - std::string s = "=== Node Catalog ===\n"; - NodeCategory lastCat = NodeCategory::Count; - for (const auto& n : m_nodeTemplates) - { - if (n.category != lastCat) - { - lastCat = n.category; - s += " [" + std::to_string(static_cast(n.category)) + "] ---\n"; - } - s += " " + n.name; - if (n.isPure) - s += " (pure)"; - s += "\n"; - } - return s; - } - - std::string EEVisualScriptingSystem::GetGraphDetailString(uint32_t graphId) const - { - const auto* g = GetGraph(graphId); - if (!g) - return "Graph not found"; - - std::string s = "=== Graph: " + g->name + " ===\n"; - s += "Nodes: " + std::to_string(g->nodes.size()) + "\n"; - for (const auto& n : g->nodes) - { - s += " [" + std::to_string(n.nodeId) + "] " + n.name; - s += " (in:" + std::to_string(n.inputs.size()) + " out:" + std::to_string(n.outputs.size()) + ")\n"; - } - s += "Connections: " + std::to_string(g->connections.size()) + "\n"; - s += "Variables: " + std::to_string(g->variables.size()) + "\n"; - for (const auto& v : g->variables) - s += " " + v.name + " (" + std::to_string(static_cast(v.type)) + ")\n"; - return s; - } - - // ------------------------------------------------------------------------- - // Built-in node registration - // ------------------------------------------------------------------------- - - void EEVisualScriptingSystem::RegisterBuiltinNodes() - { - RegisterEventNodes(); - RegisterFlowNodes(); - RegisterMathNodes(); - RegisterTransformNodes(); - RegisterPhysicsNodes(); - } - - void EEVisualScriptingSystem::RegisterEventNodes() - { - auto addEvent = [this](const std::string& name, const std::string& desc, std::vector outputs) - { - ScriptNode n; - n.nodeId = 0; // template - n.name = name; - n.description = desc; - n.category = NodeCategory::Event; - n.outputs = std::move(outputs); - m_nodeTemplates.push_back(std::move(n)); - }; - - addEvent("BeginPlay", "Called once when entity spawns", {{0, "Exec", PinType::Exec, false, false}}); - addEvent("Tick", "Called every frame", - {{0, "Exec", PinType::Exec, false, false}, {0, "DeltaTime", PinType::Float, false, false}}); - addEvent("OnCollision", "Called on physics collision", - {{0, "Exec", PinType::Exec, false, false}, - {0, "OtherEntity", PinType::Entity, false, false}, - {0, "ImpactPoint", PinType::Vector3, false, false}}); - addEvent("OnOverlap", "Called when overlap begins", - {{0, "Exec", PinType::Exec, false, false}, {0, "OtherEntity", PinType::Entity, false, false}}); - addEvent("OnInput", "Called on input action", - {{0, "Exec", PinType::Exec, false, false}, - {0, "ActionName", PinType::String, false, false}, - {0, "Value", PinType::Float, false, false}}); - addEvent("OnDestroy", "Called when entity is destroyed", {{0, "Exec", PinType::Exec, false, false}}); - } - - void EEVisualScriptingSystem::RegisterFlowNodes() - { - auto addFlow = [this](const std::string& name, const std::string& desc, std::vector inputs, - std::vector outputs) - { - ScriptNode n; - n.name = name; - n.description = desc; - n.category = NodeCategory::Flow; - n.inputs = std::move(inputs); - n.outputs = std::move(outputs); - m_nodeTemplates.push_back(std::move(n)); - }; - - addFlow("Branch", "If/else conditional", - {{0, "Exec", PinType::Exec, true}, {0, "Condition", PinType::Bool, true}}, - {{0, "True", PinType::Exec, false}, {0, "False", PinType::Exec, false}}); - addFlow("Sequence", "Execute multiple outputs in order", {{0, "Exec", PinType::Exec, true}}, - {{0, "Then 0", PinType::Exec, false}, - {0, "Then 1", PinType::Exec, false}, - {0, "Then 2", PinType::Exec, false}}); - addFlow("ForLoop", "Loop from start to end index", - {{0, "Exec", PinType::Exec, true}, {0, "Start", PinType::Int, true}, {0, "End", PinType::Int, true}}, - {{0, "LoopBody", PinType::Exec, false}, - {0, "Index", PinType::Int, false}, - {0, "Completed", PinType::Exec, false}}); - addFlow("WhileLoop", "Loop while condition is true", - {{0, "Exec", PinType::Exec, true}, {0, "Condition", PinType::Bool, true}}, - {{0, "LoopBody", PinType::Exec, false}, {0, "Completed", PinType::Exec, false}}); - addFlow("Delay", "Wait for specified seconds", - {{0, "Exec", PinType::Exec, true}, {0, "Duration", PinType::Float, true}}, - {{0, "Completed", PinType::Exec, false}}); - } - - void EEVisualScriptingSystem::RegisterMathNodes() - { - auto addPure = [this](const std::string& name, const std::string& desc, std::vector inputs, - std::vector outputs) - { - ScriptNode n; - n.name = name; - n.description = desc; - n.category = NodeCategory::Math; - n.isPure = true; - n.inputs = std::move(inputs); - n.outputs = std::move(outputs); - m_nodeTemplates.push_back(std::move(n)); - }; - - addPure("Add", "A + B", {{0, "A", PinType::Float, true}, {0, "B", PinType::Float, true}}, - {{0, "Result", PinType::Float, false}}); - addPure("Subtract", "A - B", {{0, "A", PinType::Float, true}, {0, "B", PinType::Float, true}}, - {{0, "Result", PinType::Float, false}}); - addPure("Multiply", "A * B", {{0, "A", PinType::Float, true}, {0, "B", PinType::Float, true}}, - {{0, "Result", PinType::Float, false}}); - addPure("Divide", "A / B", {{0, "A", PinType::Float, true}, {0, "B", PinType::Float, true}}, - {{0, "Result", PinType::Float, false}}); - addPure("Lerp", "Linear interpolation", - {{0, "A", PinType::Float, true}, {0, "B", PinType::Float, true}, {0, "Alpha", PinType::Float, true}}, - {{0, "Result", PinType::Float, false}}); - addPure( - "Clamp", "Clamp value to range", - {{0, "Value", PinType::Float, true}, {0, "Min", PinType::Float, true}, {0, "Max", PinType::Float, true}}, - {{0, "Result", PinType::Float, false}}); - addPure("Abs", "Absolute value", {{0, "A", PinType::Float, true}}, {{0, "Result", PinType::Float, false}}); - addPure("Sin", "Sine (radians)", {{0, "A", PinType::Float, true}}, {{0, "Result", PinType::Float, false}}); - addPure("Cos", "Cosine (radians)", {{0, "A", PinType::Float, true}}, {{0, "Result", PinType::Float, false}}); - addPure("RandomFloat", "Random float in range", - {{0, "Min", PinType::Float, true}, {0, "Max", PinType::Float, true}}, - {{0, "Result", PinType::Float, false}}); - } - - void EEVisualScriptingSystem::RegisterTransformNodes() - { - // GetPosition (pure) - { - ScriptNode n; - n.name = "GetPosition"; - n.description = "Get entity world position"; - n.category = NodeCategory::Transform; - n.isPure = true; - n.inputs = {{0, "Entity", PinType::Entity, true}}; - n.outputs = {{0, "Position", PinType::Vector3, false}}; - m_nodeTemplates.push_back(std::move(n)); - } - // SetPosition - { - ScriptNode n; - n.name = "SetPosition"; - n.description = "Set entity world position"; - n.category = NodeCategory::Transform; - n.inputs = {{0, "Exec", PinType::Exec, true}, - {0, "Entity", PinType::Entity, true}, - {0, "Position", PinType::Vector3, true}}; - n.outputs = {{0, "Exec", PinType::Exec, false}}; - m_nodeTemplates.push_back(std::move(n)); - } - // GetRotation (pure) - { - ScriptNode n; - n.name = "GetRotation"; - n.description = "Get entity world rotation"; - n.category = NodeCategory::Transform; - n.isPure = true; - n.inputs = {{0, "Entity", PinType::Entity, true}}; - n.outputs = {{0, "Rotation", PinType::Rotator, false}}; - m_nodeTemplates.push_back(std::move(n)); - } - // LookAt - { - ScriptNode n; - n.name = "LookAt"; - n.description = "Rotate entity to face target"; - n.category = NodeCategory::Transform; - n.inputs = {{0, "Exec", PinType::Exec, true}, - {0, "Entity", PinType::Entity, true}, - {0, "Target", PinType::Vector3, true}}; - n.outputs = {{0, "Exec", PinType::Exec, false}}; - m_nodeTemplates.push_back(std::move(n)); - } - // MoveToward - { - ScriptNode n; - n.name = "MoveToward"; - n.description = "Move entity toward target at speed"; - n.category = NodeCategory::Transform; - n.inputs = {{0, "Exec", PinType::Exec, true}, - {0, "Entity", PinType::Entity, true}, - {0, "Target", PinType::Vector3, true}, - {0, "Speed", PinType::Float, true}}; - n.outputs = {{0, "Exec", PinType::Exec, false}, {0, "Reached", PinType::Bool, false}}; - m_nodeTemplates.push_back(std::move(n)); - } - } - - void EEVisualScriptingSystem::RegisterPhysicsNodes() - { - // Raycast - { - ScriptNode n; - n.name = "Raycast"; - n.description = "Cast ray and return hit info"; - n.category = NodeCategory::Physics; - n.inputs = {{0, "Exec", PinType::Exec, true}, - {0, "Origin", PinType::Vector3, true}, - {0, "Direction", PinType::Vector3, true}, - {0, "MaxDistance", PinType::Float, true}}; - n.outputs = {{0, "Exec", PinType::Exec, false}, - {0, "DidHit", PinType::Bool, false}, - {0, "HitEntity", PinType::Entity, false}, - {0, "HitPoint", PinType::Vector3, false}}; - m_nodeTemplates.push_back(std::move(n)); - } - // AddForce - { - ScriptNode n; - n.name = "AddForce"; - n.description = "Apply physics force to entity"; - n.category = NodeCategory::Physics; - n.inputs = {{0, "Exec", PinType::Exec, true}, - {0, "Entity", PinType::Entity, true}, - {0, "Force", PinType::Vector3, true}}; - n.outputs = {{0, "Exec", PinType::Exec, false}}; - m_nodeTemplates.push_back(std::move(n)); - } - // SetVelocity - { - ScriptNode n; - n.name = "SetVelocity"; - n.description = "Set entity linear velocity"; - n.category = NodeCategory::Physics; - n.inputs = {{0, "Exec", PinType::Exec, true}, - {0, "Entity", PinType::Entity, true}, - {0, "Velocity", PinType::Vector3, true}}; - n.outputs = {{0, "Exec", PinType::Exec, false}}; - m_nodeTemplates.push_back(std::move(n)); - } - } - - bool EEVisualScriptingSystem::ValidateConnection(const ScriptGraph& graph, uint32_t fromNode, uint32_t fromPin, - uint32_t toNode, uint32_t toPin) const - { - if (fromNode == toNode) - return false; - - const ScriptNode* srcNode = nullptr; - const ScriptNode* dstNode = nullptr; - for (const auto& n : graph.nodes) - { - if (n.nodeId == fromNode) - srcNode = &n; - if (n.nodeId == toNode) - dstNode = &n; - } - if (!srcNode || !dstNode) - return false; - - const ScriptPin* srcPin = nullptr; - const ScriptPin* dstPin = nullptr; - for (const auto& p : srcNode->outputs) - { - if (p.pinId == fromPin) - srcPin = &p; - } - for (const auto& p : dstNode->inputs) - { - if (p.pinId == toPin) - dstPin = &p; - } - if (!srcPin || !dstPin) - return false; - - // Type compatibility: exec-to-exec or matching data types (or wildcard) - if (srcPin->type == PinType::Exec && dstPin->type == PinType::Exec) - return true; - if (srcPin->type == PinType::Exec || dstPin->type == PinType::Exec) - return false; - if (srcPin->type == PinType::Wildcard || dstPin->type == PinType::Wildcard) - return true; - return srcPin->type == dstPin->type; - } - -} // namespace EngineEditor diff --git a/GameModules/SparkGameEngineEditor/Source/VisualScripting/EEVisualScriptingSystem.h b/GameModules/SparkGameEngineEditor/Source/VisualScripting/EEVisualScriptingSystem.h deleted file mode 100644 index ff2092adb..000000000 --- a/GameModules/SparkGameEngineEditor/Source/VisualScripting/EEVisualScriptingSystem.h +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @file EEVisualScriptingSystem.h - * @brief Node-based visual scripting for no-code gameplay logic - * @author Spark Engine Team - * @date 2026 - * - * Provides a complete visual scripting graph system: node definitions with - * typed input/output pins, execution flow, variable management, graph - * compilation to bytecode, and runtime execution within the ECS. - */ - -#pragma once - -#include "Spark/IEngineContext.h" -#include "Enums/EngineEditorEnums.h" - -#include -#include -#include -#include - -namespace EngineEditor -{ - - /// @brief A single pin (input or output) on a scripting node - struct ScriptPin - { - uint32_t pinId = 0; - std::string name; - PinType type = PinType::Exec; - bool isInput = true; - bool isConnected = false; - float defaultFloat = 0.0f; - int defaultInt = 0; - std::string defaultString; - }; - - /// @brief A node in the visual scripting graph - struct ScriptNode - { - uint32_t nodeId = 0; - std::string name; - std::string description; - NodeCategory category = NodeCategory::Event; - float posX = 0.0f; ///< Canvas position X - float posY = 0.0f; ///< Canvas position Y - bool isPure = false; ///< Pure nodes have no exec pins - bool isCollapsed = false; - std::vector inputs; - std::vector outputs; - }; - - /// @brief A connection between two pins - struct ScriptConnection - { - uint32_t connectionId = 0; - uint32_t fromNodeId = 0; - uint32_t fromPinId = 0; - uint32_t toNodeId = 0; - uint32_t toPinId = 0; - }; - - /// @brief A variable defined in a script graph - struct ScriptVariable - { - uint32_t variableId = 0; - std::string name; - PinType type = PinType::Float; - bool isPublic = false; ///< Exposed to editor inspector - float valueFloat = 0.0f; - int valueInt = 0; - std::string valueString; - }; - - /// @brief A complete visual script graph - struct ScriptGraph - { - uint32_t graphId = 0; - std::string name; - std::string description; - std::vector nodes; - std::vector connections; - std::vector variables; - bool isCompiled = false; - bool hasErrors = false; - }; - - /** - * @brief Visual scripting graph system for no-code gameplay - * - * Manages script graphs with typed node connections, variable scoping, - * compile-time validation, and runtime execution per entity. - */ - class EEVisualScriptingSystem - { - public: - EEVisualScriptingSystem() = default; - ~EEVisualScriptingSystem() = default; - - bool Initialize(Spark::IEngineContext* context); - void Update(float deltaTime); - void Shutdown(); - void RenderDebugUI(); - - // Graph management - uint32_t CreateGraph(const std::string& name); - bool DeleteGraph(uint32_t graphId); - bool CompileGraph(uint32_t graphId); - bool CompileAll(); - - // Node operations - uint32_t AddNode(uint32_t graphId, NodeCategory category, const std::string& name); - bool RemoveNode(uint32_t graphId, uint32_t nodeId); - bool ConnectPins(uint32_t graphId, uint32_t fromNode, uint32_t fromPin, uint32_t toNode, uint32_t toPin); - bool DisconnectPin(uint32_t graphId, uint32_t connectionId); - - // Variable management - uint32_t AddVariable(uint32_t graphId, const std::string& name, PinType type); - - // Queries - size_t GetGraphCount() const { return m_graphs.size(); } - size_t GetNodeTemplateCount() const { return m_nodeTemplates.size(); } - const ScriptGraph* GetGraph(uint32_t graphId) const; - std::string GetGraphListString() const; - std::string GetNodeCatalogString() const; - std::string GetGraphDetailString(uint32_t graphId) const; - - private: - void RegisterBuiltinNodes(); - void RegisterEventNodes(); - void RegisterFlowNodes(); - void RegisterMathNodes(); - void RegisterTransformNodes(); - void RegisterPhysicsNodes(); - bool ValidateConnection(const ScriptGraph& graph, uint32_t fromNode, uint32_t fromPin, uint32_t toNode, - uint32_t toPin) const; - - Spark::IEngineContext* m_context{nullptr}; - std::vector m_graphs; - std::vector m_nodeTemplates; ///< Built-in node catalog - uint32_t m_nextGraphId{1}; - uint32_t m_nextNodeId{1}; - uint32_t m_nextPinId{1}; - uint32_t m_nextConnectionId{1}; - uint32_t m_nextVariableId{1}; - bool m_initialized{false}; - }; - -} // namespace EngineEditor diff --git a/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/Collectible.as b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/Collectible.as new file mode 100644 index 000000000..fda1dfae5 --- /dev/null +++ b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/Collectible.as @@ -0,0 +1,59 @@ +// Auto-generated by SparkEngine Visual Script Compiler +// Class: Collectible +// +// Visual Script Graph: Collectible.vscript +// Spinning collectible item — awards score on pickup, plays sound. + +class Collectible +{ + uint selfEntity = 0; + float spinSpeed = 180.0f; + float bobSpeed = 2.0f; + float bobHeight = 0.3f; + float baseY = 0.5f; + float timer = 0.0f; + bool collected = false; + int scoreValue = 100; + + void Start() + { + Vector3 pos = getPosition(selfEntity); + baseY = pos.y; + } + + void Update(float dt) + { + if (collected) + return; + + timer = timer + dt; + + // Spin animation + Vector3 rot = getRotation(selfEntity); + rot.y = rot.y + spinSpeed * dt; + if (rot.y > 360.0f) + rot.y = rot.y - 360.0f; + setRotation(selfEntity, rot); + + // Bob up and down + Vector3 pos = getPosition(selfEntity); + float bobOffset = sin(timer * bobSpeed) * bobHeight; + pos.y = baseY + bobOffset; + setPosition(selfEntity, pos); + } + + void OnTriggerEnter(uint triggerId) + { + if (collected) + return; + + collected = true; + playSound(selfEntity, "coin_pickup"); + playAnimation(selfEntity, "collect_burst"); + fireEvent("ScoreAdded"); + print("Collected! +" + scoreValue + " points"); + + // Move off-screen (visual removal) + setPosition(selfEntity, Vector3(0.0f, -100.0f, 0.0f)); + } +} diff --git a/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/EnemyPatrol.as b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/EnemyPatrol.as new file mode 100644 index 000000000..aed59deba --- /dev/null +++ b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/EnemyPatrol.as @@ -0,0 +1,137 @@ +// Auto-generated by SparkEngine Visual Script Compiler +// Class: EnemyPatrol +// +// Visual Script Graph: EnemyPatrol.vscript +// Enemy AI: patrols between waypoints, chases player when close, attacks. + +class EnemyPatrol +{ + uint selfEntity = 0; + float patrolSpeed = 3.0f; + float chaseSpeed = 6.0f; + float detectionRange = 12.0f; + float attackRange = 2.0f; + float attackCooldown = 1.5f; + float attackDamage = 10.0f; + float attackTimer = 0.0f; + bool isChasing = false; + + // Patrol waypoints (back and forth) + float waypointAX = 0.0f; + float waypointAZ = 0.0f; + float waypointBX = 0.0f; + float waypointBZ = 15.0f; + bool movingToB = true; + float patrolTimer = 0.0f; + + void Start() + { + Vector3 pos = getPosition(selfEntity); + waypointAX = pos.x; + waypointAZ = pos.z; + waypointBX = pos.x; + waypointBZ = pos.z + 15.0f; + print("EnemyPatrol: initialized at (" + pos.x + ", " + pos.z + ")"); + } + + void Update(float dt) + { + attackTimer = attackTimer - dt; + if (attackTimer < 0.0f) + attackTimer = 0.0f; + + Vector3 myPos = getPosition(selfEntity); + uint player = getEntityByName("VS_Player"); + Vector3 playerPos = getPosition(player); + + // Distance to player + float dx = playerPos.x - myPos.x; + float dz = playerPos.z - myPos.z; + float distSq = dx * dx + dz * dz; + + if (distSq < detectionRange * detectionRange) + { + // Chase mode + isChasing = true; + float dist = sqrt(distSq); + + if (dist > 0.1f) + { + float dirX = dx / dist; + float dirZ = dz / dist; + + if (distSq > attackRange * attackRange) + { + // Move toward player + Vector3 newPos = Vector3(myPos.x + dirX * chaseSpeed * dt, myPos.y, + myPos.z + dirZ * chaseSpeed * dt); + setPosition(selfEntity, newPos); + + // Face player + float angle = atan2(dirX, dirZ) * 57.2958f; + setRotation(selfEntity, Vector3(0.0f, angle, 0.0f)); + } + else + { + // In attack range — attack! + if (attackTimer <= 0.0f) + { + attackTimer = attackCooldown; + playAnimation(selfEntity, "attack_swing"); + playSound(selfEntity, "enemy_attack"); + print("Enemy attacks player for " + attackDamage + " damage!"); + fireEvent("PlayerDamaged"); + } + } + } + } + else + { + // Patrol mode + isChasing = false; + float targetX = movingToB ? waypointBX : waypointAX; + float targetZ = movingToB ? waypointBZ : waypointAZ; + + float pdx = targetX - myPos.x; + float pdz = targetZ - myPos.z; + float pdist = sqrt(pdx * pdx + pdz * pdz); + + if (pdist < 1.0f) + { + // Reached waypoint — switch + movingToB = !movingToB; + } + else + { + float pDirX = pdx / pdist; + float pDirZ = pdz / pdist; + Vector3 newPos = Vector3(myPos.x + pDirX * patrolSpeed * dt, myPos.y, + myPos.z + pDirZ * patrolSpeed * dt); + setPosition(selfEntity, newPos); + + float angle = atan2(pDirX, pDirZ) * 57.2958f; + setRotation(selfEntity, Vector3(0.0f, angle, 0.0f)); + } + + playAnimation(selfEntity, "walk"); + } + } + + void OnDamaged(float amount) + { + float hp = getHealth(selfEntity); + hp = hp - amount; + setHealth(selfEntity, hp); + playSound(selfEntity, "enemy_hurt"); + print("Enemy took " + amount + " damage! HP: " + hp); + + if (hp <= 0.0f) + { + playAnimation(selfEntity, "death"); + playSound(selfEntity, "enemy_death"); + print("Enemy defeated!"); + fireEvent("EnemyKilled"); + destroyEntity(selfEntity); + } + } +} diff --git a/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/GameManager.as b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/GameManager.as new file mode 100644 index 000000000..409712708 --- /dev/null +++ b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/GameManager.as @@ -0,0 +1,84 @@ +// Auto-generated by SparkEngine Visual Script Compiler +// Class: GameManager +// +// Visual Script Graph: GameManager.vscript +// Tracks score, collectibles remaining, win/lose conditions, HUD updates. + +class GameManager +{ + uint selfEntity = 0; + int score = 0; + int totalCoins = 5; + int coinsCollected = 0; + int enemiesKilled = 0; + float gameTime = 0.0f; + bool gameOver = false; + bool gameWon = false; + + void Start() + { + print("=== VISUAL SCRIPT GAME ==="); + print("Collect all 5 coins to win!"); + print("Avoid the enemies — they deal damage!"); + print("Controls: WASD + Space to jump, Shift to sprint"); + print("=========================="); + score = 0; + coinsCollected = 0; + enemiesKilled = 0; + } + + void Update(float dt) + { + if (gameOver || gameWon) + return; + + gameTime = gameTime + dt; + + // Check win condition + if (coinsCollected >= totalCoins) + { + gameWon = true; + print("*** YOU WIN! ***"); + print("Score: " + score); + print("Time: " + gameTime + " seconds"); + print("Enemies defeated: " + enemiesKilled); + playSound(selfEntity, "victory_fanfare"); + fireEvent("GameWon"); + } + + // Restart on R key + if (getKeyDown("R")) + { + print("Restarting game..."); + score = 0; + coinsCollected = 0; + enemiesKilled = 0; + gameTime = 0.0f; + gameOver = false; + gameWon = false; + fireEvent("GameRestart"); + } + } + + void OnScoreAdded() + { + coinsCollected = coinsCollected + 1; + score = score + 100; + print("Score: " + score + " | Coins: " + coinsCollected + "/" + totalCoins); + } + + void OnEnemyKilled() + { + enemiesKilled = enemiesKilled + 1; + score = score + 250; + print("Enemy killed! Score: " + score); + } + + void OnPlayerDied() + { + gameOver = true; + print("*** GAME OVER ***"); + print("Final Score: " + score); + print("Press R to restart"); + } +} diff --git a/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/HealthPickup.as b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/HealthPickup.as new file mode 100644 index 000000000..2f6fdcf49 --- /dev/null +++ b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/HealthPickup.as @@ -0,0 +1,74 @@ +// Auto-generated by SparkEngine Visual Script Compiler +// Class: HealthPickup +// +// Visual Script Graph: HealthPickup.vscript +// Healing item — restores player health, respawns after cooldown. + +class HealthPickup +{ + uint selfEntity = 0; + float healAmount = 30.0f; + float respawnTime = 10.0f; + float respawnTimer = 0.0f; + bool isActive = true; + float baseY = 0.3f; + float pulseSpeed = 3.0f; + float pulseScale = 0.15f; + float timer = 0.0f; + + void Start() + { + Vector3 pos = getPosition(selfEntity); + baseY = pos.y; + isActive = true; + } + + void Update(float dt) + { + timer = timer + dt; + + if (!isActive) + { + // Respawn countdown + respawnTimer = respawnTimer - dt; + if (respawnTimer <= 0.0f) + { + isActive = true; + // Move back to original position + Vector3 pos = getPosition(selfEntity); + pos.y = baseY; + setPosition(selfEntity, pos); + playSound(selfEntity, "pickup_respawn"); + print("Health pickup respawned"); + } + return; + } + + // Pulse animation (scale breathing effect) + Vector3 rot = getRotation(selfEntity); + rot.y = rot.y + 90.0f * dt; + if (rot.y > 360.0f) + rot.y = rot.y - 360.0f; + setRotation(selfEntity, rot); + + // Gentle bob + Vector3 pos = getPosition(selfEntity); + pos.y = baseY + sin(timer * pulseSpeed) * pulseScale; + setPosition(selfEntity, pos); + } + + void OnTriggerEnter(uint triggerId) + { + if (!isActive) + return; + + isActive = false; + respawnTimer = respawnTime; + playSound(selfEntity, "health_pickup"); + print("Player healed for " + healAmount + " HP!"); + fireEvent("PlayerHealed"); + + // Hide item + setPosition(selfEntity, Vector3(0.0f, -100.0f, 0.0f)); + } +} diff --git a/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/PlayerController.as b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/PlayerController.as new file mode 100644 index 000000000..075e93c80 --- /dev/null +++ b/GameModules/SparkGameVisualScript/Assets/Scripts/Generated/PlayerController.as @@ -0,0 +1,94 @@ +// Auto-generated by SparkEngine Visual Script Compiler +// Class: PlayerController +// +// Visual Script Graph: PlayerController.vscript +// This script handles ALL player behavior — movement, looking, jumping, health. +// No C++ game code was written to create this behavior. + +class PlayerController +{ + uint selfEntity = 0; + float moveSpeed = 8.0f; + float lookSensitivity = 2.0f; + float jumpForce = 5.0f; + float health = 100.0f; + float maxHealth = 100.0f; + bool isGrounded = true; + float yaw = 0.0f; + float pitch = 0.0f; + + void Start() + { + print("PlayerController: Player spawned and ready"); + health = maxHealth; + } + + void Update(float dt) + { + // === Movement (WASD) === + Vector3 currentPos = getPosition(selfEntity); + float moveX = 0.0f; + float moveZ = 0.0f; + + if (getKey("W")) + moveZ = moveZ + moveSpeed * dt; + if (getKey("S")) + moveZ = moveZ - moveSpeed * dt; + if (getKey("A")) + moveX = moveX - moveSpeed * dt; + if (getKey("D")) + moveX = moveX + moveSpeed * dt; + + Vector3 newPos = Vector3(currentPos.x + moveX, currentPos.y, currentPos.z + moveZ); + setPosition(selfEntity, newPos); + + // === Jump === + if (getKeyDown("Space") && isGrounded) + { + applyForce(selfEntity, Vector3(0.0f, jumpForce, 0.0f)); + isGrounded = false; + } + + // === Ground check (simple Y threshold) === + Vector3 pos = getPosition(selfEntity); + if (pos.y <= 0.1f) + { + isGrounded = true; + } + + // === Sprint === + if (getKey("LeftShift")) + { + moveSpeed = 14.0f; + } + else + { + moveSpeed = 8.0f; + } + + // === Health regen === + if (health < maxHealth) + { + health = health + 2.0f * dt; + if (health > maxHealth) + health = maxHealth; + } + } + + void OnCollision(uint other) + { + // Nothing here — collision responses handled by other scripts + } + + void OnDamaged(float amount) + { + health = health - amount; + print("Player took " + amount + " damage! Health: " + health); + if (health <= 0.0f) + { + health = 0.0f; + print("PLAYER DIED — Game Over!"); + fireEvent("PlayerDied"); + } + } +} diff --git a/GameModules/SparkGameVisualScript/CMakeLists.txt b/GameModules/SparkGameVisualScript/CMakeLists.txt new file mode 100644 index 000000000..77c627b0d --- /dev/null +++ b/GameModules/SparkGameVisualScript/CMakeLists.txt @@ -0,0 +1,160 @@ +cmake_minimum_required(VERSION 3.25) + +# ================================================================ +# SparkGameVisualScript — Zero C++ Game Logic Module +# +# ALL game logic lives in visual script graphs (.vscript/.as files): +# - PlayerController: WASD movement, jump, sprint, health regen +# - Collectible: spinning coins, pickup scoring, sound effects +# - EnemyPatrol: waypoint patrol, player detection, chase, attack +# - GameManager: score tracking, win/lose conditions, HUD +# - HealthPickup: healing items with respawn cooldown +# +# The C++ code is ONLY the IModule shell that loads scripts and +# spawns entities. No gameplay logic exists in C++. +# ================================================================ + +if(NOT CMAKE_PROJECT_NAME OR CMAKE_PROJECT_NAME STREQUAL "SparkGameVisualScript") + project(SparkGameVisualScript LANGUAGES CXX) + set(SPARK_GAME_VS_STANDALONE TRUE) +else() + set(SPARK_GAME_VS_STANDALONE FALSE) +endif() + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(SPARK_GAME_VS_STANDALONE) + if(NOT SPARK_ENGINE_DIR) + set(SPARK_ENGINE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." CACHE PATH + "Path to SparkEngine root directory") + endif() + + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + + foreach(config Debug Release RelWithDebInfo MinSizeRel) + string(TOUPPER ${config} CONFIG_UPPER) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG_UPPER} ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CONFIG_UPPER} ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFIG_UPPER} ${CMAKE_BINARY_DIR}/lib) + endforeach() + + if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + add_compile_options(/W3 /MP /bigobj /wd4005 /wd4996 /wd4244 /wd4267) + add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX _CRT_SECURE_NO_WARNINGS SPARK_PLATFORM_WINDOWS) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + add_compile_options(-Wall -Wextra -Wno-unused-parameter -fPIC) + endif() + + set(THIRDPARTY_INCLUDE_DIRS "") + if(EXISTS "${SPARK_ENGINE_DIR}/ThirdParty/ECS/entt/single_include") + list(APPEND THIRDPARTY_INCLUDE_DIRS "${SPARK_ENGINE_DIR}/ThirdParty/ECS/entt/single_include") + endif() + + set(ENGINE_SOURCE_DIR "${SPARK_ENGINE_DIR}/SparkEngine/Source") +else() + set(ENGINE_SOURCE_DIR "${CMAKE_SOURCE_DIR}/SparkEngine/Source") +endif() + +# Only two source files — the IModule shell +file(GLOB_RECURSE SPARK_GAME_VS_SOURCES CONFIGURE_DEPENDS "Source/*.cpp" "Source/*.h") + +add_library(SparkGameVisualScript SHARED ${SPARK_GAME_VS_SOURCES}) +target_compile_definitions(SparkGameVisualScript PRIVATE SPARK_GAME_DLL SPARK_MODULE_DLL) + +if(NOT SPARK_GAME_VS_STANDALONE AND TARGET SparkEngineLib) + if(WIN32) + target_link_libraries(SparkGameVisualScript PRIVATE SparkEngineLib) + elseif(TARGET SparkEngineInterface) + target_link_libraries(SparkGameVisualScript PRIVATE SparkEngineInterface) + endif() +endif() + +if(SPARK_GAME_VS_STANDALONE) + set(SPARK_SDK_INCLUDE_DIR "${SPARK_ENGINE_DIR}/SparkSDK/Include") +else() + set(SPARK_SDK_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/SparkSDK/Include") +endif() + +target_include_directories(SparkGameVisualScript PRIVATE + "Source" + "${ENGINE_SOURCE_DIR}" + "${SPARK_SDK_INCLUDE_DIR}" + ${THIRDPARTY_INCLUDE_DIRS} +) + +if(WIN32) + target_link_libraries(SparkGameVisualScript PRIVATE + d3d11 dxgi d3dcompiler dxguid + kernel32 user32 gdi32 winspool shell32 comdlg32 advapi32 + ole32 oleaut32 uuid winmm + $<$:ws2_32> + $<$:wsock32> + $<$:crypt32> + $<$:wldap32> + $<$:normaliz> + ) + target_compile_definitions(SparkGameVisualScript PRIVATE + WIN32_LEAN_AND_MEAN NOMINMAX _CRT_SECURE_NO_WARNINGS SPARK_PLATFORM_WINDOWS) +else() + find_package(Threads REQUIRED) + target_link_libraries(SparkGameVisualScript PRIVATE Threads::Threads ${CMAKE_DL_LIBS}) + if(APPLE) + target_compile_definitions(SparkGameVisualScript PRIVATE SPARK_PLATFORM_MACOS) + else() + target_compile_definitions(SparkGameVisualScript PRIVATE SPARK_PLATFORM_LINUX) + endif() +endif() + +if(MSVC) + target_link_libraries(SparkGameVisualScript PRIVATE legacy_stdio_definitions) +endif() + +if(WIN32) + if(TARGET Jolt) + target_link_libraries(SparkGameVisualScript PRIVATE Jolt) + endif() + if(TARGET miniz) + target_link_libraries(SparkGameVisualScript PRIVATE miniz) + endif() + if(TARGET tinyobjloader) + target_link_libraries(SparkGameVisualScript PRIVATE tinyobjloader) + endif() +endif() + +if(SPARK_VULKAN_AVAILABLE) + target_compile_definitions(SparkGameVisualScript PRIVATE SPARK_VULKAN_SUPPORT) + if(Vulkan_FOUND) + target_link_libraries(SparkGameVisualScript PRIVATE Vulkan::Vulkan) + else() + target_include_directories(SparkGameVisualScript PRIVATE ${Vulkan_INCLUDE_DIR}) + target_link_libraries(SparkGameVisualScript PRIVATE ${Vulkan_LIBRARY}) + endif() +endif() + +if(SPARK_OPENGL_AVAILABLE) + target_compile_definitions(SparkGameVisualScript PRIVATE SPARK_OPENGL_SUPPORT) + target_link_libraries(SparkGameVisualScript PRIVATE OpenGL::GL) + if(TARGET glad) + target_link_libraries(SparkGameVisualScript PRIVATE glad) + endif() +endif() + +if(FEATURE_DEFINITIONS) + target_compile_definitions(SparkGameVisualScript PRIVATE ${FEATURE_DEFINITIONS}) +endif() + +# Copy visual script assets alongside the built DLL +add_custom_command(TARGET SparkGameVisualScript POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "$/Assets/Scripts/Generated" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/Assets/Scripts/Generated" + "$/Assets/Scripts/Generated" + COMMENT "Copying visual script assets for SparkGameVisualScript" +) + +message(STATUS "SparkGameVisualScript configured — zero C++ game logic, all visual scripts") diff --git a/GameModules/SparkGameVisualScript/Source/Core/Main.cpp b/GameModules/SparkGameVisualScript/Source/Core/Main.cpp new file mode 100644 index 000000000..63ddd87a3 --- /dev/null +++ b/GameModules/SparkGameVisualScript/Source/Core/Main.cpp @@ -0,0 +1,290 @@ +/** + * @file Main.cpp + * @brief SparkGameVisualScript — IModule shell that loads visual scripts + * + * This is the ONLY C++ file in the game module. It does three things: + * 1. Compiles all .as script files from the Assets/Scripts directory + * 2. Spawns game entities (player, enemies, collectibles, world) + * 3. Attaches the visual scripts to entities via the Script component + * + * ALL game logic — movement, combat, scoring, AI, win/lose — lives in + * the visual script graphs (.vscript files), not in this C++ code. + */ + +#include "SparkGameVisualScript.h" +#include "Engine/ECS/Components/CoreComponents.h" +#include "Engine/Scripting/AngelScriptEngine.h" +#include "Utils/SparkConsole.h" +#include "Utils/LogMacros.h" + +#include + +#ifdef SPARK_PLATFORM_WINDOWS +#include + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hModule); + break; + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#endif + +SPARK_IMPLEMENT_MODULE(SparkGameVisualScriptModule) + +SparkGameVisualScriptModule::~SparkGameVisualScriptModule() +{ + if (m_initialized) + OnUnload(); +} + +Spark::ModuleInfo SparkGameVisualScriptModule::GetModuleInfo() const +{ + Spark::ModuleInfo info{}; + info.name = "Spark Visual Script Game — Zero C++ Logic"; + info.version = "1.0.0"; + info.sdkVersion = SPARK_SDK_VERSION; + info.loadOrder = 1010; + return info; +} + +bool SparkGameVisualScriptModule::OnLoad(Spark::IEngineContext* context) +{ + if (!context) + return false; + + m_context = context; + auto& console = Spark::SimpleConsole::GetInstance(); + + console.LogInfo("[VisualScript] Loading visual-script-only game module..."); + console.LogInfo("[VisualScript] ALL game logic is defined in visual scripts — zero C++ game code"); + + // Step 1: Compile all visual scripts + LoadAndCompileScripts(); + + // Step 2: Spawn entities and attach scripts + SpawnGameEntities(); + + m_initialized = true; + console.LogInfo("[VisualScript] Module loaded — game is running entirely on visual scripts"); + return true; +} + +void SparkGameVisualScriptModule::OnUnload() +{ + if (!m_initialized) + return; + + auto& console = Spark::SimpleConsole::GetInstance(); + console.LogInfo("[VisualScript] Unloading visual script game module"); + + m_context = nullptr; + m_initialized = false; +} + +void SparkGameVisualScriptModule::OnUpdate(float deltaTime) +{ + if (!m_initialized || m_paused) + return; + + // All update logic is handled by AngelScriptEngine calling + // Update(dt) on each entity's attached script — nothing to do here. + (void)deltaTime; +} + +void SparkGameVisualScriptModule::OnFixedUpdate(float fixedDeltaTime) +{ + (void)fixedDeltaTime; +} + +void SparkGameVisualScriptModule::OnRender() {} + +void SparkGameVisualScriptModule::OnResize(int width, int height) +{ + (void)width; + (void)height; +} + +void SparkGameVisualScriptModule::OnPause() +{ + m_paused = true; +} + +void SparkGameVisualScriptModule::OnResume() +{ + m_paused = false; +} + +void SparkGameVisualScriptModule::OnImGui() {} + +// ============================================================================ +// Script Loading — compile all .as files from the scripts directory +// ============================================================================ + +void SparkGameVisualScriptModule::LoadAndCompileScripts() +{ + auto& console = Spark::SimpleConsole::GetInstance(); + auto* asEngine = AngelScriptEngine::GetInstance(); + + if (!asEngine) + { + console.LogWarning("[VisualScript] AngelScript engine not available — scripts won't execute"); + return; + } + + // Look for .as files in multiple locations + std::vector searchPaths = { + "Assets/Scripts/Generated/", + "GameModules/SparkGameVisualScript/Assets/Scripts/Generated/", + }; + + int compiled = 0; + for (const auto& dir : searchPaths) + { + if (!std::filesystem::exists(dir)) + continue; + + for (const auto& entry : std::filesystem::directory_iterator(dir)) + { + if (entry.path().extension() == ".as") + { + std::string path = entry.path().string(); + std::string moduleName = entry.path().stem().string(); + + if (asEngine->CompileScriptFile(path)) + { + console.LogSuccess("[VisualScript] Compiled: " + moduleName); + compiled++; + } + else + { + console.LogError("[VisualScript] Failed to compile: " + path + " — " + asEngine->GetLastError()); + } + } + } + } + + console.LogInfo("[VisualScript] Compiled " + std::to_string(compiled) + " visual script(s)"); +} + +// ============================================================================ +// Entity Spawning — create game entities and attach visual scripts +// ============================================================================ + +void SparkGameVisualScriptModule::SpawnGameEntities() +{ + auto& console = Spark::SimpleConsole::GetInstance(); + auto* world = m_context->GetWorld(); + auto* asEngine = AngelScriptEngine::GetInstance(); + + if (!world || !asEngine) + { + console.LogWarning("[VisualScript] World or AngelScript not available — can't spawn entities"); + return; + } + + // Bind world for script API (getPosition, createEntity, etc.) + AngelScriptEngine::BindWorld(world); + + // --- Player entity --- + // Visual script "PlayerController" handles: WASD movement, mouse look, jump, health + { + auto player = world->CreateEntity("VS_Player"); + world->AddComponent(player, Transform{{0.0f, 1.0f, 0.0f}, {0, 0, 0}, {1, 1, 1}}); + world->AddComponent(player, NameComponent{"VS_Player"}); + + Script scriptComp; + scriptComp.scriptPath = "Assets/Scripts/Generated/PlayerController.as"; + scriptComp.className = "PlayerController"; + scriptComp.moduleName = "PlayerController"; + world->AddComponent