• Godot HelpProgramming
  • How to go about creating a new composite node type (ArrowGridContainer). Help needed.

Hello. I have a good programming background but almost no experience in Godot. I would like to learn how to create a new Node type that is a composition of existing node types and exposes its fields through the editor. For that end, I will give my current use case for this:

I am trying to program a pixel art game with a few screens in which I would like something like an inventory grid of arbitrary size for an inventory of arbitrary size. Because it is a pixel art game I don't want to add scroll bars; I would like to be an arrow on the top and bottom or the left and right which switches through the items on click. Because I have this component on multiple screens with different types of content (say on one screen one is scrolling through a 6x4 grid of inventory items, on the next it is a 8x2 grid of characters to select), my need for abstraction and aversion to code repetition commands me to not implement this separately for every screen, but instead create a Node that has the needed capabilities. Basically, I want some type of Container Node that takes Textures for the arrows and background, whether it has horizontal or vertical orientation, maybe margin sizes around the arrows as well as whatever is supposed to be shown in the inventory and determines how it is supposed to be displayed by itself by its own rules similar to how GridContainer or VBoxContainer do. I of course also want this to be visualized in Godot when it is edited.

In other languages this would mean a new class that implements the Container or BoxContainer interface, calls methods for resizing and re-rendering, and has setters for all important parameters which call those methods when needed. Its components would be added by composition. In the vertical case it might contain a VBoxContainer containing the arrows and a GridContainer, for example.

How do I go about this in Godot with GDScript? I could not find a good tutorial for this type of challenge.

Here are some things I've already tried for reference. Maybe that makes it easier to point out mistakes in my approach:

My new class: extends Container


class_name ArrowGridContainer

I have already exposed some needed parameters like this: enum ORIENTATION {HORIZONTAL, VERTICAL}


export(ORIENTATION) var orientation = ORIENTATION.VERTICAL \
		setget set_orientation
export(Texture) var arrow_texture_previous setget set_arrow_texture_previous
export(Texture) var arrow_texture_next setget set_arrow_texture_next

Some things I think I need later: var contents: GridContainer = GridContainer.new() var arrow_previous: TextureButton = TextureButton.new() var _arrow_next: TextureButton = TextureButton.new()

My signal to self to recompute everything ... I can worry about optimizing this later. I want to get to a working prototype as fast as possible: func ready(): connect("sort_children", self, "on_ArrowGridContainer_sort_children") emit_signal("sort_children")

I have a bunch of methods that emit that signal when something is changed that might affect display. Maybe 20 of those: func set_orientation(new_orientation): self.orientation = new_orientation emit_signal("sort_children")

func set_arrow_texture_previous(texture: Texture):
	self.arrow_texture_previous = texture
	emit_signal("sort_children")

My update method is a monster, of course, and unfinished to say the least. It starts by computing a lot of the sizes and then sets them on the children: func _on_ArrowGridContainer_sort_children():

Set arrow textures

arrow_previous.texture_normal = arrow_texture_previous arrow_next.texture_normal = arrow_texture_next

Get sizes in scroll direction

var scroll_direction_total = get_scroll_direction_size(self) var scroll_direction_arrow_previous_size = get_scroll_direction_size(arrow_previous) var scroll_direction_arrow_next_size = get_scroll_direction_size(arrow_next) var scroll_direction_content_size = get_scroll_direction_content_size( scroll_direction_total, scroll_direction_arrow_previous_size, scroll_direction_arrow_next_size )

What I don't like about my approach so far: - I feel like I'm re-implementing what should already be available by subclassing from something else. - Some fields I want to expose in the editor are already exposed for components of my Node. I would like to just tell Godot to expose these fields of the subcomponents instead of creating them anew on the parent and then manually setting them on subcomponents through code. - I don't know how to make this construction update in the editor once finished. - I don't know whether this will work at all.

Sorry I cannot get some of the code sections to format as code.

Help, no matter in what form, would be appreciated.

Thanks in advance, DarkNetFan

if I understood correctly what you're trying to achieve - you may be overengineering it a bit. There's no need to handle this compositing via code. Why not just make a scene with wanted functionality and then instance it wherever and whenever you need it. That instance will then be a single node that encapsulates all functionality.

I don't have much Godot experience either and often find myself trying to handle things like I'd do in a c++ project. But I think the above described approach would be the Godot way to do this.

7 days later

That was the plan anyways. I would like to do it this way if I knew how. I still need to make the nodes inside the scene to resize correctly, do I not? I have some topmost node that I will save as the scene, but the subnodes won't just do what I want them to. I also need to be able to configure it as above - a scene without parameters would not know what margins I want for the arrows, what textures, etc.

I'm pretty lost - I still have not found a tutorial that will teach me how to make such a configurable component for reuse.

I'm confused by your confusion :)

