Josh Posted Monday at 09:09 PM Posted Monday at 09:09 PM I am wondering if anyone actually uses multiple components heavily? The promise of this approach is that you would be able to develop modular components that allow you to mix and match behavior. That idea has always been confusing and unbelievable to me. If we just had a single script attachment per entity, your script code could look like this: function Script:Update() self:SetPosition(self.position + self.movespeed)--self is an entity if self.target ~= nil and self.target.health > 0 then self.target:SetHidden(true)--self.target is an entity end end Instead of the schizophrenic entity / component paradigm, where you have to code exactly for the component you expect to be there, in this case "HealthManager": function Script:Update() self.entity:SetPosition(self.entity.position + self.movespeed)--self is a component if self.target ~= nil and self.target.HealthManager.health > 0 then self.target:SetHidden(true) end end This seems to go against the dynamically typed nature of Lua, and makes design extremely rigid. I can't just check a health or team value to determine how to treat another entity, I either need to know the exact component I expect to see there, or iterate through all components, check for those values, and take the first value I find if there are multiple components with different values. What do you think? Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Dreikblack Posted yesterday at 02:20 AM Posted yesterday at 02:20 AM 5 hours ago, Josh said: I am wondering if anyone actually uses multiple components heavily? Everyone who are working on games? Same fir other engines as well. 5 hours ago, Josh said: Instead of the schizophrenic entity / component paradigm Mixing entity and component would be actual schizophrenic if anything. Even LW4 don't do it with single component/script pet entity. And such shizo approach would not change a fact that entity component may not have "health" var. You need to know what are you doing. If you really want you already can dynamically use vars and functions of all components. Fixing not existing problems would create a ton of real problems which would kill an engine at very least for me. Quote Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5: https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/
reepblue Posted yesterday at 03:11 AM Posted yesterday at 03:11 AM During the development of the engine, I've been experimenting with the best way to develop and create things using the current design decisions. I first tried the multiple components and having them check the entity for corresponding components and that ended up to be a confusing mess. How was I supposed to keep track of what components leveraged each other without some sort of internal documentation? I went back to mono components using inheritance as I normally use C++. However, there has been many opportunities where I have added multiple components to an entity. Here are the senerios when I find myself doing so: When an entity needs to do 2 separate actions (for example: Spin and change color) Instead of writing a special component that spins and changes it's color, I can reuse 2 components to do so. I don't feel like I'm recoding something because the functionality is a little different and the two components don't have to acknowledge each other. This is important in Lua since there's no inheritance. Logic components. Instead of having multiple Pivots with Relays and Timers, I can reduce the count by organizing functionality with less clutter in the map. VR. I made a VR player, and I have the teleportation code in a separate component. This is really nice because the Hmd can always be returned with GetHmd so each piece of functionality can be compartmentalized. Overall, I like adding multiple components to entities but I avoid coding any checks to see if another component is attached. I would leave this feature alone, but not advertise it or encourage it. If you believe that supporting multiple components may prevent you from adding new features to the flowgraph system or prevent support for prefabs supporting flowgraph information, then we'd need to really consider. And if we're keeping multiple components for now, it'll be a bad idea to drop it after the 5.0 release. Quote Cyclone - Ultra Game System - Component Preprocessor - Tex2TGA - Darkness Awaits Template (Leadwerks) If you like my work, consider supporting me on Patreon!
Josh Posted 22 hours ago Author Posted 22 hours ago I have always disliked the ECS approach and using it only confirmed everything I thought about it. Beginners find it confusing, and I also find it confusing. Compare the code examples below. ECS: local pickinfo = world:Pick(p0, p1, true) if pickinfo.entity then for k, v in pairs(pickinfo.entity.components) do -- I don't actually know if this is right, because I don't remember how to do this, lol if type(v.Use) == "function" then v:Use(self.entity) end end end The fact that I wrote this system and I can't even remember how to use it, without checking the documentation, is pretty damning. One script, one entity: local pickinfo = world:Pick(p0, p1, true) if pickinfo.entity then if type(pickinfo.entity.Use) == "function" then pickinfo.entity:Use(self) end end Being able to just add properties and functions to entities is so convenient, and ECS totally throws that away and makes things far less modular and reusable. For example, Alienhead's footsteps component doesn't work with the default FPS player script, through no fault of his own. All the promised modularity of this approach depends on very specific programming to make it that way, instead of just letting emergent behaviors occur. The only examples people can ever give of ECS actually delivering modular behavior are extremely simplistic. On the C++ side, in both the stock components and my SCP game, I end up relying on an object-oriented hierarchy because the ECS approach is so hare-brained. Instead of checking for common properties, I cast to a common base class like Weapon, Projectile, Enemy, etc. What if instead of parsing component headers, the parser could read the class hierarchy of each object type, and display all the properties contained therein?: GameObject int health int team Enemy shared_ptr<NavAgent> agent SCP939 SCP173 SCP097 MTF Guard Scientist Player std::set<int> keycards runspeed LocalPlayer lookspeed RemotePlayer uint64_t steamid When you placed a LocalPlayer object in the scene, the following properties would be exposed, for example: health team keycards runspeed lookspeed In the future, I think things like hiding/showing an entity, changing its color, etc. would be best done using a command that is dragged into the flowgraph. This functionality won't be implemented for a while, but that's what I am thinking in the future. Everyone wants me to teach them how to program, but with the ECS design I am teaching them the wrong way. With Lua, dynamic typing is the right way, and with C++, a class hierarchy is the right way. The ECS approach makes me hop on one leg so I can show people something confusing. Just the thought of writing tutorials for it makes me feel like I should apologize to the user for the bad design. Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Dreikblack Posted 21 hours ago Posted 21 hours ago 30 minutes ago, Josh said: When you placed a LocalPlayer object in the scene, the following properties would be exposed, for example: Do you mean in code? It would be terrible. Ideally class members should not be public at all. Only lack of @Getters and @Setters annotation (like in Java) makes me use public members sometimes to speed up coding a bit. 36 minutes ago, Josh said: Beginners find it confusing, and I also find it confusing I never was finding the most sane thing being confusing even as beginner. If anything is confusing it's lack of proper Inheritance in Lua (recently i found this tho https://www.lua.org/pil/16.2.html, can it be applied to components?) and lack of interface in C++ (and when i tried to use second parent to workaround i could not build a project). ECS itself (which is luckily not real ECS like DOTS from Unity, which is really sound terrible and confusing) is totally fine as it is. You should not make 100000500000 lines classes/scripts (speaking of confusing stuff...) when you can split it in different components with different purposes and reuse whenever you want instead of copy pasting same stuff everywhere and then change it everywhere when fixing a bug or enhancing a code. 54 minutes ago, Josh said: I cast to a common base class like Weapon, Projectile, Enemy, etc Just like it supposed to be. It would unbearable if only one var/method name could be per entity. Some kind of Interface like in Java would easily fix an issue when you want to use some abstract method without same parent. 47 minutes ago, Josh said: I end up relying on an object-oriented hierarchy because the ECS approach is so hare-brained What is that even suppose to mean? Maybe not following OOD is your issue in first place. And OOD is obviously best approach for anything complicated since it's following real life logic, meanwhile you for some reason want to put everything in same pile without categorizing things which quickly end up being terrible mess that would be impossible to support later by refactoring, fixing or improving. Quote Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5: https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/
Josh Posted 21 hours ago Author Posted 21 hours ago 10 minutes ago, Dreikblack said: Ideally class members should not be public at all. Only lack of @Getters and @Setters annotation (like in Java) makes me use public members sometimes to speed up coding a bit. I mean properties that can be set in the editor, in the entity properties editor. Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Dreikblack Posted 20 hours ago Posted 20 hours ago 49 minutes ago, Dreikblack said: lack of interface in C++ (and when i tried to use second parent to workaround i could not build a project) Managed to do it in test project. Will do it for sure in next games now. #pragma once #include "Leadwerks.h" using namespace Leadwerks; class InterfaceExample { protected: int health; public: virtual ~InterfaceExample() = default; virtual void changeHealth(int healthDelta) = 0; }; #pragma once #include "Leadwerks.h" #include "../BaseComponent.h" #include "InterfaceExample.h" using namespace Leadwerks; class Mover : public BaseComponent, public InterfaceExample { public: Vec3 movementspeed; Vec3 rotationspeed; bool globalcoords {false}; Mover(); virtual void Update(); virtual bool Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Scene> scene, const LoadFlags flags, shared_ptr<Object> extra); virtual bool Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Scene> scene, const SaveFlags flags, shared_ptr<Object> extra); virtual std::shared_ptr<Component> Copy(); void changeHealth(int healthDelta) override; }; Interface class also can have own implementation, so you can cast class to interface one (entity->GetComponent<InterfaceExample>()) and use its methods without doing anything in children classes except adding interface as a parent. Quote Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5: https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/
Josh Posted 19 hours ago Author Posted 19 hours ago Maybe in your previous attempts, InterfaceExample was derived from the Object class? https://www.geeksforgeeks.org/cpp/diamond-problem-in-cpp/ Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Josh Posted 19 hours ago Author Posted 19 hours ago 1 hour ago, Dreikblack said: If anything is confusing it's lack of proper Inheritance in Lua Lua will never have inheritance. It has dynamic typing, which means you can just add whatever properties you want. Not exactly the same thing, but it usually meets the same need. Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Josh Posted 18 hours ago Author Posted 18 hours ago 18 hours ago, reepblue said: When an entity needs to do 2 separate actions (for example: Spin and change color) Instead of writing a special component that spins and changes it's color, I can reuse 2 components to do so. I don't feel like I'm recoding something because the functionality is a little different and the two components don't have to acknowledge each other. This is important in Lua since there's no inheritance. When does this actually happen in real life? Why is spinning and changing colors the only example anyone can ever think of? One-time state changes like changing colors or hiding an object are the only thing I used multiple components for in the FPS example, and those could be done better with visual functions, if I add that to the flowgraph. I can't think of a single thing that needs multiple ongoing Update() calls, where that wouldn't just be built into the single script as an option. 18 hours ago, reepblue said: Logic components. Instead of having multiple Pivots with Relays and Timers, I can reduce the count by organizing functionality with less clutter in the map. It sounds like these type of objects don't even need an entity, and could be done even better if you could just drag the script object into the flowgraph and have it only exist there, without any entity. 18 hours ago, reepblue said: VR. I made a VR player, and I have the teleportation code in a separate component. This is really nice because the Hmd can always be returned with GetHmd so each piece of functionality can be compartmentalized. Why is this nice? Can you not decide if your game should use teleporting or not? Is this for someone else to use, who cannot make up their mind, and also cannot handle the burden of a checkbox to indicate whether teleportation should be enabled? With the pushbutton in the FPS map, for example, I use three components for the logic, sound, and emission color change. Is this actually good? Why didn't I just build these features all into a single component? Is that additional functionality a huge burden we need to try to restructure all our code to try to avoid? Are there many different styles and behaviors of push buttons that are so different that I am afraid of locking myself into a finished design? It really seems to me that the advantages of multi-component ECS are vague and theoreitcal, while the disadvantages are concrete and intrusive. In my own code I find I am constantly making effort to work around the inconveniences that ECS introduces. Even worse, I feel this approach may be holding us back from advantages we could gain if we dig into the actual nature of the languages we are using, OOP for C++ and dynamic typing for Lua. Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Josh Posted 14 hours ago Author Posted 14 hours ago @Dreikblack Apparently, the diamond problem has been solved a long time. Check out virtual inheritance. Using that, I could even make it so that user-defined components ARE the Entity class, like this: class Player : public virtual Entity { public: int health = 100; void AddDamage(const int amount); }; Example of how it could be used: auto entities = world->GetEntitiesInArea(bounds) for (auto entity : entities) { // Cast the entity to a user-defined Player object auto player = entity->As<Player>(); if (player) { float dist = player->GetDistance(grenade);// Player can call all Entity methods player->AddDamage(dist);// Player also has its own user-defined methods auto model = player->As<Model>();// Check if player is a Model Entity...I think this can actually work if (model) model->Animate("pain"); } } Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Dreikblack Posted 11 hours ago Posted 11 hours ago 3 hours ago, Josh said: @Dreikblack Apparently, the diamond problem has been solved a long time. Check out virtual inheritance. It does not work for components, because Component have inference from Object without virtual Quote Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5: https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/
Dreikblack Posted 10 hours ago Posted 10 hours ago 8 hours ago, Josh said: Lua will never have inheritance It kinda has tho https://www.lua.org/pil/16.2.html? Never tried, but wonder if it can be used for Components to avoid terrible giant scripts with copy-pasted code Quote Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5: https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/
Josh Posted 10 hours ago Author Posted 10 hours ago 26 minutes ago, Dreikblack said: It does not work for components, because Component have inference from Object without virtual Fortunately, I have full access to the source code and I can change this at any time. Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Dreikblack Posted 9 hours ago Posted 9 hours ago 7 hours ago, Josh said: It really seems to me that the advantages of multi-component ECS are vague and theoreitcal, while the disadvantages are concrete and intrusive. In my own code I find I am constantly making effort to work around the inconveniences that ECS introduces. Even worse, I feel this approach may be holding us back from advantages we could gain if we dig into the actual nature of the languages we are using, OOP for C++ and dynamic typing for Lua. Vague and theoretical are disadvantages of ECS that you are trying to make up for no any good reason. Meanwhile ESC were described many times already and people actively use them. 8 hours ago, Josh said: When does this actually happen in real life? Why is spinning and changing colors the only example anyone can ever think of? One-time state changes like changing colors or hiding an object are the only thing I used multiple components for in the FPS example, and those could be done better with visual functions, if I add that to the flowgraph. I can't think of a single thing that needs multiple ongoing Update() calls, where that wouldn't just be built into the single script as an option. Sorry for being rude in this thread, but it's just you imagination limits + lack of game development experience (SCP project does not have even a demo to be counted, empty levels with nearly zero interactivity ofc. don't need multi-components yet). Example of flow-graph for relatively little level (looks chaotic because i tried to fit in one screen): 8 hours ago, Josh said: It sounds like these type of objects don't even need an entity, and could be done even better if you could just drag the script object into the flowgraph and have it only exist there, without any entity. In such cases they still would needs to be multi-components for composition. Also how would save-load work without entities, especially custom one like mine, where i searching for entities with SAVE tag? 8 hours ago, Josh said: With the pushbutton in the FPS map, for example, I use three components for the logic, sound, and emission color change. Is this actually good? Why didn't I just build these features all into a single component? Of course it's good. Making 100 components with 90% of duplicated code is sooooooo much worse than having 10 components with different functionality. Even in the editor it will look like a mess with 50 component fields when it can be just few per component and you can use only ones you need. 8 hours ago, Josh said: s that additional functionality a huge burden we need to try to restructure all our code to try to avoid? Restructure which code? You are the one who wants to make everyone restructure their existing code. Please, just stop reinventing bicycle in worse possible way. What you want is opposite of everything that OOP stands. Quote Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5: https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/
reepblue Posted 7 hours ago Posted 7 hours ago 6 hours ago, Josh said: @Dreikblack Apparently, the diamond problem has been solved a long time. Check out virtual inheritance. Using that, I could even make it so that user-defined components ARE the Entity class, like this: class Player : public virtual Entity { public: int health = 100; void AddDamage(const int amount); }; Example of how it could be used: auto entities = world->GetEntitiesInArea(bounds) for (auto entity : entities) { // Cast the entity to a user-defined Player object auto player = entity->As<Player>(); if (player) { float dist = player->GetDistance(grenade);// Player can call all Entity methods player->AddDamage(dist);// Player also has its own user-defined methods auto model = player->As<Model>();// Check if player is a Model Entity...I think this can actually work if (model) model->Animate("pain"); } } I'm in favor of this! That would mean each component carries all functionality from the actual class itself. One thing I do notice is that Components tend to be used on fix amount of entities. For example, you're not going to apply the Monster's component to a sprite. If you can derive Monster from the model class, and change the mesh. This is what Quake and Source does. I'm all for this! 10 hours ago, Josh said: It sounds like these type of objects don't even need an entity, and could be done even better if you could just drag the script object into the flowgraph and have it only exist there, without any entity. The virtual nodes things would be interesting, but the flowgraph gets very cluttered. very fast. Adding imaginary nodes might just make it more confusing. I'm still a big fan of Hammer's I/O system. I actually think it's neater than messy nodes crossing over each other. I could set any entity's property the same and add delays to the output without a special component. 10 hours ago, Josh said: Why is this nice? Can you not decide if your game should use teleporting or not? Is this for someone else to use, who cannot make up their mind, and also cannot handle the burden of a checkbox to indicate whether teleportation should be enabled? Separation of functionality! Look how nice this is presented. This can be recreated with separators, but I like how I know this section is just for the teleport system. I will say this, if you're going to make a decision, do it now. I would not be a fan of the idea of you just cutting multiple components and leaving us hanging with planned features that multiple components can solve in the current build. Quote Cyclone - Ultra Game System - Component Preprocessor - Tex2TGA - Darkness Awaits Template (Leadwerks) If you like my work, consider supporting me on Patreon!
Josh Posted 5 hours ago Author Posted 5 hours ago 2 hours ago, reepblue said: If you can derive Monster from the model class, and change the mesh. This is what Quake and Source does. I'm all for this! That's actually not necessary with virtual inheritance. It is possible to create a Monster that appears to be derived from a Model and another from a Light, etc. This is what I have in mind. The user-defined "component" and the entity are the same object: #include "Leadwerks.h" using namespace Leadwerks; //------------------------------------------------------- // This entity class simply moves or turns around a bit each frame //------------------------------------------------------- class Mover : public virtual Entity { Vec3 movespeed = Vec3(0,0,0); Vec3 turnspeed = Vec3(0,1,0); virtual void Update() { Turn(turnspeed); Move(movespeed); } } int main(int argc, const char* argv[]) { //Get the displays auto displays = GetDisplays(); //Create a window auto window = CreateWindow("Leadwerks", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR); //Create a world auto world = CreateWorld(); //Create a framebuffer auto framebuffer = CreateFramebuffer(window); //Create a camera auto camera = CreateCamera(world); //Create a Model / Mover entity auto model = CreateBox<Mover>(world); model->SetPosition(0,0,2); //Cast the Model to a Mover object auto mover = model->As<Mover>(); mover->SetRotation(45,0,0);// calling an entity method is fine mover->turnspeed.y = -2.0f;// so is this //Main loop while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false) { world->Update(); world->Render(framebuffer); } return 0; } Quote My job is to make tools you love, with the features you want, and performance you can't live without.
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.