Jump to content

Recommended Posts

Posted

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?

My job is to make tools you love, with the features you want, and performance you can't live without.

Posted
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.

Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5:

https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/

Posted

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:

  1. 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. 
  2. 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.
  3. 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.

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Posted

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.

My job is to make tools you love, with the features you want, and performance you can't live without.

  • Josh changed the title to ECS vs. object-oriented and dynamic typing
Posted
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.

Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5:

https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/

Posted
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.

entityproperties.png?raw=true

My job is to make tools you love, with the features you want, and performance you can't live without.

Posted
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.

Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5:

https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/

Posted
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.

My job is to make tools you love, with the features you want, and performance you can't live without.

Posted
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?

image.png.56995dc25f070011f02970cda02b9fe6.png

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.

My job is to make tools you love, with the features you want, and performance you can't live without.

Posted

@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");
	}
}

 

My job is to make tools you love, with the features you want, and performance you can't live without.

Posted
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. ;)

My job is to make tools you love, with the features you want, and performance you can't live without.

Posted
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):

image.thumb.png.bdc6aeda0416fb70b8853b69b033d7f6.png

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.

Check out Slipgate Tactics demo, which is made with Ultra Engine/Leadwerks 5:

https://www.leadwerks.com/community/topic/61480-slipgate-tactics-demo/

Posted
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. 

image.png.a12ae8b844e9a29ddaed87b4bc149967.png

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. 

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Posted
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;
}

 

My job is to make tools you love, with the features you want, and performance you can't live without.

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...