Jump to content

Josh

Staff
  • Posts

    23,093
  • Joined

  • Last visited

Everything posted by Josh

  1. Adding Component:Connect in Lua. This gives you the ability to create flowgraph connections in code, with any number of arbitrary arguments, including tables, functions, and other special Lua stuff: sender:Connect(sender.Send, receiver, receiver.Receive, { true, 2, "Hello!" }) Whenever the send method is called, whether it is by code or another flowgraph input, at the end of it the receiver will be triggered to execute the Receive method, with the supplied arguments. https://www.ultraengine.com/learn/Component_Connect?lang=lua
  2. Apparently not. It's okay, I don't think I am going to use this anyways.
  3. Every example I can find online uses a chr*. You can't call ++ on an std::any. Does anyone know how to correctly iterate this to get all the arguments?: bool LuaComponent::CallMethod(const WString& name, const std::any& args...) { std::vector<std::any> v; auto vargs = va_list(); va_start(vargs, args); while (args.has_value()) { v.push_back(args); args++;//??? this does not compile } va_end(vargs); return CallMethod(name, v); }
  4. I think that's no problem. It's mostly just a matter of deciding how the user interface should work. Lua Collide function is working now. Here's an example: https://www.ultraengine.com/learn/Component_Collide?lang=lua
  5. You can now create Lua components from a table as well: local component = {} function component:Update() self.entity:Turn(10,0,0) end box:AddComponent(component, "MyComponent") You need to go through the AddComponent() command, not just add tables to your entity, because this creates the C++ component and handles a bunch of stuff. I did this primarily for ease of showing things in the documentation. If a special component is needed it's easy to just add it into the documentation example. It looks like std::any will probably work for functions-as-arguments as well: int FUNC() { return 3; } int main(int argc, const char* argv[]) { std::any thing = FUNC; Print(thing.type().name()); auto f = std::any_cast<int(__cdecl*)(void)>(thing); Print(f()); return 0; }
  6. 1.0.2 Component connections are added, with arguments. Arguments won't work in Lua yet, but connections should. Added LuaGetState
  7. Component connections are added now, with arguments. Here is a working example. A lot of stuff has to be defined manually in C++. In Lua this is all automated, including the output firing: #include "UltraEngine.h" using namespace UltraEngine; struct Sender : public Component { virtual void MyMethod() { //---------------------------------------------------- // You probably want to execute some other code here //---------------------------------------------------- Print("Firing output MyMethod"); FireOutputs("MyMethod"); } }; struct Receiver : public Component { virtual void AnotherMethod(const int i) { Print("Executing method with argument: " + String(i)); //---------------------------------------------------- // You probably want to execute some other code here //---------------------------------------------------- } virtual void ReceiveSignal(shared_ptr<Component> sender, const WString& input, std::vector<std::any> args) { Print("Signal received: " + input); //Choose the method to call if (input == "AnotherMethod") { //Fix arguments args.resize(1); if (strcmp(args[0].type().name(), "int") != 0) args[0] = 0; //Call the method AnotherMethod(std::any_cast<int>(args[0])); } else { Print("Unknown method " + input); } } }; int main(int argc, const char* argv[]) { //Create sending object auto box1 = CreateBox(NULL); auto sender = box1->AddComponent<Sender>(); //Create receiving object auto box2 = CreateBox(NULL); auto receiver = box2->AddComponent<Receiver>(); //Add connection sender->Connect("MyMethod", receiver, "AnotherMethod", { 3 }); //Trigger the connection sender->MyMethod(); return 0; } I think the same thing in Lua will work like this, without any manual processing or firing: Sender = {} function Sender:MyMethod() Print("Firing output MyMethod"); end Receiver = {} function Receiver:AnotherMethod(i) Print("Executing method with argument: "..tostring(i)) end --Create sending object auto box1 = CreateBox(NULL) auto sender = box1->AddComponent(Sender) --Create receiving object auto box2 = CreateBox(NULL) auto receiver = box2->AddComponent(Receiver) --Add connection sender->Connect(Sender.MyMethod, receiver, Receiver.AnotherMethod, { 3 }) --Trigger the connection sender->MyMethod()
  8. Here's a quick test using std::any: #include "UltraEngine.h" #include <any> using namespace UltraEngine; int main(int argc, const char* argv[]) { std::any thing = 3; //thing = String("test"); //thing = 5u; if (thing.has_value()) { auto name = String(thing.type().name()); if (name == "int") { Print(std::any_cast<int>(thing)); } else if (name == "float") { Print(std::any_cast<float>(thing)); } else if (name == "class UltraEngine::String") { Print(std::any_cast<String>(thing)); } else { Print("Unknown type \"" + name + "\""); } } else { Print("thing is empty."); } return 0; }
  9. I've worked out the basic flowgraph system: https://www.ultraengine.com/learn/Component_Connect?lang=cpp https://www.ultraengine.com/learn/Component_FireOutputs?lang=cpp https://www.ultraengine.com/learn/Component_ReceiveSignal?lang=cpp I don't know yet exactly how std::any works, or how functions as arguments will work. I doubt I can really work that out until the visual interface is usable, but the basic implementation is done.
  10. The Lua component system stuff is now stored in a single table: ComponentSystem = {}-- global variable ComponentSystem.mode = "ECS"-- default behavior --ComponentSystem.mode = "OOP"-- experimental, will break compatibility with ECS-style scripts --This function is called when a component is attached to an entity function ComponentSystem:AddComponent(entity, component, name) local table = require("Components."..name) if table == nil then return false end blah blah blah... The script itself is now responsible for the execution of the component script. It uses require() so the component script only gets executed once and the same table's values are copied to the C++ component object each time. Since it uses require() it expects a Lua module name, not a script name, so the usage will look like this: entity:AddComponent("Mover") I plan to organize components into subfolders one layer deep, so the final usage will be more like this: entity:AddComponent("Motion.Mover") I have no idea how or if this will interact with C#. What will the user syntax to add a C# component look like? Will it use a string like this, or something else? Do we plan on Lua ever working with a C# game? I'm leaning towards no, because C# users aren't going to be coming from that mindset or have that expectation.
  11. 1.0.2 "require" will now load scripts relative to "./Scripts" and DLLs relative to "./Modules". Script structure in the game template is changed. C++ interpreter for Lua code is updated here: https://github.com/UltraEngine/Documentation/blob/master/CPP/Scripting.md#c-interpreter-for-lua socket/core.dll now just renamed to "luasocket.dll" Compiled .luac files can now be run with RunScript().
  12. 1.0.2 I found a lot of settings to move all the garbage files VS creates into the hidden .vs folder. This is what a project looks like now, after compiling and closing visual studio. The only file it save in your game folder is the vxproj.user file, which I could not find a way to eliminate:
  13. The secret is that the icon file must contain multiple "mipmaps". It's not enough for it to be a large icon. This program is the only way I know to create these: http://www.aha-soft.com/anytoicon/
  14. Why is Windows Explorer showing my EXE as if it has a small icon? The icon size is 256x256 and the icon file itself appears correctly: Here is the icon section of the .rc resource file: ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON1 ICON "projecticon.ico"
  15. 1.0.2 UPX post-build process is added back into new Visual Studio projects, but this time it's done correctly. Command: "$(UltraEnginePath)\Tools\upx.exe" "$(TargetName)$(TargetExt)" Disabled by default, can be easily enabled in project settings (release mode only) Newton debug DLLs are eliminated. I'm using a static library, so Newton can still be debugged externally. DLLs are now only used for optional plugins and Lua modules. Resource files moved to /Source to try to keep as much garbage as possible out of the main folder. Removed Warn() function. Removed Microsecs() function.
  16. 1.0.2 Since the Lua stuff is stable now, I was able to compile Lua as a static lib, which means no DLL is needed. Lua 5.4 repo here is updated: https://github.com/UltraEngine/Lua5.4 Scripts/Modules/Socket/core.dll is recompiled with Lua static lib.
  17. How about this? #include "UltraEngine.h" using namespace UltraEngine; bool PrintColorHook(const Event& e, shared_ptr<Object> extra) { //https://learn.microsoft.com/en-us/windows/console/getstdhandle#handle-disposal HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); if (e.text.Left(8) == "Warning:") { SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); } else if (e.text.Left(6) == "Error:") { SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_INTENSITY); } else { SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); } return true; } int main(int argc, const char* argv[]) { ListenEvent(EVENT_PRINT, NULL, PrintColorHook); Print("Here is a normal text message."); Print("Warning: Here is a warning."); Print("Here is a normal text message."); Print("Error: Here is an error."); Print("Here is a normal text message."); return 0; }
  18. 1.0.2 New Visual Studio projects are now set up to compile with the new good icon. I guess it needs both an .ico and a .png? I don't know why. Future builds of the client app will load the file projecticon.png to display in the projects list. Added "/delayload:lua54.dll" in release mode settings so you can distribute C++ programs without including the Lua DLL if you don't use it.
  19. 1.0.2 Added String and WString constructors that play nice with nlohmann::json and eliminate the need for the horrible SetJsonString/GetJsonString functions I was using. This allows you to easily get strings from JSON structures without producing errors: j3["name"] = "Frank"; j3["number"] = 462; String s1 = j3["name"]; String s2 = j3["number"]; Removed DebugLog function Remove Language class since it's not tested and superfluous
  20. 1.0.2 New entity component system is integrated in C++ and Lua Preprocessor removed Added String and WString::Data() which is the same as .c_str() Documentation mostly updated, mostly: https://www.ultraengine.com/learn/Scripting?lang=cpp https://www.ultraengine.com/learn/Entity_AddComponent?lang=cpp https://www.ultraengine.com/learn/Component?lang=cpp
  21. The Lua entity component system will allow you to write scripts two ways. The default is ECS, which will work like unity: entity.healthmanager.health = 100 entity.player:UpdateControls() There's also an OOP mode that attaches values and functions directly to the entity class (I wanted to do this in Leadwerks but couldn't at the time): entity.health = 100 entity:UpdateControls() I like OOP mode because you can write scripts like this: function Entity:Update() self:Turn(0,1,0) end Instead of this: function Component:Update() self.entity:Turn(0,1,0) end However, the default mode and everything the documentation uses will assume ECS mode. If you use OOP mode you will break compatibility with all third-party scripts. For the people this option appeals to, that probably isn't a problem. These will be defined in a "ComponentSystem.lua" file __ULTRA_COMPONENTSYSTEMMODE = "ECS"-- default behavior --__ULTRA_COMPONENTSYSTEMMODE = "OOP"-- experimental, will break compatibility with other scripts --This function is called after a Lua component is attached to an entity function __ULTRA_FinalizeComponent(entity, component, name, table) -- Attach values to the component object if __ULTRA_COMPONENTSYSTEMMODE == "ECS" then entity[name] = component component.entity = entity end -- Attach values to the entity itself local componenthandle = component if __ULTRA_COMPONENTSYSTEMMODE == "OOP" then component = entity end local k, v for k, v in pairs(table) do --Print(k) local t = type(v) if t == "table" then component[k] = table.copy(v) elseif t == "function" then local function func(c, ...) v(c, ...) componenthandle:FireOutputs(k)-- Automatically fires outputs for this method whenever it is called end component[k] = func else component[k] = v end end end --This function is called whenever the engine wants to execute a predefined component hook function __ULTRA_CallComponentMethod(entity, component, funcname, ...) --Call methods from the entity instead of the component object if __ULTRA_COMPONENTSYSTEMMODE == "OOP" then component = entity end --Exit silently if the function doesn't exist if component[funcname] == nil then return false end --Safety check if type(component[funcname]) ~= "function" then Print("Error: Function type must be function but instead it is "..type(component[funcname])..".") return false end --Execute with component as first argument, followed by rest of arguments component[funcname](component, ...) return true end You could also modify this file to handle components another way if you wanted, but again, ECS will be the default expected behavior.
  22. Okay, so with this approach we have one system and C++, Lua, and C# are all accounted for and work in an identical manner. The next step will be to make the flowgraph connections work with the system. For Lua, the firing and inputs can be automated. For C++ you will need to manually call the outputs and have a method that interprets the inputs like this: void MyComponent::ReceiveSignal(const WString& function) { if (function == "Open") { this->Open(); } else if (function == "Close") { this->Close(); } } I don't see any other way to do it without a preprocesser. This approach might be an improvement, but you would still need to put the method pointers into a map. For C# it is probably possible to automatically trigger the right function, but for now I will rely on other people to tell me what I need to do to best support that. I think the pattern we are going to see is that the components system is mostly automated with Lua and C#, and with C++ you need to write explicit handling code, for receiving signals, and for Load and Save. I think that's okay given the nature of the language. The next step would be arguments, but I would like to get this working with all three languages before I proceed with that. I'm thinking something like this, where DynamicValue is a special class that contains an ID for the type and a value for integers, strings, floats, objects, etc., and it automatically converts to each supported type with an = operator: void MyComponent::ReceiveSignal(const WString& function, std::vector<DynamicValue> arguments) { if (function == "Open") { float speed = 1; shared_ptr<Sound> noise; if (arguments.size() > 0 and arguments[0].type == TYPE_NUMBER) speed = arguments[0]; if (arguments.size() > 1 and arguments[1].type == TYPE_OBJECT) sound = arguments[1]->As<Sound>(); this->Open(speed, noise); } } Lua can handle the above automatically with its very flexible function calling. I'm guessing something automatic can probably be done for C#?
  23. Components are working with Lua now in 1.0.2. Only Start and Update will currently be called. Here's an example: #include "UltraEngine.h" using namespace UltraEngine; int main(int argc, const char* argv[]) { //Get the displays auto displays = GetDisplays(); //Create window auto window = CreateWindow("Ultra Engine", 0, 0, 800, 600, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR); //Create world auto world = CreateWorld(); //Create framebuffer auto framebuffer = CreateFramebuffer(window); //Create a camera auto camera = CreateCamera(world); camera->SetClearColor(0, 0, 1); camera->SetPosition(0, 0, -4); auto light = CreateDirectionalLight(world); light->SetRotation(45, 25, 0); auto box = CreateBox(world); box->AddComponent("Scripts/Components/Mover.lua"); while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false) { world->Update(); world->Render(framebuffer); } return 0; }
  24. @klepto2 @gothboiclique An overload of Entity::AddComponent is added for use with other languages: shared_ptr<Component_> Entity::AddComponent(const ComponentHooks& hooks, void* extra, const bool start) The ComponentHooks structurs consists of six function pointers. Any value may be NULL or a pointer to a C-style function: struct ComponentHooks { void(*Start)(void*); void(*Update)(void*); void(*Collide)(void*, void*, const dFloat*, const dFloat*, dFloat); void*(*Copy)(void*); int(*Load)(void*, char*, uint64_t); int(*Save)(void*, char*, uint64_t*); }; The extra value is a user-defined pointer that gets passed in the first parameter of each callback. All but the Load and Save callbacks are working now. This will allow the components system to work with C# and other languages. Please let me know if I need to improve it in some way.
  25. I've made some additions: Instead of Component_::GetEntity() there is now a raw pointer for the entity. It's can't be a shared pointer or it would create a circular reference and prevent the entity from ever being deleted. I think this will be okay, I was doing the same thing in the preprocessor component system. Entity::components is now accessible (read-only vector containing all attached components). Component_::Collide method will now get called by the engine. Added Component_::Copy(). This will get called when an entity is instantiated or copied, so you can duplicate objects in memory and retain all properties. This will also allow duplication of complex C++ data that can't be stored in a JSON file: virtual shared_ptr<Component_> Copy() { auto component = std::make_shared<Mover>(*this);// copies all members // // Make any adjustments here // return component; } My current outlook is that processing data for each component isn't that hard, and with C# and Lua that process can probably be automated using reflection. What is hard is editing and storing the component data in a standard way, so that is what the engine should focus on. Filling in a Component::Save method so your class members get saved into a JSON file isn't too terribly difficult, and the overly complicated alternative I came up with is probably worse. Maybe something like this can be utilized in the future. Here is a working example that copies an entity with a component: #include "UltraEngine.h" using namespace UltraEngine; class Mover : public Component_ { public: Vec3 movement; Vec3 rotation; bool globalcoords = false; virtual void Update() { if (globalcoords) { this->entity->Translate(movement / 60.0f, true); } else { this->entity->Move(movement / 60.0f); } this->entity->Turn(rotation / 60.0f, globalcoords); } virtual shared_ptr<Component_> Copy() { return std::make_shared<Mover>(*this); } }; int main(int argc, const char* argv[]) { //Get the displays auto displays = GetDisplays(); //Create a window auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR); //Create a world auto world = CreateWorld(); world->SetAmbientLight(0); //Create a framebuffer auto framebuffer = CreateFramebuffer(window); //Load FreeImage plugin auto plg = LoadPlugin("Plugins/FITextureLoader"); //Load model //Cyber Samurai by Khoa Minh: https://sketchfab.com/3d-models/cyber-samurai-26ccafaddb2745ceb56ae5cfc65bfed5 auto model = LoadModel(world, "https://github.com/UltraEngine/Documentation/raw/master/Assets/Models/Characters/cyber_samurai.glb"); model->Turn(0, 180, 0, true); model->SetPosition(1, 0, 0); //=================================================================== // Add a component to the entity //=================================================================== auto mover = model->AddComponent<Mover>(); mover->rotation.z = 10; //=================================================================== // Copy an entity with components (very cool) //=================================================================== auto model2 = model->Instantiate(world); model2->SetPosition(-1, 0 , 0); //Environment maps auto specmap = LoadTexture("https://github.com/UltraEngine/Assets/raw/main/Materials/Environment/footprint_court/specular.dds"); auto diffmap = LoadTexture("https://github.com/UltraEngine/Assets/raw/main/Materials/Environment/footprint_court/diffuse.dds"); world->SetEnvironmentMap(diffmap, ENVIRONMENTMAP_BACKGROUND); world->SetEnvironmentMap(specmap, ENVIRONMENTMAP_SPECULAR); world->SetEnvironmentMap(diffmap, ENVIRONMENTMAP_DIFFUSE); //Create a camera auto camera = CreateCamera(world); camera->SetClearColor(0.125); camera->SetPosition(0, 0.9, -2); camera->SetFov(70); camera->AddPostEffect(LoadPostEffect("Shaders/PostEffects/FXAA.json")); //Create light auto light = CreateBoxLight(world); light->SetRange(-10, 10); light->SetArea(15, 15); light->SetRotation(45, 35, 0); light->SetColor(1.2); //Main loop while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false) { world->Update(); world->Render(framebuffer); } return 0; }
×
×
  • Create New...