Make a scene in which top node has some properties and methods that control everything in children nodes. Instantiate this scene into the "master" scene, set its properties and call its methods as needed. I don't see any problems there. You can make any number of instances of the same scene. This is analogous to making new objects from a class in any object oriented language.

Hm, I guess I just don't see a difference to my original problem. What is the difference between what I tried above plus making that exact thing a scene and your suggestion? It's just a scene now instead of a standard node. I still have the problem that I don't know how to reuse already implemented functionality effectively. Scenes do not have an inheritance hierarchy at all, do they? I also am unsure whether that solves my problem updating the new component in the editor every time the way it is displayed should change. Maybe I'm just ignorant of important properties of scenes.

Not sure I understand what you're actually trying to do and what's the main hurdle. I think you didn't define the problem precisely enough.

@DarkNetFan said: Scenes do not have an inheritance hierarchy at all, do they?

Script classes do though. And you can compose any type of a scene via the script class. So you effectively have both - composition and inheritance. Attach your script class to the top node of a scene and expose its properties to the editor. If you want to change building blocks, alter the scene (or generate its nodes via code in that class). If you want to inherit functionality, make a new class that inherits this one.

Note that nodes are not script classes. You can't really make a new type of a node.

8 days later

Thanks, I'll try to get as far as I can with that information. I still have some problems with this system. It's for example rather annoying that I can't have a script on the topmost node of a scene that defines the functionality for the scene, and then also attach a script to that scene once I have instanced it. I am very confused how to really separate concerns there.

@xyz said:

Note that nodes are not script classes. You can't really make a new type of a node.

This is rather unintuitive to me. When I inherit from a node type in my script and give it a class name, it does show up as a node when I look through the hierarchy. The problem is that when I do create one of those nodes, this also carries the node script around with it and I can't attach my own script to the instance of the node. It just feels very weird and clumsy to me. At least yet.

Thanks for the help. I'll probably learn most by experimenting some more.

Try to look at it this way.

Node class hierarchy is a native code class hierarchy. You don't have any access to it other than to instantiate its objects. You can't inherit from it and write new classes of nodes. Unless you edit Godot's source code and recompile it.

Each such native class has its functionality exposed to GDScript via GDScript class of the same name. So Label native class exposes its functionality via Label script class, and so on. This is implicit.

A GDScript file is always a GDScript class that must inherit from an existing GDScript class. That base class can be any of the implicit classes that are script "reflections" of native node classes.

So if you attach a script file to, for example, a Label node, you'd typically want to inherit from Label GDScript class. But you can also inherit from any of GDScript classes that are up the inheritance tree of the Label class. Which are: Control < CanvasItem < Node < Object. In which case you'll only have access to that upstream class' functionality

For example if a script attached to Label node start with "extends Node" instead of "extends Label", your class will only have access to Node class interface.

You can further inherit from your script class as much as you like, adding new functionality. You can then attach these new script classes to any node that inherits from a class whose script class you initially extended. For example: - write a GDScript class SmartCanvasItem that inherits CanvasItem (that's GDScript class CanvasItem, not a native node) - write a GDScript class GeniusCanvasItem that inherits SmartCanvasItem. - now you can attach GeniusCanvasItem script class to any node that's derived from CanvasItem - for example Label or Sprite.

Note that no new types of nodes were created here at all. Just script classes that extend functionality of the GDScript class associated with native node type.

Btw, you can attach scripts to nodes by calling set_script()

@DarkNetFan said: This is rather unintuitive to me. When I inherit from a node type in my script and give it a class name, it does show up as a node when I look through the hierarchy.

This is just convenience. It's actually the same native node with a GDScript class attached to it. Any GDScript class can be attached as long as it inherits one of applicable implicit classes.

On surface it may look like you're creating a new node class but it's not "real" inheritance. The attached script can be replaced or even completely removed at runtime, leaving you with "bare" native node.

@DarkNetFan said: The problem is that when I do create one of those nodes, this also carries the node script around with it and I can't attach my own script to the instance of the node.

As said before - you can. Just call set_stript() method.

Take a look at this example:

extends Node
class_name SelfDegrading

var time: float =  0.0

func _process(delta):
	time += delta
	print(time)
	if time > 1:
		set_script(null)

This will cause a seemingly new node type called SelfDegrading appear in the node creation window, looking like it's a child class of the native class Node. However, when you instantiate such node, its script will run for one second and it will then remove all scripted functionality from itself. And you're left with a plain native Node object hanging about in the scene.

Note that SelfDegrading will appear as a child "class" of the native Node class, even if you in editor initially attached this script to some other type of a node. That's because of "extends Node"

Also note that such script can be attached to every other type of native node, since they all inherit native Node.

The system is actually quite simple. Problem is that there's no adequate overview in the documentation, aimed at people with programming experience, so they can can quickly get acquainted with architecture.

a year later