+3 votes

How do I most easily create a pure code utility class? Do I have to make a Node, rename it "Utils", and then attach a new script to it for the functions? I'd like to just have that utility code exist without having to be attached to a fake/dummy node.

in Engine by (103 points)

4 Answers

+8 votes
Best answer

If your utilities are just functions or constants, you can make a script file that don't extend anything and write static func functions inside.
Then you preload it where you need it:

const Util = preload("util.gd")

func foo():
    Util.stuff()

Here is an utility script I wrote for a plugin: for example https://github.com/Zylann/godot_terrain_plugin/blob/master/addons/zylann/terrain/terrain_utils.gd
And the way it's used: https://github.com/Zylann/godot_terrain_plugin/blob/master/addons/zylann/terrain/terrain.gd#L5

If you need the script to hold some states (vars) or if your functions cannot be static for some reason, you can also make it a singleton with auto-load and give it the name "Util" so it's accessible everywhere. But I like the first solution because it has less potential for race condition errors :p

by (27,825 points)
selected by

It's also possible to create a pure script class and in another script, after preloading the class, instance it with "new()".

Edit: I misinterpreted the need for a utility class, so you won't want this specifically.

Using new is useful only if you want an instance that is not global, for example a Grid class or something carrying parameters.

(Thanks everybody for the thoughts!) How do I create the utils.gd file from inside the Editor? Or how do I import it into the project? (I find the Editor to be generally confusing wrt project management.)

Go to Script tab, File -> New?
Otherwise you can just create the .gd file in the system explorer and Godot will find it.

with 2.1 when i do file -> new it gives me a dialog box where i cannot type anything into the class name text field?! directly underneath the text field it says "N/A". confusing as all heck. I clicked on Path and give it a file name at least. i have no idea what "built-in script" toggle does. etc.

this seemed to work until i tried to use getnode("/path/to/node") and it said "Invalid call. Nonexistent function 'getnode' in base 'GDScript'."

so i guess for my purposes I have to have my utilities be derived from a real Node.

+1 vote

Edit: Nevermind, Zylanns answer is much better :) (see yourself below) i didnt know that godot support static func (cool to know & +1 :) )


Well, you can just load your script without existing node

file res://Scripts/Utils.gd

extends Node #or whatever

#silly example :)
static func plus(x, y):
    return x + y

and in 'real' node:

var x
func _ready():
     x = load("res://Scripts/Utils.gd")
     var p = x.plus(1,2)

Or maybe, you would like to extend your base class (its always better then having Utils class) like this:

file res://Scripts/SpaceshipBase.gd

extends RigidBody
var _turn_speed
func set_turn_speed(x):
     _turn_speed = x

script on your (RigidBody) node:

extends "res://Scripts/SpaceshipBase.gd"
func _ready():
    set_turn_speed(10)

edit: i re-read your question and flip examples :) Sorry for confusion :)

by (233 points)
edited by

I'm unsure about the last recommendation, as you probably want your spaceship node to keep the functionality you get from extending RigidBody2D/3D, and everything up its hierarchy.

I dont lose any functionality, its classic inheritance (see another question), SpaceShipBase extends RigidBody (RigidBody = "RigidBody3D", dont have "3D" in name), node-specific-script extends SpaceShipBase. So in node-specific-script, i have access to SpaceShipBase and RigidBody functions.

What i get is central script of common functions of all spaceships (like movement func etc) without caring of ship specific (one or dozen thrusters, etc... :) )


edit: Of course, all my spaceships have to be RigidBody node :)

+1 vote

I meant to post earlier, but I would add that another option is to make your class a global singleton if you want it available to the whole project.

Create a new script. Add that script in the Auto Load tab found in the Project Settings menu. Give it a name like Util, ensure it's marked as singleton. Then as long as everything is properly setup, you should be able to just call it directly like Util.func(x) anywhere in your scripts.

by (5,203 points)

Frankly none of the approaches I have seen really make a lot of sense to me. They all feel like they are confusing strange hacks compared to how this would be done in other programming language environments like a Java IDE or whatever. I think the whole attempt to have the "res://" stuff sounds nice at first, but seems to lead to weird things like the op and the various answers.

This here to me actually feels like the "simplest" and so i am hoping to do this. But I haven't gotten it to work (nor the other suggestions) yet, though I am still going to keep trying. :-)

I did pretty much as far as I know what you said, and I get e.g. "Invalid call. Nonexistent function 'v' in base 'Nil'." when I try to invoke a fn in there.

If you're getting that error, it might be because the GDScript isn't extending anything.

At the top see if you have something like

extends Node

The other solutions offered are valid approaches too, it's how things like this were done I think before the singleton feature was added, or if you prefer to only have the script loaded into specific scenes.

I am from c# background (actually, my main job is c#, godot is hobby) and have to say, when you get some basic things and relax muscle memory (like not writing ';' at end of every line :) ) everything become pretty simple.

  1. Just click Scripts -> File -> New -> insert Path, make sure build-in is off -> Create.

  2. add some code, for simplicity lets say

    static func Plus(x,y):
        return x + y;
    

    And save.

  3. Add this script as singleton (becouse godot doesnt have anything like static classes, this is actually simple hack, but why not :) ) via Scene -> ProjectSettings -> AutoLoad.

  4. Profit. Now you can use your script just like that:

    Utils.Plus(1,2) # 3
    

    (i just slapped it randomly in my script just to show it really works :) )

+1 vote

Use class_name to access a class everywhere. Example:

class_name MyConstants

const PLAYER_GROUP = "player"
const GLOBAL_GRAVITY = 9.8

Then, everywhere:

if body.is_in_group(MyConstants.PLAYER_GROUP):
   gravity = MyConstants.GLOBAL_GRAVITY
by (211 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 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 webmaster@godotengine.org with your username.