+3 votes

I'm coming to Godot Engine from Unity. In Unity, you use components to build entities (game objects). I have learned in the past (the hard way...) to prefer composition over inheritance, so I'd like to use a similar approach in Godot.

I'm new to the engine, but if I understand it correctly, you can only attach 1 script to a node. My question is this:

How would you make and use a component system in Godot?

How to attach multiple components to one node (game object)?

in Engine by (86 points)
Old github issue related with a similar idea: https://github.com/godotengine/godot/issues/2311
I don't know if someone will read my comment now, but never mind.

It could be great to add a new type of node who acts like a container for components.
The inspector may have a drop down list to add as many component as we want.

This would require to rewrite all nodes in a "component version". Basically in order to remove inheritance.

In the scene instead of this.
KinematicBody2D
-Sprite
-CollisionShape

We may have something like this for instance.

NodeEntity { 2D,  KinematicBody2D, Sprite, CollisionShape }

In this case there no redundance of the Node, Node2D and CanvasItem members.
I guess this system could use less space in memory.

Of course all the previous nodes can be kept into Godot and nothing will be broken. (Don't know if is a good english sentence...)
It could be just an additional choice.
I think that would defeat the central idea of the Godot engine, which is built on the nodes and the node tree. Anyway I used a similar strategy to the accepted answer below and it worked fairly well, so you should try to go with that.
@DriNeo, might be simple to do with the new plugin system for 2.1
In my mind the hypothetical " NodeEntity " inherits from Node so it can have childs and it could be a child of any node.
So Godot will not lose its originality, like the very hierarchical scene graph and the scene system.

2 Answers

+8 votes
Best answer

Just my (rather long) two cents:

In godot you can already use entity-component-like composition, by including child nodes, that extend some script (still only one), and use get_parent() to update their parent's values, and get_node("../<name>") to access sibling components.

Sample:

#player.tscn
Player - Node2D (no script)
  Component_A - Node (extends "component_a.gd")
  Component_B - Node (extends "component_b.gd")
  Sprite - Sprite (no script)
  Sword - Node2D (instance of "res://player/player_sword.tscn")

This usage has a few bad things about it, though:

  1. You have to prefix everything with get_parent(), or some variable.
  2. If you export some variables, you can edit them in the parent scene only after using the "Show children" option.

    • You can work around this one by adding a script on the parent node where you only export variables, and define mappings, but it's a bit of hassle
    • Another workaround would be to make a reusable entity script that exports an Array, and have children components access values like get_parent().values[index_swing_distance], where index_swing_distance is exported on the component.

Also, if you were to go with this method, here is a tip:

  • If you want to get another component, you can use export(NodePath) var other_component_path, and then get_node(other_component_path). This would allow for more separation, and multiple components of the same type inside the same entity (esp. useful for Sprites and the like). If you want to give a default, you can also use literal NodePath declaration, like export(NodePath) var other_component_path = @"../other_component" (or, simply NodePath("../other_component")).
by (1,602 points)
edited by
I was thinking about using children for components. Thank you for your explanation ;) this could work.
There's a literal syntax for NodePaths, so you could also say export var other_component_path = @""
@JoshGrams I would rather be explicit, and say export(NodePath) var other_component_path = @"../other_component"

Also, I would add it into the answer, thanks :)

Does GDScript have a notion of interfaces or protocols that can be used instead? Node scripts can implement "Smashable", "Scorable", "Enemy", etc...

Scripts can then check if other nodes implement an interface or specific method and call it, instead of using child nodes in a way they weren't possibly intended.

Similar to what's shown about "Duck" Typing on the GDscript More Efficiently page.

@RegalMedia The closest you can get to it is has_method checking to see if an interface is available. I just don't get what you mean by "way they weren't possibly intended".

Prolly should have phrased that, "might not have been intended". It'll work, no doubt, but when you're just tying to add some repeatable logic to disparate nodes, seems using child nodes is a bit heavy handed and not as clean as it could be if the engine supported this directly. Each node would need to know where each other is in the parent hierarchy, or you'd need to traverse a parent's sub nodes looking for one that supported a method you're looking for. Would be cool if a method call on the parent would propagate to its children, or maybe use signals to do something similar.

That being said, and as someone only three days into my Godot journey, there's so many other good things found here that this by no means would keep me from exploring further, and would in fact motivate me to find a workable solution like this.

I know it's been a long while, but for others' sake...

Would be cool if a method call on the parent would propagate to its children

This is possible using the propagate_call method in the Node class.

+2 votes

I'm sure this is not complete or finished but I have been working on this slowly.
Ouroboros So far there is no working example yet and any insight or improvements are welcome.

by (42 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.