Godot’s type system is … odd. It sits somewhere between duck and static typing. I’ve tried reading the docs and doing experiments, but I cannot figure out how to make a scene which is both:
- Usable as an instantiated resource (e.g.
var node: MyType = $MyNode
) - Usable as a typed instance and handled manually (e.g.
var node: MyType = MyType.load()
)
The pattern I’m trying to achieve is to hide as much of the preload information about a scene in the scene script itself. Thus (example from a re-worked Creep demo):
class_name Game extends Node2D
# Since the game is a scene and has properties set via the editor, we cannot use
# Game.new() to instantiate it (that doesn't load the resources).
static var _factory = preload("res://game.tscn")
# Users who want to manually handle scene instances can just call "load()" and
# get back a strongly typed instance.
static func load(game_over_handler: Callable) -> Game:
var game: Game = _factory.instantiate()
game._you_died.connect(func():
game_over_handler.call(game._score)
game.queue_free())
return game
This lets a class using the “Game” type do:
add_child(Game.load($Hud.show_final_score))
without using an instantiated scene node in the editor. This is neat, reduces code and leaves things more refactorable later. This isn’t my problem.
However, I also want to support a user who wants to instantiate the “Game” node as a child in the editor, so they can do things like:
$Game.get_score()
without having to care about instantiation.
But, if I set up the Game scene via the editor and then add “class_name Game” to it, it now longer works as a scene you can install via the editor. Worse than this, Godot declares the node to now be corrupt and just won’t load it.
Is there are way to achieve both of these behaviours in a single script, so you can provide a user with a “strongly typed” scene which can either be loaded via a static function and managed manually, or added in the editor as an automatically instantiated child scene?