1. 45
  1.  

  2. 9

    Interesting take. It sounds like the author of Godot is very comfortable with OOP and it’s been working so far, so why stop doing something that is working?

    Writing many things with the ECS pattern can definitely feel more verbose than inheritance in my experience so far, but Rust doesn’t really make inheritance possible, so an ECS approach makes a lot more sense.

    It was a shame that the main thing the author pointed at was the generic “Node” base class at the center of Godot and didn’t really go into detail on how you would solve problems in both Godot and in ECS.

    For example, if a soldier enters the enemy base for more than 10 seconds they capture it. In ECS, I would create a system for checking if a soldier is in a base “SoldierInBase(Base)”, then I would add a system for checking whether the soldier and base are on opposite sides with a start time “SoldierInEnemyBaseSince(Time)” then a final system for SoldierCapturedBase that would look for any “SoldierInEnemyBaseSince(Time)” for over 10 seconds.

    What would any of that have to do with inheriting from the Node class?

    1. 5

      The difference comes into play when you’re inheriting, extending or overriding behavior. Your example in OOP would involve for example inheriting BaseCapper. So far, no big difference. But what if you want to customize that behavior? In OOP, you might for example override canCaptureBase(), which decides if a soldier is allowed to capture at a given moment. In ECS, you’d have to have a tag component CanCaptureBase that is added or removed by the system that handles this condition.

      It doesn’t seem too different, but in OOP it’s much easier to see what’s being overridden where: I can take a look at BaseCapper.class to see exactly what inherits it and trace back its different uses. Under ECS, there’s not an easy to see what would happen with a given entity, since components can be added or removed to it at runtime by arbitrary systems. In your example, there’s no easy way for me to know that an entity created in one system would eventually be the target of SoldierCapturedBase, since it might have to be processed by System A to gain CanCaptureBase, then System B to gain SoldierInBase, etc.

      Simpler example: It’s a lot easier to override an “onPlayerHit()” method than PlayerHitSystem.

      I’m generally not a fan of OOP and have spent years evangelizing against it. I’ve been doing a lot of ECS work and lately I’m coming to appreciate the points raised in the article. Namely, if you don’t need the performance benefits, OOP might be easier to work with. Despite OOP’s legendary verbosity, it can be a lot more concise than ECS (where you might have to declare several components and systems, and constantly repeat iteration boilerplate).

      1. 5

        Writing many things with the ECS pattern can definitely feel more verbose than inheritance in my experience so far, but Rust doesn’t really make inheritance possible, so an ECS approach makes a lot more sense.

        Interestingly, one of the most popular language bindings to Godot is Rust which makes inheritance work with a bunch of macro magic.

        The example you gave allows to show quite well how Godot makes it easy to use both composition and inheritance. In Godot for that I would make a Node Capturable that inherits from a Timer, that has on_area_enter and on_area_exit methods which get connected to corresponding signals of Area that covers the capture zone. The methods would check if whatever entered the base are eligible to cap it (via classes or methods, doesn’t really matter) and if so would start the Timer. It would then emit a captured() signal when capture is complete. Some of the benefits this gives is the easy extensibility, e.g. if I wanted the capture speed to change with the number of soldiers, or blocking the capture if a friendly soldier is in the area, I could do that with a few lines of code. To use it, you would add the Capturable node to the scene tree of a base and connect the area accordingly.

        What would any of that have to do with inheriting from the Node class?

        Node class in Godot allows inclusion into the scene tree, and is the main way composition is done. Node actually inherits from Object, which is the “real base class” that everything inherits from, but it is less often used. Every Node object in the scene tree gets processing calls every render/physics frame, handles pausing, etc. which allows you to quickly add behavior to your game without worrying about interfering with pausing or destruction, etc. as that is handled by other systems.

        1. 3

          Your example was very interesting! Is there a place where I could read more about example ecs systems and best practices in general?

          1. 2

            If you don’t mind a focus on Rust and have a bit of time, I really liked this talk draft, which approaches ECS from a data-oriented programming direction.

            Lots of glorious nitty-gritty details in this one.

        2. 5

          Note: the article has seen some quite significant changes clarifying what it meant since this has been posted, I would recommend to skim it once again.