diff --git a/Content/Content.vcxitems b/Content/Content.vcxitems index 877e71ce6..a19676dab 100644 --- a/Content/Content.vcxitems +++ b/Content/Content.vcxitems @@ -34,6 +34,10 @@ true true + + true + true + true true @@ -358,6 +362,10 @@ true true + + true + true + true true @@ -378,6 +386,14 @@ true true + + true + true + + + true + true + true true diff --git a/Content/Content.vcxitems.filters b/Content/Content.vcxitems.filters index e870423c8..4cdf2d159 100644 --- a/Content/Content.vcxitems.filters +++ b/Content/Content.vcxitems.filters @@ -216,6 +216,18 @@ scripts\subscript_demo + + models + + + models + + + models + + + models + diff --git a/Content/Documentation/ScriptingAPI-Documentation.md b/Content/Documentation/ScriptingAPI-Documentation.md index ffcd1b3c8..55e1696e5 100644 --- a/Content/Documentation/ScriptingAPI-Documentation.md +++ b/Content/Documentation/ScriptingAPI-Documentation.md @@ -456,7 +456,8 @@ The scene holds components. Entity handles can be used to retrieve associated co - Component_CreateLight(Entity entity) : LightComponent result -- attach a light component to an entity. The returned LightComponent is associated with the entity and can be manipulated - Component_CreateObject(Entity entity) : ObjectComponent result -- attach an object component to an entity. The returned ObjectComponent is associated with the entity and can be manipulated - Component_CreateInverseKinematics(Entity entity) : InverseKinematicsComponent result -- attach an IK component to an entity. The returned InverseKinematicsComponent is associated with the entity and can be manipulated -- Component_CreateSpring(Entity entity) : SpringComponent result -- attach a spring component to an entity. The returned InverseKinematicsComponent is associated with the entity and can be manipulated +- Component_CreateSpring(Entity entity) : SpringComponent result -- attach a spring component to an entity. The returned SpringKinematicsComponent is associated with the entity and can be manipulated +- Component_CreateSpring(Entity entity) : ScriptComponent result -- attach a script component to an entity. The returned ScriptKinematicsComponent is associated with the entity and can be manipulated - Component_GetName(Entity entity) : NameComponent? result -- query the name component of the entity (if exists) - Component_GetLayer(Entity entity) : LayerComponent? result -- query the layer component of the entity (if exists) @@ -469,6 +470,7 @@ The scene holds components. Entity handles can be used to retrieve associated co - Component_GetObject(Entity entity) : ObjectComponent? result -- query the object component of the entity (if exists) - Component_GetInverseKinematics(Entity entity) : InverseKinematicsComponent? result -- query the IK component of the entity (if exists) - Component_GetSpring(Entity entity) : SpringComponent? result -- query the spring component of the entity (if exists) +- Component_GetSpring(Entity entity) : ScriptComponent? result -- query the script component of the entity (if exists) - Component_GetNameArray() : NameComponent[] result -- returns the array of all components of this type - Component_GetLayerArray() : LayerComponent[] result -- returns the array of all components of this type @@ -480,7 +482,8 @@ The scene holds components. Entity handles can be used to retrieve associated co - Component_GetLightArray() : LightComponent[] result -- returns the array of all components of this type - Component_GetObjectArray() : ObjectComponent[] result -- returns the array of all components of this type - Component_GetInverseKinematicsArray() : InverseKinematicsComponent[] result -- returns the array of all components of this type -- Component_GetSpringArray() : InverseKinematicsComponent[] result -- returns the array of all components of this type +- Component_GetSpringArray() : SpringComponent[] result -- returns the array of all components of this type +- Component_GetScriptArray() : ScriptComponent[] result -- returns the array of all components of this type - Entity_GetNameArray() : Entity[] result -- returns the array of all entities that have this component type - Entity_GetLayerArray() : Entity[] result -- returns the array of all entities that have this component type @@ -493,6 +496,7 @@ The scene holds components. Entity handles can be used to retrieve associated co - Entity_GetObjectArray() : Entity[] result -- returns the array of all entities that have this component type - Entity_GetInverseKinematicsArray() : Entity[] result -- returns the array of all entities that have this component type - Entity_GetSpringArray() : Entity[] result -- returns the array of all entities that have this component type +- Entity_GetScriptArray() : Entity[] result -- returns the array of all entities that have this component type - Component_Attach(Entity entity,parent) -- attaches entity to parent (adds a hierarchy component to entity). From now on, entity will inherit certain properties from parent, such as transform (entity will move with parent) or layer (entity's layer will be a sublayer of parent's layer) - Component_Detach(Entity entity) -- detaches entity from parent (if hierarchycomponent exists for it). Restores entity's original layer, and applies current transformation to entity @@ -624,6 +628,14 @@ Enables jiggle effect on transforms such as bones for example. - SetDamping(float value) - SetWindAffection(float value) +#### ScriptComponent +A lua script bound to an entity +- CreateFromFile(string filename) +- Play() +- IsPlaying() : bool result +- SetPlayOnce(bool once = true) +- Stop() + ## Canvas This is used to describe a drawable area - GetDPI() : float -- pixels per inch diff --git a/Content/models/script_component_test.lua b/Content/models/script_component_test.lua new file mode 100644 index 000000000..b1f3cf59e --- /dev/null +++ b/Content/models/script_component_test.lua @@ -0,0 +1,14 @@ +local scene = GetScene() -- GetScene() is from global scope +local entity = GetEntity() -- GetEntity() is local to this script (only available if this is running from a ScriptComponent) +local transform = scene.Component_GetTransform(entity) +if transform ~= nil then + transform.Rotate(Vector(0, math.pi * getDeltaTime(), 0)) + + local text = "This sample shows usage of a ScriptComponent, which is attached to a scene entity.\n" + text = text .. "Each ScriptComponent will get these functions that can be used to reference data:\n" + text = text .. "script_file() : " .. script_file() .. "\n" + text = text .. "script_dir() : " .. script_dir() .. "\n" + text = text .. "script_pid() : " .. script_pid() .. "\n" + text = text .. "GetEntity() : " .. GetEntity() .. "\n" + DrawDebugText(text, vector.Add(transform.GetPosition(), Vector(0,3,0)), Vector(1,1,1,1), 0.25) +end diff --git a/Content/models/script_component_test.wiscene b/Content/models/script_component_test.wiscene new file mode 100644 index 000000000..f2e3f50d6 Binary files /dev/null and b/Content/models/script_component_test.wiscene differ diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index bbfb65d22..c61040352 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -24,6 +24,7 @@ set (SOURCE_FILES GraphicsWindow.cpp SoundWindow.cpp SpringWindow.cpp + ScriptWindow.cpp stdafx.cpp TransformWindow.cpp Translator.cpp diff --git a/Editor/ComponentsWindow.cpp b/Editor/ComponentsWindow.cpp index a6f9b5c4e..31b713014 100644 --- a/Editor/ComponentsWindow.cpp +++ b/Editor/ComponentsWindow.cpp @@ -32,6 +32,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) transformWnd.Create(editor); layerWnd.Create(editor); nameWnd.Create(editor); + scriptWnd.Create(editor); newComponentCombo.Create("Add: "); @@ -55,6 +56,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) newComponentCombo.AddItem("Weather " ICON_WEATHER, 12); newComponentCombo.AddItem("Force Field " ICON_FORCE, 13); newComponentCombo.AddItem("Animation " ICON_ANIMATION, 14); + newComponentCombo.AddItem("Script " ICON_SCRIPT, 15); newComponentCombo.OnSelect([=](wi::gui::EventArgs args) { newComponentCombo.SetSelectedWithoutCallback(-1); if (editor->translator.selected.empty()) @@ -130,6 +132,10 @@ void ComponentsWindow::Create(EditorComponent* _editor) if (scene.animations.Contains(entity)) return; break; + case 15: + if (scene.scripts.Contains(entity)) + return; + break; default: return; } @@ -208,6 +214,9 @@ void ComponentsWindow::Create(EditorComponent* _editor) case 14: scene.animations.Create(entity); break; + case 15: + scene.scripts.Create(entity); + break; default: break; } @@ -237,6 +246,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) AddWidget(&transformWnd); AddWidget(&layerWnd); AddWidget(&nameWnd); + AddWidget(&scriptWnd); materialWnd.SetVisible(false); weatherWnd.SetVisible(false); @@ -255,6 +265,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) transformWnd.SetVisible(false); layerWnd.SetVisible(false); nameWnd.SetVisible(false); + scriptWnd.SetVisible(false); SetSize(editor->optionsWnd.GetSize()); @@ -520,4 +531,17 @@ void ComponentsWindow::ResizeLayout() { weatherWnd.SetVisible(false); } + + if (scene.scripts.Contains(scriptWnd.entity)) + { + scriptWnd.SetVisible(true); + scriptWnd.SetPos(pos); + scriptWnd.SetSize(XMFLOAT2(width, scriptWnd.GetScale().y)); + pos.y += scriptWnd.GetSize().y; + pos.y += padding; + } + else + { + scriptWnd.SetVisible(false); + } } diff --git a/Editor/ComponentsWindow.h b/Editor/ComponentsWindow.h index f212e1e07..97d121b33 100644 --- a/Editor/ComponentsWindow.h +++ b/Editor/ComponentsWindow.h @@ -17,6 +17,7 @@ #include "TransformWindow.h" #include "LayerWindow.h" #include "NameWindow.h" +#include "ScriptWindow.h" class EditorComponent; @@ -47,4 +48,5 @@ public: TransformWindow transformWnd; LayerWindow layerWnd; NameWindow nameWnd; + ScriptWindow scriptWnd; }; diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 4bb98daeb..dae5e9e89 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -362,6 +362,7 @@ void EditorComponent::Load() componentsWnd.layerWnd.SetEntity(INVALID_ENTITY); componentsWnd.nameWnd.SetEntity(INVALID_ENTITY); componentsWnd.animWnd.SetEntity(INVALID_ENTITY); + componentsWnd.scriptWnd.SetEntity(INVALID_ENTITY); optionsWnd.RefreshEntityTree(); ResetHistory(); @@ -1309,6 +1310,7 @@ void EditorComponent::Update(float dt) componentsWnd.nameWnd.SetEntity(INVALID_ENTITY); componentsWnd.weatherWnd.SetEntity(INVALID_ENTITY); componentsWnd.animWnd.SetEntity(INVALID_ENTITY); + componentsWnd.scriptWnd.SetEntity(INVALID_ENTITY); } else { @@ -1341,6 +1343,7 @@ void EditorComponent::Update(float dt) componentsWnd.nameWnd.SetEntity(picked.entity); componentsWnd.weatherWnd.SetEntity(picked.entity); componentsWnd.animWnd.SetEntity(picked.entity); + componentsWnd.scriptWnd.SetEntity(picked.entity); if (picked.subsetIndex >= 0) { diff --git a/Editor/Editor_SOURCE.vcxitems b/Editor/Editor_SOURCE.vcxitems index d7f154681..854f4a245 100644 --- a/Editor/Editor_SOURCE.vcxitems +++ b/Editor/Editor_SOURCE.vcxitems @@ -96,6 +96,7 @@ + @@ -150,6 +151,7 @@ + diff --git a/Editor/Editor_SOURCE.vcxitems.filters b/Editor/Editor_SOURCE.vcxitems.filters index fc3bd904b..bdcd9efb7 100644 --- a/Editor/Editor_SOURCE.vcxitems.filters +++ b/Editor/Editor_SOURCE.vcxitems.filters @@ -76,6 +76,7 @@ + @@ -116,6 +117,7 @@ + diff --git a/Editor/IconDefinitions.h b/Editor/IconDefinitions.h index 854228ca2..6c9cdb6fa 100644 --- a/Editor/IconDefinitions.h +++ b/Editor/IconDefinitions.h @@ -27,6 +27,7 @@ #define ICON_IK ICON_FA_HAND_FIST #define ICON_NAME ICON_FA_COMMENT_DOTS #define ICON_COLLIDER ICON_FA_CAPSULES +#define ICON_SCRIPT ICON_FA_SCROLL #define ICON_TERRAIN ICON_FA_MOUNTAIN_SUN diff --git a/Editor/NameWindow.cpp b/Editor/NameWindow.cpp index 51c6991f5..69b4cfcdc 100644 --- a/Editor/NameWindow.cpp +++ b/Editor/NameWindow.cpp @@ -77,6 +77,6 @@ void NameWindow::SetEntity(Entity entity) void NameWindow::Update() { - nameInput.SetPos(XMFLOAT2(60, 0)); + nameInput.SetPos(XMFLOAT2(60, 4)); nameInput.SetSize(XMFLOAT2(GetSize().x - 65, nameInput.GetSize().y)); } diff --git a/Editor/OptionsWindow.cpp b/Editor/OptionsWindow.cpp index 633dbbc76..324391e69 100644 --- a/Editor/OptionsWindow.cpp +++ b/Editor/OptionsWindow.cpp @@ -135,8 +135,6 @@ void OptionsWindow::Create(EditorComponent* _editor) newCombo.selected_font.anim.typewriter.time = 2; newCombo.selected_font.anim.typewriter.character_start = 1; newCombo.SetInvalidSelectionText("..."); - newCombo.AddItem("Cube " ICON_CUBE, 13); - newCombo.AddItem("Plane " ICON_SQUARE, 14); newCombo.AddItem("Transform " ICON_TRANSFORM, 0); newCombo.AddItem("Material " ICON_MATERIAL, 1); newCombo.AddItem("Point Light " ICON_POINTLIGHT, 2); @@ -150,7 +148,10 @@ void OptionsWindow::Create(EditorComponent* _editor) newCombo.AddItem("Emitter " ICON_EMITTER, 10); newCombo.AddItem("HairParticle " ICON_HAIR, 11); newCombo.AddItem("Camera " ICON_CAMERA, 12); + newCombo.AddItem("Cube " ICON_CUBE, 13); + newCombo.AddItem("Plane " ICON_SQUARE, 14); newCombo.AddItem("Animation " ICON_ANIMATION, 15); + newCombo.AddItem("Script " ICON_SCRIPT, 16); newCombo.OnSelect([&](wi::gui::EventArgs args) { newCombo.SetSelectedWithoutCallback(-1); const EditorComponent::EditorScene& editorscene = editor->GetCurrentEditorScene(); @@ -250,6 +251,11 @@ void OptionsWindow::Create(EditorComponent* _editor) scene.animations.Create(pick.entity); scene.names.Create(pick.entity) = "animation"; break; + case 16: + pick.entity = CreateEntity(); + scene.scripts.Create(pick.entity); + scene.names.Create(pick.entity) = "script"; + break; default: break; } @@ -293,6 +299,7 @@ void OptionsWindow::Create(EditorComponent* _editor) filterCombo.AddItem("Inverse Kinematics " ICON_IK, (uint64_t)Filter::IK); filterCombo.AddItem("Camera " ICON_CAMERA, (uint64_t)Filter::Camera); filterCombo.AddItem("Armature " ICON_ARMATURE, (uint64_t)Filter::Armature); + filterCombo.AddItem("Script " ICON_SCRIPT, (uint64_t)Filter::Script); filterCombo.SetTooltip("Apply filtering to the Entities"); filterCombo.OnSelect([&](wi::gui::EventArgs args) { filter = (Filter)args.userdata; @@ -861,6 +868,10 @@ void OptionsWindow::PushToEntityTree(wi::ecs::Entity entity, int level) { item.name += ICON_COLLIDER " "; } + if (scene.scripts.Contains(entity)) + { + item.name += ICON_SCRIPT " "; + } if (entity == terragen.terrainEntity) { item.name += ICON_TERRAIN " "; @@ -1077,7 +1088,7 @@ void OptionsWindow::RefreshEntityTree() } } - if (has_flag(filter, Filter::All)) + if (has_flag(filter, Filter::Collider)) { for (size_t i = 0; i < scene.colliders.GetCount(); ++i) { @@ -1101,6 +1112,14 @@ void OptionsWindow::RefreshEntityTree() } } + if (has_flag(filter, Filter::Script)) + { + for (size_t i = 0; i < scene.scripts.GetCount(); ++i) + { + PushToEntityTree(scene.scripts.GetEntity(i), 0); + } + } + entitytree_added_items.clear(); entitytree_opened_items.clear(); } diff --git a/Editor/OptionsWindow.h b/Editor/OptionsWindow.h index 830f3877c..437054c50 100644 --- a/Editor/OptionsWindow.h +++ b/Editor/OptionsWindow.h @@ -53,6 +53,8 @@ public: IK = 1 << 13, Camera = 1 << 14, Armature = 1 << 15, + Collider = 1 << 16, + Script = 1 << 17, All = ~0ull, } filter = Filter::All; diff --git a/Editor/ScriptWindow.cpp b/Editor/ScriptWindow.cpp new file mode 100644 index 000000000..ac7717638 --- /dev/null +++ b/Editor/ScriptWindow.cpp @@ -0,0 +1,161 @@ +#include "stdafx.h" +#include "ScriptWindow.h" +#include "Editor.h" + +void ScriptWindow::Create(EditorComponent* _editor) +{ + editor = _editor; + wi::gui::Window::Create(ICON_SCRIPT " Script", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); + SetSize(XMFLOAT2(520, 80)); + + closeButton.SetTooltip("Delete Script"); + OnClose([=](wi::gui::EventArgs args) { + + wi::Archive& archive = editor->AdvanceHistory(); + archive << EditorComponent::HISTORYOP_COMPONENT_DATA; + editor->RecordEntity(archive, entity); + + editor->GetCurrentScene().scripts.Remove(entity); + + editor->RecordEntity(archive, entity); + + editor->optionsWnd.RefreshEntityTree(); + }); + + float hei = 20; + float wid = 100; + + fileButton.Create("Open File..."); + fileButton.SetDescription("File: "); + fileButton.SetSize(XMFLOAT2(wid, hei)); + fileButton.OnClick([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + wi::scene::ScriptComponent* script = scene.scripts.GetComponent(entity); + if (script == nullptr) + return; + + if (script->resource.IsValid()) + { + script->resource = {}; + script->filename = {}; + script->script = {}; + fileButton.SetText("Open File..."); + } + else + { + wi::helper::FileDialogParams params; + params.type = wi::helper::FileDialogParams::OPEN; + params.description = "Lua Script (*.lua)"; + params.extensions = wi::resourcemanager::GetSupportedScriptExtensions(); + wi::helper::FileDialog(params, [=](std::string fileName) { + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + script->CreateFromFile(fileName); + fileButton.SetText(wi::helper::GetFileNameFromPath(fileName)); + }); + }); + } + }); + AddWidget(&fileButton); + + playonceCheckBox.Create("Once: "); + playonceCheckBox.SetTooltip("Play the script only one time, and stop immediately.\nUseful for having custom update frequency logic in the script."); + playonceCheckBox.SetSize(XMFLOAT2(hei, hei)); + playonceCheckBox.OnClick([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + wi::scene::ScriptComponent* script = scene.scripts.GetComponent(entity); + if (script == nullptr) + return; + + script->SetPlayOnce(args.bValue); + }); + AddWidget(&playonceCheckBox); + + playstopButton.Create(""); + playstopButton.SetTooltip("Play / Stop script"); + playstopButton.SetSize(XMFLOAT2(wid, hei)); + playstopButton.OnClick([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + wi::scene::ScriptComponent* script = scene.scripts.GetComponent(entity); + if (script == nullptr) + return; + + if (script->IsPlaying()) + { + script->Stop(); + } + else + { + script->Play(); + } + }); + AddWidget(&playstopButton); + + SetMinimized(true); + SetVisible(false); +} + +void ScriptWindow::SetEntity(wi::ecs::Entity entity) +{ + if (this->entity == entity) + return; + + this->entity = entity; + + wi::scene::Scene& scene = editor->GetCurrentScene(); + wi::scene::ScriptComponent* script = scene.scripts.GetComponent(entity); + + if (script != nullptr) + { + if (script->resource.IsValid()) + { + fileButton.SetText(wi::helper::GetFileNameFromPath(script->filename)); + } + else + { + fileButton.SetText("Open File..."); + } + } +} + +void ScriptWindow::Update(const wi::Canvas& canvas, float dt) +{ + wi::scene::Scene& scene = editor->GetCurrentScene(); + wi::scene::ScriptComponent* script = scene.scripts.GetComponent(entity); + if (script != nullptr) + { + if (script->IsPlaying()) + { + playstopButton.SetText(ICON_STOP); + } + else + { + playstopButton.SetText(ICON_PLAY); + } + } + + wi::gui::Window::Update(canvas, dt); +} +void ScriptWindow::ResizeLayout() +{ + wi::gui::Window::ResizeLayout(); + + fileButton.SetPos(XMFLOAT2(60, 4)); + fileButton.SetSize(XMFLOAT2(GetSize().x - 65, fileButton.GetSize().y)); + + wi::scene::Scene& scene = editor->GetCurrentScene(); + wi::scene::ScriptComponent* script = scene.scripts.GetComponent(entity); + if (script != nullptr && script->resource.IsValid()) + { + playstopButton.SetVisible(true); + playstopButton.SetPos(XMFLOAT2(84, fileButton.GetPos().y + fileButton.GetSize().y + 4)); + playstopButton.SetSize(XMFLOAT2(GetSize().x - 90, playstopButton.GetSize().y)); + + playonceCheckBox.SetVisible(true); + playonceCheckBox.SetPos(XMFLOAT2(playstopButton.GetPos().x - playonceCheckBox.GetSize().x - 4, playstopButton.GetPos().y)); + } + else + { + playstopButton.SetVisible(false); + playonceCheckBox.SetVisible(false); + } +} diff --git a/Editor/ScriptWindow.h b/Editor/ScriptWindow.h new file mode 100644 index 000000000..d3f93f4f0 --- /dev/null +++ b/Editor/ScriptWindow.h @@ -0,0 +1,22 @@ +#pragma once +#include "WickedEngine.h" + +class EditorComponent; + +class ScriptWindow : public wi::gui::Window +{ +public: + void Create(EditorComponent* editor); + + EditorComponent* editor = nullptr; + wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY; + void SetEntity(wi::ecs::Entity entity); + + wi::gui::Button fileButton; + wi::gui::CheckBox playonceCheckBox; + wi::gui::Button playstopButton; + + void Update(const wi::Canvas& canvas, float dt) override; + void ResizeLayout() override; +}; + diff --git a/WickedEngine/wiImageParams_BindLua.cpp b/WickedEngine/wiImageParams_BindLua.cpp index 68e95d6c5..8ad3aaf27 100644 --- a/WickedEngine/wiImageParams_BindLua.cpp +++ b/WickedEngine/wiImageParams_BindLua.cpp @@ -47,10 +47,10 @@ namespace wi::lua lunamethod(ImageParams_BindLua, DisableBackgroundBlur), lunamethod(ImageParams_BindLua, EnableBackground), lunamethod(ImageParams_BindLua, DisableBackground), - { NULL, NULL } + { nullptr, nullptr } }; Luna::PropertyType ImageParams_BindLua::properties[] = { - { NULL, NULL } + { nullptr, nullptr } }; diff --git a/WickedEngine/wiLua.cpp b/WickedEngine/wiLua.cpp index 76d367a14..1bc2d0d94 100644 --- a/WickedEngine/wiLua.cpp +++ b/WickedEngine/wiLua.cpp @@ -43,12 +43,14 @@ namespace wi::lua }; LuaInternal luainternal; - uint32_t Internal_GenScriptPID(){ + uint32_t GeneratePID() + { static std::atomic scriptpid_next{ 0 + 1 }; return scriptpid_next.fetch_add(1); } - uint32_t Internal_EncapsulateScript(std::string& script, const std::string& filename = "", uint32_t PID = 0){ + uint32_t AttachScriptParameters(std::string& script, const std::string& filename, uint32_t PID) + { static const std::string persistent_inject = R"( local runProcess = function(func) success, co = Internal_runProcess(script_file(), script_pid(), func) @@ -62,19 +64,15 @@ namespace wi::lua end )"; - if(PID == 0){ - PID = Internal_GenScriptPID(); - } - // Make sure the file path doesn't contain backslash characters, replace them with forward slash. // - backslash would be recognized by lua as escape character // - the path string could be coming from unknown location (content, programmer, filepicker), so always do this std::string filepath = filename; std::replace(filepath.begin(), filepath.end(), '\\', '/'); - std::string dynamic_inject = "local function script_file() return \""+ filepath +"\" end\n"; - dynamic_inject += "local function script_pid() return \""+std::to_string(PID)+"\" end\n"; - dynamic_inject += "local function script_dir() return \""+wi::helper::GetDirectoryFromPath(filepath)+"\" end\n"; + std::string dynamic_inject = "local function script_file() return \"" + filepath + "\" end\n"; + dynamic_inject += "local function script_pid() return \"" + std::to_string(PID) + "\" end\n"; + dynamic_inject += "local function script_dir() return \"" + wi::helper::GetDirectoryFromPath(filepath) + "\" end\n"; dynamic_inject += persistent_inject; script = dynamic_inject + script; @@ -98,7 +96,7 @@ namespace wi::lua if (wi::helper::FileRead(filename, filedata)) { std::string command = std::string(filedata.begin(), filedata.end()); - PID = Internal_EncapsulateScript(command, filename, PID); + PID = AttachScriptParameters(command, filename, PID); int status = luaL_loadstring(L, command.c_str()); if (status == 0) @@ -210,7 +208,7 @@ namespace wi::lua if (wi::helper::FileRead(filename, filedata)) { auto script = std::string(filedata.begin(), filedata.end()); - Internal_EncapsulateScript(script, filename); + AttachScriptParameters(script, filename); return RunText(script); } return false; diff --git a/WickedEngine/wiLua.h b/WickedEngine/wiLua.h index ba8172638..c14c8ceff 100644 --- a/WickedEngine/wiLua.h +++ b/WickedEngine/wiLua.h @@ -61,6 +61,13 @@ namespace wi::lua //kill every running background task (coroutine) void KillProcesses(); + // Generates a unique identifier for a script instance: + uint32_t GeneratePID(); + + // Adds some local management functions to the script + // returns the PID + uint32_t AttachScriptParameters(std::string& script, const std::string& filename = "", uint32_t PID = GeneratePID()); + //Following functions are "static", operating on specified lua state: //get string from lua on stack position diff --git a/WickedEngine/wiPrimitive.h b/WickedEngine/wiPrimitive.h index d74f7eacc..82ee215f6 100644 --- a/WickedEngine/wiPrimitive.h +++ b/WickedEngine/wiPrimitive.h @@ -123,11 +123,14 @@ namespace wi::primitive float TMax = std::numeric_limits::max(); XMFLOAT3 direction_inverse; - Ray(const XMFLOAT3& newOrigin = XMFLOAT3(0, 0, 0), const XMFLOAT3& newDirection = XMFLOAT3(0, 0, 1), float newTMin = 0, float newTMax = std::numeric_limits::max()) : Ray(XMLoadFloat3(&newOrigin), XMLoadFloat3(&newDirection), TMin, TMax) {} - Ray(const XMVECTOR& newOrigin, const XMVECTOR& newDirection, float newTMin = 0, float newTMax = std::numeric_limits::max()) { + Ray(const XMFLOAT3& newOrigin = XMFLOAT3(0, 0, 0), const XMFLOAT3& newDirection = XMFLOAT3(0, 0, 1), float newTMin = 0, float newTMax = std::numeric_limits::max()) : + Ray(XMLoadFloat3(&newOrigin), XMLoadFloat3(&newDirection), newTMin, newTMax) + {} + Ray(const XMVECTOR& newOrigin, const XMVECTOR& newDirection, float newTMin = 0, float newTMax = std::numeric_limits::max()) + { XMStoreFloat3(&origin, newOrigin); XMStoreFloat3(&direction, newDirection); - XMStoreFloat3(&direction_inverse, XMVectorDivide(XMVectorReplicate(1.0f), newDirection)); + XMStoreFloat3(&direction_inverse, XMVectorReciprocal(newDirection)); TMin = newTMin; TMax = newTMax; } diff --git a/WickedEngine/wiResourceManager.cpp b/WickedEngine/wiResourceManager.cpp index ad97205d7..24e38ca6a 100644 --- a/WickedEngine/wiResourceManager.cpp +++ b/WickedEngine/wiResourceManager.cpp @@ -23,6 +23,7 @@ namespace wi resourcemanager::Flags flags = resourcemanager::Flags::NONE; wi::graphics::Texture texture; wi::audio::Sound sound; + std::string script; wi::vector filedata; }; @@ -41,6 +42,11 @@ namespace wi const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get(); return resourceinternal->sound; } + const std::string& Resource::GetScript() const + { + const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get(); + return resourceinternal->script; + } void Resource::SetFileData(const wi::vector& data) { @@ -78,6 +84,15 @@ namespace wi ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get(); resourceinternal->sound = sound; } + void Resource::SetScript(const std::string& script) + { + if (internal_state == nullptr) + { + internal_state = std::make_shared(); + } + ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get(); + resourceinternal->script = script; + } namespace resourcemanager { @@ -98,6 +113,7 @@ namespace wi { IMAGE, SOUND, + SCRIPT, }; static const wi::unordered_map types = { {"BASIS", DataType::IMAGE}, @@ -111,6 +127,7 @@ namespace wi {"QOI", DataType::IMAGE}, {"WAV", DataType::SOUND}, {"OGG", DataType::SOUND}, + {"LUA", DataType::SCRIPT}, }; wi::vector GetSupportedImageExtensions() { @@ -136,6 +153,18 @@ namespace wi } return ret; } + wi::vector GetSupportedScriptExtensions() + { + wi::vector ret; + for (auto& x : types) + { + if (x.second == DataType::SCRIPT) + { + ret.push_back(x.first); + } + } + return ret; + } Resource Load(const std::string& name, Flags flags, const uint8_t* filedata, size_t filesize) { @@ -641,11 +670,21 @@ namespace wi } } break; + case DataType::SOUND: { success = wi::audio::CreateSound(filedata, filesize, &resource->sound); } break; + + case DataType::SCRIPT: + { + resource->script.resize(filesize); + std::memcpy(resource->script.data(), filedata, filesize); + success = true; + } + break; + }; if (success) diff --git a/WickedEngine/wiResourceManager.h b/WickedEngine/wiResourceManager.h index d3aeee497..579078404 100644 --- a/WickedEngine/wiResourceManager.h +++ b/WickedEngine/wiResourceManager.h @@ -20,11 +20,13 @@ namespace wi const wi::vector& GetFileData() const; const wi::graphics::Texture& GetTexture() const; const wi::audio::Sound& GetSound() const; + const std::string& GetScript() const; void SetFileData(const wi::vector& data); void SetFileData(wi::vector&& data); void SetTexture(const wi::graphics::Texture& texture); void SetSound(const wi::audio::Sound& sound); + void SetScript(const std::string& script); }; namespace resourcemanager @@ -39,6 +41,7 @@ namespace wi Mode GetMode(); wi::vector GetSupportedImageExtensions(); wi::vector GetSupportedSoundExtensions(); + wi::vector GetSupportedScriptExtensions(); // Order of these must not change as the flags can be serialized! enum class Flags diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 52e861d64..c78ecfcf8 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -11,6 +11,7 @@ #include "wiBacklog.h" #include "wiTimer.h" #include "wiUnorderedMap.h" +#include "wiLua.h" #include "shaders/ShaderInterop_SurfelGI.h" #include "shaders/ShaderInterop_DDGI.h" @@ -1466,6 +1467,13 @@ namespace wi::scene UpdateCamera(); } + void ScriptComponent::CreateFromFile(const std::string& filename) + { + this->filename = filename; + resource = wi::resourcemanager::Load(filename, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA); + script.clear(); // will be created on first Update() + } + const uint32_t small_subtask_groupsize = 64u; @@ -1612,6 +1620,8 @@ namespace wi::scene } }); + RunScriptUpdateSystem(ctx); + wi::physics::RunPhysicsUpdateSystem(ctx, *this, dt); RunAnimationUpdateSystem(ctx); @@ -4808,6 +4818,30 @@ namespace wi::scene wi::audio::SetVolume(sound.volume, &sound.soundinstance); } } + void Scene::RunScriptUpdateSystem(wi::jobsystem::context& ctx) + { + for (size_t i = 0; i < scripts.GetCount(); ++i) + { + ScriptComponent& script = scripts[i]; + Entity entity = scripts.GetEntity(i); + + if (script.IsPlaying()) + { + if (script.script.empty() && script.resource.IsValid()) + { + script.script += "local function GetEntity() return " + std::to_string(entity) + "; end\n"; + script.script += script.resource.GetScript(); + wi::lua::AttachScriptParameters(script.script, script.filename); + } + wi::lua::RunText(script.script); + + if (script.IsPlayingOnlyOnce()) + { + script.Stop(); + } + } + } + } void Scene::PutWaterRipple(const std::string& image, const XMFLOAT3& pos) { diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 9a026cdaa..dcc3e2452 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -1342,6 +1342,34 @@ namespace wi::scene void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); }; + struct ScriptComponent + { + enum FLAGS + { + EMPTY = 0, + PLAYING = 1 << 0, + PLAY_ONCE = 1 << 1, + }; + uint32_t _flags = EMPTY; + + std::string filename; + + // Non-serialized attributes: + std::string script; + wi::Resource resource; + + inline void Play() { _flags |= PLAYING; } + inline void SetPlayOnce(bool once = true) { if (once) { _flags |= PLAY_ONCE; } else { _flags &= ~PLAY_ONCE; } } + inline void Stop() { _flags &= ~PLAYING; } + + inline bool IsPlaying() const { return _flags & PLAYING; } + inline bool IsPlayingOnlyOnce() const { return _flags & PLAY_ONCE; } + + void CreateFromFile(const std::string& filename); + + void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); + }; + struct Scene { wi::ecs::ComponentLibrary componentLibrary; @@ -1375,6 +1403,7 @@ namespace wi::scene wi::ecs::ComponentManager& inverse_kinematics = componentLibrary.Register("wi::scene::Scene::inverse_kinematics"); wi::ecs::ComponentManager& springs = componentLibrary.Register("wi::scene::Scene::springs", 1); // version = 1 wi::ecs::ComponentManager& colliders = componentLibrary.Register("wi::scene::Scene::colliders"); + wi::ecs::ComponentManager& scripts = componentLibrary.Register("wi::scene::Scene::scripts"); // Non-serialized attributes: float dt = 0; @@ -1631,6 +1660,7 @@ namespace wi::scene void RunParticleUpdateSystem(wi::jobsystem::context& ctx); void RunWeatherUpdateSystem(wi::jobsystem::context& ctx); void RunSoundUpdateSystem(wi::jobsystem::context& ctx); + void RunScriptUpdateSystem(wi::jobsystem::context& ctx); }; // Returns skinned vertex position in armature local space diff --git a/WickedEngine/wiScene_BindLua.cpp b/WickedEngine/wiScene_BindLua.cpp index 9a1d4e47a..100248569 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -314,6 +314,7 @@ void Bind() Luna::Register(L); Luna::Register(L); Luna::Register(L); + Luna::Register(L); } } @@ -335,6 +336,7 @@ Luna::FunctionType Scene_BindLua::methods[] = { lunamethod(Scene_BindLua, Component_CreateObject), lunamethod(Scene_BindLua, Component_CreateInverseKinematics), lunamethod(Scene_BindLua, Component_CreateSpring), + lunamethod(Scene_BindLua, Component_CreateScript), lunamethod(Scene_BindLua, Component_GetName), lunamethod(Scene_BindLua, Component_GetLayer), @@ -347,6 +349,7 @@ Luna::FunctionType Scene_BindLua::methods[] = { lunamethod(Scene_BindLua, Component_GetObject), lunamethod(Scene_BindLua, Component_GetInverseKinematics), lunamethod(Scene_BindLua, Component_GetSpring), + lunamethod(Scene_BindLua, Component_GetScript), lunamethod(Scene_BindLua, Component_GetNameArray), lunamethod(Scene_BindLua, Component_GetLayerArray), @@ -359,6 +362,7 @@ Luna::FunctionType Scene_BindLua::methods[] = { lunamethod(Scene_BindLua, Component_GetObjectArray), lunamethod(Scene_BindLua, Component_GetInverseKinematicsArray), lunamethod(Scene_BindLua, Component_GetSpringArray), + lunamethod(Scene_BindLua, Component_GetScriptArray), lunamethod(Scene_BindLua, Entity_GetNameArray), lunamethod(Scene_BindLua, Entity_GetLayerArray), @@ -371,6 +375,7 @@ Luna::FunctionType Scene_BindLua::methods[] = { lunamethod(Scene_BindLua, Entity_GetObjectArray), lunamethod(Scene_BindLua, Entity_GetInverseKinematicsArray), lunamethod(Scene_BindLua, Entity_GetSpringArray), + lunamethod(Scene_BindLua, Entity_GetScriptArray), lunamethod(Scene_BindLua, Component_Attach), lunamethod(Scene_BindLua, Component_Detach), @@ -599,6 +604,23 @@ int Scene_BindLua::Component_CreateSpring(lua_State* L) } return 0; } +int Scene_BindLua::Component_CreateScript(lua_State* L) +{ + int argc = wi::lua::SGetArgCount(L); + if (argc > 0) + { + Entity entity = (Entity)wi::lua::SGetLongLong(L, 1); + + ScriptComponent& component = scene->scripts.Create(entity); + Luna::push(L, new ScriptComponent_BindLua(&component)); + return 1; + } + else + { + wi::lua::SError(L, "Scene::Component_CreateScript(Entity entity) not enough arguments!"); + } + return 0; +} int Scene_BindLua::Component_GetName(lua_State* L) { @@ -842,6 +864,28 @@ int Scene_BindLua::Component_GetSpring(lua_State* L) } return 0; } +int Scene_BindLua::Component_GetScript(lua_State* L) +{ + int argc = wi::lua::SGetArgCount(L); + if (argc > 0) + { + Entity entity = (Entity)wi::lua::SGetLongLong(L, 1); + + ScriptComponent* component = scene->scripts.GetComponent(entity); + if (component == nullptr) + { + return 0; + } + + Luna::push(L, new ScriptComponent_BindLua(component)); + return 1; + } + else + { + wi::lua::SError(L, "Scene::Component_GetScript(Entity entity) not enough arguments!"); + } + return 0; +} int Scene_BindLua::Component_GetNameArray(lua_State* L) { @@ -964,6 +1008,17 @@ int Scene_BindLua::Component_GetSpringArray(lua_State* L) } return 1; } +int Scene_BindLua::Component_GetScriptArray(lua_State* L) +{ + lua_createtable(L, (int)scene->scripts.GetCount(), 0); + int newTable = lua_gettop(L); + for (size_t i = 0; i < scene->scripts.GetCount(); ++i) + { + Luna::push(L, new ScriptComponent_BindLua(&scene->scripts[i])); + lua_rawseti(L, newTable, lua_Integer(i + 1)); + } + return 1; +} int Scene_BindLua::Entity_GetNameArray(lua_State* L) { @@ -1086,6 +1141,17 @@ int Scene_BindLua::Entity_GetSpringArray(lua_State* L) } return 1; } +int Scene_BindLua::Entity_GetScriptArray(lua_State* L) +{ + lua_createtable(L, (int)scene->scripts.GetCount(), 0); + int newTable = lua_gettop(L); + for (size_t i = 0; i < scene->scripts.GetCount(); ++i) + { + wi::lua::SSetLongLong(L, scene->scripts.GetEntity(i)); + lua_rawseti(L, newTable, lua_Integer(i + 1)); + } + return 1; +} int Scene_BindLua::Component_Attach(lua_State* L) { @@ -2665,4 +2731,77 @@ int SpringComponent_BindLua::SetWindAffection(lua_State* L) } + + + + + +const char ScriptComponent_BindLua::className[] = "ScriptComponent"; + +Luna::FunctionType ScriptComponent_BindLua::methods[] = { + lunamethod(ScriptComponent_BindLua, CreateFromFile), + lunamethod(ScriptComponent_BindLua, Play), + lunamethod(ScriptComponent_BindLua, IsPlaying), + lunamethod(ScriptComponent_BindLua, SetPlayOnce), + lunamethod(ScriptComponent_BindLua, Stop), + { NULL, NULL } +}; +Luna::PropertyType ScriptComponent_BindLua::properties[] = { + { NULL, NULL } +}; + +ScriptComponent_BindLua::ScriptComponent_BindLua(lua_State* L) +{ + owning = true; + component = new ScriptComponent; +} +ScriptComponent_BindLua::~ScriptComponent_BindLua() +{ + if (owning) + { + delete component; + } +} + +int ScriptComponent_BindLua::CreateFromFile(lua_State* L) +{ + int argc = wi::lua::SGetArgCount(L); + if (argc > 0) + { + component->CreateFromFile(wi::lua::SGetString(L, 1)); + } + else + { + wi::lua::SError(L, "CreateFromFile(string filename) not enough arguments!"); + } + return 0; +} +int ScriptComponent_BindLua::Play(lua_State* L) +{ + component->Play(); + return 0; +} +int ScriptComponent_BindLua::IsPlaying(lua_State* L) +{ + wi::lua::SSetBool(L, component->IsPlaying()); + return 1; +} +int ScriptComponent_BindLua::SetPlayOnce(lua_State* L) +{ + int argc = wi::lua::SGetArgCount(L); + bool once = true; + if (argc > 0) + { + once = wi::lua::SGetBool(L, 1); + } + component->SetPlayOnce(once); + return 0; +} +int ScriptComponent_BindLua::Stop(lua_State* L) +{ + component->Stop(); + return 0; +} + + } diff --git a/WickedEngine/wiScene_BindLua.h b/WickedEngine/wiScene_BindLua.h index 12b2a4472..08e895c4f 100644 --- a/WickedEngine/wiScene_BindLua.h +++ b/WickedEngine/wiScene_BindLua.h @@ -48,6 +48,7 @@ namespace wi::lua::scene int Component_CreateObject(lua_State* L); int Component_CreateInverseKinematics(lua_State* L); int Component_CreateSpring(lua_State* L); + int Component_CreateScript(lua_State* L); int Component_GetName(lua_State* L); int Component_GetLayer(lua_State* L); @@ -60,6 +61,7 @@ namespace wi::lua::scene int Component_GetObject(lua_State* L); int Component_GetInverseKinematics(lua_State* L); int Component_GetSpring(lua_State* L); + int Component_GetScript(lua_State* L); int Component_GetNameArray(lua_State* L); int Component_GetLayerArray(lua_State* L); @@ -72,6 +74,7 @@ namespace wi::lua::scene int Component_GetObjectArray(lua_State* L); int Component_GetInverseKinematicsArray(lua_State* L); int Component_GetSpringArray(lua_State* L); + int Component_GetScriptArray(lua_State* L); int Entity_GetNameArray(lua_State* L); int Entity_GetLayerArray(lua_State* L); @@ -84,6 +87,7 @@ namespace wi::lua::scene int Entity_GetObjectArray(lua_State* L); int Entity_GetInverseKinematicsArray(lua_State* L); int Entity_GetSpringArray(lua_State* L); + int Entity_GetScriptArray(lua_State* L); int Component_Attach(lua_State* L); int Component_Detach(lua_State* L); @@ -366,5 +370,26 @@ namespace wi::lua::scene int SetWindAffection(lua_State* L); }; + class ScriptComponent_BindLua + { + public: + bool owning = false; + wi::scene::ScriptComponent* component = nullptr; + + static const char className[]; + static Luna::FunctionType methods[]; + static Luna::PropertyType properties[]; + + ScriptComponent_BindLua(wi::scene::ScriptComponent* component) :component(component) {} + ScriptComponent_BindLua(lua_State* L); + ~ScriptComponent_BindLua(); + + int CreateFromFile(lua_State* L); + int Play(lua_State* L); + int IsPlaying(lua_State* L); + int SetPlayOnce(lua_State* L); + int Stop(lua_State* L); + }; + } diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index b6d281357..b02eedef7 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -1428,6 +1428,36 @@ namespace wi::scene archive << tail; } } + void ScriptComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) + { + const std::string& dir = archive.GetSourceDirectory(); + + if (archive.IsReadMode()) + { + archive >> _flags; + archive >> filename; + + if (IsPlayingOnlyOnce()) + { + Play(); + } + + wi::jobsystem::Execute(seri.ctx, [&](wi::jobsystem::JobArgs args) { + CreateFromFile(dir + filename); + }); + } + else + { + std::string relative_filename = filename; // don't modify actual filename, because script_file() and script_dir() can rely on it + if (!dir.empty()) + { + wi::helper::MakePathRelative(dir, relative_filename); + } + + archive << _flags; + archive << relative_filename; + } + } void Scene::Serialize(wi::Archive& archive) { diff --git a/WickedEngine/wiSpriteAnim_BindLua.cpp b/WickedEngine/wiSpriteAnim_BindLua.cpp index 1ed20be72..92b9a3502 100644 --- a/WickedEngine/wiSpriteAnim_BindLua.cpp +++ b/WickedEngine/wiSpriteAnim_BindLua.cpp @@ -28,10 +28,10 @@ namespace wi::lua lunamethod(SpriteAnim_BindLua, GetScaleY), lunamethod(SpriteAnim_BindLua, GetMovingTexAnim), lunamethod(SpriteAnim_BindLua, GetDrawRecAnim), - { NULL, NULL } + { nullptr, nullptr } }; Luna::PropertyType SpriteAnim_BindLua::properties[] = { - { NULL, NULL } + { nullptr, nullptr } }; SpriteAnim_BindLua::SpriteAnim_BindLua(const wi::Sprite::Anim& anim) :anim(anim) @@ -264,10 +264,10 @@ namespace wi::lua lunamethod(MovingTexAnim_BindLua, GetSpeedX), lunamethod(MovingTexAnim_BindLua, GetSpeedY), - { NULL, NULL } + { nullptr, nullptr } }; Luna::PropertyType MovingTexAnim_BindLua::properties[] = { - { NULL, NULL } + { nullptr, nullptr } }; MovingTexAnim_BindLua::MovingTexAnim_BindLua(const wi::Sprite::Anim::MovingTexAnim& anim) :anim(anim) @@ -348,7 +348,7 @@ namespace wi::lua { NULL, NULL } }; - DrawRectAnim_BindLua::DrawRectAnim_BindLua(const wi::Sprite::Anim::DrawRectAnim& data) :anim(anim) + DrawRectAnim_BindLua::DrawRectAnim_BindLua(const wi::Sprite::Anim::DrawRectAnim& anim) :anim(anim) { } diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index f522e957b..9b1298afc 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 20; + const int revision = 21; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);