gdscriptorganizationmodularity

Organizing complex libraries in GDScript


I'd like to build a reusable library in GDScript. When building libraries in the other languages I'm familiar with, JavaScript and C#, I typically utilize two features that GDScript doesn't seem to have:

  1. Public/private functions to clearly define the public API
  2. Explicitly linking two files together (imports in JS, classes in C#)

In GDScript, (1) seems to be solved by underscoring private methods. While I find this less useful than explicit private functions, I did find the Godot proposal where adding this feature is being discussed.

This question is about how to work around (2), which allows large and complicated libraries to be split up into an arbitrary number of files. The two options I can think of are:

  1. Don't split the library at all, and write everything in one file. While this works for simple libraries, it doesn't scale to very large libraries.
  2. Using a naming convention built on top of class_names to split the functionality into multiple files. For example, having one script that exports the public API which specifies the library class_name. For example class_name MyLibrary. Then, the other files would specify class names like class_name MyLibrary_UtilA, MyLibrary_UtilB, and so on. The documentation would only refer to MyLibrary, and users would hopefully not tinker with the internals.

I dislike (2) because it pollutes the global scope, while the solutions to this problem in JS/C# don't have this side effect.

Am I missing any GDScript functionality that would be better suited to organizing large/complex libraries, or are these two options the best approaches?

The specific project I'm working on now is a port of a large library that in C# or JS would be split up into maybe something like 50+ different files.


Solution

  • Yes, there another way, which might work for you.

    You can use preload with a relative path to reference your scripts. For example:

    const MyClass := preload("my_class.gd")
    

    You can then use that constant as the class. For example:

    var obj := MyClass.new()
    

    This is because:

    1. The instances of GDScript (which is a Resource) are GDScript classes. That is, GDScript is a meta class.
    2. Both const and preload are resolved during parsing (i.e. preload is a constant expression). So - unlike using load - the type information is available while writing the code.

    This way:

    1. You do not use class_name, so you do not pollute the global name space. Addendum: You can still use class_name, for example you might use class_name only on classes that are intended to be instantiated by client code.
    2. You are explicit about the dependencies of your script.
    3. And if you keep all your files in the same folder, you don't need to worry about paths. Plus, keeping everything in the same folder, it is easy to copy the folder to a different location (for example to a different project).